import glob import json import os import shutil import stat import html from typing import List import bpy import lnx.assets as assets import lnx.make_renderpath as make_renderpath import lnx.make_state as state import lnx.utils if lnx.is_reload(__name__): import lnx assets = lnx.reload_module(assets) make_renderpath = lnx.reload_module(make_renderpath) state = lnx.reload_module(state) lnx.utils = lnx.reload_module(lnx.utils) else: lnx.enable_reload(__name__) def on_same_drive(path1: str, path2: str) -> bool: drive_path1, _ = os.path.splitdrive(path1) drive_path2, _ = os.path.splitdrive(path2) return drive_path1 == drive_path2 def add_leenkx_library(sdk_path: str, name: str, rel_path=False) -> str: if rel_path: sdk_path = '../' + os.path.relpath(sdk_path, lnx.utils.get_fp()).replace('\\', '/') return ('project.addLibrary("' + sdk_path + '/' + name + '");\n').replace('\\', '/').replace('//', '/') def add_assets(path: str, quality=1.0, use_data_dir=False, rel_path=False) -> str: if not bpy.data.worlds['Lnx'].lnx_minimize and path.endswith('.lnx'): path = path[:-4] + '.json' if rel_path: path = os.path.relpath(path, lnx.utils.get_fp()).replace('\\', '/') notinlist = not path.endswith('.ttf') # TODO s = 'project.addAssets("' + path + '", { notinlist: ' + str(notinlist).lower() + ' ' if quality < 1.0: s += ', quality: ' + str(quality) if use_data_dir: s += ', destination: "data/{name}"' s += '});\n' return s def add_shaders(path: str, rel_path=False) -> str: if rel_path: path = os.path.relpath(path, lnx.utils.get_fp()) return 'project.addShaders("' + path.replace('\\', '/').replace('//', '/') + '");\n' def remove_readonly(func, path, excinfo): os.chmod(path, stat.S_IWRITE) func(path) def write_khafilejs(is_play, export_physics: bool, export_navigation: bool, export_ui: bool, export_network: bool, is_publish: bool, import_traits: List[str]) -> None: wrd = bpy.data.worlds['Lnx'] rpdat = lnx.utils.get_rp() sdk_path = lnx.utils.get_sdk_path() rel_path = lnx.utils.get_relative_paths() # Convert absolute paths to relative project_path = lnx.utils.get_fp() build_dir = lnx.utils.build_dir() # Whether to use relative paths for paths inside the SDK do_relpath_sdk = rel_path and on_same_drive(sdk_path, project_path) with open('khafile.js', 'w', encoding="utf-8") as khafile: khafile.write( """// Auto-generated let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' + wrd.lnx_project_version) + """'); project.addSources('Sources'); """) # Auto-add assets located in Bundled directory if os.path.exists('Bundled'): for file in glob.glob("Bundled/**", recursive=True): if os.path.isfile(file): assets.add(file) # Auto-add shape key textures if exists if os.path.exists('MorphTargets'): for file in glob.glob("MorphTargets/**", recursive=True): if os.path.isfile(file): assets.add(file) # Add project shaders if os.path.exists('Shaders'): # Copy to enable includes shader_path = os.path.join(build_dir, 'compiled', 'Shaders', 'Project') if os.path.exists(shader_path): shutil.rmtree(shader_path, onerror=remove_readonly) shutil.copytree('Shaders', shader_path) khafile.write("project.addShaders('" + build_dir + "/compiled/Shaders/Project/**');\n") # for file in glob.glob("Shaders/**", recursive=True): # if os.path.isfile(file): # assets.add_shader(file) # Add engine sources if the project does not use its own leenkx/iron versions if not os.path.exists(os.path.join('Libraries', 'leenkx')): khafile.write(add_leenkx_library(sdk_path, 'leenkx', rel_path=do_relpath_sdk)) if not os.path.exists(os.path.join('Libraries', 'iron')): khafile.write(add_leenkx_library(sdk_path, 'iron', rel_path=do_relpath_sdk)) # Project libraries if os.path.exists('Libraries'): libs = os.listdir('Libraries') for lib in libs: if os.path.isdir('Libraries/' + lib): khafile.write('project.addLibrary("{0}");\n'.format(lib.replace('//', '/'))) # Subprojects, merge this with libraries if os.path.exists('Subprojects'): libs = os.listdir('Subprojects') for lib in libs: if os.path.isdir('Subprojects/' + lib): khafile.write('await project.addProject("Subprojects/{0}");\n'.format(lib)) if state.target.startswith('krom'): assets.add_khafile_def('js-es=6') if export_physics: assets.add_khafile_def('lnx_physics') if wrd.lnx_physics_engine == 'Bullet': assets.add_khafile_def('lnx_bullet') if not os.path.exists('Libraries/haxebullet'): khafile.write(add_leenkx_library(sdk_path + '/lib/', 'haxebullet', rel_path=do_relpath_sdk)) if state.target.startswith('krom'): ammojs_path = sdk_path + '/lib/haxebullet/ammo/ammo.wasm.js' ammojs_path = ammojs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(ammojs_path, rel_path=do_relpath_sdk)) ammojs_wasm_path = sdk_path + '/lib/haxebullet/ammo/ammo.wasm.wasm' ammojs_wasm_path = ammojs_wasm_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(ammojs_wasm_path, rel_path=do_relpath_sdk)) elif state.target == 'html5' or state.target == 'node': ammojs_path = sdk_path + '/lib/haxebullet/ammo/ammo.js' ammojs_path = ammojs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(ammojs_path, rel_path=do_relpath_sdk)) elif wrd.lnx_physics_engine == 'Oimo': assets.add_khafile_def('lnx_oimo') if not os.path.exists('Libraries/oimo'): khafile.write(add_leenkx_library(sdk_path + '/lib/', 'oimo', rel_path=do_relpath_sdk)) if export_navigation: assets.add_khafile_def('lnx_navigation') if not os.path.exists('Libraries/haxerecast'): khafile.write(add_leenkx_library(sdk_path + '/lib/', 'haxerecast', rel_path=do_relpath_sdk)) if state.target.startswith('krom'): recastjs_path = sdk_path + '/lib/haxerecast/recastjs/recast.wasm.js' recastjs_path = recastjs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(recastjs_path, rel_path=do_relpath_sdk)) recastjs_wasm_path = sdk_path + '/lib/haxerecast/recastjs/recast.wasm.wasm' recastjs_wasm_path = recastjs_wasm_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(recastjs_wasm_path, rel_path=do_relpath_sdk)) elif state.target == 'html5' or state.target == 'node': recastjs_path = sdk_path + '/lib/haxerecast/recastjs/recast.js' recastjs_path = recastjs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(recastjs_path, rel_path=do_relpath_sdk)) if is_publish: assets.add_khafile_def('lnx_published') if wrd.lnx_dce: khafile.write("project.addParameter('-dce full');\n") if wrd.lnx_no_traces: khafile.write("project.addParameter('--no-traces');\n") if wrd.lnx_asset_compression: assets.add_khafile_def('lnx_compress') else: assets.add_khafile_def(f'lnx_assert_level={wrd.lnx_assert_level}') if wrd.lnx_assert_quit: assets.add_khafile_def('lnx_assert_quit') # khafile.write("""project.addParameter("--macro include('leenkx.trait')");\n""") # khafile.write("""project.addParameter("--macro include('leenkx.trait.internal')");\n""") # if export_physics: # khafile.write("""project.addParameter("--macro include('leenkx.trait.physics')");\n""") # if wrd.lnx_physics_engine == 'Bullet': # khafile.write("""project.addParameter("--macro include('leenkx.trait.physics.bullet')");\n""") # else: # khafile.write("""project.addParameter("--macro include('leenkx.trait.physics.oimo')");\n""") # if export_navigation: # khafile.write("""project.addParameter("--macro include('leenkx.trait.navigation')");\n""") if not wrd.lnx_compiler_inline: khafile.write("project.addParameter('--no-inline');\n") use_live_patch = lnx.utils.is_livepatch_enabled() if wrd.lnx_debug_console or use_live_patch: import_traits.append('leenkx.trait.internal.Bridge') if use_live_patch: assets.add_khafile_def('lnx_patch') # Include all logic node classes so that they can later # get instantiated khafile.write("""project.addParameter("--macro include('leenkx.logicnode')");\n""") import_traits = list(set(import_traits)) for i in range(0, len(import_traits)): khafile.write("project.addParameter('" + import_traits[i] + "');\n") khafile.write("""project.addParameter("--macro keep('""" + import_traits[i] + """')");\n""") noembed = wrd.lnx_cache_build and not is_publish and state.target == 'krom' if noembed: # Load shaders manually assets.add_khafile_def('lnx_noembed') noembed = False # TODO: always embed shaders for now, check compatibility with haxe compile server shaders_path = build_dir + '/compiled/Shaders/*.glsl' if rel_path: shaders_path = os.path.relpath(shaders_path, project_path).replace('\\', '/') khafile.write('project.addShaders("' + shaders_path + '", { noembed: ' + str(noembed).lower() + '});\n') if lnx.utils.get_gapi() == 'direct3d11': # noprocessing flag - gets renamed to .d3d11 shaders_path = build_dir + '/compiled/Hlsl/*.glsl' if rel_path: shaders_path = os.path.relpath(shaders_path, project_path).replace('\\', '/') khafile.write('project.addShaders("' + shaders_path + '", { noprocessing: true, noembed: ' + str(noembed).lower() + ' });\n') # Move assets for published game to /data folder use_data_dir = is_publish and (state.target == 'krom-windows' or state.target == 'krom-linux' or state.target == 'windows-hl' or state.target == 'linux-hl' or state.target == 'html5') if use_data_dir: assets.add_khafile_def('lnx_data_dir') ext = 'lnx' if wrd.lnx_minimize else 'json' assets_path = build_dir + '/compiled/Assets/**' assets_path_sh = build_dir + '/compiled/Shaders/*.' + ext if rel_path: assets_path = os.path.relpath(assets_path, project_path).replace('\\', '/') assets_path_sh = os.path.relpath(assets_path_sh, project_path).replace('\\', '/') dest = '' if use_data_dir: dest += ', destination: "data/{name}"' khafile.write('project.addAssets("' + assets_path + '", { notinlist: true ' + dest + '});\n') khafile.write('project.addAssets("' + assets_path_sh + '", { notinlist: true ' + dest + '});\n') shader_data_references = sorted(list(set(assets.shader_datas))) for ref in shader_data_references: ref = ref.replace('\\', '/').replace('//', '/') if '/compiled/' in ref: # Asset already included continue do_relpath_shaders = rel_path and on_same_drive(ref, project_path) khafile.write(add_assets(ref, use_data_dir=use_data_dir, rel_path=do_relpath_shaders)) asset_references = sorted(list(set(assets.assets))) for ref in asset_references: ref = ref.replace('\\', '/').replace('//', '/') if '/compiled/' in ref: # Asset already included continue quality = 1.0 s = ref.lower() if s.endswith('.wav'): quality = wrd.lnx_sound_quality elif s.endswith('.png') or s.endswith('.jpg'): quality = wrd.lnx_texture_quality do_relpath_assets = rel_path and on_same_drive(ref, project_path) khafile.write(add_assets(ref, quality=quality, use_data_dir=use_data_dir, rel_path=do_relpath_assets)) if wrd.lnx_sound_quality < 1.0 or state.target == 'html5': assets.add_khafile_def('lnx_soundcompress') if wrd.lnx_audio == 'Disabled': assets.add_khafile_def('kha_no_ogg') else: assets.add_khafile_def('lnx_audio') lnxAudio_path = sdk_path + '/lib/aura' lnxAudio_path = lnxAudio_path.replace('\\', '/').replace('//', '/') khafile.write("await project.addProject('" + lnxAudio_path + "');\n".format("aura")) if wrd.lnx_texture_quality < 1.0: assets.add_khafile_def('lnx_texcompress') if wrd.lnx_debug_console: assets.add_khafile_def('lnx_debug') if rpdat.rp_renderer == 'Forward': # deferred line frag shader is currently handled in make.py, # only add forward shader here khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/debug_draw/line.frag.glsl", rel_path=do_relpath_sdk)) khafile.write(add_shaders(sdk_path + "/leenkx/Shaders/debug_draw/line.vert.glsl", rel_path=do_relpath_sdk)) if not is_publish and state.target == 'html5': khafile.write("project.addParameter('--debug');\n") if lnx.utils.get_pref_or_default('haxe_times', False): khafile.write("project.addParameter('--times');\n") if export_ui or wrd.lnx_debug_console: if not os.path.exists('Libraries/zui'): khafile.write(add_leenkx_library(sdk_path, 'lib/zui', rel_path=do_relpath_sdk)) p = sdk_path + '/leenkx/Assets/font_default.ttf' p = p.replace('//', '/') khafile.write(add_assets(p.replace('\\', '/'), use_data_dir=use_data_dir, rel_path=do_relpath_sdk)) assets.add_khafile_def('lnx_ui') if export_network: if not os.path.exists('Libraries/network'): khafile.write(add_leenkx_library(sdk_path, 'lib/network', rel_path=do_relpath_sdk)) assets.add_khafile_def('lnx_network') lnxjs_path = sdk_path + '/lib/leenkx_tools/lnxjs/Leenkx.js' lnxjs_path = lnxjs_path.replace('\\', '/').replace('//', '/') khafile.write(add_assets(lnxjs_path, rel_path=do_relpath_sdk)) if not wrd.lnx_minimize: assets.add_khafile_def('lnx_json') if wrd.lnx_deinterleaved_buffers: assets.add_khafile_def('lnx_deinterleaved') if wrd.lnx_batch_meshes: assets.add_khafile_def('lnx_batch') if wrd.lnx_stream_scene: assets.add_khafile_def('lnx_stream') rpdat = lnx.utils.get_rp() if rpdat.lnx_skin != 'Off': assets.add_khafile_def('lnx_skin') if rpdat.lnx_morph_target != 'Off': assets.add_khafile_def('lnx_morph_target') if rpdat.lnx_particles != 'Off': assets.add_khafile_def('lnx_particles') if rpdat.rp_draw_order == 'Shader': assets.add_khafile_def('lnx_draworder_shader') if lnx.utils.get_viewport_controls() == 'azerty': assets.add_khafile_def('lnx_azerty') if os.path.exists(project_path + '/Bundled/config.lnx'): assets.add_khafile_def('lnx_config') if is_publish and wrd.lnx_loadscreen: assets.add_khafile_def('lnx_loadscreen') if wrd.lnx_winresize or state.target == 'html5': assets.add_khafile_def('lnx_resizable') if get_winmode(wrd.lnx_winmode) == 1 and state.target.startswith('html5'): assets.add_khafile_def('kha_html5_disable_automatic_size_adjust') # if bpy.data.scenes[0].unit_settings.system_rotation == 'DEGREES': # assets.add_khafile_def('lnx_degrees') # Allow libraries to recognize Leenkx assets.add_khafile_def('leenkx') for d in assets.khafile_defs: khafile.write("project.addDefine('" + d + "');\n") for p in assets.khafile_params: khafile.write("project.addParameter('" + p + "');\n") if state.target.startswith('android'): bundle = 'org.leenkx3d.' + wrd.lnx_project_package if wrd.lnx_project_bundle == '' else wrd.lnx_project_bundle khafile.write("project.targetOptions.android_native.package = '{0}';\n".format(lnx.utils.safestr(bundle))) if wrd.lnx_winorient != 'Multi': khafile.write("project.targetOptions.android_native.screenOrientation = '{0}';\n".format(wrd.lnx_winorient.lower())) # Android SDK Versions khafile.write("project.targetOptions.android_native.compileSdkVersion = '{0}';\n".format(wrd.lnx_project_android_sdk_compile)) khafile.write("project.targetOptions.android_native.minSdkVersion = '{0}';\n".format(wrd.lnx_project_android_sdk_min)) khafile.write("project.targetOptions.android_native.targetSdkVersion = '{0}';\n".format(wrd.lnx_project_android_sdk_target)) # Permissions if len(wrd.lnx_exporter_android_permission_list) > 0: perms = '' for item in wrd.lnx_exporter_android_permission_list: perm = "'android.permission."+ item.lnx_android_permissions + "'" # Checking In if perms.find(perm) == -1: if len(perms) > 0: perms = perms + ', ' + perm else: perms = perm if len(perms) > 0: khafile.write("project.targetOptions.android_native.permissions = [{0}];\n".format(perms)) # Android ABI Filters if len(wrd.lnx_exporter_android_abi_list) > 0: abis = '' for item in wrd.lnx_exporter_android_abi_list: abi = "'"+ item.lnx_android_abi + "'" # Checking In if abis.find(abi) == -1: if len(abis) > 0: abis = abis + ', ' + abi else: abis = abi if len(abis) > 0: khafile.write("project.targetOptions.android_native.abiFilters = [{0}];\n".format(abis)) elif state.target.startswith('ios'): bundle = 'org.leenkx3d.' + wrd.lnx_project_package if wrd.lnx_project_bundle == '' else wrd.lnx_project_bundle khafile.write("project.targetOptions.ios.bundle = '{0}';\n".format(lnx.utils.safestr(bundle))) if wrd.lnx_project_icon != '': shutil.copy(bpy.path.abspath(wrd.lnx_project_icon), project_path + '/icon.png') if wrd.lnx_khafile is not None: khafile.write(wrd.lnx_khafile.as_string()) khafile.write("\n\nresolve(project);\n") def get_winmode(lnx_winmode): if lnx_winmode == 'Window': return 0 else: # Fullscreen return 1 def write_config(resx, resy): wrd = bpy.data.worlds['Lnx'] p = os.path.join(lnx.utils.get_fp(), 'Bundled') if not os.path.exists(p): os.makedirs(p) rpdat = lnx.utils.get_rp() rp_shadowmap_cube = int(rpdat.rp_shadowmap_cube) if rpdat.rp_shadows else 0 rp_shadowmap_cascade = lnx.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0 output = { 'window_mode': get_winmode(wrd.lnx_winmode), 'window_w': int(resx), 'window_h': int(resy), 'window_resizable': wrd.lnx_winresize, 'window_maximizable': wrd.lnx_winresize and wrd.lnx_winmaximize, 'window_minimizable': wrd.lnx_winminimize, 'window_vsync': wrd.lnx_vsync, 'window_msaa': int(rpdat.lnx_samples_per_pixel), 'window_scale': 1.0, 'rp_supersample': float(rpdat.rp_supersampling), 'rp_shadowmap_cube': rp_shadowmap_cube, 'rp_shadowmap_cascade': rp_shadowmap_cascade, 'rp_ssgi': rpdat.rp_ssgi != 'Off', 'rp_ssr': rpdat.rp_ssr != 'Off', 'rp_ss_refraction': rpdat.rp_ss_refraction != 'Off', 'rp_bloom': rpdat.rp_bloom != 'Off', 'rp_motionblur': rpdat.rp_motionblur != 'Off', 'rp_gi': rpdat.rp_voxels != "Off", 'rp_dynres': rpdat.rp_dynres } with open(os.path.join(p, 'config.lnx'), 'w') as configfile: configfile.write(json.dumps(output, sort_keys=True, indent=4)) def write_mainhx(scene_name, resx, resy, is_play, is_publish): wrd = bpy.data.worlds['Lnx'] rpdat = lnx.utils.get_rp() scene_ext = '.lz4' if (wrd.lnx_asset_compression and is_publish) else '' if scene_ext == '' and not wrd.lnx_minimize: scene_ext = '.json' winmode = get_winmode(wrd.lnx_winmode) # Detect custom render path pathpack = 'leenkx' if os.path.isfile(lnx.utils.get_fp() + '/Sources/' + wrd.lnx_project_package + '/renderpath/RenderPathCreator.hx'): pathpack = wrd.lnx_project_package elif rpdat.rp_driver != 'Leenkx': pathpack = rpdat.rp_driver.lower() with open('Sources/Main.hx', 'w', encoding="utf-8") as f: f.write( """// Auto-generated package;\n""") f.write(""" class Main { public static inline var projectName = '""" + lnx.utils.safestr(wrd.lnx_project_name) + """'; public static inline var projectVersion = '""" + lnx.utils.safestr(wrd.lnx_project_version) + """'; public static inline var projectPackage = '""" + lnx.utils.safestr(wrd.lnx_project_package) + """';""") if rpdat.rp_voxels == 'Voxel GI' or rpdat.rp_voxels == 'Voxel AO': f.write(""" public static inline var voxelgiClipmapCount = """ + str(rpdat.lnx_voxelgi_clipmap_count) + """; public static inline var voxelgiVoxelSize = """ + str(round(rpdat.lnx_voxelgi_size * 100) / 100) + """;""") if rpdat.rp_bloom: if bpy.app.version <= (4, 2, 4): f.write(f"public static var bloomRadius = {bpy.context.scene.eevee.bloom_radius if rpdat.lnx_bloom_follow_blender else rpdat.lnx_bloom_radius};") else: f.write(f"public static var bloomRadius = {rpdat.lnx_bloom_radius};") if rpdat.lnx_rp_resolution == 'Custom': f.write(""" public static inline var resolutionSize = """ + str(rpdat.lnx_rp_resolution_size) + """;""") f.write("""\n public static function main() {""") if rpdat.lnx_skin != 'Off': f.write(""" iron.object.BoneAnimation.skinMaxBones = """ + str(rpdat.lnx_skin_max_bones) + """;""") if rpdat.rp_shadows: if rpdat.rp_shadowmap_cascades != '1': f.write(""" iron.object.LightObject.cascadeCount = """ + str(rpdat.rp_shadowmap_cascades) + """; iron.object.LightObject.cascadeSplitFactor = """ + str(rpdat.lnx_shadowmap_split) + """;""") if rpdat.lnx_shadowmap_bounds != 1.0: f.write(""" iron.object.LightObject.cascadeBounds = """ + str(rpdat.lnx_shadowmap_bounds) + """;""") if is_publish and wrd.lnx_loadscreen: asset_references = list(set(assets.assets)) loadscreen_class = 'leenkx.trait.internal.LoadingScreen' if os.path.isfile(lnx.utils.get_fp() + '/Sources/' + wrd.lnx_project_package + '/LoadingScreen.hx'): loadscreen_class = wrd.lnx_project_package + '.LoadingScreen' f.write(""" leenkx.system.Starter.numAssets = """ + str(len(asset_references)) + """; leenkx.system.Starter.drawLoading = """ + loadscreen_class + """.render;""") if wrd.lnx_ui == 'Enabled': if wrd.lnx_canvas_img_scaling_quality == 'low': f.write(""" leenkx.ui.Canvas.imageScaleQuality = kha.graphics2.ImageScaleQuality.Low;""") elif wrd.lnx_canvas_img_scaling_quality == 'high': f.write(""" leenkx.ui.Canvas.imageScaleQuality = kha.graphics2.ImageScaleQuality.High;""") f.write(""" leenkx.system.Starter.main( '""" + lnx.utils.safestr(scene_name) + scene_ext + """', """ + str(winmode) + """, """ + ('true' if wrd.lnx_winresize else 'false') + """, """ + ('true' if wrd.lnx_winminimize else 'false') + """, """ + ('true' if (wrd.lnx_winresize and wrd.lnx_winmaximize) else 'false') + """, """ + str(resx) + """, """ + str(resy) + """, """ + str(int(rpdat.lnx_samples_per_pixel)) + """, """ + ('true' if wrd.lnx_vsync else 'false') + """, """ + pathpack + """.renderpath.RenderPathCreator.get ); } }""") def write_indexhtml(w, h, is_publish): wrd = bpy.data.worlds['Lnx'] rpdat = lnx.utils.get_rp() dest = '/html5' if is_publish else '/debug/html5' if not os.path.exists(lnx.utils.build_dir() + dest): os.makedirs(lnx.utils.build_dir() + dest) popupmenu_in_browser = '' if wrd.lnx_project_html5_popupmenu_in_browser: popupmenu_in_browser = ' oncontextmenu="return false"' with open(lnx.utils.build_dir() + dest + '/index.html', 'w') as f: f.write( """ """) if rpdat.rp_stereo or wrd.lnx_winmode == 'Fullscreen': f.write(""" """) f.write(""" """+html.escape( wrd.lnx_project_name)+""" """) if rpdat.rp_stereo or wrd.lnx_winmode == 'Fullscreen': f.write(""" """) else: f.write("""

