diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 112514be..4e60f244 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman)", - "version": (2, 0, "dd0ff0b"), + "version": (2, 1, "ce5d64bc"), "blender": (2, 81, 0), "location": "File > Import-Export", "wiki_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index ad1a4009..edb78fd7 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -497,6 +497,8 @@ class VehicleTypes: """Name of the object inside the group which visibility tells us either group should be exported or no.""" model_variant_prop = ".scs_variant" """Name of the property for saving variant of the model inside group encapsulating imported paintable model.""" + main_coll_name = ".scs_main_collection" + """Name of the collection where left over objects will be linked to, when importing from sii data.""" id_mask_colors = ( (51, 0, 0), diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index 33303fe0..b3b89057 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -193,6 +193,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat missing_skinned_verts = set() # indicates if object is having only partial skin, which is not allowed in our models has_unnormalized_skin = False # indicates if object has vertices which bones weight sum is smaller then one last_tangents_uv_layer = None # stores uv layer for which tangents were calculated, so tangents won't be calculated all over again + max_vcolor = 0 # indicates maximum vertex color inside this model and is used to report unnormalized vertex color over 1.0 for poly in mesh.polygons: @@ -343,20 +344,35 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat uvs_aliases.append(aliases) # 4. vcol -> vcol_lay = mesh.vertex_colors[0].data; vcol_lay[loop_i].color - vcol_multi = mesh_obj.data.scs_props.vertex_color_multiplier if _MESH_consts.default_vcol not in mesh.vertex_colors: # get RGB component of RGBA vcol = (1.0,) * 3 missing_vcolor = True else: - color = mesh.vertex_colors[_MESH_consts.default_vcol].data[loop_i].color - vcol = (color[0] * 2 * vcol_multi, color[1] * 2 * vcol_multi, color[2] * 2 * vcol_multi) + color = list(mesh.vertex_colors[_MESH_consts.default_vcol].data[loop_i].color[:3]) + + for i in range(0, 3): + # since blender is saving vcolor in 8-bits 0.5 can not be set, thus clamp 128/255 to 0.5 or report to big vcolor otherwise + if 0.5 < color[i] <= 0.501960813999176: + color[i] = 0.5 + elif color[i] > 0.501960813999176 and color[i] > max_vcolor: + max_vcolor = color[i] + + vcol = (color[0] * 2, color[1] * 2, color[2] * 2) if _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix not in mesh.vertex_colors: # get A component of RGBA vcol += (1.0,) missing_vcolor_a = True else: - alpha = mesh.vertex_colors[_MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix].data[loop_i].color - vcol += ((alpha[0] + alpha[1] + alpha[2]) / 3.0 * 2 * vcol_multi,) # take avg of colors for alpha + alpha = mesh.vertex_colors[_MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix].data[loop_i].color[:3] + alpha = (alpha[0] + alpha[1] + alpha[2]) / 3.0 # take avg of colors for alpha + + # since blender is saving vcolor in 8-bits 0.5 can not be set, thus clamp 128/255 to 0.5 or report to big vcolor otherwise + if 0.5 < alpha <= 0.501960813999176: + alpha = 0.5 + elif alpha > 0.501960813999176 and alpha > max_vcolor: + max_vcolor = alpha + + vcol += (alpha * 2,) # 5. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before if pim_materials[pim_mat_name].get_nmap_uv_name(): # calculate tangents only if needed @@ -466,6 +482,9 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat lprint("W Object %r from SCS Root %r has unormalized skinning, exporting normalized weights!\n\t " "You can normalize weights by selecting object & executing 'Normalize All Vertex Groups'.", (mesh_obj.name, root_object.name)) + if max_vcolor != 0: + lprint("W Object %r from SCS Root %r has unormalized vertex color, some vertices use: %.5f. instead of max 0.5", + (mesh_obj.name, root_object.name, max_vcolor)) # add rest of the pieces to global list for piece_key in mesh_pieces: diff --git a/addon/io_scs_tools/exp/pim_ef/exporter.py b/addon/io_scs_tools/exp/pim_ef/exporter.py index 172b1127..dd09c0fc 100644 --- a/addon/io_scs_tools/exp/pim_ef/exporter.py +++ b/addon/io_scs_tools/exp/pim_ef/exporter.py @@ -260,20 +260,19 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat # 4. vcol -> vcol_lay = mesh.vertex_colors[0].data; vcol_lay[loop_i].color rgbas = [] - vcol_multi = mesh_obj.data.scs_props.vertex_color_multiplier if _MESH_consts.default_vcol not in mesh.vertex_colors: # get RGB component of RGBA vcol = (1.0,) * 3 missing_vcolor = True else: color = mesh.vertex_colors[_MESH_consts.default_vcol].data[loop_i].color - vcol = (color[0] * 2 * vcol_multi, color[1] * 2 * vcol_multi, color[2] * 2 * vcol_multi) + vcol = (color[0] * 2, color[1] * 2, color[2] * 2) if _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix not in mesh.vertex_colors: # get A component of RGBA vcol += (1.0,) missing_vcolor_a = True else: alpha = mesh.vertex_colors[_MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix].data[loop_i].color - vcol += ((alpha[0] + alpha[1] + alpha[2]) / 3.0 * 2 * vcol_multi,) # take avg of colors for alpha + vcol += ((alpha[0] + alpha[1] + alpha[2]) / 3.0 * 2,) # take avg of colors for alpha rgbas.append(vcol) rgbas_names[_MESH_consts.default_vcol] = True @@ -286,7 +285,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat continue color = vcol_layer.data[loop_i].color - vcol = (color[0] * 2 * vcol_multi, color[1] * 2 * vcol_multi, color[2] * 2 * vcol_multi) + vcol = (color[0] * 2, color[1] * 2, color[2] * 2) rgbas.append(vcol) rgbas_names[vcol_layer.name] = True diff --git a/addon/io_scs_tools/exp/pit.py b/addon/io_scs_tools/exp/pit.py index dd091ce1..754150c6 100644 --- a/addon/io_scs_tools/exp/pit.py +++ b/addon/io_scs_tools/exp/pit.py @@ -132,6 +132,8 @@ def get_texture_path_from_material(material, texture_type, export_path): :type material: bpy.types.Material :param texture_type: type of texture which should be readed from material (example "texture_base") :type texture_type: str + :param export_path: path where PIT of this material and texture is gonna be exported + :type export_path: str :return: relative path for Texture section data of PIT material :rtype: str """ @@ -160,7 +162,7 @@ def get_texture_path_from_material(material, texture_type, export_path): # search for relative path inside current scs project base and # possible dlc/mod parent folders; use first found - for infix in ("", "../base/", "../../base/"): + for infix in ("", "../base/", "../base_vehicle/", "../../base/", "../../base_vehicle/"): curr_path = os.path.join(scs_project_path, infix + texture_raw_path[2:] + ext) @@ -189,12 +191,14 @@ def get_texture_path_from_material(material, texture_type, export_path): # if we are exporting somewhere into SCS Project Base Path texture still can be saved if scs_project_path != "" and _path_utils.startswith(export_path, scs_project_path): - tex_dir, tex_filename = os.path.split(texture_raw_path_with_ext) + tex_dir, tex_filename = os.path.split(texture_raw_path) tobj_filename = tex_filename + ".tobj" + texture_copied_path_with_ext = os.path.join(export_path, tex_filename) + ext + # copy texture beside exported files try: - shutil.copy2(texture_raw_path_with_ext, os.path.join(export_path, tex_filename)) + shutil.copy2(texture_raw_path_with_ext, texture_copied_path_with_ext) except OSError as e: # ignore copying the same file # NOTE: happens if absolute texture paths are used @@ -215,6 +219,13 @@ def get_texture_path_from_material(material, texture_type, export_path): tobj_rel_filepath = tobj_rel_filepath + os.sep + tobj_filename[:-5] tobj_abs_filepath = os.path.join(export_path, tobj_filename) texture_abs_filepath = texture_raw_path_with_ext + + lprint("W Material %r texture of type %r uses absolute path!\n\t " + + "Texture copied into the Project Base Path beside exported PIT file:\n\t " + + "Original path: %r\n\t " + + "Copied path: %r", + (material.name, texture_type, texture_abs_filepath, texture_copied_path_with_ext)) + break else: @@ -225,7 +236,7 @@ def get_texture_path_from_material(material, texture_type, export_path): else: lprint("E Texture file %r from material %r doesn't exists inside current Project Base Path.\n\t " + - "TOBJ won't be exported and reference will remain empty, expect problems!", + "TOBJ won't be exported and reference will remain empty, expect problems!", (texture_raw_path, material.name)) return "" @@ -562,6 +573,8 @@ def export(root_object, filepath, name_suffix, used_parts, used_materials): elif attr_prop == "Tag" and "aux" in attribute_dict[attr_prop]: attribute_section.props.append((attr_prop, "aux[" + attribute_dict[attr_prop][3:] + "]")) + elif attr_prop == "FriendlyTag": + continue else: attribute_section.props.append((attr_prop, attribute_dict[attr_prop])) diff --git a/addon/io_scs_tools/imp/pim.py b/addon/io_scs_tools/imp/pim.py index 275a4082..0af65e6c 100644 --- a/addon/io_scs_tools/imp/pim.py +++ b/addon/io_scs_tools/imp/pim.py @@ -117,7 +117,7 @@ def get_material_properties(section): def get_piece_properties(section): """Receives a Piece section and returns its properties in its own variables. For any item that fails to be found, it returns None.""" - ob_index = ob_material = ob_vertex_cnt = ob_edge_cnt = ob_face_cnt = ob_stream_cnt = 0 + ob_index = ob_material = ob_vertex_cnt = ob_tris_cnt = ob_stream_cnt = 0 for prop in section.props: if prop[0] in ("", "#"): pass @@ -127,15 +127,13 @@ def get_piece_properties(section): ob_material = prop[1] elif prop[0] == "VertexCount": ob_vertex_cnt = prop[1] - elif prop[0] == "EdgeCount": - ob_edge_cnt = prop[1] - elif prop[0] in ("TriangleCount", "FaceCount"): - ob_face_cnt = prop[1] + elif prop[0] == "TriangleCount": + ob_tris_cnt = prop[1] elif prop[0] == "StreamCount": ob_stream_cnt = prop[1] else: lprint('\nW Unknown property in "Piece" data: "%s"!', prop[0]) - return ob_index, ob_material, ob_vertex_cnt, ob_edge_cnt, ob_face_cnt, ob_stream_cnt + return ob_index, ob_material, ob_vertex_cnt, ob_tris_cnt, ob_stream_cnt def _get_piece_streams(section): @@ -447,18 +445,21 @@ def _create_piece( else: mesh_rgb_final = [] + vcolor_corrupt = False for vc_layer_name in mesh_rgb_final: - max_value = mesh_rgb_final[vc_layer_name][0][0] / 2 - for vc_entry in mesh_rgb_final[vc_layer_name]: - for i, value in enumerate(vc_entry): - if max_value < value / 2: - max_value = value / 2 + # check for vcolor bigger than possible float range (since we divide our vcolor by 2 max value is 2) + max_vcolor = 2.0 + for k, vc_entry in enumerate(mesh_rgb_final[vc_layer_name]): + for j, value in enumerate(vc_entry): + if value > max_vcolor: + mesh_rgb_final[vc_layer_name][k][j] = max_vcolor + vcolor_corrupt = True - if max_value > mesh.scs_props.vertex_color_multiplier: - mesh.scs_props.vertex_color_multiplier = max_value + _mesh_utils.bm_make_vc_layer(5, bm, vc_layer_name, mesh_rgb_final[vc_layer_name]) - _mesh_utils.bm_make_vc_layer(5, bm, vc_layer_name, mesh_rgb_final[vc_layer_name], mesh.scs_props.vertex_color_multiplier) + if vcolor_corrupt: + lprint("W Piece %r has vertices with vertex color greater the 1.0, clamping it!", (name, )) context.window_manager.progress_update(0.5) @@ -742,9 +743,13 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa ] elif section.type == 'Piece': if scs_globals.import_pim_file: - ob_index, ob_material, ob_vertex_cnt, ob_edge_cnt, ob_face_cnt, ob_stream_cnt = get_piece_properties(section) + ob_index, ob_material, ob_vertex_cnt, ob_tris_cnt, ob_stream_cnt = get_piece_properties(section) piece_name = 'piece_' + str(ob_index) + if ob_vertex_cnt == 0 or ob_tris_cnt == 0: + lprint("W Piece with index %i has no vertices or triangles, ignoring piece import in model:\n\t %r!", (ob_index, filepath)) + continue + # print('Piece %i going to "get_piece_5_streams"...' % ob_index) (mesh_vertices, mesh_normals, @@ -1023,8 +1028,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa # CREATE SKELETON (ARMATURE) armature = None if scs_globals.import_pis_file and bones: - bpy.ops.object.add(type='ARMATURE') - bpy.ops.object.editmode_toggle() + bpy.ops.object.add(type='ARMATURE', enter_editmode=True) for bone in bones: bpy.ops.armature.bone_primitive_add(name=bone) bpy.ops.object.editmode_toggle() diff --git a/addon/io_scs_tools/imp/pim_ef.py b/addon/io_scs_tools/imp/pim_ef.py index e4a77730..071ef5fe 100644 --- a/addon/io_scs_tools/imp/pim_ef.py +++ b/addon/io_scs_tools/imp/pim_ef.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2017-2019: SCS Software +# Copyright (C) 2017-2020: SCS Software import bpy import bmesh @@ -26,7 +26,6 @@ from io_scs_tools.imp.pim import get_header from io_scs_tools.imp.pim import get_global from io_scs_tools.imp.pim import get_material_properties -from io_scs_tools.imp.pim import get_piece_properties from io_scs_tools.imp.pim import get_part_properties from io_scs_tools.imp.pim import get_locator_properties from io_scs_tools.imp.pim import get_bones_properties @@ -42,6 +41,30 @@ from io_scs_tools.utils import get_scs_globals as _get_scs_globals +def _get_piece_properties(section): + """Receives a Piece section and returns its properties in its own variables. + For any item that fails to be found, it returns None.""" + ob_index = ob_material = ob_vertex_cnt = ob_edge_cnt = ob_face_cnt = ob_stream_cnt = 0 + for prop in section.props: + if prop[0] in ("", "#"): + pass + elif prop[0] == "Index": + ob_index = prop[1] + elif prop[0] == "Material": + ob_material = prop[1] + elif prop[0] == "VertexCount": + ob_vertex_cnt = prop[1] + elif prop[0] == "EdgeCount": + ob_edge_cnt = prop[1] + elif prop[0] == "FaceCount": + ob_face_cnt = prop[1] + elif prop[0] == "StreamCount": + ob_stream_cnt = prop[1] + else: + lprint('\nW Unknown property in "Piece" data: "%s"!', prop[0]) + return ob_index, ob_material, ob_vertex_cnt, ob_edge_cnt, ob_face_cnt, ob_stream_cnt + + def _get_piece_streams(section): """Receives a Piece (Exchange Format version) section and returns all its data in its own variables. For any item that fails to be found, it returns None.""" @@ -263,19 +286,22 @@ def _create_piece( if mesh_rgb: mesh_rgb_final.update(mesh_rgb) + vcolor_corrupt = False for vc_layer_name in mesh_rgb_final: - max_value = mesh_rgb_final[vc_layer_name][0][0][0] / 2 - for vc_entry in mesh_rgb_final[vc_layer_name]: - for v_i in vc_entry: + # check for vcolor bigger than possible float range (since we divide our vcolor by 2 max value is 2) + max_vcolor = 2.0 + for k, vc_entry in enumerate(mesh_rgb_final[vc_layer_name]): + for j, v_i in enumerate(vc_entry): for i, value in enumerate(v_i): - if max_value < value / 2: - max_value = value / 2 + if value > max_vcolor: + mesh_rgb_final[vc_layer_name][k][j][i] = max_vcolor + vcolor_corrupt = True - if max_value > mesh.scs_props.vertex_color_multiplier: - mesh.scs_props.vertex_color_multiplier = max_value + _mesh_utils.bm_make_vc_layer(7, bm, vc_layer_name, mesh_rgb_final[vc_layer_name]) - _mesh_utils.bm_make_vc_layer(7, bm, vc_layer_name, mesh_rgb_final[vc_layer_name], mesh.scs_props.vertex_color_multiplier) + if vcolor_corrupt: + lprint("W Piece %r has vertices with vertex color greater the 1.0, clamping it!", (name,)) bm.to_mesh(mesh) mesh.update() @@ -489,9 +515,13 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa ] elif section.type == 'Piece': if scs_globals.import_pim_file: - ob_index, ob_material, ob_vertex_cnt, ob_edge_cnt, ob_face_cnt, ob_stream_cnt = get_piece_properties(section) + ob_index, ob_material, ob_vertex_cnt, ob_edge_cnt, ob_face_cnt, ob_stream_cnt = _get_piece_properties(section) piece_name = 'piece_' + str(ob_index) + if ob_vertex_cnt == 0 or ob_face_cnt == 0: + lprint("W Piece with index %i has no vertices or faces, ignoring piece import in model:\n\t %r!", (ob_index, filepath)) + continue + (mesh_vertices, mesh_normals, mesh_tangents, diff --git a/addon/io_scs_tools/internals/looks/__init__.py b/addon/io_scs_tools/internals/looks/__init__.py index 149e14ec..2703fdb8 100644 --- a/addon/io_scs_tools/internals/looks/__init__.py +++ b/addon/io_scs_tools/internals/looks/__init__.py @@ -194,6 +194,11 @@ def update_look_from_material(root_obj, material, preset_change=False): for look_id in root_obj[_MAIN_DICT]: if look_id != look_id_str or preset_change: + # skip desynced material entry in the look + if mat_id_str not in root_obj[_MAIN_DICT][look_id]: + lprint("D Can't update material: %r on look entry with ID: %s", (material.name, look_id)) + continue + curr_mat = root_obj[_MAIN_DICT][look_id][mat_id_str] for key in curr_mat: @@ -201,8 +206,38 @@ def update_look_from_material(root_obj, material, preset_change=False): if preset_change: # preset change write through if key in ("active_shader_preset_name", "mat_effect_name") or __is_texture_locked__(material, key): # overwrite + curr_mat[key] = new_mat[key] + + elif key.startswith("shader_attribute_aux") and key in new_mat: + + new_mat_key_size = len(new_mat[key]["entries"]) + curr_mat_key_size = len(curr_mat[key]["entries"]) + + # don't sync if size is the same + if new_mat_key_size == curr_mat_key_size: + continue + + # since dynamic update of collection property is not possible we have to create new one + coll_prop_entry = {"CollectionProperty": 1, "entries": []} + for coll_entry in curr_mat[key]["entries"]: + entry = dict(coll_entry) + coll_prop_entry["entries"].append(entry) + + # clip unused values + while len(coll_prop_entry["entries"]) > new_mat_key_size: + coll_prop_entry["entries"].pop() + + # add default ones if missing + while curr_mat_key_size < new_mat_key_size: + entry_copy = dict(new_mat[key]["entries"][curr_mat_key_size]) + coll_prop_entry["entries"].append(entry_copy) + curr_mat_key_size = curr_mat_key_size + 1 + + curr_mat[key] = coll_prop_entry + elif key not in new_mat: # delete if not in newly created material entry + del curr_mat[key] else: # general write through diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 3d2da9b0..ffd322b5 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software import bpy from bpy.app.handlers import persistent @@ -49,6 +49,7 @@ def post_load(scene): ("0.6", apply_fixes_for_0_6), ("1.4", apply_fixes_for_1_4), ("1.12", apply_fixes_for_1_12), + ("2.0", apply_fixes_for_2_0), ) for version, func in VERSIONS_LIST: @@ -62,6 +63,14 @@ def post_load(scene): _get_scs_globals().last_load_bt_version = _info_utils.get_tools_version() +def _reload_materials(): + """Triggers materials nodes and UI attributes reloading. + """ + override = bpy.context.copy() + override["window"] = bpy.data.window_managers[0].windows[0] + bpy.ops.material.scs_tools_reload_materials(override, 'INVOKE_DEFAULT') + + def apply_fixes_for_0_6(): """ Applies fixes for v0.6 or less: @@ -80,7 +89,7 @@ def apply_fixes_for_0_6(): if material.scs_props.mat_effect_name == "": continue - for tex_type in material.scs_props.get_texture_types().keys(): + for tex_type in material.scs_props.get_texture_types(): texture_attr_str = "shader_texture_" + tex_type if texture_attr_str in material.scs_props.keys(): @@ -153,9 +162,7 @@ def apply_fixes_for_0_6(): _looks.write_through(scs_root, material, "active_shader_preset_name") # 4. reload all materials once all corrections to materials has been done - override = bpy.context.copy() - override["window"] = bpy.data.window_managers[0].windows[0] - bpy.ops.material.scs_tools_reload_materials(override, 'INVOKE_DEFAULT') + _reload_materials() def apply_fixes_for_1_4(): @@ -168,9 +175,7 @@ def apply_fixes_for_1_4(): print("INFO\t- Applying fixes for version <= 1.4") # 1. reload all materials - override = bpy.context.copy() - override["window"] = bpy.data.window_managers[0].windows[0] - bpy.ops.material.scs_tools_reload_materials(override, 'INVOKE_DEFAULT') + _reload_materials() # 2. remove all obsolete ".scs_nmap_" + str(i) materials, as of 2.78 we are using new normal maps node i = 1 @@ -239,9 +244,7 @@ def apply_fixes_for_1_12(): bpy.data.collections.remove(col) # 3. reload materials node trees - override = bpy.context.copy() - override["window"] = bpy.data.window_managers[0].windows[0] - bpy.ops.material.scs_tools_reload_materials(override, 'INVOKE_DEFAULT') + _reload_materials() # 4. update preview models to get new material assigned _preview_models.update(force=True) @@ -276,3 +279,52 @@ def apply_fixes_for_1_12(): override = bpy.context.copy() override["window"] = bpy.data.window_managers[0].windows[0] bpy.ops.wm.scs_tools_show_3dview_report(override, 'INVOKE_DEFAULT', message="\n".join(msg)) + + +def apply_fixes_for_2_0(): + """ + Applies fixes for 2.0 or less: + 1. Reload materials since some got removed/restructed attributes + 2. Remove .linv flavor from dif.lum.spec shaders (it's not supported anymore) + """ + + print("INFO\t- Applying fixes for version <= 2.0") + + # dictinary of materials with "eut2.sky" effect and their former uv sets + # to be reapplied to new texture types + sky_uvs_mat = {} + + # 1. do pre-reload changes and collect data + for mat in bpy.data.materials: + + effect_name = mat.scs_props.mat_effect_name + + # dif.lum.spec got removed linv flavor, thus remove it. + if effect_name.startswith("eut2.dif.lum.spec") and ".linv" in effect_name: + start_idx = effect_name.index(".linv") + end_idx = start_idx + 5 + mat.scs_props.mat_effect_name = mat.scs_props.mat_effect_name[:start_idx] + mat.scs_props.mat_effect_name[end_idx:] + + # window.[day|night] got transformed into window.lit + if effect_name in ("eut2.window.day", "eut2.window.night"): + mat.scs_props.mat_effect_name = "eut2.window.lit" + mat.scs_props.active_shader_preset_name = "window.lit" + + # sky got different texture types preserver uvs + if effect_name.startswith("eut2.sky") and len(mat.scs_props.shader_texture_base_uv) == 1: + sky_uvs_mat[mat] = mat.scs_props.shader_texture_base_uv[0].value + + # 2. reload materials node trees and possible param changes in UI as last + _reload_materials() + + # 3. post-reload changes + # recover uvs for sky shaders + for mat in sky_uvs_mat: + mat.scs_props.shader_texture_sky_weather_base_a_uv[0].value = sky_uvs_mat[mat] + mat.scs_props.shader_texture_sky_weather_base_b_uv[0].value = sky_uvs_mat[mat] + mat.scs_props.shader_texture_sky_weather_over_a_uv[0].value = sky_uvs_mat[mat] + mat.scs_props.shader_texture_sky_weather_over_b_uv[0].value = sky_uvs_mat[mat] + + # we need to update looks so that new values get propagated to all of the looks + for scs_root in _object_utils.gather_scs_roots(bpy.data.objects): + _looks.update_look_from_material(scs_root, mat) diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 8e9cd4e9..973e4993 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015: SCS Software +# Copyright (C) 2015-2021: SCS Software def get_shader(effect): @@ -36,13 +36,9 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.water import Water as Shader - elif effect == "window.day": + elif effect == "window.lit": - from io_scs_tools.internals.shaders.eut2.window.day import WindowDay as Shader - - elif effect == "window.night": - - from io_scs_tools.internals.shaders.eut2.window.night import WindowNight as Shader + from io_scs_tools.internals.shaders.eut2.window.lit import WindowLit as Shader elif effect == "reflective": @@ -84,10 +80,6 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.retroreflective import Retroreflective as Shader - elif effect.startswith("unlit.tex.a8"): - - from io_scs_tools.internals.shaders.eut2.unlit_tex.a8 import UnlitTexA8 as Shader - elif effect.startswith("unlit.tex"): from io_scs_tools.internals.shaders.eut2.unlit_tex import UnlitTex as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/decalshadow/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/decalshadow/__init__.py index 779b5657..cd1f7d53 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/decalshadow/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/decalshadow/__init__.py @@ -16,12 +16,12 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software -from io_scs_tools.internals.shaders.eut2.unlit_tex.a8 import UnlitTexA8 +from io_scs_tools.internals.shaders.eut2.shadowmap import Shadowmap -class Decalshadow(UnlitTexA8): +class Decalshadow(Shadowmap): @staticmethod def get_name(): """Get name of this shader file with full modules path.""" @@ -36,7 +36,4 @@ def init(node_tree): """ # init parent - UnlitTexA8.init(node_tree) - - # enable hardcoded flavors: DEPTH, BLEND_OVER - UnlitTexA8.set_blend_over_flavor(node_tree, True) + Shadowmap.init(node_tree) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py index f3570f01..8f3123df 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py @@ -16,19 +16,17 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software from io_scs_tools.internals.shaders.eut2.dif import Dif +from io_scs_tools.internals.shaders.eut2.std_passes.lum import StdLum from io_scs_tools.internals.shaders.flavors import alpha_test from io_scs_tools.internals.shaders.flavors import blend_over from io_scs_tools.internals.shaders.flavors import blend_add from io_scs_tools.internals.shaders.flavors import blend_mult -class DifLum(Dif): - LUM_MIX_NODE = "LuminosityMix" - LUM_A_INVERSE_NODE = "LumTransp=1-Alpha" - LUM_OUT_SHADER_NODE = "LumShader" +class DifLum(Dif, StdLum): @staticmethod def get_name(): @@ -43,8 +41,6 @@ def init(node_tree): :type node_tree: bpy.types.NodeTree """ - pos_x_shift = 185 - # init parent Dif.init(node_tree) @@ -52,40 +48,12 @@ def init(node_tree): compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] base_tex_n = node_tree.nodes[Dif.BASE_TEX_NODE] - # move existing - output_n.location.x += pos_x_shift * 3 - - # nodes creation - lum_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") - lum_mix_n.name = DifLum.LUM_MIX_NODE - lum_mix_n.label = DifLum.LUM_MIX_NODE - lum_mix_n.location = (compose_lighting_n.location.x + pos_x_shift * 2, compose_lighting_n.location.y - 100) - lum_mix_n.blend_type = "MIX" - - lum_a_inv_n = node_tree.nodes.new("ShaderNodeMath") - lum_a_inv_n.name = lum_a_inv_n.label = DifLum.LUM_A_INVERSE_NODE - lum_a_inv_n.location = (compose_lighting_n.location.x + pos_x_shift * 2, compose_lighting_n.location.y - 300) - lum_a_inv_n.operation = "SUBTRACT" - lum_a_inv_n.use_clamp = True - lum_a_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 - - lum_out_shader_n = node_tree.nodes.new("ShaderNodeEeveeSpecular") - lum_out_shader_n.name = lum_out_shader_n.label = DifLum.LUM_OUT_SHADER_NODE - lum_out_shader_n.location = (compose_lighting_n.location.x + pos_x_shift * 3, compose_lighting_n.location.y - 200) - lum_out_shader_n.inputs["Base Color"].default_value = (0.0,) * 4 - lum_out_shader_n.inputs["Specular"].default_value = (0.0,) * 4 - - # links creation - node_tree.links.new(lum_mix_n.inputs['Fac'], base_tex_n.outputs['Alpha']) - node_tree.links.new(lum_mix_n.inputs['Color1'], compose_lighting_n.outputs['Color']) - node_tree.links.new(lum_mix_n.inputs['Color2'], base_tex_n.outputs['Color']) - - node_tree.links.new(lum_a_inv_n.inputs[1], compose_lighting_n.outputs['Alpha']) - - node_tree.links.new(lum_out_shader_n.inputs['Emissive Color'], lum_mix_n.outputs['Color']) - node_tree.links.new(lum_out_shader_n.inputs['Transparency'], lum_a_inv_n.outputs['Value']) - - node_tree.links.new(output_n.inputs['Surface'], lum_out_shader_n.outputs['BSDF']) + StdLum.add(node_tree, + base_tex_n.outputs['Color'], + base_tex_n.outputs['Alpha'], + compose_lighting_n.outputs['Color'], + compose_lighting_n.outputs['Alpha'], + output_n.inputs['Surface']) @staticmethod def finalize(node_tree, material): @@ -107,7 +75,7 @@ def finalize(node_tree, material): # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - lum_out_shader_n = node_tree.nodes[DifLum.LUM_OUT_SHADER_NODE] + lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] @@ -129,6 +97,18 @@ def finalize(node_tree, material): if blend_over.is_set(node_tree): material.blend_method = "BLEND" + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminosity boost factor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity output represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + StdLum.set_aux5(node_tree, aux_property) + @staticmethod def set_alpha_test_flavor(node_tree, switch_on): """Set alpha test flavor to this shader. @@ -171,7 +151,7 @@ def set_blend_add_flavor(node_tree, switch_on): if switch_on: out_node = node_tree.nodes[Dif.OUTPUT_NODE] - in_node = node_tree.nodes[DifLum.LUM_OUT_SHADER_NODE] + in_node = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # put it on location of output node & move output node for one slot to the right location = tuple(out_node.location) @@ -193,13 +173,13 @@ def set_blend_mult_flavor(node_tree, switch_on): if switch_on: out_node = node_tree.nodes[Dif.OUTPUT_NODE] - in_node = node_tree.nodes[DifLum.LUM_OUT_SHADER_NODE] + in_node = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # break link to lum out shader transparency as mult uses DST_COLOR as source factor in blend function compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] if compose_lighting_n.inputs['Alpha'].links: node_tree.links.remove(compose_lighting_n.inputs['Alpha'].links[0]) - lum_out_shader_n = node_tree.nodes[DifLum.LUM_OUT_SHADER_NODE] + lum_out_shader_n = in_node if lum_out_shader_n.inputs['Transparency'].links: node_tree.links.remove(lum_out_shader_n.inputs['Transparency'].links[0]) @@ -229,3 +209,25 @@ def set_lvcol_flavor(node_tree, switch_on): node_tree.links.new(lum_mix_n.inputs['Color2'], vcol_mult_n.outputs[0]) else: node_tree.links.new(lum_mix_n.inputs['Color2'], base_tex_n.outputs['Color']) + + @staticmethod + def set_lvcol_flavor(node_tree, switch_on): + """Set (vertex color*luminance) flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + vcol_mult_n = node_tree.nodes[Dif.VCOLOR_MULT_NODE] + vcol_opacity_n = node_tree.nodes[Dif.OPACITY_NODE] + lum_col_vcol_modul_n = node_tree.nodes[StdLum.LUM_COL_LVCOL_MULT_NODE] + lum_a_vcol_modul_n = node_tree.nodes[StdLum.LUM_A_LVCOL_MULT_NODE] + + if switch_on: + node_tree.links.new(lum_col_vcol_modul_n.inputs[1], vcol_mult_n.outputs[0]) + node_tree.links.new(lum_a_vcol_modul_n.inputs[1], vcol_opacity_n.outputs[0]) + else: + node_tree.links.remove(lum_col_vcol_modul_n.inputs[1].links[0]) + node_tree.links.remove(lum_a_vcol_modul_n.inputs[1].links[0]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py index 1a3d1e6f..2e66d8f0 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py @@ -16,21 +16,17 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.internals.shaders.eut2.std_passes.lum import StdLum from io_scs_tools.internals.shaders.flavors import alpha_test from io_scs_tools.internals.shaders.flavors import blend_over from io_scs_tools.internals.shaders.flavors import blend_add from io_scs_tools.internals.shaders.flavors import blend_mult -class DifLumSpec(DifSpec): - LUM_MIX_NODE = "LuminosityMix" - LUM_A_INVERSE_NODE = "LumTransp=1-Alpha" - LUM_OUT_SHADER_NODE = "LumShader" - LUM_BOOST_MIX_NODE = "LuminosityBoostMix" - LUM_BOOST_VALUE_NODE = "LuminosityBoost" +class DifLumSpec(DifSpec, StdLum): @staticmethod def get_name(): @@ -45,61 +41,19 @@ def init(node_tree): :type node_tree: bpy.types.NodeTree """ - pos_x_shift = 185 - # init parent DifSpec.init(node_tree) output_n = node_tree.nodes[DifSpec.OUTPUT_NODE] compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] base_tex_n = node_tree.nodes[DifSpec.BASE_TEX_NODE] - spec_col_n = node_tree.nodes[DifSpec.SPEC_COL_NODE] - - # move existing - output_n.location.x += pos_x_shift * 4 - - # nodes creation - lum_boost_val_n = node_tree.nodes.new("ShaderNodeValue") - lum_boost_val_n.name = lum_boost_val_n.label = DifLumSpec.LUM_BOOST_VALUE_NODE - lum_boost_val_n.location = (spec_col_n.location.x, spec_col_n.location.y + 100) - - lum_boost_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") - lum_boost_mix_n.name = lum_boost_mix_n.label = DifLumSpec.LUM_BOOST_MIX_NODE - lum_boost_mix_n.location = (compose_lighting_n.location.x, compose_lighting_n.location.y + 200) - lum_boost_mix_n.operation = "MULTIPLY" - - lum_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") - lum_mix_n.name = lum_mix_n.label = DifLumSpec.LUM_MIX_NODE - lum_mix_n.location = (compose_lighting_n.location.x + pos_x_shift * 2, compose_lighting_n.location.y + 100) - lum_mix_n.blend_type = "MIX" - - lum_a_inv_n = node_tree.nodes.new("ShaderNodeMath") - lum_a_inv_n.name = lum_a_inv_n.label = DifLumSpec.LUM_A_INVERSE_NODE - lum_a_inv_n.location = (compose_lighting_n.location.x + pos_x_shift * 2, compose_lighting_n.location.y - 300) - lum_a_inv_n.operation = "SUBTRACT" - lum_a_inv_n.use_clamp = True - lum_a_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 - - lum_out_shader_n = node_tree.nodes.new("ShaderNodeEeveeSpecular") - lum_out_shader_n.name = lum_out_shader_n.label = DifLumSpec.LUM_OUT_SHADER_NODE - lum_out_shader_n.location = (compose_lighting_n.location.x + pos_x_shift * 3, compose_lighting_n.location.y - 200) - lum_out_shader_n.inputs["Base Color"].default_value = (0.0,) * 4 - lum_out_shader_n.inputs["Specular"].default_value = (0.0,) * 4 - # links creation - node_tree.links.new(lum_boost_mix_n.inputs[0], lum_boost_val_n.outputs['Value']) - node_tree.links.new(lum_boost_mix_n.inputs[1], base_tex_n.outputs['Color']) - - node_tree.links.new(lum_mix_n.inputs['Fac'], base_tex_n.outputs['Alpha']) - node_tree.links.new(lum_mix_n.inputs['Color1'], compose_lighting_n.outputs['Color']) - node_tree.links.new(lum_mix_n.inputs['Color2'], lum_boost_mix_n.outputs[0]) - - node_tree.links.new(lum_a_inv_n.inputs[1], compose_lighting_n.outputs['Alpha']) - - node_tree.links.new(lum_out_shader_n.inputs['Emissive Color'], lum_mix_n.outputs['Color']) - node_tree.links.new(lum_out_shader_n.inputs['Transparency'], lum_a_inv_n.outputs['Value']) - - node_tree.links.new(output_n.inputs['Surface'], lum_out_shader_n.outputs['BSDF']) + StdLum.add(node_tree, + base_tex_n.outputs['Color'], + base_tex_n.outputs['Alpha'], + compose_lighting_n.outputs['Color'], + compose_lighting_n.outputs['Alpha'], + output_n.inputs['Surface']) @staticmethod def finalize(node_tree, material): @@ -121,7 +75,7 @@ def finalize(node_tree, material): # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - lum_out_shader_n = node_tree.nodes[DifLumSpec.LUM_OUT_SHADER_NODE] + lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] @@ -149,11 +103,11 @@ def set_aux5(node_tree, aux_property): :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param aux_property: secondary specular color represented with property group + :param aux_property: luminosity output represented with property group :type aux_property: bpy.types.IDPropertyGroup """ - node_tree.nodes[DifLumSpec.LUM_BOOST_VALUE_NODE].outputs[0].default_value = 1 + aux_property[0]['value'] + StdLum.set_aux5(node_tree, aux_property) @staticmethod def set_alpha_test_flavor(node_tree, switch_on): @@ -197,7 +151,7 @@ def set_blend_add_flavor(node_tree, switch_on): if switch_on: out_node = node_tree.nodes[DifSpec.OUTPUT_NODE] - in_node = node_tree.nodes[DifLumSpec.LUM_OUT_SHADER_NODE] + in_node = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # put it on location of output node & move output node for one slot to the right location = tuple(out_node.location) @@ -219,13 +173,13 @@ def set_blend_mult_flavor(node_tree, switch_on): if switch_on: out_node = node_tree.nodes[DifSpec.OUTPUT_NODE] - in_node = node_tree.nodes[DifLumSpec.LUM_OUT_SHADER_NODE] + in_node = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # break link to lum out shader transparency as mult uses DST_COLOR as source factor in blend function compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] if compose_lighting_n.inputs['Alpha'].links: node_tree.links.remove(compose_lighting_n.inputs['Alpha'].links[0]) - lum_out_shader_n = node_tree.nodes[DifLumSpec.LUM_OUT_SHADER_NODE] + lum_out_shader_n = in_node if lum_out_shader_n.inputs['Transparency'].links: node_tree.links.remove(lum_out_shader_n.inputs['Transparency'].links[0]) @@ -237,27 +191,6 @@ def set_blend_mult_flavor(node_tree, switch_on): else: blend_mult.delete(node_tree) - @staticmethod - def set_linv_flavor(node_tree, switch_on): - """Set luminance inverse flavor to this shader. - - :param node_tree: node tree of current shader - :type node_tree: bpy.types.NodeTree - :param switch_on: flag indication if flavor should be switched on or off - :type switch_on: bool - """ - - lum_mix_n = node_tree.nodes[DifLumSpec.LUM_MIX_NODE] - lum_boost_mix_n = node_tree.nodes[DifLumSpec.LUM_BOOST_MIX_NODE] - compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] - - if switch_on: - node_tree.links.new(lum_mix_n.inputs['Color1'], lum_boost_mix_n.outputs[0]) - node_tree.links.new(lum_mix_n.inputs['Color2'], compose_lighting_n.outputs['Color']) - else: - node_tree.links.new(lum_mix_n.inputs['Color1'], compose_lighting_n.outputs['Color']) - node_tree.links.new(lum_mix_n.inputs['Color2'], lum_boost_mix_n.outputs[0]) - @staticmethod def set_lvcol_flavor(node_tree, switch_on): """Set (vertex color*luminance) flavor to this shader. @@ -268,11 +201,14 @@ def set_lvcol_flavor(node_tree, switch_on): :type switch_on: bool """ - base_tex_n = node_tree.nodes[DifSpec.BASE_TEX_NODE] vcol_mult_n = node_tree.nodes[DifSpec.VCOLOR_MULT_NODE] - lum_boost_mix_n = node_tree.nodes[DifLumSpec.LUM_BOOST_MIX_NODE] + vcol_opacity_n = node_tree.nodes[DifSpec.OPACITY_NODE] + lum_col_vcol_modul_n = node_tree.nodes[StdLum.LUM_COL_LVCOL_MULT_NODE] + lum_a_vcol_modul_n = node_tree.nodes[StdLum.LUM_A_LVCOL_MULT_NODE] if switch_on: - node_tree.links.new(lum_boost_mix_n.inputs[1], vcol_mult_n.outputs[0]) + node_tree.links.new(lum_col_vcol_modul_n.inputs[1], vcol_mult_n.outputs[0]) + node_tree.links.new(lum_a_vcol_modul_n.inputs[1], vcol_opacity_n.outputs[0]) else: - node_tree.links.new(lum_boost_mix_n.inputs[1], base_tex_n.outputs['Color']) + node_tree.links.remove(lum_col_vcol_modul_n.inputs[1].links[0]) + node_tree.links.remove(lum_a_vcol_modul_n.inputs[1].links[0]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py index ec046ce9..214f48a2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py @@ -21,6 +21,7 @@ from mathutils import Color from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_glass from io_scs_tools.internals.shaders.eut2.std_node_groups import compose_lighting_ng from io_scs_tools.internals.shaders.eut2.std_node_groups import add_env_ng from io_scs_tools.internals.shaders.eut2.std_node_groups import lighting_evaluator_ng @@ -212,6 +213,7 @@ def init(node_tree): add_env_n.name = add_env_n.label = Glass.ADD_ENV_GROUP_NODE add_env_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 2500) add_env_n.node_tree = add_env_ng.get_node_group() + add_env_n.inputs['Fresnel Type'].default_value = 1.0 add_env_n.inputs['Fresnel Scale'].default_value = 2.0 add_env_n.inputs['Fresnel Bias'].default_value = 1.0 add_env_n.inputs['Apply Fresnel'].default_value = 1.0 @@ -519,8 +521,10 @@ def set_fresnel(node_tree, bias_scale): :type bias_scale: (float, float) """ - node_tree.nodes[Glass.ADD_ENV_GROUP_NODE].inputs['Fresnel Bias'].default_value = bias_scale[0] - node_tree.nodes[Glass.ADD_ENV_GROUP_NODE].inputs['Fresnel Scale'].default_value = bias_scale[1] + bias, scale = get_fresnel_glass(bias_scale[0], bias_scale[1]) + + node_tree.nodes[Glass.ADD_ENV_GROUP_NODE].inputs['Fresnel Bias'].default_value = bias + node_tree.nodes[Glass.ADD_ENV_GROUP_NODE].inputs['Fresnel Scale'].default_value = scale @staticmethod def set_tint_opacity(node_tree, opacity): diff --git a/addon/io_scs_tools/internals/shaders/eut2/parameters.py b/addon/io_scs_tools/internals/shaders/eut2/parameters.py new file mode 100644 index 00000000..9d22f9f0 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/parameters.py @@ -0,0 +1,114 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + +import math + + +def get_material_luminosity(luminance_boost): + """Gets perceptual material luminosity from luminance boost given in nits. + + :param luminance_boost: luminance boost in nits + :type luminance_boost: float + :return: perceptual material lumionosity + :rtype: float + """ + + scale = 1.0 + if luminance_boost < 0.0: + scale = abs(luminance_boost) + else: + MIDGRAY_NITS = 50.0 + if luminance_boost == 0: + desired_nits = MIDGRAY_NITS + else: + desired_nits = luminance_boost + + if desired_nits <= MIDGRAY_NITS: + scale = 0.25 * (desired_nits / MIDGRAY_NITS) + else: + MAX_LOG2_SCALE = 10.0 + normalized = min(1.0, max(0.0, math.log2(desired_nits / MIDGRAY_NITS) / MAX_LOG2_SCALE)) + perceptual = 0.5 + 0.5 * normalized + scale = perceptual * perceptual + + return scale + + +def get_fresnel_v1(bias, scale, default_bias): + """Gets fresnel as in-game, if scale is -1 use given bias from material otherwise default bias. + + :param bias: material_fresnel[0] + :type bias: float + :param scale: material_fresnel[1] + :type scale: float + :param default_bias: default bias to use if scale is not -1 + :type default_bias: float + :return: recalculated fresnel + :rtype: float, float + """ + + if scale == -1.0: + f0 = bias + else: + f0 = default_bias + + return f0, 1.0 + + +def get_fresnel_glass(bias, scale): + """Get fresnel params for the glass shader. + + :param bias: material_fresnel[0] + :type bias: float + :param scale: material_fresnel[1] + :type scale: float + :return: recalculated fresnel + :rtype: float, float + """ + + return get_fresnel_v1(bias, scale, 0.07) + + +def get_fresnel_truckpaint(bias, scale): + """Get fresnel params for the truckpaint shader. + + :param bias: material_fresnel[0] + :type bias: float + :param scale: material_fresnel[1] + :type scale: float + :return: recalculated fresnel + :rtype: float, float + """ + + return get_fresnel_v1(bias, scale, 0.025) + + +def get_fresnel_window(bias, scale): + """Get fresnel params for the window shader. + + :param bias: material_fresnel[0] + :type bias: float + :param scale: material_fresnel[1] + :type scale: float + :return: recalculated fresnel + :rtype: float, float + """ + + return get_fresnel_v1(bias, scale, 0.14) diff --git a/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py index 053413a8..14696292 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py @@ -18,10 +18,18 @@ # Copyright (C) 2015-2019: SCS Software -from io_scs_tools.internals.shaders.eut2.unlit_tex import UnlitTex +from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils -class Shadowmap(UnlitTex): +class Shadowmap(BaseShader): + UV_MAP_NODE = "UVMap" + BASE_TEX_NODE = "BaseTex" + ALPHA_INV_NODE = "AlphaInv" + OUT_SHADER_NODE = "OutShader" + OUTPUT_NODE = "Output" + @staticmethod def get_name(): """Get name of this shader file with full modules path.""" @@ -35,8 +43,97 @@ def init(node_tree): :type node_tree: bpy.types.NodeTree """ - # init parent - UnlitTex.init(node_tree) + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # node creation + uv_map_n = node_tree.nodes.new("ShaderNodeUVMap") + uv_map_n.name = uv_map_n.label = Shadowmap.UV_MAP_NODE + uv_map_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1500) + uv_map_n.uv_map = _MESH_consts.none_uv + + base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_tex_n.name = base_tex_n.label = Shadowmap.BASE_TEX_NODE + base_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1500) + base_tex_n.width = 140 + + alpha_inv_n = node_tree.nodes.new("ShaderNodeMath") + alpha_inv_n.name = alpha_inv_n.label = Shadowmap.ALPHA_INV_NODE + alpha_inv_n.location = (start_pos_x + pos_x_shift * 2, 1300) + alpha_inv_n.operation = "SUBTRACT" + alpha_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 + alpha_inv_n.inputs[1].default_value = 1.0 + alpha_inv_n.use_clamp = True + + out_shader_node = node_tree.nodes.new("ShaderNodeEeveeSpecular") + out_shader_node.name = out_shader_node.label = Shadowmap.OUT_SHADER_NODE + out_shader_node.location = (start_pos_x + pos_x_shift * 3, 1500) + out_shader_node.inputs["Emissive Color"].default_value = (0.0,) * 4 + out_shader_node.inputs["Base Color"].default_value = (0.0,) * 4 + out_shader_node.inputs["Specular"].default_value = (0.0,) * 4 + + output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") + output_n.name = output_n.label = Shadowmap.OUTPUT_NODE + output_n.location = (start_pos_x + + pos_x_shift * 4, start_pos_y + 1500) + + # links creation + node_tree.links.new(base_tex_n.inputs['Vector'], uv_map_n.outputs['UV']) + + node_tree.links.new(alpha_inv_n.inputs[1], base_tex_n.outputs['Color']) + + node_tree.links.new(out_shader_node.inputs['Transparency'], alpha_inv_n.outputs['Value']) + + node_tree.links.new(output_n.inputs['Surface'], out_shader_node.outputs['BSDF']) + + @staticmethod + def finalize(node_tree, material): + """Set output material for this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param material: blender material for used in this tree node as output + :type material: bpy.types.Material + """ + + material.use_backface_culling = True + material.blend_method = "BLEND" + + @staticmethod + def set_base_texture(node_tree, image): + """Set base texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to base texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Shadowmap.BASE_TEX_NODE].image = image + + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Shadowmap.BASE_TEX_NODE], settings) + + @staticmethod + def set_base_uv(node_tree, uv_layer): + """Set UV layer to base texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv - # enable hardcoded flavors: DEPTH, BLEND_OVER - UnlitTex.set_blend_over_flavor(node_tree, True) + node_tree.nodes[Shadowmap.UV_MAP_NODE].uv_map = uv_layer \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py index 28b1e866..e4a1dd4f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py @@ -21,32 +21,41 @@ from mathutils import Color from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader -from io_scs_tools.internals.shaders.flavors import blend_over +from io_scs_tools.internals.shaders.flavors import sky_back, sky_stars from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng +from io_scs_tools.internals.shaders.eut2.sky import texture_types +from io_scs_tools.internals.shaders.eut2.sky import uv_rescale_ng from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils +from io_scs_tools.utils import math as _math_utils class Sky(BaseShader): VCOL_GROUP_NODE = "VColorGroup" UV_MAP_NODE = "UVMap" - SEC_UV_MAP_NODE = "SecUVMap" + RESCALE_UV_GROUP_NODE = "RescaleUV" DIFF_COL_NODE = "DiffuseColor" - BASE_TEX_NODE = "BaseTex" - OVER_TEX_NODE = "OverTex" - MASK_TEX_NODE = "MaskTex" - BLEND_VAL_NODE = "BlendInput" - MASK_TEX_SEP_NODE = "SeparateMask" - MASK_FACTOR_MIX_NODE = "MaskFactorMix" - MASK_FACTOR_BLEND_MULT_NODE = "MaskFactorBlendMultiplier" - BASE_OVER_MIX_NODE = "BaseOverMix" - BASE_OVER_A_MIX_NODE = "BaseOverAlphaMix" VCOLOR_MULT_NODE = "VertexColorMultiplier" DIFF_MULT_NODE = "DiffuseMultiplier" + WEATHER_BASE_MIX_NODE = "WeatherBaseMix" + WEATHER_BASE_A_MIX_NODE = "WeatherBaseAMix" + WEATHER_OVER_MIX_NODE = "WeatherOverMix" + WEATHER_OVER_A_MIX_NODE = "WeatherOverAMix" + WEATHER_MIX_NODE = "WeatherMix" + WEATHER_A_MIX_NODE = "WeatherAMix" + WEATHER_DIFF_MULT_NODE = "WeatherDiffMult" + OPACITY_STARS_MIX_NODE = "OpacityStarsMix" + ALPHA_INV_NODE = "AlphaInv" OUT_SHADER_NODE = "OutShader" OUTPUT_NODE = "Output" + SEPARATE_UV_NODE_PREFIX = "Separate UV " + TEX_NODE_PREFIX = "Tex " + TEX_OOB_BOOL_NODE_PREFIX = "OOBBool " + TEX_FINAL_MIX_NODE_PREFIX = "FinalTexel " + TEX_FINAL_A_MIX_NODE_PREFIX = "FinalTexelA " + @staticmethod def get_name(): """Get name of this shader file with full modules path.""" @@ -68,84 +77,92 @@ def init(node_tree): # node creation vcol_group_n = node_tree.nodes.new("ShaderNodeGroup") vcol_group_n.name = vcol_group_n.label = Sky.VCOL_GROUP_NODE - vcol_group_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1650) + vcol_group_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1800) vcol_group_n.node_tree = vcolor_input_ng.get_node_group() uv_map_n = node_tree.nodes.new("ShaderNodeUVMap") uv_map_n.name = uv_map_n.label = Sky.UV_MAP_NODE - uv_map_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1500) + uv_map_n.location = (start_pos_x - pos_x_shift * 2, start_pos_y + 1500) uv_map_n.uv_map = _MESH_consts.none_uv - sec_uv_map_n = node_tree.nodes.new("ShaderNodeUVMap") - sec_uv_map_n.name = sec_uv_map_n.label = Sky.SEC_UV_MAP_NODE - sec_uv_map_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) - sec_uv_map_n.uv_map = _MESH_consts.none_uv + uv_rescale_n = node_tree.nodes.new("ShaderNodeGroup") + uv_rescale_n.name = uv_rescale_n.label = Sky.RESCALE_UV_GROUP_NODE + uv_rescale_n.location = (start_pos_x - pos_x_shift * 1, start_pos_y + 1500) + uv_rescale_n.node_tree = uv_rescale_ng.get_node_group() + uv_rescale_n.inputs['Rescale Enabled'].default_value = 0 diff_col_n = node_tree.nodes.new("ShaderNodeRGB") diff_col_n.name = diff_col_n.label = Sky.DIFF_COL_NODE - diff_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1700) - - base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") - base_tex_n.name = base_tex_n.label = Sky.BASE_TEX_NODE - base_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1500) - base_tex_n.width = 140 - - over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") - over_tex_n.name = over_tex_n.label = Sky.OVER_TEX_NODE - over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) - over_tex_n.width = 140 - - mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") - mask_tex_n.name = mask_tex_n.label = Sky.MASK_TEX_NODE - mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) - mask_tex_n.width = 140 - - blend_input_n = node_tree.nodes.new("ShaderNodeValue") - blend_input_n.name = blend_input_n.label = Sky.BLEND_VAL_NODE - blend_input_n.location = (start_pos_x + pos_x_shift, start_pos_y + 600) - - mask_tex_sep_n = node_tree.nodes.new("ShaderNodeSeparateRGB") - mask_tex_sep_n.name = mask_tex_sep_n.label = Sky.MASK_TEX_SEP_NODE - mask_tex_sep_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 900) - - mask_factor_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") - mask_factor_mix_n.name = mask_factor_mix_n.label = Sky.MASK_FACTOR_MIX_NODE - mask_factor_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 900) - mask_factor_mix_n.blend_type = "MIX" - mask_factor_mix_n.inputs['Color1'].default_value = (1,) * 4 - mask_factor_mix_n.inputs['Color2'].default_value = (16,) * 4 - - mask_factor_blend_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") - mask_factor_blend_mult_n.name = mask_factor_blend_mult_n.label = Sky.MASK_FACTOR_BLEND_MULT_NODE - mask_factor_blend_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 900) - mask_factor_blend_mult_n.blend_type = "MULTIPLY" - mask_factor_blend_mult_n.inputs['Fac'].default_value = 1 - mask_factor_blend_mult_n.use_clamp = True - - base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") - base_over_mix_n.name = base_over_mix_n.label = Sky.BASE_OVER_MIX_NODE - base_over_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1400) - base_over_mix_n.blend_type = "MIX" - - base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") - base_over_a_mix_n.name = base_over_a_mix_n.label = Sky.BASE_OVER_A_MIX_NODE - base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1200) - base_over_a_mix_n.blend_type = "MIX" + diff_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2000) + + for tex_type_i, tex_type in enumerate(texture_types.get()): + Sky.__create_texel_component_nodes__(node_tree, tex_type, (start_pos_x + pos_x_shift, start_pos_y + 1500 - tex_type_i * 450)) + + base_a_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[0]] + base_b_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[1]] + over_a_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[2]] + over_b_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[3]] + + base_a_tex_final_alpha_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + texture_types.get()[0]] + base_b_tex_final_alpha_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + texture_types.get()[1]] + over_a_tex_final_alpha_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + texture_types.get()[2]] + over_b_tex_final_alpha_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + texture_types.get()[3]] vcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") vcol_mult_n.name = vcol_mult_n.label = Sky.VCOLOR_MULT_NODE - vcol_mult_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1500) + vcol_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1900) vcol_mult_n.operation = "MULTIPLY" + vcol_mult_n.inputs[1].default_value = (2,) * 3 diff_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") diff_mult_n.name = diff_mult_n.label = Sky.DIFF_MULT_NODE - diff_mult_n.location = (start_pos_x + pos_x_shift * 8, start_pos_y + 1650) + diff_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 2000) diff_mult_n.operation = "MULTIPLY" - diff_mult_n.inputs[1].default_value = (0, 0, 0) + + weather_base_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_base_mix_n.name = weather_base_mix_n.label = Sky.WEATHER_BASE_MIX_NODE + weather_base_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + weather_base_mix_n.blend_type = "MIX" + + weather_base_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_base_alpha_mix_n.name = weather_base_alpha_mix_n.label = Sky.WEATHER_BASE_A_MIX_NODE + weather_base_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1300) + weather_base_alpha_mix_n.blend_type = "MIX" + + weather_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_over_mix_n.name = weather_over_mix_n.label = Sky.WEATHER_OVER_MIX_NODE + weather_over_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 700) + weather_over_mix_n.blend_type = "MIX" + + weather_over_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_over_alpha_mix_n.name = weather_over_alpha_mix_n.label = Sky.WEATHER_OVER_A_MIX_NODE + weather_over_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 500) + weather_over_alpha_mix_n.blend_type = "MIX" + + weather_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_mix_n.name = weather_mix_n.label = Sky.WEATHER_MIX_NODE + weather_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1100) + weather_mix_n.blend_type = "MIX" + + weather_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_alpha_mix_n.name = weather_alpha_mix_n.label = Sky.WEATHER_A_MIX_NODE + weather_alpha_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 900) + weather_alpha_mix_n.blend_type = "MIX" + + weather_diff_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + weather_diff_mult_n.name = weather_diff_mult_n.label = Sky.WEATHER_DIFF_MULT_NODE + weather_diff_mult_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1500) + weather_diff_mult_n.operation = "MULTIPLY" + + opacity_stars_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") + opacity_stars_mix_n.name = opacity_stars_mix_n.label = Sky.OPACITY_STARS_MIX_NODE + opacity_stars_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 900) + opacity_stars_mix_n.operation = "MULTIPLY" + opacity_stars_mix_n.inputs[1].default_value = (1.0,) * 3 alpha_inv_n = node_tree.nodes.new("ShaderNodeMath") alpha_inv_n.name = alpha_inv_n.label = Sky.ALPHA_INV_NODE - alpha_inv_n.location = (start_pos_x + pos_x_shift * 9, 1300) + alpha_inv_n.location = (start_pos_x + pos_x_shift * 8, 1300) alpha_inv_n.operation = "SUBTRACT" alpha_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 alpha_inv_n.inputs[1].default_value = 1.0 @@ -153,55 +170,132 @@ def init(node_tree): out_shader_node = node_tree.nodes.new("ShaderNodeEeveeSpecular") out_shader_node.name = out_shader_node.label = Sky.OUT_SHADER_NODE - out_shader_node.location = (start_pos_x + pos_x_shift * 10, 1500) + out_shader_node.location = (start_pos_x + pos_x_shift * 9, 1500) out_shader_node.inputs["Base Color"].default_value = (0.0,) * 4 out_shader_node.inputs["Specular"].default_value = (0.0,) * 4 output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = output_n.label = Sky.OUTPUT_NODE - output_n.location = (start_pos_x + + pos_x_shift * 11, start_pos_y + 1500) + output_n.location = (start_pos_x + + pos_x_shift * 10, start_pos_y + 1500) # links creation - node_tree.links.new(base_tex_n.inputs['Vector'], uv_map_n.outputs['UV']) - node_tree.links.new(over_tex_n.inputs['Vector'], uv_map_n.outputs['UV']) - node_tree.links.new(mask_tex_n.inputs['Vector'], sec_uv_map_n.outputs['UV']) - # pass 1 - node_tree.links.new(mask_tex_sep_n.inputs['Image'], mask_tex_n.outputs['Color']) + node_tree.links.new(uv_rescale_n.inputs['UV'], uv_map_n.outputs['UV']) - # pass 2 - node_tree.links.new(mask_factor_mix_n.inputs['Fac'], mask_tex_sep_n.outputs['R']) + # pass 1, 2, 3 + for tex_type in texture_types.get(): + Sky.__create_texel_component_links__(node_tree, tex_type, node_tree.nodes[Sky.RESCALE_UV_GROUP_NODE].outputs['UV ' + tex_type]) # pass 3 - node_tree.links.new(mask_factor_blend_mult_n.inputs['Color1'], mask_factor_mix_n.outputs['Color']) - node_tree.links.new(mask_factor_blend_mult_n.inputs['Color2'], blend_input_n.outputs['Value']) + node_tree.links.new(vcol_mult_n.inputs[0], vcol_group_n.outputs['Vertex Color']) # pass 4 - node_tree.links.new(base_over_mix_n.inputs['Fac'], mask_factor_blend_mult_n.outputs['Color']) - node_tree.links.new(base_over_mix_n.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(base_over_mix_n.inputs['Color2'], over_tex_n.outputs['Color']) + node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs[0]) + node_tree.links.new(diff_mult_n.inputs[1], vcol_mult_n.outputs[0]) + + node_tree.links.new(weather_base_mix_n.inputs['Color1'], base_a_tex_final_n.outputs[0]) + node_tree.links.new(weather_base_mix_n.inputs['Color2'], base_b_tex_final_n.outputs[0]) - node_tree.links.new(base_over_a_mix_n.inputs['Fac'], mask_factor_blend_mult_n.outputs['Color']) - node_tree.links.new(base_over_a_mix_n.inputs['Color1'], base_tex_n.outputs['Alpha']) - node_tree.links.new(base_over_a_mix_n.inputs['Color2'], over_tex_n.outputs['Alpha']) + node_tree.links.new(weather_base_alpha_mix_n.inputs['Color1'], base_a_tex_final_alpha_n.outputs[0]) + node_tree.links.new(weather_base_alpha_mix_n.inputs['Color2'], base_b_tex_final_alpha_n.outputs[0]) + + node_tree.links.new(weather_over_mix_n.inputs['Color1'], over_a_tex_final_n.outputs[0]) + node_tree.links.new(weather_over_mix_n.inputs['Color2'], over_b_tex_final_n.outputs[0]) + + node_tree.links.new(weather_over_alpha_mix_n.inputs['Color1'], over_a_tex_final_alpha_n.outputs[0]) + node_tree.links.new(weather_over_alpha_mix_n.inputs['Color2'], over_b_tex_final_alpha_n.outputs[0]) # pass 5 - node_tree.links.new(vcol_mult_n.inputs[0], vcol_group_n.outputs['Vertex Color']) - node_tree.links.new(vcol_mult_n.inputs[1], base_over_mix_n.outputs['Color']) + node_tree.links.new(weather_mix_n.inputs['Color1'], weather_base_mix_n.outputs[0]) + node_tree.links.new(weather_mix_n.inputs['Color2'], weather_over_mix_n.outputs[0]) + + node_tree.links.new(weather_alpha_mix_n.inputs['Color1'], weather_base_alpha_mix_n.outputs[0]) + node_tree.links.new(weather_alpha_mix_n.inputs['Color2'], weather_over_alpha_mix_n.outputs[0]) # pass 6 - node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs['Color']) - node_tree.links.new(diff_mult_n.inputs[1], vcol_mult_n.outputs[0]) + node_tree.links.new(weather_diff_mult_n.inputs[0], diff_mult_n.outputs[0]) + node_tree.links.new(weather_diff_mult_n.inputs[1], weather_mix_n.outputs[0]) + + node_tree.links.new(opacity_stars_mix_n.inputs[0], weather_alpha_mix_n.outputs[0]) # pass 7 - node_tree.links.new(out_shader_node.inputs['Emissive Color'], diff_mult_n.outputs[0]) - node_tree.links.new(out_shader_node.inputs['Transparency'], alpha_inv_n.outputs['Value']) + node_tree.links.new(alpha_inv_n.inputs[1], opacity_stars_mix_n.outputs[0]) - node_tree.links.new(alpha_inv_n.inputs[1], base_over_a_mix_n.outputs['Color']) + # pass 8 + node_tree.links.new(out_shader_node.inputs['Emissive Color'], weather_diff_mult_n.outputs[0]) + node_tree.links.new(out_shader_node.inputs['Transparency'], alpha_inv_n.outputs['Value']) # output pass node_tree.links.new(output_n.inputs['Surface'], out_shader_node.outputs['BSDF']) + @staticmethod + def __create_texel_component_nodes__(node_tree, tex_type, location): + """Create one texel component nodes. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + :param tex_type: texture type of the component + :type tex_type: str + :param location: location of top left node + :type location: (int, int) + """ + + pos_x_shift = 185 + + separate_uv_n = node_tree.nodes.new("ShaderNodeSeparateXYZ") + separate_uv_n.name = separate_uv_n.label = Sky.SEPARATE_UV_NODE_PREFIX + tex_type + separate_uv_n.location = (location[0], location[1] + 150) + + texel_obb_n = node_tree.nodes.new("ShaderNodeMath") + texel_obb_n.name = texel_obb_n.label = Sky.TEX_OOB_BOOL_NODE_PREFIX + tex_type + texel_obb_n.location = (location[0] + pos_x_shift, location[1] + 150) + texel_obb_n.operation = "LESS_THAN" + + tex_n = node_tree.nodes.new("ShaderNodeTexImage") + tex_n.name = tex_n.label = Sky.TEX_NODE_PREFIX + tex_type + tex_n.location = (location[0], location[1]) + tex_n.width = 140 + + mix_col_n = node_tree.nodes.new("ShaderNodeMixRGB") + mix_col_n.name = mix_col_n.label = Sky.TEX_FINAL_MIX_NODE_PREFIX + tex_type + mix_col_n.location = (location[0] + pos_x_shift * 2, location[1] + 100) + mix_col_n.blend_type = "MIX" + mix_col_n.inputs['Color2'].default_value = (0.0,) * 4 + + mix_a_n = node_tree.nodes.new("ShaderNodeMixRGB") + mix_a_n.name = mix_a_n.label = Sky.TEX_FINAL_A_MIX_NODE_PREFIX + tex_type + mix_a_n.location = (location[0] + pos_x_shift * 2, location[1] - 100) + mix_a_n.blend_type = "MIX" + mix_a_n.inputs['Color2'].default_value = (0.0,) * 4 + + @staticmethod + def __create_texel_component_links__(node_tree, tex_type, uv_socket): + """Creates links for texel components. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + :param tex_type: texture type of the component + :type tex_type: str + :param uv_socket: UVs of the texture type + :type uv_socket: bpy.types.NodeSocketVector + """ + + separate_uv_n = node_tree.nodes[Sky.SEPARATE_UV_NODE_PREFIX + tex_type] + texel_obb_n = node_tree.nodes[Sky.TEX_OOB_BOOL_NODE_PREFIX + tex_type] + tex_n = node_tree.nodes[Sky.TEX_NODE_PREFIX + tex_type] + mix_col_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + tex_type] + mix_a_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + tex_type] + + node_tree.links.new(separate_uv_n.inputs[0], uv_socket) + + node_tree.links.new(texel_obb_n.inputs[0], separate_uv_n.outputs['Y']) + node_tree.links.new(tex_n.inputs[0], uv_socket) + + node_tree.links.new(mix_col_n.inputs['Fac'], texel_obb_n.outputs[0]) + node_tree.links.new(mix_col_n.inputs['Color1'], tex_n.outputs['Color']) + node_tree.links.new(mix_a_n.inputs['Fac'], texel_obb_n.outputs[0]) + node_tree.links.new(mix_a_n.inputs['Color1'], tex_n.outputs['Alpha']) + @staticmethod def finalize(node_tree, material): """Finalize node tree and material settings. Should be called as last. @@ -213,10 +307,12 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.blend_method = "BLEND" - if blend_over.is_set(node_tree): + if sky_stars.is_set(node_tree): material.blend_method = "BLEND" + if sky_back.is_set(node_tree): + material.blend_method = "OPAQUE" @staticmethod def set_diffuse(node_tree, color): @@ -233,35 +329,35 @@ def set_diffuse(node_tree, color): node_tree.nodes[Sky.DIFF_COL_NODE].outputs['Color'].default_value = color @staticmethod - def set_base_texture(node_tree, image): - """Set base texture to shader. + def set_sky_weather_base_a_texture(node_tree, image): + """Set base_a texture to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assignet to base texture node + :param image: texture image which should be assigned to texture node :type image: bpy.types.Image """ - node_tree.nodes[Sky.BASE_TEX_NODE].image = image + node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_base_a()].image = image @staticmethod - def set_base_texture_settings(node_tree, settings): - """Set base texture settings to shader. + def set_sky_weather_base_a_texture_settings(node_tree, settings): + """Set base_a texture settings to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.BASE_TEX_NODE], settings) + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_base_a()], settings) @staticmethod - def set_base_uv(node_tree, uv_layer): - """Set UV layer to base texture in shader. + def set_sky_weather_base_a_uv(node_tree, uv_layer): + """Set UV layer to base_a texture in shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for base texture + :param uv_layer: uv layer string used for base_a texture :type uv_layer: str """ @@ -271,81 +367,161 @@ def set_base_uv(node_tree, uv_layer): node_tree.nodes[Sky.UV_MAP_NODE].uv_map = uv_layer @staticmethod - def set_over_texture(node_tree, image): - """Set over texture to shader. + def set_sky_weather_base_b_texture(node_tree, image): + """Set base_b texture to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assigned to over texture node + :param image: texture image which should be assigned to texture node :type image: bpy.types.Image """ - node_tree.nodes[Sky.OVER_TEX_NODE].image = image + node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_base_b()].image = image @staticmethod - def set_over_texture_settings(node_tree, settings): - """Set over texture settings to shader. + def set_sky_weather_base_b_texture_settings(node_tree, settings): + """Set base_b texture settings to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.OVER_TEX_NODE], settings) + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_base_b()], settings) @staticmethod - def set_over_uv(node_tree, uv_layer): - """Set UV layer to over texture in shader. + def set_sky_weather_base_b_uv(node_tree, uv_layer): + """Set UV layer to base_b texture in shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for over texture + :param uv_layer: uv layer string used for base_b texture :type uv_layer: str """ - Sky.set_base_uv(node_tree, uv_layer) + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) @staticmethod - def set_mask_texture(node_tree, image): - """Set mask texture to shader. + def set_sky_weather_over_a_texture(node_tree, image): + """Set over_a texture to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assignet to mask texture node + :param image: texture image which should be assigned to texture node :type image: bpy.types.Image """ - node_tree.nodes[Sky.MASK_TEX_NODE].image = image + node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_over_a()].image = image @staticmethod - def set_mask_texture_settings(node_tree, settings): - """Set mask texture settings to shader. + def set_sky_weather_over_a_texture_settings(node_tree, settings): + """Set over_a texture settings to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.MASK_TEX_NODE], settings) + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_over_a()], settings) @staticmethod - def set_mask_uv(node_tree, uv_layer): - """Set UV layer to mask texture in shader. + def set_sky_weather_over_a_uv(node_tree, uv_layer): + """Set UV layer to over_a texture in shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for mask texture + :param uv_layer: uv layer string used for over_a texture :type uv_layer: str """ - if uv_layer is None or uv_layer == "": - uv_layer = _MESH_consts.none_uv + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + + @staticmethod + def set_sky_weather_over_b_texture(node_tree, image): + """Set over_b texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_over_b()].image = image + + @staticmethod + def set_sky_weather_over_b_texture_settings(node_tree, settings): + """Set over_b texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.TEX_NODE_PREFIX + texture_types.get_over_b()], settings) + + @staticmethod + def set_sky_weather_over_b_uv(node_tree, uv_layer): + """Set UV layer to over_b texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over_b texture + :type uv_layer: str + """ - node_tree.nodes[Sky.SEC_UV_MAP_NODE].uv_map = uv_layer + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) @staticmethod def set_aux0(node_tree, aux_property): - """Set layer blend factor. + """Set blend factors. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: layer blend factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + profile_blend = _math_utils.clamp(aux_property[0]['value']) + + node_tree.nodes[Sky.WEATHER_BASE_MIX_NODE].inputs['Fac'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_BASE_A_MIX_NODE].inputs['Fac'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_OVER_MIX_NODE].inputs['Fac'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_OVER_A_MIX_NODE].inputs['Fac'].default_value = profile_blend + + weather_blend = _math_utils.clamp(aux_property[1]['value']) + + node_tree.nodes[Sky.WEATHER_MIX_NODE].inputs['Fac'].default_value = weather_blend + node_tree.nodes[Sky.WEATHER_A_MIX_NODE].inputs['Fac'].default_value = weather_blend + + if sky_stars.is_set(node_tree): + stars_opacity = (_math_utils.clamp(aux_property[2]['value']),) * 3 + else: + stars_opacity = (1.0,) * 3 + + node_tree.nodes[Sky.OPACITY_STARS_MIX_NODE].inputs[1].default_value = stars_opacity + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set v cutoff factors. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: layer blend factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + for tex_type_i, tex_type in enumerate(texture_types.get()): + + if (not sky_stars.is_set(node_tree)) and (not sky_back.is_set(node_tree)): # enabled + v_cutoff = aux_property[tex_type_i]['value'] + else: # disabled + v_cutoff = float("-inf") + + node_tree.nodes[Sky.TEX_OOB_BOOL_NODE_PREFIX + tex_type].inputs[1].default_value = v_cutoff + + @staticmethod + def set_aux2(node_tree, aux_property): + """Set v scale factors. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -353,19 +529,41 @@ def set_aux0(node_tree, aux_property): :type aux_property: bpy.types.IDPropertyGroup """ - node_tree.nodes[Sky.BLEND_VAL_NODE].outputs[0].default_value = aux_property[0]['value'] + for tex_type_i, tex_type in enumerate(texture_types.get()): + node_tree.nodes[Sky.RESCALE_UV_GROUP_NODE].inputs['V Scale ' + tex_type].default_value = aux_property[tex_type_i]['value'] + + if sky_stars.is_set(node_tree): + rescale_enabled = 1.0 + else: + rescale_enabled = 0.0 + node_tree.nodes[Sky.RESCALE_UV_GROUP_NODE].inputs['Rescale Enabled'].default_value = rescale_enabled + + @staticmethod + def set_sky_stars_flavor(node_tree, switch_on): + """Set sky stars flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if it should be switched on or off + :type switch_on: bool + """ + + if switch_on: + sky_stars.init(node_tree) + else: + sky_stars.delete(node_tree) @staticmethod - def set_blend_over_flavor(node_tree, switch_on): - """Set blend over flavor to this shader. + def set_sky_back_flavor(node_tree, switch_on): + """Set sky back flavor to this shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param switch_on: flag indication if blend over should be switched on or off + :param switch_on: flag indication if it should be switched on or off :type switch_on: bool """ if switch_on: - blend_over.init(node_tree) + sky_back.init(node_tree) else: - blend_over.delete(node_tree) + sky_back.delete(node_tree) diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/texture_types.py b/addon/io_scs_tools/internals/shaders/eut2/sky/texture_types.py new file mode 100644 index 00000000..1a009537 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/texture_types.py @@ -0,0 +1,44 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + + +def get(): + """Returns texture types names used in eut2.sky shader. + + :return: set of names + :rtype: tuple + """ + return 'Base A', 'Base B', 'Over A', 'Over B' + + +def get_base_a(): + return get()[0] + + +def get_base_b(): + return get()[1] + + +def get_over_a(): + return get()[2] + + +def get_over_b(): + return get()[3] diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py new file mode 100644 index 00000000..34b1eedf --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py @@ -0,0 +1,175 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + +import bpy +from io_scs_tools.consts import Material as _MAT_consts +from io_scs_tools.internals.shaders.eut2.sky import texture_types + +SKY_UV_RESCALE_G = _MAT_consts.node_group_prefix + "SkyUVRescale" + +_RESCALE_INV_NODE = "Rescale Inv" +_SEP_UV_NODE = "Separate UV" +_MULT_V_SCALE_PREFIX = "V Scale Mult " +_COMBINE_V_SCALE_PREFIX = "Combine UV " +_MULT_RESCALED_UV_PREFIX = "Mult Rescaled UV " +_MULT_UNSCALED_UV_PREFIX = "Mult Unscaled UV " +_COMBINE_UV_PREFIX = "Final UV " + + +def get_node_group(): + """Gets node group for DDS 16-bit normal map texture. + + :return: node group which handles 16-bit DDS + :rtype: bpy.types.NodeGroup + """ + + if SKY_UV_RESCALE_G not in bpy.data.node_groups: + __create_node_group__() + + return bpy.data.node_groups[SKY_UV_RESCALE_G] + + +def __create_node_group__(): + """Creates node group for remapping alpha to weight + + Inputs: Alpha, Factor1, Factor2 + Outputs: Weighted Alpha + """ + + pos_x_shift = 185 + + rescale_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SKY_UV_RESCALE_G) + + # inputs defining + rescale_g.inputs.new("NodeSocketFloat", "Rescale Enabled") + rescale_g.inputs.new("NodeSocketFloat", "V Scale Base A") + rescale_g.inputs.new("NodeSocketFloat", "V Scale Base B") + rescale_g.inputs.new("NodeSocketFloat", "V Scale Over A") + rescale_g.inputs.new("NodeSocketFloat", "V Scale Over B") + rescale_g.inputs.new("NodeSocketVector", "UV") + input_n = rescale_g.nodes.new("NodeGroupInput") + input_n.location = (0, 0) + + # outputs defining + rescale_g.outputs.new("NodeSocketVector", "UV Base A") + rescale_g.outputs.new("NodeSocketVector", "UV Base B") + rescale_g.outputs.new("NodeSocketVector", "UV Over A") + rescale_g.outputs.new("NodeSocketVector", "UV Over B") + output_n = rescale_g.nodes.new("NodeGroupOutput") + output_n.location = (pos_x_shift * 9, 0) + + # create nodes + rescale_inv_n = rescale_g.nodes.new("ShaderNodeMath") + rescale_inv_n.name = rescale_inv_n.label = _RESCALE_INV_NODE + rescale_inv_n.location = (pos_x_shift * 2, 100) + rescale_inv_n.operation = "SUBTRACT" + rescale_inv_n.inputs[0].default_value = 1.0 + + separate_uv_n = rescale_g.nodes.new("ShaderNodeSeparateXYZ") + separate_uv_n.name = separate_uv_n.label = _SEP_UV_NODE + separate_uv_n.location = (pos_x_shift * 2, -100) + + # group links + rescale_g.links.new(rescale_inv_n.inputs[1], input_n.outputs['Rescale Enabled']) + rescale_g.links.new(separate_uv_n.inputs[0], input_n.outputs['UV']) + + # create components for each texture type + for tex_type_i, tex_type in enumerate(texture_types.get()): + __init_rescale_component__(rescale_g, + tex_type, + (pos_x_shift * 3, tex_type_i * -400 + 500), + input_n.outputs['UV'], + separate_uv_n.outputs['X'], + separate_uv_n.outputs['Y'], + input_n.outputs['V Scale ' + tex_type], + input_n.outputs['Rescale Enabled'], + rescale_inv_n.outputs[0], + output_n.inputs['UV ' + tex_type]) + + +def __init_rescale_component__(node_tree, uv_type, location, uv_socket, uv_x_socket, uv_y_socket, + v_scale_socket, rescale_socket, rescale_inv_socket, uv_output_socket): + """Creates nodes for one UV rescale component. + + :param node_tree: node tree of the uv rescale group + :type node_tree: bpy.types.NodeTree + :param uv_type: string for prefixing nodes + :type uv_type: str + :param location: location of the first component element + :type location: (int, int) + :param uv_socket: original UV + :type uv_socket: bpy.types.NodeSocketVector + :param uv_x_socket: U part of original UV + :type uv_x_socket: bpy.types.NodeSocketFloat + :param uv_y_socket: V part of original UV + :type uv_y_socket: bpy.types.NodeSocketFloat + :param v_scale_socket: v scale input parameter + :type v_scale_socket: bpy.types.NodeSocketFloat + :param rescale_socket: + :type rescale_socket: bpy.types.NodeSocketFloat + :param rescale_inv_socket: + :type rescale_inv_socket: bpy.types.NodeSocketFloat + :param uv_output_socket: + :type uv_output_socket: bpy.types.NodeSocketVector + """ + + pos_x_shift = 185 + + # create nodes + mult_vscale_n = node_tree.nodes.new("ShaderNodeMath") + mult_vscale_n.name = mult_vscale_n.label = _MULT_V_SCALE_PREFIX + uv_type + mult_vscale_n.location = (location[0] + pos_x_shift * 1, location[1] + 200) + mult_vscale_n.operation = "MULTIPLY" + + combine_vscale_uv_n = node_tree.nodes.new("ShaderNodeCombineXYZ") + combine_vscale_uv_n.name = combine_vscale_uv_n.label = _COMBINE_V_SCALE_PREFIX + uv_type + combine_vscale_uv_n.location = (location[0] + pos_x_shift * 2, location[1] + 200) + + mult_rescaled_uv_n = node_tree.nodes.new("ShaderNodeVectorMath") + mult_rescaled_uv_n.name = mult_rescaled_uv_n.label = _MULT_RESCALED_UV_PREFIX + uv_type + mult_rescaled_uv_n.location = (location[0] + pos_x_shift * 3, location[1] + 200) + mult_rescaled_uv_n.operation = "MULTIPLY" + + mult_unscaled_uv_n = node_tree.nodes.new("ShaderNodeVectorMath") + mult_unscaled_uv_n.name = mult_unscaled_uv_n.label = _MULT_UNSCALED_UV_PREFIX + uv_type + mult_unscaled_uv_n.location = (location[0] + pos_x_shift * 3, location[1]) + mult_unscaled_uv_n.operation = "MULTIPLY" + + combine_uv_n = node_tree.nodes.new("ShaderNodeVectorMath") + combine_uv_n.name = combine_uv_n.label = _COMBINE_UV_PREFIX + uv_type + combine_uv_n.location = (location[0] + pos_x_shift * 4, location[1] + 100) + combine_uv_n.operation = "ADD" + + # create links + node_tree.links.new(mult_vscale_n.inputs[0], uv_y_socket) + node_tree.links.new(mult_vscale_n.inputs[1], v_scale_socket) + + node_tree.links.new(combine_vscale_uv_n.inputs[0], uv_x_socket) + node_tree.links.new(combine_vscale_uv_n.inputs[1], mult_vscale_n.outputs[0]) + + node_tree.links.new(mult_rescaled_uv_n.inputs[0], combine_vscale_uv_n.outputs[0]) + node_tree.links.new(mult_rescaled_uv_n.inputs[1], rescale_socket) + node_tree.links.new(mult_unscaled_uv_n.inputs[0], uv_socket) + node_tree.links.new(mult_unscaled_uv_n.inputs[1], rescale_inv_socket) + + node_tree.links.new(combine_uv_n.inputs[0], mult_rescaled_uv_n.outputs[0]) + node_tree.links.new(combine_uv_n.inputs[1], mult_unscaled_uv_n.outputs[0]) + + node_tree.links.new(uv_output_socket, combine_uv_n.outputs[0]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py index a29a5ede..d46d2026 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py @@ -16,12 +16,13 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software import bpy from io_scs_tools.consts import Material as _MAT_consts from io_scs_tools.consts import SCSLigthing as _LIGHTING_consts -from io_scs_tools.internals.shaders.eut2.std_node_groups import fresnel_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import fresnel_legacy_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import fresnel_schlick_ng ADD_ENV_G = _MAT_consts.node_group_prefix + "AddEnvGroup" @@ -33,7 +34,9 @@ _TEX_FRESNEL_MULT_NODE = "TextureFresnelMultiplier" _GLOBAL_ENV_FACTOR_NODE = "GlobalEnvFactor" _GLOBAL_ENV_MULT_NODE = "GlobalEnvMultiplier" -_FRESNEL_GNODE = "FresnelGroup" +_FRESNEL_LEGACY_GNODE = "FresnelLegacyGroup" +_FRESNEL_SCHLICK_GNODE = "FresnelSchlickGroup" +_FRESNEL_TYPE_MIX_NODE = "Fresnel Mix" def get_node_group(): @@ -121,6 +124,7 @@ def __create_node_group__(): add_env_g.nodes.clear() # inputs defining + add_env_g.inputs.new("NodeSocketFloat", "Fresnel Type") add_env_g.inputs.new("NodeSocketFloat", "Fresnel Scale") add_env_g.inputs.new("NodeSocketFloat", "Fresnel Bias") add_env_g.inputs.new("NodeSocketVector", "Normal Vector") @@ -148,6 +152,19 @@ def __create_node_group__(): env_weight_mult_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y - 500) env_weight_mult_n.operation = "MULTIPLY" + fresnel_legacy_n = add_env_g.nodes.new("ShaderNodeGroup") + fresnel_legacy_n.name = fresnel_legacy_n.label = _FRESNEL_LEGACY_GNODE + fresnel_legacy_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 300) + fresnel_legacy_n.node_tree = fresnel_legacy_ng.get_node_group() + fresnel_legacy_n.inputs['Scale'].default_value = 0.9 + fresnel_legacy_n.inputs['Bias'].default_value = 0.2 + + fresnel_schlick_n = add_env_g.nodes.new("ShaderNodeGroup") + fresnel_schlick_n.name = fresnel_schlick_n.label = _FRESNEL_SCHLICK_GNODE + fresnel_schlick_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 100) + fresnel_schlick_n.node_tree = fresnel_schlick_ng.get_node_group() + fresnel_schlick_n.inputs['Bias'].default_value = 0.2 + env_spec_mult_n = add_env_g.nodes.new("ShaderNodeVectorMath") env_spec_mult_n.name = env_spec_mult_n.label = _ENV_SPEC_MULT_NODE env_spec_mult_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y - 350) @@ -158,18 +175,16 @@ def __create_node_group__(): refl_tex_mult_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y - 150) refl_tex_mult_n.operation = "MULTIPLY" + fresnel_type_mix_n = add_env_g.nodes.new("ShaderNodeMixRGB") + fresnel_type_mix_n.name = fresnel_type_mix_n.label = _FRESNEL_TYPE_MIX_NODE + fresnel_type_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 200) + fresnel_type_mix_n.blend_type = "MIX" + refl_tex_col_mult_n = add_env_g.nodes.new("ShaderNodeVectorMath") refl_tex_col_mult_n.name = refl_tex_col_mult_n.label = _REFL_TEX_COL_MULT_NODE refl_tex_col_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y - 200) refl_tex_col_mult_n.operation = "MULTIPLY" - fresnel_n = add_env_g.nodes.new("ShaderNodeGroup") - fresnel_n.name = fresnel_n.label = _FRESNEL_GNODE - fresnel_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 150) - fresnel_n.node_tree = fresnel_ng.get_node_group() - fresnel_n.inputs['Scale'].default_value = 0.9 - fresnel_n.inputs['Bias'].default_value = 0.2 - tex_fresnel_mult_n = add_env_g.nodes.new("ShaderNodeMixRGB") tex_fresnel_mult_n.name = tex_fresnel_mult_n.label = _TEX_FRESNEL_MULT_NODE tex_fresnel_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y) @@ -201,11 +216,19 @@ def __create_node_group__(): add_env_g.links.new(refl_tex_mult_n.inputs[0], input_n.outputs['Reflection Texture Color']) add_env_g.links.new(refl_tex_mult_n.inputs[1], input_n.outputs['Base Texture Alpha']) + add_env_g.links.new(fresnel_legacy_n.inputs['Reflection Normal Vector'], input_n.outputs['Reflection Normal Vector']) + add_env_g.links.new(fresnel_legacy_n.inputs['Normal Vector'], input_n.outputs['Normal Vector']) + add_env_g.links.new(fresnel_legacy_n.inputs['Scale'], input_n.outputs['Fresnel Scale']) + add_env_g.links.new(fresnel_legacy_n.inputs['Bias'], input_n.outputs['Fresnel Bias']) + + add_env_g.links.new(fresnel_schlick_n.inputs['Reflection Normal Vector'], input_n.outputs['Reflection Normal Vector']) + add_env_g.links.new(fresnel_schlick_n.inputs['Normal Vector'], input_n.outputs['Normal Vector']) + add_env_g.links.new(fresnel_schlick_n.inputs['Bias'], input_n.outputs['Fresnel Bias']) + # pass 3 - add_env_g.links.new(fresnel_n.inputs['Reflection Normal Vector'], input_n.outputs['Reflection Normal Vector']) - add_env_g.links.new(fresnel_n.inputs['Normal Vector'], input_n.outputs['Normal Vector']) - add_env_g.links.new(fresnel_n.inputs['Scale'], input_n.outputs['Fresnel Scale']) - add_env_g.links.new(fresnel_n.inputs['Bias'], input_n.outputs['Fresnel Bias']) + add_env_g.links.new(fresnel_type_mix_n.inputs['Fac'], input_n.outputs['Fresnel Type']) + add_env_g.links.new(fresnel_type_mix_n.inputs['Color1'], fresnel_legacy_n.outputs['Fresnel Factor']) + add_env_g.links.new(fresnel_type_mix_n.inputs['Color2'], fresnel_schlick_n.outputs['Fresnel Factor']) add_env_g.links.new(refl_tex_col_mult_n.inputs[0], refl_tex_mult_n.outputs[0]) add_env_g.links.new(refl_tex_col_mult_n.inputs[1], env_spec_mult_n.outputs[0]) @@ -213,7 +236,7 @@ def __create_node_group__(): # pass 4 add_env_g.links.new(tex_fresnel_mult_n.inputs['Fac'], input_n.outputs['Apply Fresnel']) add_env_g.links.new(tex_fresnel_mult_n.inputs['Color1'], refl_tex_col_mult_n.outputs[0]) - add_env_g.links.new(tex_fresnel_mult_n.inputs['Color2'], fresnel_n.outputs['Fresnel Factor']) + add_env_g.links.new(tex_fresnel_mult_n.inputs['Color2'], fresnel_type_mix_n.outputs['Color']) # pass 5 add_env_g.links.new(global_env_mult_n.inputs[0], tex_fresnel_mult_n.outputs['Color']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py similarity index 93% rename from addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_ng.py rename to addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py index 23059d94..7abd9eab 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py @@ -16,12 +16,12 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015: SCS Software +# Copyright (C) 2015-2021: SCS Software import bpy from io_scs_tools.consts import Material as _MAT_consts -FRESNEL_G = _MAT_consts.node_group_prefix + "FresnelGroup" +FRESNEL_LEGACY_G = _MAT_consts.node_group_prefix + "FresnelLegacyGroup" def get_node_group(): @@ -31,10 +31,10 @@ def get_node_group(): :rtype: bpy.types.NodeGroup """ - if FRESNEL_G not in bpy.data.node_groups: + if FRESNEL_LEGACY_G not in bpy.data.node_groups: __create_fresnel_group__() - return bpy.data.node_groups[FRESNEL_G] + return bpy.data.node_groups[FRESNEL_LEGACY_G] def __create_fresnel_group__(): @@ -44,7 +44,7 @@ def __create_fresnel_group__(): Outputs: Fresnel Factor """ - fresnel_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=FRESNEL_G) + fresnel_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=FRESNEL_LEGACY_G) # inputs defining fresnel_g.inputs.new("NodeSocketFloat", "Scale") diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py new file mode 100644 index 00000000..fa5233cb --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py @@ -0,0 +1,113 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + +import bpy +from io_scs_tools.consts import Material as _MAT_consts + +FRESNEL_SCHLICK_G = _MAT_consts.node_group_prefix + "FresnelSchlickGroup" + + +def get_node_group(): + """Gets node group for calcualtion of fresnel. + + :return: node group which calculates fresnel factor + :rtype: bpy.types.NodeGroup + """ + + if FRESNEL_SCHLICK_G not in bpy.data.node_groups: + __create_fresnel_group__() + + return bpy.data.node_groups[FRESNEL_SCHLICK_G] + + +def __create_fresnel_group__(): + """Creates fresnel group. + + Inputs: Scale, Bias, Reflection Normal Vector, Normal Vector + Outputs: Fresnel Factor + """ + + fresnel_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=FRESNEL_SCHLICK_G) + + pos_x_shift = 185 + + # inputs defining + fresnel_g.inputs.new("NodeSocketFloat", "Bias") + fresnel_g.inputs.new("NodeSocketVector", "Normal Vector") + fresnel_g.inputs.new("NodeSocketVector", "Reflection Normal Vector") + input_n = fresnel_g.nodes.new("NodeGroupInput") + input_n.location = (0, 0) + + # outputs defining + fresnel_g.outputs.new("NodeSocketFloat", "Fresnel Factor") + output_n = fresnel_g.nodes.new("NodeGroupOutput") + output_n.location = (pos_x_shift * 6, 0) + + # group nodes + dot_n = fresnel_g.nodes.new("ShaderNodeVectorMath") + dot_n.location = (pos_x_shift, 100) + dot_n.operation = "DOT_PRODUCT" + + subtract_dot_n = fresnel_g.nodes.new("ShaderNodeMath") + subtract_dot_n.location = (pos_x_shift * 2, 100) + subtract_dot_n.operation = "SUBTRACT" + subtract_dot_n.use_clamp = False + subtract_dot_n.inputs[0].default_value = 1.0 + + subtract_bias_n = fresnel_g.nodes.new("ShaderNodeMath") + subtract_bias_n.location = (pos_x_shift * 2, -100) + subtract_bias_n.operation = "SUBTRACT" + subtract_bias_n.use_clamp = False + subtract_bias_n.inputs[0].default_value = 1.0 + + pow5_bias_n = fresnel_g.nodes.new("ShaderNodeMath") + pow5_bias_n.location = (pos_x_shift * 3, 100) + pow5_bias_n.operation = "POWER" + pow5_bias_n.use_clamp = False + pow5_bias_n.inputs[1].default_value = 5.0 + + mult_pow5_bias_n = fresnel_g.nodes.new("ShaderNodeMath") + mult_pow5_bias_n.location = (pos_x_shift * 4, 50) + mult_pow5_bias_n.operation = "MULTIPLY" + mult_pow5_bias_n.use_clamp = False + + add_mult_bias_n = fresnel_g.nodes.new("ShaderNodeMath") + add_mult_bias_n.location = (pos_x_shift * 5, 0) + add_mult_bias_n.operation = "ADD" + add_mult_bias_n.use_clamp = True + + # group links + # formula: pow5(1 - dot(normal, reflection)) * (1 - bias) + bias + fresnel_g.links.new(dot_n.inputs[0], input_n.outputs['Normal Vector']) + fresnel_g.links.new(dot_n.inputs[1], input_n.outputs['Reflection Normal Vector']) + + fresnel_g.links.new(subtract_dot_n.inputs[1], dot_n.outputs['Value']) + + fresnel_g.links.new(subtract_bias_n.inputs[1], input_n.outputs['Bias']) + + fresnel_g.links.new(pow5_bias_n.inputs[0], subtract_dot_n.outputs['Value']) + + fresnel_g.links.new(mult_pow5_bias_n.inputs[0], pow5_bias_n.outputs['Value']) + fresnel_g.links.new(mult_pow5_bias_n.inputs[1], subtract_bias_n.outputs['Value']) + + fresnel_g.links.new(add_mult_bias_n.inputs[0], mult_pow5_bias_n.outputs['Value']) + fresnel_g.links.new(add_mult_bias_n.inputs[1], input_n.outputs['Bias']) + + fresnel_g.links.new(output_n.inputs['Fresnel Factor'], add_mult_bias_n.outputs['Value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index 1eec2330..15da572b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2019: SCS Software +# Copyright (C) 2019-2021: SCS Software import bpy from io_scs_tools.consts import LampTools as _LT_consts @@ -32,6 +32,7 @@ LAMPMASK_MIX_G = _MAT_consts.node_group_prefix + "LampmaskMixerGroup" +_ALPHA_DECODE_NODE = "Alpha Decode" _UV_DOT_X_NODE = "UV_X_Separator" _UV_DOT_Y_NODE = "UV_Y_Separator" _TEX_COL_SEP_NODE = "TexColorSeparator" @@ -77,29 +78,33 @@ def __create_node_group__(): output_n.location = (pos_x_shift * 9, 0) # nodes creation + alpha_decode_n = lampmask_g.nodes.new("ShaderNodeMath") + alpha_decode_n.name = alpha_decode_n.label = _TEX_COL_SEP_NODE + alpha_decode_n.location = (pos_x_shift, 50) + alpha_decode_n.operation = "POWER" + alpha_decode_n.inputs[1].default_value = 2.2 + tex_col_sep_n = lampmask_g.nodes.new("ShaderNodeSeparateRGB") - tex_col_sep_n.name = _TEX_COL_SEP_NODE - tex_col_sep_n.label = _TEX_COL_SEP_NODE + tex_col_sep_n.name = tex_col_sep_n.label = _TEX_COL_SEP_NODE tex_col_sep_n.location = (pos_x_shift, 400) uv_x_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") - uv_x_dot_n.name = _UV_DOT_X_NODE - uv_x_dot_n.label = _UV_DOT_X_NODE + uv_x_dot_n.name = uv_x_dot_n.label = _UV_DOT_X_NODE uv_x_dot_n.location = (pos_x_shift, -200) uv_x_dot_n.operation = "DOT_PRODUCT" uv_x_dot_n.inputs[1].default_value = (1.0, 0, 0) uv_y_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") - uv_y_dot_n.name = _UV_DOT_Y_NODE - uv_y_dot_n.label = _UV_DOT_Y_NODE + uv_y_dot_n.name = uv_y_dot_n.label = _UV_DOT_Y_NODE uv_y_dot_n.location = (pos_x_shift, -450) uv_y_dot_n.operation = "DOT_PRODUCT" uv_y_dot_n.inputs[1].default_value = (0, 1.0, 0) # links creation - lampmask_g.links.new(tex_col_sep_n.inputs["Image"], input_n.outputs["Lampmask Tex Color"]) - lampmask_g.links.new(uv_x_dot_n.inputs[0], input_n.outputs["UV Vector"]) - lampmask_g.links.new(uv_y_dot_n.inputs[0], input_n.outputs["UV Vector"]) + lampmask_g.links.new(alpha_decode_n.inputs[0], input_n.outputs['Lampmask Tex Alpha']) + lampmask_g.links.new(tex_col_sep_n.inputs['Image'], input_n.outputs['Lampmask Tex Color']) + lampmask_g.links.new(uv_x_dot_n.inputs[0], input_n.outputs['UV Vector']) + lampmask_g.links.new(uv_y_dot_n.inputs[0], input_n.outputs['UV Vector']) nodes_for_addition = [] @@ -144,7 +149,7 @@ def __create_node_group__(): pos_y -= 100 __init_vehicle_switch_nodes__(lampmask_g, - input_n.outputs["Lampmask Tex Alpha"], + alpha_decode_n.outputs[0], tex_col_sep_n.outputs["R"], tex_col_sep_n.outputs["G"], tex_col_sep_n.outputs["B"], @@ -158,7 +163,7 @@ def __create_node_group__(): for aux_lamp_type in AUX_LAMP_TYPES: __init_aux_switch_nodes__(lampmask_g, - input_n.outputs["Lampmask Tex Alpha"], + alpha_decode_n.outputs[0], tex_col_sep_n.outputs["R"], tex_col_sep_n.outputs["G"], aux_lamp_type, @@ -172,7 +177,7 @@ def __create_node_group__(): for traffic_light_type in TRAFFIC_LIGHT_TYPES: __init_traffic_light_switch_nodes__(lampmask_g, - input_n.outputs["Lampmask Tex Alpha"], + alpha_decode_n.outputs[0], traffic_light_type, pos_x_shift * 5, pos_y, nodes_for_addition) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py index 779aba76..3834702e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py @@ -16,7 +16,7 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software from io_scs_tools.internals.shaders.eut2.std_node_groups import add_env_ng from io_scs_tools.internals.shaders.eut2.std_node_groups import refl_normal_ng @@ -78,12 +78,14 @@ def add(node_tree, geom_n_name, spec_col_socket, alpha_socket, final_normal_sock add_env_n.name = add_env_n.label = StdAddEnv.ADD_ENV_GROUP_NODE add_env_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2300) add_env_n.node_tree = add_env_ng.get_node_group() + add_env_n.inputs['Fresnel Type'].default_value = 0.0 add_env_n.inputs['Apply Fresnel'].default_value = 1.0 add_env_n.inputs['Fresnel Scale'].default_value = 0.9 add_env_n.inputs['Fresnel Bias'].default_value = 0.2 - add_env_n.inputs['Base Texture Alpha'].default_value = 0.5 + add_env_n.inputs['Base Texture Alpha'].default_value = 1.0 add_env_n.inputs['Weighted Color'].default_value = (1.0,) * 4 add_env_n.inputs['Strength Multiplier'].default_value = 1.0 + add_env_n.inputs['Specular Color'].default_value = (1.0,) * 4 # geometry links node_tree.links.new(refl_normal_n.inputs['Incoming'], geometry_n.outputs['Incoming']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_passes/lum.py b/addon/io_scs_tools/internals/shaders/eut2/std_passes/lum.py new file mode 100644 index 00000000..73b2fbea --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/std_passes/lum.py @@ -0,0 +1,149 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + +from io_scs_tools.internals.shaders.eut2 import parameters + + +class StdLum: + LUM_A_DECODE_NODE = "LumDecodeGammaLumScale" + LUM_COL_MODULATE_NODE = "LumColModulate" + LUM_A_MODULATE_NODE = "LumAModulate" + LUM_COL_LVCOL_MULT_NODE = "LumColVColModulate" + LUM_A_LVCOL_MULT_NODE = "LumAVColModulate" + LUM_RESULT_MULT_NODE = "LumResult=RGB*A" + LUM_MIX_FINAL_NODE = "LumOutFinal=LitResult+LuminanceResult" + LUM_A_INVERSE_NODE = "LumTransp=1-Alpha" + LUM_OUT_SHADER_NODE = "LumShader" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def add(node_tree, base_texel_socket, base_texel_a_socket, lit_result_col_socket, lit_result_a_socket, output_socket): + """Add add env pass to node tree with links. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + :param base_texel_socket: base texel node socket from which color will be taken + :type base_texel_socket: bpy.type.NodeSocket + :param base_texel_a_socket: socket from which alpha will be taken (if None it won't be used) + :type base_texel_a_socket: bpy.type.NodeSocket + :param lit_result_col_socket: lit result node socket from which color will be taken + :type lit_result_col_socket: bpy.type.NodeSocket + :param lit_result_a_socket: lit result alpha node socket from which alpha will be taken + :type lit_result_a_socket: bpy.type.NodeSocket + :param output_socket: output socket to which result will be given + :type output_socket: bpy.type.NodeSocket + """ + + pos_x_shift = 185 + + output_n = output_socket.node + output_n.location.x += pos_x_shift * 7 + + # node creation + lum_a_decode_n = node_tree.nodes.new("ShaderNodeMath") + lum_a_decode_n.name = lum_a_decode_n.label = StdLum.LUM_A_DECODE_NODE + lum_a_decode_n.location = (output_n.location.x - pos_x_shift * 6, output_n.location.y - 200) + lum_a_decode_n.operation = "MULTIPLY" + + lum_col_modul_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_col_modul_n.name = lum_col_modul_n.label = StdLum.LUM_COL_MODULATE_NODE + lum_col_modul_n.location = (output_n.location.x - pos_x_shift * 5, output_n.location.y) + lum_col_modul_n.operation = "MULTIPLY" + lum_col_modul_n.inputs[0].default_value = (1,) * 3 + + lum_a_modul_n = node_tree.nodes.new("ShaderNodeMath") + lum_a_modul_n.name = lum_a_modul_n.label = StdLum.LUM_A_MODULATE_NODE + lum_a_modul_n.location = (output_n.location.x - pos_x_shift * 5, output_n.location.y - 200) + lum_a_modul_n.operation = "MULTIPLY" + lum_a_modul_n.inputs[0].default_value = 1 + + lum_col_lvcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_col_lvcol_mult_n.name = lum_col_lvcol_mult_n.label = StdLum.LUM_COL_LVCOL_MULT_NODE + lum_col_lvcol_mult_n.location = (output_n.location.x - pos_x_shift * 4, output_n.location.y) + lum_col_lvcol_mult_n.operation = "MULTIPLY" + lum_col_lvcol_mult_n.inputs[1].default_value = (1,) * 3 + + lum_a_lvcol_mult_n = node_tree.nodes.new("ShaderNodeMath") + lum_a_lvcol_mult_n.name = lum_a_lvcol_mult_n.label = StdLum.LUM_A_LVCOL_MULT_NODE + lum_a_lvcol_mult_n.location = (output_n.location.x - pos_x_shift * 4, output_n.location.y - 200) + lum_a_lvcol_mult_n.operation = "MULTIPLY" + lum_a_lvcol_mult_n.inputs[1].default_value = 1 + + lum_result_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_result_mult_n.name = lum_result_mult_n.label = StdLum.LUM_RESULT_MULT_NODE + lum_result_mult_n.location = (output_n.location.x - pos_x_shift * 3, output_n.location.y) + lum_result_mult_n.operation = "MULTIPLY" + + lum_mix_final_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_mix_final_n.name = lum_mix_final_n.label = StdLum.LUM_MIX_FINAL_NODE + lum_mix_final_n.location = (output_n.location.x - pos_x_shift * 2, output_n.location.y) + lum_mix_final_n.operation = "ADD" + + lum_a_inv_n = node_tree.nodes.new("ShaderNodeMath") + lum_a_inv_n.name = lum_a_inv_n.label = StdLum.LUM_A_INVERSE_NODE + lum_a_inv_n.location = (output_n.location.x - pos_x_shift * 2, output_n.location.y - 200) + lum_a_inv_n.operation = "SUBTRACT" + lum_a_inv_n.use_clamp = True + lum_a_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 + + lum_out_shader_n = node_tree.nodes.new("ShaderNodeEeveeSpecular") + lum_out_shader_n.name = lum_out_shader_n.label = StdLum.LUM_OUT_SHADER_NODE + lum_out_shader_n.location = (output_n.location.x - pos_x_shift * 1, output_n.location.y) + lum_out_shader_n.inputs["Base Color"].default_value = (0.0,) * 4 + lum_out_shader_n.inputs["Specular"].default_value = (0.0,) * 4 + + # geometry links + node_tree.links.new(lum_a_decode_n.inputs[0], base_texel_a_socket) + node_tree.links.new(lum_a_decode_n.inputs[1], base_texel_a_socket) + + node_tree.links.new(lum_col_modul_n.inputs[1], base_texel_socket) + node_tree.links.new(lum_a_modul_n.inputs[1], lum_a_decode_n.outputs[0]) + + node_tree.links.new(lum_col_lvcol_mult_n.inputs[0], lum_col_modul_n.outputs[0]) + node_tree.links.new(lum_a_lvcol_mult_n.inputs[0], lum_a_modul_n.outputs[0]) + + node_tree.links.new(lum_result_mult_n.inputs[0], lum_col_lvcol_mult_n.outputs[0]) + node_tree.links.new(lum_result_mult_n.inputs[1], lum_a_lvcol_mult_n.outputs[0]) + + node_tree.links.new(lum_mix_final_n.inputs[0], lit_result_col_socket) + node_tree.links.new(lum_mix_final_n.inputs[1], lum_result_mult_n.outputs[0]) + node_tree.links.new(lum_a_inv_n.inputs[1], lit_result_a_socket) + + node_tree.links.new(lum_out_shader_n.inputs['Emissive Color'], lum_mix_final_n.outputs[0]) + node_tree.links.new(lum_out_shader_n.inputs['Transparency'], lum_a_inv_n.outputs[0]) + + node_tree.links.new(output_socket, lum_out_shader_n.outputs['BSDF']) + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminosity boost factor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity output represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + luminance_boost = aux_property[0]['value'] + node_tree.nodes[StdLum.LUM_A_MODULATE_NODE].inputs[0].default_value = parameters.get_material_luminosity(luminance_boost) diff --git a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py index 6107794a..35e7ef5f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py @@ -16,9 +16,10 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_truckpaint from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils @@ -72,6 +73,9 @@ def init(node_tree): # init parent DifSpecAddEnv.init(node_tree) + # set fresnel type to schlick + node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE].inputs['Fresnel Type'].default_value = 1.0 + base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] env_color_n = node_tree.nodes[DifSpecAddEnv.ENV_COLOR_NODE] spec_color_n = node_tree.nodes[DifSpecAddEnv.SPEC_COL_NODE] @@ -273,6 +277,20 @@ def init_colormask_or_airbrush(node_tree): node_tree.links.new(paint_diff_mult_n.inputs['Color2'], blend_mix_n.outputs['Color']) node_tree.links.new(paint_spec_mult_n.inputs['Color2'], paint_tex_n.outputs['Alpha']) + @staticmethod + def set_fresnel(node_tree, bias_scale): + """Set fresnel bias and scale value to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param bias_scale: bias and scale factors as tuple: (bias, scale) + :type bias_scale: (float, float) + """ + + bias_scale_truckpaint = get_fresnel_truckpaint(bias_scale[0], bias_scale[1]) + + DifSpecAddEnv.set_fresnel(node_tree, bias_scale_truckpaint) + @staticmethod def set_base_paint_color(node_tree, color): """Set paint color to this node tree if basic truckpaint shader is used (no colormask or airbrush or altuv) diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py index fd36f938..9fb86490 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py @@ -21,6 +21,7 @@ from mathutils import Color from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.eut2 import parameters from io_scs_tools.internals.shaders.flavors import alpha_test from io_scs_tools.internals.shaders.flavors import blend_over from io_scs_tools.internals.shaders.flavors import blend_add @@ -35,6 +36,7 @@ class UnlitTex(BaseShader): UV_MAP_NODE = "UVMap" BASE_TEX_NODE = "BaseTex" TEX_MULT_NODE = "TextureMultiplier" + LUM_MULT_NODE = "LuminanceMultiplier" ALPHA_INV_NODE = "AlphaInv" OUT_SHADER_NODE = "OutShader" OUTPUT_NODE = "Output" @@ -77,6 +79,12 @@ def init(node_tree): tex_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1500) tex_mult_n.operation = "MULTIPLY" + lum_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_mult_n.name = lum_mult_n.label = UnlitTex.LUM_MULT_NODE + lum_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + lum_mult_n.operation = "MULTIPLY" + lum_mult_n.inputs[0].default_value = (1.0,) * 3 + alpha_inv_n = node_tree.nodes.new("ShaderNodeMath") alpha_inv_n.name = alpha_inv_n.label = UnlitTex.ALPHA_INV_NODE alpha_inv_n.location = (start_pos_x + pos_x_shift * 4, 1300) @@ -103,7 +111,9 @@ def init(node_tree): node_tree.links.new(alpha_inv_n.inputs[1], base_tex_n.outputs['Alpha']) - node_tree.links.new(out_shader_node.inputs['Emissive Color'], tex_mult_n.outputs[0]) + node_tree.links.new(lum_mult_n.inputs[1], tex_mult_n.outputs[0]) + + node_tree.links.new(out_shader_node.inputs['Emissive Color'], lum_mult_n.outputs[0]) node_tree.links.new(out_shader_node.inputs['Transparency'], alpha_inv_n.outputs['Value']) node_tree.links.new(output_n.inputs['Surface'], out_shader_node.outputs['BSDF']) @@ -173,6 +183,19 @@ def set_queue_bias(node_tree, value): pass # NOTE: shadow bias won't be visualized as game uses it's own implementation + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminosity boost factor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity output represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + luminance_boost = aux_property[0]['value'] + node_tree.nodes[UnlitTex.LUM_MULT_NODE].inputs[0].default_value = (parameters.get_material_luminosity(luminance_boost),) * 3 + @staticmethod def set_base_texture(node_tree, image): """Set base texture to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/a8.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/a8.py deleted file mode 100644 index bb6c3f2d..00000000 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/a8.py +++ /dev/null @@ -1,62 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# Copyright (C) 2019: SCS Software - -from io_scs_tools.internals.shaders.eut2.unlit_tex import UnlitTex - - -class UnlitTexA8(UnlitTex): - - @staticmethod - def get_name(): - """Get name of this shader file with full modules path.""" - return __name__ - - @staticmethod - def init(node_tree): - """Initialize node tree with links for this shader. - - Color result in this shader is always (0,0,0) and transparency is donate by grayscale texture and not alpha channel. - - :param node_tree: node tree on which this shader should be created - :type node_tree: bpy.types.NodeTree - """ - - # init parent - UnlitTex.init(node_tree) - - # remove texture multiplier, which will break link for resulting color - node_tree.nodes.remove(node_tree.nodes[UnlitTex.TEX_MULT_NODE]) - - # then set output color to (0,0,0) - node_tree.nodes[UnlitTex.OUT_SHADER_NODE].inputs['Emissive Color'].default_value = (0,) * 4 - - # and instead alpha use color channel (as this shader requests grayscale) - node_tree.links.new(node_tree.nodes[UnlitTex.ALPHA_INV_NODE].inputs[1], node_tree.nodes[UnlitTex.BASE_TEX_NODE].outputs['Color']) - - @staticmethod - def set_paint_flavor(node_tree, switch_on): - """Set paint flavor to this shader. - - :param node_tree: node tree of current shader - :type node_tree: bpy.types.NodeTree - :param switch_on: flag indication if flavor should be switched on or off - :type switch_on: bool - """ - pass # no matter the color, we always output (0,0,0) opaque color in this shader, thus useless to do anything diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py index d23e4efa..f76fe96f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py @@ -21,6 +21,7 @@ from mathutils import Color from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.eut2 import parameters from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng from io_scs_tools.internals.shaders.flavors import alpha_test from io_scs_tools.internals.shaders.flavors import awhite @@ -36,10 +37,12 @@ class UnlitVcolTex(BaseShader): DIFF_COL_NODE = "DiffuseColor" UVMAP_NODE = "UVMap" VCOL_GROUP_NODE = "VColorGroup" + VCOLOR_SCALE_NODE = "VertexColorScale" OPACITY_NODE = "OpacityMultiplier" BASE_TEX_NODE = "BaseTex" DIFF_MULT_NODE = "DiffMultiplier" TEX_MULT_NODE = "TextureMultiplier" + LUM_MULT_NODE = "LuminanceMultiplier" ALPHA_INV_NODE = "AlphaInv" OUT_SHADER_NODE = "OutShader" OUTPUT_NODE = "Output" @@ -79,7 +82,7 @@ def init(node_tree): opacity_n = node_tree.nodes.new("ShaderNodeMath") opacity_n.name = opacity_n.label = UnlitVcolTex.OPACITY_NODE - opacity_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + opacity_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1300) opacity_n.operation = "MULTIPLY" base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") @@ -87,20 +90,32 @@ def init(node_tree): base_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1500) base_tex_n.width = 140 + vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_scale_n.name = vcol_scale_n.label = UnlitVcolTex.VCOLOR_SCALE_NODE + vcol_scale_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1600) + vcol_scale_n.operation = "MULTIPLY" + vcol_scale_n.inputs[1].default_value = (2.0,) * 3 + diff_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") diff_mult_n.name = diff_mult_n.label = UnlitVcolTex.DIFF_MULT_NODE - diff_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1700) + diff_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1700) diff_mult_n.operation = "MULTIPLY" diff_mult_n.inputs[1].default_value = (0, 0, 0) tex_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") tex_mult_n.name = tex_mult_n.label = UnlitVcolTex.TEX_MULT_NODE - tex_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + tex_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1500) tex_mult_n.operation = "MULTIPLY" + lum_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + lum_mult_n.name = lum_mult_n.label = UnlitVcolTex.LUM_MULT_NODE + lum_mult_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1500) + lum_mult_n.operation = "MULTIPLY" + lum_mult_n.inputs[0].default_value = (1.0,) * 3 + alpha_inv_n = node_tree.nodes.new("ShaderNodeMath") alpha_inv_n.name = alpha_inv_n.label = UnlitVcolTex.ALPHA_INV_NODE - alpha_inv_n.location = (start_pos_x + pos_x_shift * 5, 1300) + alpha_inv_n.location = (start_pos_x + pos_x_shift * 7, 1300) alpha_inv_n.operation = "SUBTRACT" alpha_inv_n.inputs[0].default_value = 0.999999 # TODO: change back to 1.0 after bug is fixed: https://developer.blender.org/T71426 alpha_inv_n.inputs[1].default_value = 1.0 @@ -108,29 +123,33 @@ def init(node_tree): out_shader_node = node_tree.nodes.new("ShaderNodeEeveeSpecular") out_shader_node.name = out_shader_node.label = UnlitVcolTex.OUT_SHADER_NODE - out_shader_node.location = (start_pos_x + pos_x_shift * 6, 1500) + out_shader_node.location = (start_pos_x + pos_x_shift * 8, 1500) out_shader_node.inputs["Base Color"].default_value = (0.0,) * 4 out_shader_node.inputs["Specular"].default_value = (0.0,) * 4 output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = output_n.label = UnlitVcolTex.OUTPUT_NODE - output_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1500) + output_n.location = (start_pos_x + pos_x_shift * 9, start_pos_y + 1500) # links creation node_tree.links.new(base_tex_n.inputs['Vector'], uv_n.outputs['UV']) - node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs['Color']) - node_tree.links.new(diff_mult_n.inputs[1], vcol_group_n.outputs['Vertex Color']) + node_tree.links.new(vcol_scale_n.inputs[0], vcol_group_n.outputs['Vertex Color']) node_tree.links.new(opacity_n.inputs[0], base_tex_n.outputs["Alpha"]) node_tree.links.new(opacity_n.inputs[1], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs['Color']) + node_tree.links.new(diff_mult_n.inputs[1], vcol_scale_n.outputs[0]) + node_tree.links.new(tex_mult_n.inputs[0], diff_mult_n.outputs[0]) node_tree.links.new(tex_mult_n.inputs[1], base_tex_n.outputs['Color']) node_tree.links.new(alpha_inv_n.inputs[1], opacity_n.outputs['Value']) - node_tree.links.new(out_shader_node.inputs['Emissive Color'], tex_mult_n.outputs[0]) + node_tree.links.new(lum_mult_n.inputs[1], tex_mult_n.outputs[0]) + + node_tree.links.new(out_shader_node.inputs['Emissive Color'], lum_mult_n.outputs[0]) node_tree.links.new(out_shader_node.inputs['Transparency'], alpha_inv_n.outputs['Value']) node_tree.links.new(output_n.inputs['Surface'], out_shader_node.outputs['BSDF']) @@ -202,6 +221,20 @@ def set_queue_bias(node_tree, value): pass # NOTE: shadow bias won't be visualized as game uses it's own implementation + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminosity boost factor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity output represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + luminance_boost = aux_property[0]['value'] + node_tree.nodes[UnlitVcolTex.LUM_MULT_NODE].inputs[0].default_value = (parameters.get_material_luminosity(luminance_boost),) * 3 + + @staticmethod def set_base_texture(node_tree, image): """Set base texture to shader. @@ -340,7 +373,7 @@ def set_awhite_flavor(node_tree, switch_on): if switch_on: out_shader_node = node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE] from_mix_factor = node_tree.nodes[UnlitVcolTex.OPACITY_NODE].outputs[0] - from_color_socket = node_tree.nodes[UnlitVcolTex.TEX_MULT_NODE].outputs[0] + from_color_socket = node_tree.nodes[UnlitVcolTex.LUM_MULT_NODE].outputs[0] # remove link to transparency as awhite sets alpha to 1 if out_shader_node.inputs['Transparency'].links: diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/day.py b/addon/io_scs_tools/internals/shaders/eut2/window/day.py deleted file mode 100644 index b718f495..00000000 --- a/addon/io_scs_tools/internals/shaders/eut2/window/day.py +++ /dev/null @@ -1,100 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# Copyright (C) 2015-2019: SCS Software - -from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv -from io_scs_tools.internals.shaders.eut2.window import window_uv_offset_ng - - -class WindowDay(DifSpecAddEnv): - @staticmethod - def get_name(): - """Get name of this shader file with full modules path.""" - return __name__ - - @staticmethod - def init(node_tree): - """Initialize node tree with links for this shader. - - :param node_tree: node tree on which this shader should be created - :type node_tree: bpy.types.NodeTree - """ - - start_pos_x = 0 - start_pos_y = 0 - - # init parent - DifSpecAddEnv.init(node_tree) - - geom_n = node_tree.nodes[DifSpecAddEnv.GEOM_NODE] - uv_map_n = node_tree.nodes[DifSpecAddEnv.UVMAP_NODE] - spec_col_n = node_tree.nodes[DifSpecAddEnv.SPEC_COL_NODE] - base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] - compose_lighting_n = node_tree.nodes[DifSpecAddEnv.COMPOSE_LIGHTING_NODE] - - node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.SPEC_MULT_NODE]) - - # create nodes - uv_recalc_n = node_tree.nodes.new("ShaderNodeGroup") - uv_recalc_n.name = window_uv_offset_ng.WINDOW_UV_OFFSET_G - uv_recalc_n.label = window_uv_offset_ng.WINDOW_UV_OFFSET_G - uv_recalc_n.location = (start_pos_x, start_pos_y + 1500) - uv_recalc_n.node_tree = window_uv_offset_ng.get_node_group() - - # create links - node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_col_n.outputs['Color']) - - node_tree.links.new(uv_recalc_n.inputs['UV'], uv_map_n.outputs['UV']) - node_tree.links.new(uv_recalc_n.inputs['Normal'], geom_n.outputs['Normal']) - node_tree.links.new(uv_recalc_n.inputs['Incoming'], geom_n.outputs['Incoming']) - - node_tree.links.new(base_tex_n.inputs['Vector'], uv_recalc_n.outputs['UV Final']) - - @staticmethod - def set_lightmap_texture(node_tree, texture): - """Set lightmap texture to shader. - - :param node_tree: node tree of current shader - :type node_tree: bpy.types.NodeTree - :param texture: texture which should be assignet to lightmap texture node - :type texture: bpy.types.Texture - """ - pass # NOTE: light map texture is not used in day version of effect, so pass it - - @staticmethod - def set_lightmap_texture_settings(node_tree, settings): - """Set lightmap texture settings to shader. - - :param node_tree: node tree of current shader - :type node_tree: bpy.types.NodeTree - :param settings: binary string of TOBJ settings gotten from tobj import - :type settings: str - """ - pass # NOTE: light map texture is not used in day version of effect, so pass it - - @staticmethod - def set_lightmap_uv(node_tree, uv_layer): - """Set UV layer to lightmap texture in shader. - - :param node_tree: node tree of current shader - :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for lightmap texture - :type uv_layer: str - """ - pass # NOTE: light map texture is not used in day version of effect, so pass it diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/night.py b/addon/io_scs_tools/internals/shaders/eut2/window/lit.py similarity index 52% rename from addon/io_scs_tools/internals/shaders/eut2/window/night.py rename to addon/io_scs_tools/internals/shaders/eut2/window/lit.py index 122e7103..d903590b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/night.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/lit.py @@ -16,19 +16,23 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2015-2019: SCS Software +# Copyright (C) 2015-2021: SCS Software from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_window from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv from io_scs_tools.internals.shaders.eut2.window import window_uv_offset_ng from io_scs_tools.utils import material as _material_utils -class WindowNight(DifSpecAddEnv): +class WindowLit(DifSpecAddEnv): SEC_UV_MAP = "SecUVMap" LIGHTMAP_TEX_NODE = "LightmapTex" - BASE_LIGHTMAP_MULT_NODE = "BaseLightmapMultiplier" - BASE_ALPHA_MULT_NODE = "BaseAlphaMultiplier" + INTERIOR_RGB_NODE = "InteriorRgb" + INTERIOR_LIGHT_LUM_NODE = "InteriorLightLuminosity" + INTERIOR_LIGHT_FINAL_NODE = "InteriorLightFinal" + FINAL_MIX_NODE = "Final=LitResult+InteriorLight" + WINDOW_OUT_SHADER_NODE = "WindowOutShader" @staticmethod def get_name(): @@ -51,46 +55,66 @@ def init(node_tree): # init parent DifSpecAddEnv.init(node_tree) + # set fresnel type to schlick + node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE].inputs['Fresnel Type'].default_value = 1.0 + uv_map_n = node_tree.nodes[DifSpecAddEnv.UVMAP_NODE] geom_n = node_tree.nodes[DifSpecAddEnv.GEOM_NODE] + diff_col_n = node_tree.nodes[DifSpecAddEnv.DIFF_COL_NODE] spec_col_n = node_tree.nodes[DifSpecAddEnv.SPEC_COL_NODE] base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] - diff_mult_n = node_tree.nodes[DifSpecAddEnv.DIFF_MULT_NODE] compose_lighting_n = node_tree.nodes[DifSpecAddEnv.COMPOSE_LIGHTING_NODE] + output_n = node_tree.nodes[DifSpecAddEnv.OUTPUT_NODE] + + # move existing + output_n.location.x += pos_x_shift * 2 # remove existing node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.SPEC_MULT_NODE]) - node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.VCOL_GROUP_NODE]) - node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.VCOLOR_SCALE_NODE]) - node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.VCOLOR_MULT_NODE]) node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.OPACITY_NODE]) # create nodes uv_recalc_n = node_tree.nodes.new("ShaderNodeGroup") - uv_recalc_n.name = window_uv_offset_ng.WINDOW_UV_OFFSET_G - uv_recalc_n.label = window_uv_offset_ng.WINDOW_UV_OFFSET_G + uv_recalc_n.name = uv_recalc_n.label = window_uv_offset_ng.WINDOW_UV_OFFSET_G uv_recalc_n.location = (start_pos_x, start_pos_y + 1500) uv_recalc_n.node_tree = window_uv_offset_ng.get_node_group() sec_uv_map_n = node_tree.nodes.new("ShaderNodeUVMap") - sec_uv_map_n.name = sec_uv_map_n.label = WindowNight.SEC_UV_MAP + sec_uv_map_n.name = sec_uv_map_n.label = WindowLit.SEC_UV_MAP sec_uv_map_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) sec_uv_map_n.uv_map = _MESH_consts.none_uv lightmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") - lightmap_tex_n.name = lightmap_tex_n.label = WindowNight.LIGHTMAP_TEX_NODE + lightmap_tex_n.name = lightmap_tex_n.label = WindowLit.LIGHTMAP_TEX_NODE lightmap_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) lightmap_tex_n.width = 140 - base_lightmap_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") - base_lightmap_mult_n.name = base_lightmap_mult_n.label = WindowNight.BASE_LIGHTMAP_MULT_NODE - base_lightmap_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) - base_lightmap_mult_n.operation = "MULTIPLY" - - base_alpha_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") - base_alpha_mult_n.name = base_alpha_mult_n.label = WindowNight.BASE_ALPHA_MULT_NODE - base_alpha_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) - base_alpha_mult_n.operation = "MULTIPLY" + interior_rgb_n = node_tree.nodes.new("ShaderNodeVectorMath") + interior_rgb_n.name = interior_rgb_n.label = WindowLit.INTERIOR_RGB_NODE + interior_rgb_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + interior_rgb_n.operation = "MULTIPLY" + + # TODO: implement decode_window_luminance, for now use direct alpha since Blender yields approximation anyway + interior_lum_n = node_tree.nodes.new("ShaderNodeVectorMath") + interior_lum_n.name = interior_lum_n.label = WindowLit.INTERIOR_LIGHT_LUM_NODE + interior_lum_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1100) + interior_lum_n.operation = "MULTIPLY" + + interior_light_n = node_tree.nodes.new("ShaderNodeVectorMath") + interior_light_n.name = interior_light_n.label = WindowLit.INTERIOR_LIGHT_FINAL_NODE + interior_light_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1200) + interior_light_n.operation = "MULTIPLY" + + final_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") + final_mix_n.name = final_mix_n.label = WindowLit.FINAL_MIX_NODE + final_mix_n.location = (output_n.location.x - pos_x_shift * 2, output_n.location.y - 200) + final_mix_n.operation = "ADD" + + out_shader_n = node_tree.nodes.new("ShaderNodeEeveeSpecular") + out_shader_n.name = out_shader_n.label = WindowLit.WINDOW_OUT_SHADER_NODE + out_shader_n.location = (output_n.location.x - pos_x_shift * 1, output_n.location.y) + out_shader_n.inputs["Base Color"].default_value = (0.0,) * 4 + out_shader_n.inputs["Specular"].default_value = (0.0,) * 4 # create links node_tree.links.new(uv_recalc_n.inputs['UV'], uv_map_n.outputs['UV']) @@ -101,18 +125,42 @@ def init(node_tree): node_tree.links.new(lightmap_tex_n.inputs['Vector'], sec_uv_map_n.outputs['UV']) # pass 1 - node_tree.links.new(base_lightmap_mult_n.inputs[0], base_tex_n.outputs['Color']) - node_tree.links.new(base_lightmap_mult_n.inputs[1], lightmap_tex_n.outputs['Color']) + node_tree.links.new(interior_rgb_n.inputs[0], diff_col_n.outputs['Color']) + node_tree.links.new(interior_rgb_n.inputs[1], base_tex_n.outputs['Color']) + + node_tree.links.new(interior_lum_n.inputs[0], base_tex_n.outputs['Alpha']) + node_tree.links.new(interior_lum_n.inputs[1], lightmap_tex_n.outputs['Color']) # pass 2 - node_tree.links.new(base_alpha_mult_n.inputs[0], base_tex_n.outputs['Alpha']) - node_tree.links.new(base_alpha_mult_n.inputs[1], base_lightmap_mult_n.outputs[0]) + node_tree.links.new(interior_light_n.inputs[0], interior_rgb_n.outputs[0]) + node_tree.links.new(interior_light_n.inputs[1], interior_lum_n.outputs[0]) # pass 3 - node_tree.links.new(diff_mult_n.inputs[1], base_alpha_mult_n.outputs[0]) + node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_col_n.outputs['Color']) # pass 4 - node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_col_n.outputs['Color']) + node_tree.links.new(final_mix_n.inputs[0], compose_lighting_n.outputs['Color']) + node_tree.links.new(final_mix_n.inputs[1], interior_light_n.outputs[0]) + + # pass 5 + node_tree.links.new(out_shader_n.inputs['Emissive Color'], final_mix_n.outputs[0]) + + # output + node_tree.links.new(output_n.inputs['Surface'], out_shader_n.outputs['BSDF']) + + @staticmethod + def set_fresnel(node_tree, bias_scale): + """Set fresnel bias and scale value to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param bias_scale: bias and scale factors as tuple: (bias, scale) + :type bias_scale: (float, float) + """ + + bias_scale_window = get_fresnel_window(bias_scale[0], bias_scale[1]) + + DifSpecAddEnv.set_fresnel(node_tree, bias_scale_window) @staticmethod def set_lightmap_texture(node_tree, image): @@ -123,7 +171,7 @@ def set_lightmap_texture(node_tree, image): :param image: texture image which should be assignet to lightmap texture node :type image: bpy.types.Image """ - node_tree.nodes[WindowNight.LIGHTMAP_TEX_NODE].image = image + node_tree.nodes[WindowLit.LIGHTMAP_TEX_NODE].image = image @staticmethod def set_lightmap_texture_settings(node_tree, settings): @@ -134,7 +182,7 @@ def set_lightmap_texture_settings(node_tree, settings): :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - _material_utils.set_texture_settings_to_node(node_tree.nodes[WindowNight.LIGHTMAP_TEX_NODE], settings) + _material_utils.set_texture_settings_to_node(node_tree.nodes[WindowLit.LIGHTMAP_TEX_NODE], settings) @staticmethod def set_lightmap_uv(node_tree, uv_layer): @@ -149,4 +197,4 @@ def set_lightmap_uv(node_tree, uv_layer): if uv_layer is None or uv_layer == "": uv_layer = _MESH_consts.none_uv - node_tree.nodes[WindowNight.SEC_UV_MAP].uv_map = uv_layer + node_tree.nodes[WindowLit.SEC_UV_MAP].uv_map = uv_layer diff --git a/addon/io_scs_tools/internals/shaders/flavors/sky_back.py b/addon/io_scs_tools/internals/shaders/flavors/sky_back.py new file mode 100644 index 00000000..39fe47f1 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/flavors/sky_back.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + + +FLAVOR_ID = "sky_back" + + +def init(node_tree): + """Initialize sky background flavor. + + :param node_tree: node tree on which it will be used + :type node_tree: bpy.types.NodeTree + """ + + # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved + flavor_frame = node_tree.nodes.new(type="NodeFrame") + flavor_frame.name = flavor_frame.label = FLAVOR_ID + + +def delete(node_tree): + """Delete flavor from node tree. + + :param node_tree: node tree from which flavor should be deleted + :type node_tree: bpy.types.NodeTree + """ + + if FLAVOR_ID in node_tree.nodes: + node_tree.nodes.remove(node_tree.nodes[FLAVOR_ID]) + + +def is_set(node_tree): + """Check if flavor is set or not. + + :param node_tree: node tree which should be checked for existance of this flavor + :type node_tree: bpy.types.NodeTree + :return: True if flavor exists; False otherwise + :rtype: bool + """ + return FLAVOR_ID in node_tree.nodes diff --git a/addon/io_scs_tools/internals/shaders/flavors/sky_stars.py b/addon/io_scs_tools/internals/shaders/flavors/sky_stars.py new file mode 100644 index 00000000..ed68595d --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/flavors/sky_stars.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2021: SCS Software + + +FLAVOR_ID = "sky_stars" + + +def init(node_tree): + """Initialize sky stars flavor. + + :param node_tree: node tree on which it will be used + :type node_tree: bpy.types.NodeTree + """ + + # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved + flavor_frame = node_tree.nodes.new(type="NodeFrame") + flavor_frame.name = flavor_frame.label = FLAVOR_ID + + +def delete(node_tree): + """Delete flavor from node tree. + + :param node_tree: node tree from which flavor should be deleted + :type node_tree: bpy.types.NodeTree + """ + + if FLAVOR_ID in node_tree.nodes: + node_tree.nodes.remove(node_tree.nodes[FLAVOR_ID]) + + +def is_set(node_tree): + """Check if flavor is set or not. + + :param node_tree: node tree which should be checked for existance of this flavor + :type node_tree: bpy.types.NodeTree + :return: True if flavor exists; False otherwise + :rtype: bool + """ + return FLAVOR_ID in node_tree.nodes diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 91ca321a..f5027118 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -95,6 +95,12 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".decal.over") and ".retroreflective" in effect: flavors["retroreflective_decal"] = True + if effect.endswith(".stars") and "sky" in effect: + flavors["sky_stars"] = True + + if effect.endswith(".back") and "sky" in effect: + flavors["sky_back"] = True + __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, {}, flavors, recreate) diff --git a/addon/io_scs_tools/operators/bases/export.py b/addon/io_scs_tools/operators/bases/export.py index 4ac0eb19..6cd4230a 100644 --- a/addon/io_scs_tools/operators/bases/export.py +++ b/addon/io_scs_tools/operators/bases/export.py @@ -42,6 +42,8 @@ def __init__(self): """:type bpy.types.Scene: Scene to which we will temporarly link objects that needs to be exported.""" self.active_scene = None """:type bpy.types.Scene: Scene which was active before export and should be recovered as active once export is done.""" + self.active_view_layer = None + """:type bpy.types.ViewLayer: View layer which was active before export and should be recovered as active once export is done.""" def get_objects_for_export(self): """Get objects for export, list filtered and extended depending on export scope. @@ -103,6 +105,7 @@ def init(self): # create export scene self.scene = bpy.data.scenes.new("SCS Export") self.active_scene = bpy.context.window.scene + self.active_view_layer = bpy.context.window.view_layer bpy.context.window.scene = self.scene # link objects to export scene and unhide all of them @@ -127,6 +130,7 @@ def finish(self): # recover old active scene and remove temporary one bpy.context.window.scene = self.active_scene + bpy.context.window.view_layer = self.active_view_layer bpy.data.scenes.remove(self.scene) self.scene = None diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index 2ac574a6..d4be627a 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -2262,9 +2262,6 @@ def execute(self, context): # 3. add scs root empty object, set it's properties and fix scene objects count to avoid callback of new object name = _name_utils.get_unique(self.scs_root_object_string, bpy.data.objects) new_scs_root = bpy.data.objects.new(name, None) - new_scs_root.empty_display_size = 5.0 - new_scs_root.empty_display_type = "ARROWS" - new_scs_root.show_name = True new_scs_root.location = context.scene.cursor.location new_scs_root.scs_props.object_identity = new_scs_root.name diff --git a/addon/io_scs_tools/operators/scene.py b/addon/io_scs_tools/operators/scene.py index 34deac4a..f9a11788 100644 --- a/addon/io_scs_tools/operators/scene.py +++ b/addon/io_scs_tools/operators/scene.py @@ -243,7 +243,13 @@ def execute(self, context): if 0 <= self.index < len(anim_inventory): anim = anim_inventory[self.index] - _export.pia.export(scs_root_obj, armature, anim, self.directory, skeleton_filepath) + + # check extension for EF format and properly assign it to name suffix + ef_name_suffix = "" + if _get_scs_globals().export_output_type == "EF": + ef_name_suffix = ".ef" + + _export.pia.export(scs_root_obj, armature, anim, self.directory, ef_name_suffix, skeleton_filepath) lprint("", report_errors=1, report_warnings=1) return {'FINISHED'} @@ -1358,6 +1364,12 @@ class SCS_TOOLS_OT_ImportFromDataSII(bpy.types.Operator): ) filter_glob: StringProperty(default="*.sii", options={'HIDDEN'}) + hide_collections: BoolProperty( + name="Hide collections after import", + description="Should collections be hidden or shown after import?", + default=False + ) + vehicle_type = _PT_consts.VehicleTypes.NONE start_time = None # saving start time when initialize is called @@ -1709,6 +1721,7 @@ def execute(self, context): possible_upgrade_locators = {} # dictionary holding all locators that can be used as candidates for upgrades positioning already_imported = set() # set holding imported path of already imported model, to avoid double importing multiple_project_vehicle_models = set() # set of model paths found in multiple projects (for reporting purposes) + vehicle_import_count = 0 # counter for number of properly imported vehicle models for project_path in project_paths: for vehicle_model_path in vehicle_model_paths: @@ -1746,9 +1759,12 @@ def execute(self, context): os.path.basename(vehicle_model_path), vehicle_model_paths[vehicle_model_path]) + # update the import count + vehicle_import_count = vehicle_import_count + 1 + # if none vehicle models were properly imported it makes no sense to go forward on upgrades - if len(already_imported) <= 0: - message = "No vehicle models properly imported!" + if vehicle_import_count <= 0: + message = "No vehicle models imported, either none exist or they are corrupted or they are missing 'truckpaint' material!" lprint("E " + message) self.report({"ERROR"}, message) self.finalize() @@ -1868,7 +1884,22 @@ def execute(self, context): # make our layer collections hidden, as user should later select what to export for layer_col in bpy.context.view_layer.layer_collection.children: - layer_col.hide_viewport = True + layer_col.exclude = self.hide_collections + + # move all objects still in master collection to our main collection + if _PT_consts.main_coll_name not in bpy.data.collections: + main_collection = bpy.data.collections.new(_PT_consts.main_coll_name) + else: + main_collection = bpy.data.collections[_PT_consts.main_coll_name] + + bpy.context.view_layer.layer_collection.collection.children.link(main_collection) + + for obj in bpy.context.view_layer.layer_collection.collection.objects: + main_collection.objects.link(obj) + bpy.context.view_layer.layer_collection.collection.objects.unlink(obj) + + # hide main collection everywhere + main_collection.hide_viewport = main_collection.hide_render = main_collection.hide_select = True # on the end report multiple project model problems if len(multiple_project_vehicle_models) > 0: @@ -2626,6 +2657,10 @@ def export_to_sii(self, op_inst, config_path): # 2. export overrides return _sii_container.write_data_to_file(config_path, tuple(export_units), create_dirs=True) + class ColorVariantItem(bpy.types.PropertyGroup): + """Color variant item.""" + value: FloatVectorProperty(default=(0, 0, 0)) + vehicle_type = _PT_consts.VehicleTypes.NONE img_node = None @@ -2700,6 +2735,7 @@ def export_to_sii(self, op_inst, config_path): pjs_flipflake: BoolProperty(default=False) pjs_airbrush: BoolProperty(default=False) pjs_stock: BoolProperty(default=False) + pjs_color_variant: CollectionProperty(type=ColorVariantItem) @staticmethod def do_report(the_type, message, do_report=False): @@ -3028,9 +3064,35 @@ def append_prop_if_not_default(self, unit, prop_name, prop_value=None): lprint("E Invalid property for paintjob settings: %r, contact the developer!", (prop_name,)) return False - # do comparison of property differently for each type + # do comparison of property differently for each type and convert current value if needed is_different = False - if isinstance(default_value, tuple): + if isinstance(default_value, list): + + values_as_list = [] + for item in current_value: + + # we need prescribed interface, which is that array unit properties needs class which defines 'value' + # property where value of the property is saved + if not hasattr(item, "value"): + lprint("E Collection property %r is missing 'value' attribute, contact the developer!", (prop_name,)) + return False + + default_item_value = _get_default(item, "value") + + if isinstance(default_item_value, tuple): + current_item_value = tuple(item.value) + else: + current_item_value = item.value + + values_as_list.append(current_item_value) + + # collection properties are empty by default, thus any item added means it's different than default + is_different = len(values_as_list) > 0 + + # copy created values list back to current value to write it into unit as array item + current_value = values_as_list + + elif isinstance(default_value, tuple): current_value = tuple(current_value) # convert to tuple for proper export @@ -3484,6 +3546,7 @@ def execute(self, context): Log.SCS_TOOLS_OT_CopyLogToClipboard, PaintjobTools.SCS_TOOLS_OT_ExportPaintjobUVLayoutAndMesh, + PaintjobTools.SCS_TOOLS_OT_GeneratePaintjob.ColorVariantItem, PaintjobTools.SCS_TOOLS_OT_GeneratePaintjob, PaintjobTools.SCS_TOOLS_OT_ImportFromDataSII, diff --git a/addon/io_scs_tools/operators/wm.py b/addon/io_scs_tools/operators/wm.py index bf3ec938..462507e2 100644 --- a/addon/io_scs_tools/operators/wm.py +++ b/addon/io_scs_tools/operators/wm.py @@ -161,7 +161,7 @@ def get_scs_banner_img_data(window): img_path = os.path.join(_path_utils.get_addon_installation_paths()[0], "ui", "banners", img_name) img = bpy.data.images.load(img_path, check_existing=True) - img.colorspace_settings.name = 'Raw' + img.colorspace_settings.name = 'sRGB' else: diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 6334df1f..7b0a52bd 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -271,11 +271,9 @@ def get_texture_types(): :return: SCS Shader Texture Types :rtype: dict """ - return {'base': 5, 'reflection': 6, 'over': 7, 'oclu': 8, - 'mask': 9, 'mult': 10, 'iamod': 11, 'lightmap': 12, - 'paintjob': 13, 'flakenoise': 14, 'nmap': 15, - 'base_1': 16, 'mult_1': 17, 'detail': 18, 'nmap_detail': 19, - 'layer0': 20, 'layer1': 21} + return {'base', 'reflection', 'over', 'oclu', 'mask', 'mult', 'iamod', 'lightmap', 'paintjob', + 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'layer0', 'layer1', + 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. @@ -446,6 +444,30 @@ def update_shader_texture_reflection(self, context): def update_shader_texture_reflection_settings(self, context): __update_shader_texture_tobj_file__(self, context, "reflection") + def update_shader_texture_sky_weather_base_a(self, context): + __update_shader_texture__(self, context, "sky_weather_base_a") + + def update_shader_texture_sky_weather_base_a_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_base_a") + + def update_shader_texture_sky_weather_base_b(self, context): + __update_shader_texture__(self, context, "sky_weather_base_b") + + def update_shader_texture_sky_weather_base_b_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_base_b") + + def update_shader_texture_sky_weather_over_a(self, context): + __update_shader_texture__(self, context, "sky_weather_over_a") + + def update_shader_texture_sky_weather_over_a_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_over_a") + + def update_shader_texture_sky_weather_over_b(self, context): + __update_shader_texture__(self, context, "sky_weather_over_b") + + def update_shader_texture_sky_weather_over_b_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_over_b") + _cached_mat_num = -1 """Caching number of all materials to properly fix material ids when duplicating material""" @@ -1562,6 +1584,210 @@ def update_shader_texture_reflection_settings(self, context): options={'HIDDEN'}, ) + # TEXTURE: SKY_WEATHER_BASE_A + shader_texture_sky_weather_base_a: StringProperty( + name="Texture Sky Weather Base A", + description="Texture Sky Weather Base A for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_base_a, + ) + shader_texture_sky_weather_base_a_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_base_a_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_base_a_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_base_a_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_base_a_settings + ) + shader_texture_sky_weather_base_a_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_base_a_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_base_a_uv: CollectionProperty( + name="Texture Sky Weather Base A UV Sets", + description="Texture Sky Weather Base A UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_BASE_B + shader_texture_sky_weather_base_b: StringProperty( + name="Texture Sky Weather Base B", + description="Texture Sky Weather Base B for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_base_b, + ) + shader_texture_sky_weather_base_b_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_base_b_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_base_b_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_base_b_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_base_b_settings + ) + shader_texture_sky_weather_base_b_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_base_b_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_base_b_uv: CollectionProperty( + name="Texture Sky Weather Base B UV Sets", + description="Texture Sky Weather Base B UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_OVER_A + shader_texture_sky_weather_over_a: StringProperty( + name="Texture Sky Weather Over A", + description="Texture Sky Weather Over A for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_over_a, + ) + shader_texture_sky_weather_over_a_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_over_a_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_over_a_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_over_a_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_over_a_settings + ) + shader_texture_sky_weather_over_a_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_over_a_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_over_a_uv: CollectionProperty( + name="Texture Sky Weather Over A UV Sets", + description="Texture Sky Weather Over A UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_OVER_B + shader_texture_sky_weather_over_b: StringProperty( + name="Texture Sky Weather Over B", + description="Texture Sky Weather Over B for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_over_b, + ) + shader_texture_sky_weather_over_b_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_over_b_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_over_b_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_over_b_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_over_b_settings + ) + shader_texture_sky_weather_over_b_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_over_b_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_over_b_uv: CollectionProperty( + name="Texture Sky Weather Over B UV Sets", + description="Texture Sky Weather Over B UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + # # Following String property is fed from MATERIAL SUBSTANCE data list, which is usually loaded from # # "//base/material/material.db" file and stored at "scs_inventories.matsubs". substance: StringProperty( diff --git a/addon/io_scs_tools/properties/mesh.py b/addon/io_scs_tools/properties/mesh.py index a00f2e6f..1d7a63e7 100644 --- a/addon/io_scs_tools/properties/mesh.py +++ b/addon/io_scs_tools/properties/mesh.py @@ -19,7 +19,7 @@ # Copyright (C) 2013-2019: SCS Software import bpy -from bpy.props import StringProperty, FloatProperty +from bpy.props import StringProperty class MeshSCSTools(bpy.types.PropertyGroup): @@ -34,14 +34,6 @@ class MeshSCSTools(bpy.types.PropertyGroup): subtype="FILE_PATH", # subtype='NONE', ) - vertex_color_multiplier: FloatProperty( - name="Vertex Color Multiplier", - description="All of the vertices will have their color multiplied for this factor upon export.", - default=1, - min=0, - max=10, - step=0.1 - ) classes = ( diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 4a8d3498..49999ba0 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -286,16 +286,17 @@ def empty_object_type_items(self, context): def update_empty_object_type(self, context): - if context.object: + # get owner object with id_data since context may have different active object + obj = self.id_data - if self.empty_object_type == "SCS_Root": - context.object.empty_display_size = 5.0 - context.object.empty_display_type = "ARROWS" - context.object.show_name = True - else: - context.object.empty_display_size = 1.0 - context.object.empty_display_type = "PLAIN_AXES" - context.object.show_name = False + if self.empty_object_type == "SCS_Root": + obj.empty_display_size = 5.0 + obj.empty_display_type = "ARROWS" + obj.show_name = True + else: + obj.empty_display_size = 1.0 + obj.empty_display_type = "PLAIN_AXES" + obj.show_name = False # EMPTY OBJECT TYPE empty_object_type: EnumProperty( diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 582ca277..fb0f0105 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -41,10 +41,10 @@ Shader { Value: ( 0.2 0.9 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -82,10 +82,10 @@ Shader { Value: ( 0.0 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -118,10 +118,10 @@ Shader { Value: ( 0.0 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -154,10 +154,10 @@ Shader { Value: ( 0.0 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -205,11 +205,6 @@ Shader { PresetName: "decalshadow" Effect: "eut2.decalshadow" Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -330,6 +325,12 @@ Shader { Tag: "reflection" Value: ( 0.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -339,7 +340,7 @@ Shader { Shader { PresetName: "dif.lum.spec" Effect: "eut2.dif.lum.spec" - Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "INVERTLUM" "LUMMULVCOL" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "LUMMULVCOL" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -367,10 +368,10 @@ Shader { Value: ( 0.0 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -1220,6 +1221,12 @@ Shader { Tag: "diffuse" Value: ( 1.0 1.0 1.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "/model/flare/flare" @@ -1238,37 +1245,32 @@ Shader { Attribute { Format: FLOAT3 Tag: "specular" - Value: ( 0.5 0.5 0.5 ) + Value: ( 1.0 1.0 1.0 ) } Attribute { Format: FLOAT Tag: "shininess" - Value: ( 95.0 ) + Value: ( 25.0 ) } Attribute { Format: FLOAT Tag: "add_ambient" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 3.0 3.0 3.0 ) - } Attribute { Format: FLOAT2 Tag: "fresnel" - Value: ( 1.0 2.0 ) + Value: ( 0.01 -1 ) } Attribute { Format: FLOAT3 Tag: "tint" - Value: ( 0.78 0.8 0.79 ) + Value: ( 0.97 0.97 0.97 ) } Attribute { Format: FLOAT Tag: "tint_opacity" - Value: ( -0.01 ) + Value: ( 0.0 ) } Attribute { Format: INT @@ -1367,9 +1369,9 @@ Shader { Value: ( 0.0 0.0 0.0 ) } Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 0.0 ) + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.0 0.0 0.0 ) } Attribute { Format: FLOAT @@ -1377,6 +1379,12 @@ Shader { FriendlyTag: "Depth Bias" Value: ( 0.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1398,9 +1406,9 @@ Shader { Value: ( 0.0 0.0 0.0 ) } Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 0.0 ) + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.0 0.0 0.0 ) } Attribute { Format: FLOAT @@ -1408,6 +1416,12 @@ Shader { FriendlyTag: "Depth Bias" Value: ( 0.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1450,11 +1464,6 @@ Shader { PresetName: "shadowmap" Effect: "eut2.shadowmap" Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1505,34 +1514,55 @@ Shader { Shader { PresetName: "sky" Effect: "eut2.sky" - Flavors: ( "BLEND_OVER" ) + Flavors: ( "SKYPART_BACKGROUND|SKYPART_STARS" ) Flags: 0 Attribute { - Format: FLOAT + Format: FLOAT3 Tag: "aux[0]" FriendlyTag: "Layer Blend" - Value: ( 0.5 ) + Value: ( 0.0 0.0 1.0) PreviewOnly: "True" } + Attribute { + Format: FLOAT4 + Tag: "aux[1]" + FriendlyTag: "V Cutoff" + Value: ( 0.0 0.0 0.0 0.0 ) + PreviewOnly: "True" + Hide: "False" + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "V Scale" + Value: ( 1.0 1.0 1.0 1.0 ) + PreviewOnly: "True" + Hide: "True" + } Attribute { Format: FLOAT3 Tag: "diffuse" Value: ( 1.0 1.0 1.0 ) } Texture { - Tag: "texture[X]:texture_base" - Value: "/model/skybox/nice0/nice0_11-15_g" + Tag: "texture[X]:texture_sky_weather_base_a" + Value: "/model/skybox/nice_00/nice00_08_80_g" TexCoord: ( 0 ) } Texture { - Tag: "texture[X]:texture_over" - Value: "/model/skybox/nice0/nice0_11-15_g" + Tag: "texture[X]:texture_sky_weather_base_b" + Value: "/model/skybox/nice_00/nice00_08_80_g" TexCoord: ( 0 ) } Texture { - Tag: "texture[X]:texture_mask" - Value: "" - TexCoord: ( 1 ) + Tag: "texture[X]:texture_sky_weather_over_a" + Value: "/model/skybox/bad_00/bad00_08_80_g" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_sky_weather_over_b" + Value: "/model/skybox/nice_00/nice00_08_80_g" + TexCoord: ( 0 ) } } Shader { @@ -1548,7 +1578,7 @@ Shader { Attribute { Format: FLOAT3 Tag: "specular" - Value: ( 1.5 1.5 1.5 ) + Value: ( 1.0 1.0 1.0 ) } Attribute { Format: FLOAT @@ -1560,11 +1590,6 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 0.3 0.3 0.3 ) - } Attribute { Format: FLOAT2 Tag: "fresnel" @@ -1591,6 +1616,12 @@ Shader { Tag: "diffuse" Value: ( 1.0 1.0 1.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1607,6 +1638,12 @@ Shader { Tag: "diffuse" Value: ( 1.0 1.0 1.0 ) } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1638,7 +1675,7 @@ Shader { Value: ( 1.0 1.0 1.0 ) } Attribute { - Format: FLOAT3 + Format: FLOAT2 Tag: "fresnel" Value: ( 0.2 0.9 ) } @@ -1695,58 +1732,8 @@ Shader { } } Shader { - PresetName: "window.day" - Effect: "eut2.window.day" - Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT3 - Tag: "specular" - Value: ( 0.5454490185 0.5454490185 0.5454490185 ) - } - Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 65.0 ) - } - Attribute { - Format: FLOAT - Tag: "add_ambient" - Value: ( 0.0 ) - } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.200000003 0.8999999762 ) - } - Texture { - Tag: "texture[X]:texture_base" - Value: "/material/special/window_interior_01" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/building_reflection/building_ref" - TexCoord: ( -1 ) - } - Texture { - Tag: "texture[X]:texture_lightmap" - Value: "/material/special/window_interior_01" - TexCoord: ( 1 ) - } -} -Shader { - PresetName: "window.night" - Effect: "eut2.window.night" + PresetName: "window.lit" + Effect: "eut2.window.lit" Flags: 0 Attribute { Format: FLOAT3 @@ -1768,11 +1755,6 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } Attribute { Format: FLOAT2 Tag: "fresnel" @@ -1906,10 +1888,6 @@ Flavor { Type: "INDENV" Name: "indenv" } -Flavor { - Type: "INVERTLUM" - Name: "linv" -} Flavor { Type: "LUMMULVCOL" Name: "lvcol" @@ -1991,6 +1969,46 @@ Flavor { Value: ( 0.0 ) } } +Flavor { + Type: "SKYPART_BACKGROUND" + Name: "back" + Attribute { + Format: FLOAT4 + Tag: "aux[1]" + FriendlyTag: "V Cutoff" + Value: ( 0.0 0.0 0.0 0.0 ) + PreviewOnly: "True" + Hide: "True" + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "V Scale" + Value: ( 1.0 1.0 1.0 1.0 ) + PreviewOnly: "True" + Hide: "True" + } +} +Flavor { + Type: "SKYPART_STARS" + Name: "stars" + Attribute { + Format: FLOAT4 + Tag: "aux[1]" + FriendlyTag: "V Cutoff" + Value: ( 0.0 0.0 0.0 0.0 ) + PreviewOnly: "True" + Hide: "True" + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "V Scale" + Value: ( 1.0 1.0 1.0 1.0 ) + PreviewOnly: "True" + Hide: "False" + } +} Flavor { Type: "PAINT" Name: "paint" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 76459280..2f3338b8 100644 Binary files a/addon/io_scs_tools/supported_effects.bin and b/addon/io_scs_tools/supported_effects.bin differ diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index a8ece5e7..29ac1fc1 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -85,8 +85,8 @@ class SCS_TOOLS_PT_Material(_shared.HeaderIconPanel, _MaterialPanelBlDefs, Panel def draw_shader_presets(layout, scs_props, scs_globals, scs_inventories, is_imported_shader=False): """Creates Shader Presets sub-panel.""" layout_box = layout.box() - layout_box.enabled = not is_imported_shader - if scs_props.shader_presets_expand: + + if scs_props.shader_presets_expand and not is_imported_shader: panel_header = layout_box.split(factor=0.5) panel_header_1 = panel_header.row() panel_header_1.prop(scs_props, 'shader_presets_expand', text="Shader Presets:", icon='TRIA_DOWN', icon_only=True, emboss=False) @@ -113,10 +113,15 @@ def draw_shader_presets(layout, scs_props, scs_globals, scs_inventories, is_impo row = column.row(align=True) if is_imported_shader: - row.prop(bpy.context.material.scs_props, "active_shader_preset_name", icon='COLOR', text="") + row_col = row.column() + row_col.enabled = False + row_col.prop(bpy.context.material.scs_props, "active_shader_preset_name", icon='COLOR', text="") + row_col = row.column() + row.operator("material.scs_tools_search_shader_preset", text="", icon='SOLO_ON') else: row.prop(scs_inventories, 'shader_presets', text="") - row.operator("material.scs_tools_search_shader_preset", text="", icon='VIEWZOOM') + row.operator("material.scs_tools_search_shader_preset", text="", icon='VIEWZOOM') + @staticmethod def draw_flavors(layout, mat): diff --git a/addon/io_scs_tools/ui/mesh.py b/addon/io_scs_tools/ui/mesh.py index cd44e573..b01c4b46 100644 --- a/addon/io_scs_tools/ui/mesh.py +++ b/addon/io_scs_tools/ui/mesh.py @@ -66,10 +66,6 @@ def draw(self, context): layout.use_property_split = True layout.use_property_decorate = False - # show this only for meshes not for empties and other kinda objects - if mesh: - layout.prop(mesh.scs_props, "vertex_color_multiplier") - classes = ( SCS_TOOLS_PT_Mesh, @@ -77,13 +73,19 @@ def draw(self, context): def register(): - for cls in classes: - bpy.utils.register_class(cls) + # for cls in classes: + # bpy.utils.register_class(cls) + # + # from io_scs_tools import SCS_TOOLS_MT_MainMenu + # SCS_TOOLS_MT_MainMenu.append_props_entry("Mesh Properties", SCS_TOOLS_PT_Mesh.__name__) - from io_scs_tools import SCS_TOOLS_MT_MainMenu - SCS_TOOLS_MT_MainMenu.append_props_entry("Mesh Properties", SCS_TOOLS_PT_Mesh.__name__) + # No mesh settings available currently, thus commented out and just passing + pass def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) + # for cls in classes: + # bpy.utils.unregister_class(cls) + + # No mesh settings available currently, thus commented out and just passing + pass diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index ddeffc58..f7d4e0e9 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -236,9 +236,7 @@ def get_reflection_image(texture_path, report_invalid=False): # 4. setup scene, create planes, create camera projector and assign images - old_scene = bpy.context.window.scene tmp_scene = bpy.data.scenes.new("cubemap") - bpy.context.window.scene = tmp_scene meshes = [] materials = [] @@ -353,7 +351,6 @@ def get_reflection_image(texture_path, report_invalid=False): bpy.data.objects.remove(cam_obj) bpy.data.cameras.remove(camera) - bpy.context.window.scene = old_scene bpy.data.scenes.remove(tmp_scene) # 7. load temp image and pack it @@ -578,7 +575,7 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac for tex_type in used_texture_types: # skip unknown texture type - if tex_type not in material.scs_props.get_texture_types().keys(): + if tex_type not in material.scs_props.get_texture_types(): lprint("D Trying to apply unknown texture type to SCS material: %r", (tex_type,)) continue diff --git a/addon/io_scs_tools/utils/math.py b/addon/io_scs_tools/utils/math.py index 6a47b912..0067032b 100644 --- a/addon/io_scs_tools/utils/math.py +++ b/addon/io_scs_tools/utils/math.py @@ -110,3 +110,20 @@ def saasin(fac): return math.pi / 2.0 else: return math.asin(fac) + + +def clamp(value, min_value=0, max_value=1): + """Clamp value to given interval. Default interval is [0,1]. + + :param value: value to clamp + :type value: float|int + :param min_value: minimum value allowed + :type min_value: float|int + :param max_value: maximum value allowed + :type max_value: float|int + :return: clamped value + :rtype: float|int + """ + assert min_value < max_value + + return min(max_value, max(min_value, value)) diff --git a/addon/io_scs_tools/utils/mesh.py b/addon/io_scs_tools/utils/mesh.py index 3d089b85..fe183c09 100644 --- a/addon/io_scs_tools/utils/mesh.py +++ b/addon/io_scs_tools/utils/mesh.py @@ -395,7 +395,7 @@ def bm_make_uv_layer(pim_version, bm, faces, uv_layer_name, uv_layer_data): loop[uv_lay].uv = _convert.change_to_scs_uv_coordinates(uv_layer_data[face_i][loop_i]) -def bm_make_vc_layer(pim_version, bm, vc_layer_name, vc_layer_data, multiplier=1.0): +def bm_make_vc_layer(pim_version, bm, vc_layer_name, vc_layer_data): """Add Vertex Color Layer to the BMesh object. :param pim_version: PIM version of the File from which data have been read @@ -435,12 +435,12 @@ def bm_make_vc_layer(pim_version, bm, vc_layer_name, vc_layer_data, multiplier=1 vcol = vc_layer_data[face_i][loop_i][:3] alpha = vc_layer_data[face_i][loop_i][3] - vcol = (vcol[0] / 2 / multiplier, vcol[1] / 2 / multiplier, vcol[2] / 2 / multiplier, 1.0) + vcol = (vcol[0] / 2, vcol[1] / 2, vcol[2] / 2, 1.0) loop[color_lay] = vcol if alpha != -1.0: assert color_a_lay - vcol_a = (alpha / 2 / multiplier,) * 3 + (1.0,) + vcol_a = (alpha / 2,) * 3 + (1.0,) loop[color_a_lay] = vcol_a diff --git a/addon/io_scs_tools/utils/path.py b/addon/io_scs_tools/utils/path.py index 3334a49b..913f0965 100644 --- a/addon/io_scs_tools/utils/path.py +++ b/addon/io_scs_tools/utils/path.py @@ -129,17 +129,12 @@ def get_abs_path(path_in, subdir_path='', is_dir=False, skip_mod_check=False): else: result = path_in + # use subdir_path as last item, so that if file/dir not found we return correct abs path, not the last checked from parents dir + infixes = ["../base", "../base_vehicle", "../../base", "../../base_vehicle", subdir_path] existance_check = os.path.isdir if is_dir else os.path.isfile - if result is not None and not existance_check(result) and not skip_mod_check: - result = get_abs_path(path_in, subdir_path="../base", is_dir=is_dir, skip_mod_check=True) - - if result is not None and not existance_check(result) and not skip_mod_check: - result = get_abs_path(path_in, subdir_path="../../base", is_dir=is_dir, skip_mod_check=True) - - # finally if file/dir not found return correct abs path, not the one from parent dirs - if result is not None and not existance_check(result) and not skip_mod_check: - result = get_abs_path(path_in, subdir_path=subdir_path, is_dir=is_dir, skip_mod_check=True) + while infixes and result is not None and not existance_check(result) and not skip_mod_check: + result = get_abs_path(path_in, subdir_path=infixes.pop(0), is_dir=is_dir, skip_mod_check=True) return result @@ -168,7 +163,7 @@ def get_abs_paths(filepath, is_dir=False, include_nonexist_alternative_bases=Fal existance_check = os.path.isdir if is_dir else os.path.isfile - for i, sub_dir in enumerate(("", "../base", "../../base")): + for i, sub_dir in enumerate(("", "../base", "../base_vehicle", "../../base", "../../base_vehicle")): # only search for additional absolute paths if usage of alternative bases isn't switched off by user if i > 0 and not _get_scs_globals().use_alternative_bases: @@ -300,14 +295,14 @@ def is_valid_traffic_rules_library_rel_path(): def is_valid_hookup_library_rel_path(): """It returns True if there is at least one "*.sii" file in - the resulting CgFX Library directory, otherwise False.""" + the resulting unit hookup directory or it's sub-directories, otherwise False.""" hookup_library_abs_path = get_abs_path(_get_scs_globals().hookup_library_rel_path, is_dir=True) if hookup_library_abs_path: for root, dirs, files in os.walk(hookup_library_abs_path): for file in files: if file.endswith(".sii"): return True - return False + return False else: return False @@ -509,12 +504,22 @@ def get_scs_texture_str(texture_string): nonmatched_path_part = texture_string[common_path_len:] modif_texture_string = os.path.join(scs_project_path, infix + nonmatched_path_part) + # if one or two directories up is the same path as texture string - # and non matched path part is starting with /base we got a hit: - # resulting relative path is non matched path part with stripped "/base" start - if is_samepath(modif_texture_string, texture_string) and startswith(nonmatched_path_part, os.sep + "base"): - texture_string = nonmatched_path_part[5:] - break + if is_samepath(modif_texture_string, texture_string): + + strip_length = 0 + + # and non matched path part is starting with "/[base|base_*]/" we got a hit + for base_prefix in ("base_vehicle", "base"): + if nonmatched_path_part.startswith(os.sep + base_prefix + os.sep): + strip_length = len(base_prefix) + 1 + break + + # resulting relative path is non matched path part with stripped "/base" start + if strip_length > 0: + texture_string = nonmatched_path_part[strip_length:] + break # check for relative TOBJ, TGA, PNG for ext in extensions: @@ -725,7 +730,7 @@ def get_projects_paths(game_project_path): if os.path.isfile(os.path.join(game_project_path, dir_entry)): continue - if dir_entry == "base" or dir_entry.startswith("dlc_"): + if dir_entry == "base" or dir_entry.startswith("base_") or dir_entry.startswith("dlc_"): project_paths.append(readable_norm(os.path.join(game_project_path, dir_entry))) @@ -738,7 +743,7 @@ def get_projects_paths(game_project_path): if os.path.isfile(os.path.join(mod_dir, dir_entry)): continue - if dir_entry2 == "base" or dir_entry2.startswith("dlc_"): + if dir_entry2 == "base" or dir_entry2.startswith("base_") or dir_entry2.startswith("dlc_"): project_paths.append(readable_norm(os.path.join(mod_dir, dir_entry2))) return project_paths diff --git a/addon/io_scs_tools/utils/property.py b/addon/io_scs_tools/utils/property.py index 91cf5bb7..2478057c 100644 --- a/addon/io_scs_tools/utils/property.py +++ b/addon/io_scs_tools/utils/property.py @@ -41,7 +41,9 @@ def get_default(from_object, prop_name): return None bl_rna_prop = from_object.bl_rna.properties[prop_name] - if getattr(bl_rna_prop, "is_array", False): + if bl_rna_prop.type == "COLLECTION": + return [] # collection doesn't have default values, thus return empty array + elif getattr(bl_rna_prop, "is_array", False): return bl_rna_prop.default_array[:] else: return bl_rna_prop.default