""") f.write(""" """) add_compiledglsl = '' def write_compiledglsl(defs, make_variants): rpdat = lnx.utils.get_rp() wrd = bpy.data.worlds['Lnx'] shadowmap_size = lnx.utils.get_cascade_size(rpdat) if rpdat.rp_shadows else 0 with open(lnx.utils.build_dir() + '/compiled/Shaders/compiled.inc', 'w') as f: f.write( """#ifndef _COMPILED_GLSL_ #define _COMPILED_GLSL_ """) for d in defs: if make_variants and d.endswith('var'): continue # Write a shader variant instead f.write("#define " + d + "\n") if rpdat.rp_renderer == "Deferred": gbuffer_size = make_renderpath.get_num_gbuffer_rts_deferred() f.write(f'#define GBUF_SIZE {gbuffer_size}\n') # Write indices of G-Buffer render targets f.write('#define GBUF_IDX_0 0\n') f.write('#define GBUF_IDX_1 1\n') idx_emission = 2 idx_refraction = 2 if '_gbuffer2' in wrd.world_defs: f.write('#define GBUF_IDX_2 2\n') idx_emission += 1 idx_refraction += 1 if '_EmissionShaded' in wrd.world_defs: f.write(f'#define GBUF_IDX_EMISSION {idx_emission}\n') idx_refraction += 1 if '_SSRefraction' in wrd.world_defs or '_VoxelRefract' in wrd.world_defs: f.write(f'#define GBUF_IDX_REFRACTION {idx_refraction}\n') f.write("""#if defined(HLSL) || defined(METAL) #define _InvY #endif """) if state.target == 'html5' or lnx.utils.get_gapi() == 'direct3d11': f.write("#define _FlipY\n") f.write("""const float PI = 3.1415926535; const float PI2 = PI * 2.0; const vec2 shadowmapSize = vec2(""" + str(shadowmap_size) + """, """ + str(shadowmap_size) + """); const float shadowmapCubePcfSize = """ + str((round(rpdat.lnx_pcfsize * 100) / 100) / 1000) + """; const int shadowmapCascades = """ + str(rpdat.rp_shadowmap_cascades) + """; """) if rpdat.rp_water: f.write( """const float waterLevel = """ + str(round(rpdat.lnx_water_level * 100) / 100) + """; const float waterDisplace = """ + str(round(rpdat.lnx_water_displace * 100) / 100) + """; const float waterSpeed = """ + str(round(rpdat.lnx_water_speed * 100) / 100) + """; const float waterFreq = """ + str(round(rpdat.lnx_water_freq * 100) / 100) + """; const vec3 waterColor = vec3(""" + str(round(rpdat.lnx_water_color[0] * 100) / 100) + """, """ + str(round(rpdat.lnx_water_color[1] * 100) / 100) + """, """ + str(round(rpdat.lnx_water_color[2] * 100) / 100) + """); const float waterDensity = """ + str(round(rpdat.lnx_water_density * 100) / 100) + """; const float waterRefract = """ + str(round(rpdat.lnx_water_refract * 100) / 100) + """; const float waterReflect = """ + str(round(rpdat.lnx_water_reflect * 100) / 100) + """; """) if rpdat.rp_ssgi == 'SSAO' or rpdat.rp_ssgi == 'RTAO' or rpdat.rp_volumetriclight: f.write( """const float ssaoRadius = """ + str(round(rpdat.lnx_ssgi_radius * 100) / 100) + """; const float ssaoStrength = """ + str(round(rpdat.lnx_ssgi_strength * 100) / 100) + """; const float ssaoScale = """ + ("2.0" if rpdat.lnx_ssgi_half_res else "20.0") + """; """) if rpdat.rp_ssgi == 'RTGI' or rpdat.rp_ssgi == 'RTAO': f.write( """const int ssgiMaxSteps = """ + str(rpdat.lnx_ssgi_max_steps) + """; const float ssgiRayStep = 0.005 * """ + str(round(rpdat.lnx_ssgi_step * 100) / 100) + """; const float ssgiStrength = """ + str(round(rpdat.lnx_ssgi_strength * 100) / 100) + """; """) if rpdat.rp_bloom: follow_blender = rpdat.lnx_bloom_follow_blender eevee_settings = bpy.context.scene.eevee if bpy.app.version <= (4, 2, 4): threshold = eevee_settings.bloom_threshold if follow_blender else rpdat.lnx_bloom_threshold strength = eevee_settings.bloom_intensity if follow_blender else rpdat.lnx_bloom_strength knee = eevee_settings.bloom_knee if follow_blender else rpdat.lnx_bloom_knee else: threshold = rpdat.lnx_bloom_threshold strength = rpdat.lnx_bloom_strength knee = rpdat.lnx_bloom_knee f.write( """const float bloomThreshold = """ + str(round(threshold * 100) / 100) + """; const float bloomStrength = """ + str(round(strength * 100) / 100) + """; const float bloomKnee = """ + str(round(knee * 100) / 100) + """; const float bloomRadius = """ + str(round(rpdat.lnx_bloom_radius * 100) / 100) + """; """) # TODO remove radius if old bloom pass is removed if rpdat.rp_motionblur != 'Off': f.write( """const float motionBlurIntensity = """ + str(round(rpdat.lnx_motion_blur_intensity * 100) / 100) + """; """) if rpdat.rp_ssr: f.write( """const float ssrRayStep = """ + str(round(rpdat.lnx_ssr_ray_step * 100) / 100) + """; const float ssrSearchDist = """ + str(round(rpdat.lnx_ssr_search_dist * 100) / 100) + """; const float ssrFalloffExp = """ + str(round(rpdat.lnx_ssr_falloff_exp * 100) / 100) + """; const float ssrJitter = """ + str(round(rpdat.lnx_ssr_jitter * 100) / 100) + """; """) if rpdat.rp_ss_refraction: f.write( """const float ss_refractionRayStep = """ + str(round(rpdat.lnx_ss_refraction_ray_step * 100) / 100) + """; const float ss_refractionSearchDist = """ + str(round(rpdat.lnx_ss_refraction_search_dist * 100) / 100) + """; const float ss_refractionFalloffExp = """ + str(round(rpdat.lnx_ss_refraction_falloff_exp * 100) / 100) + """; const float ss_refractionJitter = """ + str(round(rpdat.lnx_ss_refraction_jitter * 100) / 100) + """; """) if rpdat.lnx_ssrs: f.write( """const float ssrsRayStep = """ + str(round(rpdat.lnx_ssrs_ray_step * 100) / 100) + """; """) if rpdat.rp_volumetriclight: f.write( """const float volumAirTurbidity = """ + str(round(rpdat.lnx_volumetric_light_air_turbidity * 100) / 100) + """; const vec3 volumAirColor = vec3(""" + str(round(rpdat.lnx_volumetric_light_air_color[0] * 100) / 100) + """, """ + str(round(rpdat.lnx_volumetric_light_air_color[1] * 100) / 100) + """, """ + str(round(rpdat.lnx_volumetric_light_air_color[2] * 100) / 100) + """); const int volumSteps = """ + str(rpdat.lnx_volumetric_light_steps) + """; """) if rpdat.rp_autoexposure: f.write( """const float autoExposureStrength = """ + str(rpdat.lnx_autoexposure_strength) + """; const float autoExposureSpeed = """ + str(rpdat.lnx_autoexposure_speed) + """; """) # Compositor if rpdat.lnx_letterbox: f.write( """const float compoLetterboxSize = """ + str(round(rpdat.lnx_letterbox_size * 100) / 100) + """; const vec3 compoLetterboxColor = vec3(""" + str(round(rpdat.lnx_letterbox_color[0] * 100) / 100) + """, """ + str(round(rpdat.lnx_letterbox_color[1] * 100) / 100) + """, """ + str(round(rpdat.lnx_letterbox_color[2] * 100) / 100) + """); """) if rpdat.lnx_distort: f.write( """const float compoDistortStrength = """ + str(round(rpdat.lnx_distort_strength * 100) / 100) + """; """) if rpdat.lnx_grain: f.write( """const float compoGrainStrength = """ + str(round(rpdat.lnx_grain_strength * 100) / 100) + """; """) if rpdat.lnx_vignette: f.write( """const float compoVignetteStrength = """ + str(round(rpdat.lnx_vignette_strength * 100) / 100) + """; """) if rpdat.lnx_sharpen: f.write( """const float compoSharpenStrength = """ + str(round(rpdat.lnx_sharpen_strength * 100) / 100) + """; """) if bpy.data.scenes[0].view_settings.exposure != 0.0: f.write( """const float compoExposureStrength = """ + str(round(bpy.data.scenes[0].view_settings.exposure * 100) / 100) + """; """) if rpdat.lnx_fog: f.write( """const float compoFogAmountA = """ + str(round(rpdat.lnx_fog_amounta * 100) / 100) + """; const float compoFogAmountB = """ + str(round(rpdat.lnx_fog_amountb * 100) / 100) + """; const vec3 compoFogColor = vec3(""" + str(round(rpdat.lnx_fog_color[0] * 100) / 100) + """, """ + str(round(rpdat.lnx_fog_color[1] * 100) / 100) + """, """ + str(round(rpdat.lnx_fog_color[2] * 100) / 100) + """); """) if rpdat.lnx_lens_texture_masking: f.write( """const float compoCenterMinClip = """ + str(round(rpdat.lnx_lens_texture_masking_centerMinClip * 100) / 100) + """; const float compoCenterMaxClip = """ + str(round(rpdat.lnx_lens_texture_masking_centerMaxClip * 100) / 100) + """; const float compoLuminanceMin = """ + str(round(rpdat.lnx_lens_texture_masking_luminanceMin * 100) / 100) + """; const float compoLuminanceMax = """ + str(round(rpdat.lnx_lens_texture_masking_luminanceMax * 100) / 100) + """; const float compoBrightnessExponent = """ + str(round(rpdat.lnx_lens_texture_masking_brightnessExp * 100) / 100) + """; """) if rpdat.rp_chromatic_aberration: f.write( f"""const float compoChromaticStrength = {round(rpdat.lnx_chromatic_aberration_strength * 100) / 100}; const int compoChromaticSamples = {rpdat.lnx_chromatic_aberration_samples}; """) if rpdat.lnx_chromatic_aberration_type == "Spectral": f.write("const int compoChromaticType = 1;") else: f.write("const int compoChromaticType = 0;") focus_distance = 0.0 fstop = 0.0 if len(bpy.data.cameras) > 0 and bpy.data.cameras[0].dof.use_dof: focus_distance = bpy.data.cameras[0].dof.focus_distance fstop = bpy.data.cameras[0].dof.aperture_fstop if focus_distance > 0.0: f.write( """const float compoDOFDistance = """ + str(round(focus_distance * 100) / 100) + """; const float compoDOFFstop = """ + str(round(fstop * 100) / 100) + """; const float compoDOFLength = 160.0; """) # str(round(bpy.data.cameras[0].lens * 100) / 100) if rpdat.rp_voxels != 'Off': f.write("""const ivec3 voxelgiResolution = ivec3(""" + str(rpdat.rp_voxelgi_resolution) + """, """ + str(rpdat.rp_voxelgi_resolution) + """, """ + str(rpdat.rp_voxelgi_resolution) + """); const int voxelgiClipmapCount = """ + str(rpdat.lnx_voxelgi_clipmap_count) + """; const float voxelgiOcc = """ + str(round(rpdat.lnx_voxelgi_occ * 100) / 100) + """; const float voxelgiVoxelSize = """ + str(round(rpdat.lnx_voxelgi_size * 1000) / 1000) + """; const float voxelgiStep = """ + str(round(rpdat.lnx_voxelgi_step * 1000) / 1000) + """; const float voxelgiRange = """ + str(round(rpdat.lnx_voxelgi_range * 100) / 100) + """; const float voxelgiOffset = """ + str(round(rpdat.lnx_voxelgi_offset * 1000) / 1000) + """; const float voxelgiAperture = """ + str(round(rpdat.lnx_voxelgi_aperture * 100) / 100) + """; const float voxelgiShad = """ + str(round(rpdat.lnx_voxelgi_shad * 100) / 100) + """; """) if rpdat.rp_voxels == 'Voxel GI': f.write(""" const float voxelgiDiff = """ + str(round(rpdat.lnx_voxelgi_diff * 100) / 100) + """; const float voxelgiRefl = """ + str(round(rpdat.lnx_voxelgi_spec * 100) / 100) + """; const float voxelgiRefr = """ + str(round(rpdat.lnx_voxelgi_refr * 100) / 100) + """; """) if rpdat.rp_sss: f.write(f"const float sssWidth = {rpdat.lnx_sss_width / 10.0};\n") # Skinning if rpdat.lnx_skin == 'On': f.write( """const int skinMaxBones = """ + str(rpdat.lnx_skin_max_bones) + """; """) if '_Clusters' in wrd.world_defs: max_lights = "4" max_lights_clusters = "4" if rpdat.rp_shadowmap_atlas: max_lights = str(rpdat.rp_max_lights) max_lights_clusters = str(rpdat.rp_max_lights_cluster) # prevent max lights cluster being higher than max lights if (int(max_lights_clusters) > int(max_lights)): max_lights_clusters = max_lights f.write( """const int maxLights = """ + max_lights + """; const int maxLightsCluster = """ + max_lights_clusters + """; const float clusterNear = 3.0; """) f.write(add_compiledglsl + '\n') # External defined constants f.write("""#endif // _COMPILED_GLSL_ """) def write_traithx(class_path): wrd = bpy.data.worlds['Lnx'] # Split the haxe package syntax in components that will compose the path path_components = class_path.split('.') # extract the full file name (file + ext) from the components class_name = path_components[-1] # Create the absolute trait path (os-safe) package_path = os.sep.join([lnx.utils.get_fp(), 'Sources', lnx.utils.safestr(wrd.lnx_project_package)] + path_components[:-1]) if not os.path.exists(package_path): os.makedirs(package_path) package = '.'.join([lnx.utils.safestr(wrd.lnx_project_package)] + path_components[:-1]); with open(package_path + '/' + class_name + '.hx', 'w') as f: f.write( """package """ + package + """; class """ + class_name + """ extends iron.Trait { \tpublic function new() { \t\tsuper(); \t\t// notifyOnInit(function() { \t\t// }); \t\t// notifyOnUpdate(function() { \t\t// }); \t\t// notifyOnRemove(function() { \t\t// }); \t} } """) def write_canvasjson(canvas_name): canvas_path = lnx.utils.get_fp() + '/Bundled/canvas' if not os.path.exists(canvas_path): os.makedirs(canvas_path) with open(canvas_path + '/' + canvas_name + '.json', 'w') as f: f.write( """{ "name": "untitled", "x": 0.0, "y": 0.0, "width": 1280, "height": 720, "theme": "Default Light", "elements": [], "assets": [] }""")