From a59cffdf39a667af0b5101d6cc095e9e0727d709 Mon Sep 17 00:00:00 2001 From: Astrofra Date: Fri, 3 Jun 2022 22:57:29 +0200 Subject: [PATCH] Tutorials update for HARFANG v3.2.2. Core update for for HARFANG v3.2.2. The tutorials can now be stopped using the 'close' button of the render window. New tutorial showing OGG streaming. Fixed a missing 'integer divide' in the Lua multiple viewports tutorial. --- audio_stream_ogg_stereo.lua | 18 ++ audio_stream_ogg_stereo.py | 18 ++ basic_loop.lua | 2 +- basic_loop.py | 2 +- draw_lines.lua | 2 +- draw_lines.py | 2 +- draw_lines_starfield.lua | 2 +- draw_lines_starfield.py | 2 +- draw_model_no_pipeline.lua | 2 +- draw_model_no_pipeline.py | 2 +- draw_text.lua | 2 +- draw_text.py | 2 +- draw_text_over_models.lua | 2 +- draw_text_over_models.py | 2 +- game_mouse_flight.lua | 2 +- game_mouse_flight.py | 2 +- imgui_basic.lua | 2 +- imgui_basic.py | 2 +- imgui_edit.lua | 2 +- imgui_edit.py | 2 +- imgui_mouse_capture.lua | 2 +- imgui_mouse_capture.py | 2 +- input_read_gamepad.py | 2 +- material_update_value.lua | 2 +- material_update_value.py | 2 +- physics_impulse.lua | 2 +- physics_impulse.py | 2 +- physics_kapla.lua | 2 +- physics_kapla.py | 2 +- physics_manual_setup.lua | 2 +- physics_manual_setup.py | 2 +- physics_overrides_matrix.lua | 2 +- physics_overrides_matrix.py | 2 +- physics_pool_of_objects.lua | 2 +- physics_pool_of_objects.py | 2 +- render_resize_to_window.lua | 2 +- render_resize_to_window.py | 2 +- resources/car_engine/engine.scn | 107 ++++++---- resources/car_engine/engine.scn.aaa | 12 ++ resources/car_engine/engine.scn.editor | 57 +++++- resources/core/pbr/probe.hdr.meta | 2 +- resources/core/shader/bloom_downsample_fs.sc | 35 ++-- resources/core/shader/bloom_upsample_fs.sc | 28 ++- resources/core/shader/compositing_fs.sc | 50 +++-- resources/core/shader/default_fs.sc | 67 ++++--- resources/core/shader/default_vs.sc | 44 ++--- resources/core/shader/forward_pipeline.sh | 89 +++++++-- resources/core/shader/hiz_compute_cs.sc | 64 +++--- resources/core/shader/hiz_copy_cs.sc | 9 +- resources/core/shader/hiz_trace.sh | 196 ++++++++++--------- resources/core/shader/pbr_fs.sc | 97 +++++---- resources/core/shader/pbr_vs.sc | 34 ++-- resources/core/shader/ssgi_fs.sc | 32 +-- resources/core/shader/ssr_fs.sc | 65 ++++-- resources/core/shader/taa_fs.sc | 14 ++ resources/sounds/metro_announce.ogg | Bin 0 -> 73374 bytes scene_aaa.lua | 2 +- scene_aaa.py | 2 +- scene_draw_to_multiple_viewports.lua | 10 +- scene_draw_to_multiple_viewports.py | 2 +- scene_draw_to_texture.lua | 2 +- scene_draw_to_texture.py | 2 +- scene_instances.lua | 2 +- scene_instances.py | 2 +- scene_light_priority.lua | 2 +- scene_light_priority.py | 2 +- scene_many_nodes.lua | 2 +- scene_many_nodes.py | 2 +- scene_pbr.lua | 2 +- scene_pbr.py | 2 +- scene_vr.lua | 2 +- scene_vr.py | 2 +- 72 files changed, 726 insertions(+), 422 deletions(-) create mode 100644 audio_stream_ogg_stereo.lua create mode 100644 audio_stream_ogg_stereo.py create mode 100644 resources/car_engine/engine.scn.aaa create mode 100644 resources/sounds/metro_announce.ogg diff --git a/audio_stream_ogg_stereo.lua b/audio_stream_ogg_stereo.lua new file mode 100644 index 0000000..189ba8a --- /dev/null +++ b/audio_stream_ogg_stereo.lua @@ -0,0 +1,18 @@ +-- Stream a mono OGG file with stereo panning + +hg = require("harfang") + +hg.InputInit() +hg.AudioInit() + +src_ref = hg.StreamOGGFileStereo("resources_compiled/sounds/metro_announce.ogg", hg.StereoSourceState(1, hg.SR_Loop)) -- OGG 44.1kHz 16bit mono + +angle = 0 + +while not hg.ReadKeyboard('raw'):Key(hg.K_Escape) do + angle = angle + hg.time_to_sec_f(hg.TickClock()) * 0.5 + hg.SetSourcePanning(src_ref, math.sin(angle)) -- panning left = -1, panning right = 1 +end + +hg.AudioShutdown() +hg.InputShutdown() diff --git a/audio_stream_ogg_stereo.py b/audio_stream_ogg_stereo.py new file mode 100644 index 0000000..05891d0 --- /dev/null +++ b/audio_stream_ogg_stereo.py @@ -0,0 +1,18 @@ +# Stream a mono OGG file with stereo panning + +import harfang as hg +import math + +hg.InputInit() +hg.AudioInit() + +src_ref = hg.StreamOGGFileStereo("resources_compiled/sounds/metro_announce.ogg", hg.StereoSourceState(1, hg.SR_Loop)) # OGG 44.1kHz 16bit mono + +angle = 0 + +while not hg.ReadKeyboard('raw').Key(hg.K_Escape): + angle += hg.time_to_sec_f(hg.TickClock()) * 0.5 + hg.SetSourcePanning(src_ref, math.sin(angle)) # panning left = -1, panning right = 1 + +hg.AudioShutdown() +hg.InputShutdown() diff --git a/basic_loop.lua b/basic_loop.lua index da69a31..d96b3eb 100644 --- a/basic_loop.lua +++ b/basic_loop.lua @@ -8,7 +8,7 @@ hg.WindowSystemInit() width, height = 1280, 720 window = hg.RenderInit('Harfang - Basic Loop', width, height, hg.RF_VSync) -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(window) do hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.Color.Green, 1, 0) hg.SetViewRect(0, 0, 0, width, height) diff --git a/basic_loop.py b/basic_loop.py index 9cd8ed4..a4c7ce3 100644 --- a/basic_loop.py +++ b/basic_loop.py @@ -8,7 +8,7 @@ width, height = 1280, 720 window = hg.RenderInit('Harfang - Basic Loop', width, height, hg.RF_VSync) -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(window): hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.Color.Green, 1, 0) hg.SetViewRect(0, 0, 0, width, height) diff --git a/draw_lines.lua b/draw_lines.lua index 618ebfc..051d6a1 100644 --- a/draw_lines.lua +++ b/draw_lines.lua @@ -23,7 +23,7 @@ vtx = hg.Vertices(vtx_layout, line_count * 2) -- main loop angle = 0 -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.ColorI(64, 64, 64), 1, 0) hg.SetViewRect(0, 0, 0, res_x, res_y) diff --git a/draw_lines.py b/draw_lines.py index 56df02b..4336fab 100644 --- a/draw_lines.py +++ b/draw_lines.py @@ -24,7 +24,7 @@ # main loop angle = 0 -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.ColorI(64, 64, 64), 1, 0) hg.SetViewRect(0, 0, 0, res_x, res_y) diff --git a/draw_lines_starfield.lua b/draw_lines_starfield.lua index 25d5994..98b48a6 100644 --- a/draw_lines_starfield.lua +++ b/draw_lines_starfield.lua @@ -30,7 +30,7 @@ for i = 1, max_stars do end -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(window) do hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.Color.Black, 1, 0) hg.SetViewRect(0, 0, 0, width, height) diff --git a/draw_lines_starfield.py b/draw_lines_starfield.py index 716d4c0..ec5a96f 100644 --- a/draw_lines_starfield.py +++ b/draw_lines_starfield.py @@ -29,7 +29,7 @@ stars.append(hg.RandomVec3(-starfield_size, starfield_size)) # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(window): hg.SetViewClear(0, hg.CF_Color | hg.CF_Depth, hg.Color.Black, 1, 0) hg.SetViewRect(0, 0, 0, width, height) diff --git a/draw_model_no_pipeline.lua b/draw_model_no_pipeline.lua index fe921e0..e44435b 100644 --- a/draw_model_no_pipeline.lua +++ b/draw_model_no_pipeline.lua @@ -19,7 +19,7 @@ shader = hg.LoadProgramFromFile('resources_compiled/shaders/mdl') -- main loop angle = 0 -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() angle = angle + hg.time_to_sec_f(dt) diff --git a/draw_model_no_pipeline.py b/draw_model_no_pipeline.py index 34c4429..22847a3 100644 --- a/draw_model_no_pipeline.py +++ b/draw_model_no_pipeline.py @@ -19,7 +19,7 @@ # main loop angle = 0 -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() angle = angle + hg.time_to_sec_f(dt) diff --git a/draw_text.lua b/draw_text.lua index cba7de4..debc708 100644 --- a/draw_text.lua +++ b/draw_text.lua @@ -17,7 +17,7 @@ text_uniform_values = {hg.MakeUniformSetValue('u_color', hg.Vec4(1, 1, 0))} text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Always, hg.FC_Disabled) -- main loop -while not hg.ReadKeyboard('default'):Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do hg.SetView2D(0, 0, 0, res_x, res_y, -1, 1, hg.CF_Color | hg.CF_Depth, hg.ColorI(32, 32, 32), 0, 1) hg.DrawText(0, font, 'Hello world!', font_prg, 'u_tex', 0, hg.Mat4.Identity, hg.Vec3(res_x / 2, res_y / 2, 0), hg.DTHA_Center, hg.DTVA_Center, text_uniform_values, {}, text_render_state) diff --git a/draw_text.py b/draw_text.py index 0ee559e..e306f44 100644 --- a/draw_text.py +++ b/draw_text.py @@ -17,7 +17,7 @@ text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Always, hg.FC_Disabled) # main loop -while not hg.ReadKeyboard('default').Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): hg.SetView2D(0, 0, 0, res_x, res_y, -1, 1, hg.CF_Color | hg.CF_Depth, hg.ColorI(32, 32, 32), 0, 1) hg.DrawText(0, font, 'Hello world!', font_prg, 'u_tex', 0, hg.Mat4.Identity, hg.Vec3(res_x / 2, res_y / 2, 0), hg.DTHA_Center, hg.DTVA_Center, text_uniform_values, [], text_render_state) diff --git a/draw_text_over_models.lua b/draw_text_over_models.lua index 7c5c053..dcc8afc 100644 --- a/draw_text_over_models.lua +++ b/draw_text_over_models.lua @@ -27,7 +27,7 @@ text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Always, hg.FC_Disab -- main loop angle = 0 -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() angle = angle + hg.time_to_sec_f(dt) diff --git a/draw_text_over_models.py b/draw_text_over_models.py index b4564b6..8c81205 100644 --- a/draw_text_over_models.py +++ b/draw_text_over_models.py @@ -27,7 +27,7 @@ # main loop angle = 0 -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() angle = angle + hg.time_to_sec_f(dt) diff --git a/game_mouse_flight.lua b/game_mouse_flight.lua index 26c5252..b88a6e1 100644 --- a/game_mouse_flight.lua +++ b/game_mouse_flight.lua @@ -88,7 +88,7 @@ function update_chase_camera(target_pos) end -- game loop -while not keyboard:Down(hg.K_Escape) do +while not keyboard:Down(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() -- tick clock, retrieve elapsed clock since last call -- update mouse/keyboard devices diff --git a/game_mouse_flight.py b/game_mouse_flight.py index 965da1c..3878b02 100644 --- a/game_mouse_flight.py +++ b/game_mouse_flight.py @@ -90,7 +90,7 @@ def update_chase_camera(target_pos): # game loop -while not keyboard.Down(hg.K_Escape): +while not keyboard.Down(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() # tick clock, retrieve elapsed clock since last call # update mouse/keyboard devices diff --git a/imgui_basic.lua b/imgui_basic.lua index 973cf51..77c6a48 100644 --- a/imgui_basic.lua +++ b/imgui_basic.lua @@ -17,7 +17,7 @@ imgui_img_prg = hg.LoadProgramFromAssets('core/shader/imgui_image') hg.ImGuiInit(10, imgui_prg, imgui_img_prg) -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do hg.ImGuiBeginFrame(res_x, res_y, hg.TickClock(), hg.ReadMouse(), hg.ReadKeyboard()) if hg.ImGuiBegin('Window') then diff --git a/imgui_basic.py b/imgui_basic.py index df84a9e..0037e6a 100644 --- a/imgui_basic.py +++ b/imgui_basic.py @@ -17,7 +17,7 @@ hg.ImGuiInit(10, imgui_prg, imgui_img_prg) # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): hg.ImGuiBeginFrame(res_x, res_y, hg.TickClock(), hg.ReadMouse(), hg.ReadKeyboard()) if hg.ImGuiBegin('Window'): diff --git a/imgui_edit.lua b/imgui_edit.lua index c2533db..e0c8bf8 100644 --- a/imgui_edit.lua +++ b/imgui_edit.lua @@ -21,7 +21,7 @@ imgui_view_clear_color = hg.Color(0, 0, 0) imgui_clear_color_preset = 0 -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() -- ImGui frame diff --git a/imgui_edit.py b/imgui_edit.py index 1f94e32..3d0ad63 100644 --- a/imgui_edit.py +++ b/imgui_edit.py @@ -21,7 +21,7 @@ imgui_clear_color_preset = 0 # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() # ImGui frame diff --git a/imgui_mouse_capture.lua b/imgui_mouse_capture.lua index a0eabec..d9b9659 100644 --- a/imgui_mouse_capture.lua +++ b/imgui_mouse_capture.lua @@ -18,7 +18,7 @@ text_value = 'Clicking into this field will not clear the screen in red.' mouse = hg.Mouse() keyboard = hg.Keyboard() -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do mouse:Update() keyboard:Update() diff --git a/imgui_mouse_capture.py b/imgui_mouse_capture.py index 78450a5..cf5485f 100644 --- a/imgui_mouse_capture.py +++ b/imgui_mouse_capture.py @@ -18,7 +18,7 @@ mouse = hg.Mouse() keyboard = hg.Keyboard() -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): mouse.Update() keyboard.Update() diff --git a/input_read_gamepad.py b/input_read_gamepad.py index fae78cf..224db78 100644 --- a/input_read_gamepad.py +++ b/input_read_gamepad.py @@ -9,7 +9,7 @@ gamepad = hg.Gamepad() -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): gamepad.Update() if gamepad.Connected(): diff --git a/material_update_value.lua b/material_update_value.lua index 0b4341e..ebeaa5d 100644 --- a/material_update_value.lua +++ b/material_update_value.lua @@ -46,7 +46,7 @@ mat_update_delay = 0 texture_ref = hg.LoadTextureFromAssets('textures/squares.png', 0, res) -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() mat_update_delay = mat_update_delay - dt diff --git a/material_update_value.py b/material_update_value.py index 43265b2..8c4a88b 100644 --- a/material_update_value.py +++ b/material_update_value.py @@ -46,7 +46,7 @@ texture_ref = hg.LoadTextureFromAssets('textures/squares.png', 0, res) # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() mat_update_delay = mat_update_delay - dt diff --git a/physics_impulse.lua b/physics_impulse.lua index 69ef2b0..005e4f2 100644 --- a/physics_impulse.lua +++ b/physics_impulse.lua @@ -47,7 +47,7 @@ keyboard = hg.Keyboard() use_force = true -while not keyboard:Down(hg.K_Escape) do +while not keyboard:Down(hg.K_Escape) and hg.IsWindowOpen(win) do keyboard:Update() dt = hg.TickClock() diff --git a/physics_impulse.py b/physics_impulse.py index 5af0637..f2c3d92 100644 --- a/physics_impulse.py +++ b/physics_impulse.py @@ -47,7 +47,7 @@ use_force = True -while not keyboard.Down(hg.K_Escape): +while not keyboard.Down(hg.K_Escape) and hg.IsWindowOpen(win): keyboard.Update() dt = hg.TickClock() diff --git a/physics_kapla.lua b/physics_kapla.lua index c7e1de2..d677854 100644 --- a/physics_kapla.lua +++ b/physics_kapla.lua @@ -94,7 +94,7 @@ physics:SceneCreatePhysicsFromAssets(scene) physics_step = hg.time_from_sec_f(1 / 60) -- main loop -while not keyboard:Down(hg.K_Escape) do +while not keyboard:Down(hg.K_Escape) and hg.IsWindowOpen(win) do keyboard:Update() mouse:Update() diff --git a/physics_kapla.py b/physics_kapla.py index 2d22cb9..2888360 100644 --- a/physics_kapla.py +++ b/physics_kapla.py @@ -92,7 +92,7 @@ def fill_ring(r, ring_y, size, r_adjust, y_off): physics_step = hg.time_from_sec_f(1 / 60) # main loop -while not keyboard.Down(hg.K_Escape): +while not keyboard.Down(hg.K_Escape) and hg.IsWindowOpen(win): keyboard.Update() mouse.Update() diff --git a/physics_manual_setup.lua b/physics_manual_setup.lua index 226866b..02a1d7e 100644 --- a/physics_manual_setup.lua +++ b/physics_manual_setup.lua @@ -54,7 +54,7 @@ physics = hg.SceneBullet3Physics() physics:SceneCreatePhysicsFromAssets(scene) -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do dt = hg.TickClock() hg.SceneUpdateSystems(scene, clocks, dt, physics, hg.time_from_sec_f(1 / 60), 1) diff --git a/physics_manual_setup.py b/physics_manual_setup.py index 9e3ca12..13d3f61 100644 --- a/physics_manual_setup.py +++ b/physics_manual_setup.py @@ -54,7 +54,7 @@ physics.SceneCreatePhysicsFromAssets(scene) # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): dt = hg.TickClock() hg.SceneUpdateSystems(scene, clocks, dt, physics, hg.time_from_sec_f(1 / 60), 1) diff --git a/physics_overrides_matrix.lua b/physics_overrides_matrix.lua index 5a375fe..ac76b44 100644 --- a/physics_overrides_matrix.lua +++ b/physics_overrides_matrix.lua @@ -47,7 +47,7 @@ physics:SceneCreatePhysicsFromAssets(scene) -- main loop mouse, keyboard = hg.Mouse(), hg.Keyboard() -while not keyboard:Pressed(hg.K_Escape) do +while not keyboard:Pressed(hg.K_Escape) and hg.IsWindowOpen(win) do keyboard:Update() mouse:Update() diff --git a/physics_overrides_matrix.py b/physics_overrides_matrix.py index 027f392..74c0930 100644 --- a/physics_overrides_matrix.py +++ b/physics_overrides_matrix.py @@ -47,7 +47,7 @@ # main loop mouse, keyboard = hg.Mouse(), hg.Keyboard() -while not keyboard.Pressed(hg.K_Escape): +while not keyboard.Pressed(hg.K_Escape) and hg.IsWindowOpen(win): keyboard.Update() mouse.Update() diff --git a/physics_pool_of_objects.lua b/physics_pool_of_objects.lua index 3d87c34..c21c582 100644 --- a/physics_pool_of_objects.lua +++ b/physics_pool_of_objects.lua @@ -72,7 +72,7 @@ text_uniform_values = {hg.MakeUniformSetValue('u_color', hg.Vec4(1, 1, 0.5))} text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Always, hg.FC_Disabled) -- main loop -while true do +while hg.IsWindowOpen(win) do state = hg.ReadKeyboard() if state:Key(hg.K_S) then diff --git a/physics_pool_of_objects.py b/physics_pool_of_objects.py index 3b91e81..84217c6 100644 --- a/physics_pool_of_objects.py +++ b/physics_pool_of_objects.py @@ -72,7 +72,7 @@ def create_material(diffuse, specular, self): text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Always, hg.FC_Disabled) # main loop -while True: +while hg.IsWindowOpen(win): state = hg.ReadKeyboard() if state.Key(hg.K_S): diff --git a/render_resize_to_window.lua b/render_resize_to_window.lua index b3b51db..d1f388c 100644 --- a/render_resize_to_window.lua +++ b/render_resize_to_window.lua @@ -15,7 +15,7 @@ cube_mdl = hg.CreateCubeModel(vtx_layout, 1, 1, 1) cube_prg = hg.LoadProgramFromFile('resources_compiled/shaders/mdl') -- main loop -while not hg.ReadKeyboard():Key(hg.K_Escape) do +while not hg.ReadKeyboard():Key(hg.K_Escape) and hg.IsWindowOpen(win) do render_was_reset, res_x, res_y = hg.RenderResetToWindow(win, res_x, res_y, hg.RF_VSync | hg.RF_MSAA4X | hg.RF_MaxAnisotropy) if render_was_reset then print(string.format('Render reset to %dx%d', res_x, res_y)) diff --git a/render_resize_to_window.py b/render_resize_to_window.py index dc7bb2b..8f4f791 100644 --- a/render_resize_to_window.py +++ b/render_resize_to_window.py @@ -15,7 +15,7 @@ cube_prg = hg.LoadProgramFromFile('resources_compiled/shaders/mdl') # main loop -while not hg.ReadKeyboard().Key(hg.K_Escape): +while not hg.ReadKeyboard().Key(hg.K_Escape) and hg.IsWindowOpen(win): render_was_reset, res_x, res_y = hg.RenderResetToWindow(win, res_x, res_y, hg.RF_VSync | hg.RF_MSAA4X | hg.RF_MaxAnisotropy) if render_was_reset: print('Render reset to %dx%d' % (res_x, res_y)) diff --git a/resources/car_engine/engine.scn b/resources/car_engine/engine.scn index b6cc136..099bc8e 100644 --- a/resources/car_engine/engine.scn +++ b/resources/car_engine/engine.scn @@ -27419,8 +27419,27 @@ ], "fog_far": 0.0, "fog_near": 0.0, - "irradiance_map": "core/pbr/blue_sky.hdr.irradiance", - "radiance_map": "core/pbr/blue_sky.hdr.radiance" + "probe": { + "irradiance_map": "core/pbr/blue_sky.hdr.irradiance", + "parallax": 0.0, + "pos": [ + 0.0, + 0.0, + 0.0 + ], + "radiance_map": "core/pbr/blue_sky.hdr.radiance", + "rot": [ + 0.0, + 0.0, + 0.0 + ], + "scl": [ + 1.0, + 1.0, + 1.0 + ], + "type": "sphere" + } }, "instances": [ { @@ -27442,6 +27461,7 @@ 2550, 255 ], + "diffuse_intensity": 1.0, "inner_angle": 9.999999747378752e-05, "outer_angle": 0.7853981852531433, "priority": 1.0, @@ -27452,7 +27472,7 @@ 500.0 ], "radius": 0.0, - "shadow_bias": 9.999999747378752e-06, + "shadow_bias": 0.0005000000237487257, "shadow_type": "map", "specular": [ 0, @@ -27460,6 +27480,7 @@ 0, 255 ], + "specular_intensity": 1.0, "type": "spot" }, { @@ -27469,6 +27490,7 @@ 255, 255 ], + "diffuse_intensity": 1.0, "inner_angle": 0.5235987901687622, "outer_angle": 0.7853981852531433, "priority": 1.0, @@ -27487,6 +27509,7 @@ 0, 255 ], + "specular_intensity": 1.0, "type": "linear" } ], @@ -35044,7 +35067,7 @@ ], "scene_anims": [ { - "anim": null, + "anim": 4294967295, "frame_duration": 50000000, "name": "Take 001", "node_anims": [ @@ -35178,7 +35201,7 @@ -0.1635737419128418 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -35196,7 +35219,7 @@ 0.16039180755615234 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -35214,7 +35237,7 @@ -0.21844716370105743 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -35232,7 +35255,7 @@ 0.1838085651397705 ], "rot": [ - 85.22080993652344, + 85.2208023071289, 180.0, 180.0 ], @@ -35844,7 +35867,7 @@ -0.004945273976773024 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -35862,7 +35885,7 @@ -5.018959045410156 ], "rot": [ - -3.12603759765625, + -3.126037359237671, 0.01269339770078659, -0.0005423406837508082 ], @@ -35899,7 +35922,7 @@ ], "rot": [ -2.4620609283447266, - 4.0438233816185443e-13, + 4.043823110568001e-13, 3.9484938756106924e-12 ], "scl": [ @@ -35972,7 +35995,7 @@ "rot": [ -42.31441116333008, 179.9835662841797, - -179.98562622070313 + -179.98561096191406 ], "scl": [ 1.0, @@ -36006,7 +36029,7 @@ 2.119384288787842 ], "rot": [ - -57.537986755371094, + -57.53798294067383, -180.0, 180.0 ], @@ -36042,7 +36065,7 @@ -0.004945273976773024 ], "rot": [ - -27.53790283203125, + -27.537900924682617, 0.0, 0.0 ], @@ -36061,8 +36084,8 @@ ], "rot": [ 45.58206558227539, - 179.9827423095703, - 179.9840087890625 + 179.98272705078125, + 179.98399353027344 ], "scl": [ 1.0, @@ -36096,7 +36119,7 @@ -2.1211297512054443 ], "rot": [ - 62.46209716796875, + 62.46208953857422, 180.0, 180.0 ], @@ -36132,7 +36155,7 @@ -0.004945273976773024 ], "rot": [ - 47.53793716430664, + 47.537933349609375, -180.0, -180.0 ], @@ -36150,8 +36173,8 @@ -5.01895809173584 ], "rot": [ - -54.93225860595703, - 0.021506860852241516, + -54.932254791259766, + 0.021506858989596367, -0.014868209138512611 ], "scl": [ @@ -36186,8 +36209,8 @@ -2.4197490215301514 ], "rot": [ - -42.46205520629883, - -1.411154700037276e-11, + -42.46205139160156, + -1.4111546133011021e-11, -4.01617142320454e-11 ], "scl": [ @@ -36241,8 +36264,8 @@ ], "rot": [ 73.44593048095703, - 0.04269658774137497, - 0.03746206685900688 + 0.04269658401608467, + 0.03746206313371658 ], "scl": [ 1.0, @@ -36276,7 +36299,7 @@ 2.194354295730591 ], "rot": [ - 57.537986755371094, + 57.53798294067383, 2.7536471366995663e-11, -1.0942671213598487e-09 ], @@ -36330,9 +36353,9 @@ -5.018959045410156 ], "rot": [ - 1.0554357767105103, - 179.9873809814453, - 179.99945068359375 + 1.0554356575012207, + 179.98736572265625, + 179.9994354248047 ], "scl": [ 1.0, @@ -36366,7 +36389,7 @@ -0.25069767236709595 ], "rot": [ - 2.4620397090911865, + 2.4620394706726074, -180.0, 180.0 ], @@ -36654,7 +36677,7 @@ -0.0050963168032467365 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -36690,7 +36713,7 @@ -0.0050963168032467365 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -36726,7 +36749,7 @@ 0.06785663962364197 ], "rot": [ - -2.13443399843527e-07, + -2.134433856326723e-07, 90.00000762939453, 2.462165117263794 ], @@ -36870,7 +36893,7 @@ -0.0050963168032467365 ], "rot": [ - 87.5377197265625, + 87.53771209716797, -180.0, -180.0 ], @@ -36888,7 +36911,7 @@ -0.07684630900621414 ], "rot": [ - -2.13443399843527e-07, + -2.134433856326723e-07, 90.00000762939453, 2.462165117263794 ], @@ -37050,7 +37073,7 @@ 0.053020551800727844 ], "rot": [ - 85.22080993652344, + 85.2208023071289, 180.0, 180.0 ], @@ -37086,7 +37109,7 @@ 2.5816879272460938 ], "rot": [ - 25.210643768310547, + 25.210641860961914, 138.3695068359375, 0.0 ], @@ -37122,7 +37145,7 @@ 2.4617958068847656 ], "rot": [ - 11.760019302368164, + 11.760018348693848, 135.82164001464844, 0.0 ], @@ -37320,9 +37343,9 @@ 0.0 ], "rot": [ - 32.325401306152344, - 2.0207064608257497e-06, - 1.0103533440997126e-06 + 32.32539749145508, + 2.0207062334520742e-06, + 1.0103532304128748e-06 ], "scl": [ 0.9999999403953552, @@ -37339,7 +37362,7 @@ ], "rot": [ -0.0, - 111.87700653076172, + 111.87699890136719, 0.0 ], "scl": [ @@ -37357,7 +37380,7 @@ ], "rot": [ 21.77312469482422, - 124.33213806152344, + 124.33212280273438, 0.0 ], "scl": [ diff --git a/resources/car_engine/engine.scn.aaa b/resources/car_engine/engine.scn.aaa new file mode 100644 index 0000000..03b674f --- /dev/null +++ b/resources/car_engine/engine.scn.aaa @@ -0,0 +1,12 @@ +{ + "bloom_bias": 0.5, + "bloom_intensity": 0.10000000149011612, + "bloom_threshold": 5.0, + "exposure": 1.0, + "gamma": 2.200000047683716, + "max_distance": 100.0, + "motion_blur": 1.0, + "sample_count": 2, + "taa_weight": 0.10000000149011612, + "z_thickness": 0.10000000149011612 +} \ No newline at end of file diff --git a/resources/car_engine/engine.scn.editor b/resources/car_engine/engine.scn.editor index 9bc52ef..6d6f792 100644 --- a/resources/car_engine/engine.scn.editor +++ b/resources/car_engine/engine.scn.editor @@ -1,5 +1,11 @@ { "editor": { + "animeditor_plugin": { + "quantize_time": true, + "show_trajectories": null, + "trajectories_tick_seconds": 1.0, + "trajectories_tick_size": 0.10000000149011612 + }, "explorer": { "node_custom_order": { "refs": [ @@ -185,6 +191,16 @@ "node_sort_method": "custom", "show_node_uid": false }, + "grid_plugin": { + "enabled": true, + "grid_color_b": 1.0, + "grid_color_g": 1.0, + "grid_color_r": 1.0, + "n_subdivs": 10, + "offset": 0.0, + "opacity": 0.5, + "subdiv_size": 1.0 + }, "navigation_plugin": { "orbit_distance": 7.836572647094727, "speed": 0.10000000149011612, @@ -192,12 +208,37 @@ }, "transform_gizmo_plugin": { "mode": "local", + "snap": "none", + "snap_rotation": 5.0, + "snap_rotation_fine": 1.0, + "snap_scale": 10.0, + "snap_scale_fine": 1.0, + "snap_translation": 1.0, + "snap_translation_fine": 0.10000000149011612, "tool": "rotation" }, "view_plugin": { - "show_node_origin": false + "show_cameras": true, + "show_collisions": false, + "show_lights": true, + "show_nodes": true, + "show_probe": true }, "views": { + "back": [ + -0.9999999403953552, + 0.0, + -8.742277657347586e-08, + 0.0, + 0.0, + 1.0, + -0.0, + 0.0, + 8.742277657347586e-08, + -0.0, + -0.9999999403953552, + 0.0 + ], "bottom": [ 1.0, -0.0, @@ -217,6 +258,20 @@ "znear": -1000.0 }, "current_camera": 7, + "front": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + -0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0 + ], "left": [ -8.742277657347586e-08, 0.0, diff --git a/resources/core/pbr/probe.hdr.meta b/resources/core/pbr/probe.hdr.meta index 0cfad74..0528fa0 100644 --- a/resources/core/pbr/probe.hdr.meta +++ b/resources/core/pbr/probe.hdr.meta @@ -2,7 +2,7 @@ "profiles": { "default": { "generate-probe": true, - "max-probe-size": 128, + "max-probe-size": 256, "radiance-edge-fixup": true } } diff --git a/resources/core/shader/bloom_downsample_fs.sc b/resources/core/shader/bloom_downsample_fs.sc index 8a88b93..e142d0f 100644 --- a/resources/core/shader/bloom_downsample_fs.sc +++ b/resources/core/shader/bloom_downsample_fs.sc @@ -3,29 +3,38 @@ $input v_texcoord0 #include SAMPLER2D(u_source, 0); +uniform vec4 u_source_rect; + +vec2 compute_texel(vec2 uv, vec2 center, vec4 bounds) { + vec4 w = vec4(step(bounds.xy, uv), step(uv, bounds.zw)); + return mix(center, uv, vec2(w.x*w.z, w.y*w.w)); +} void main() { vec2 uv = v_texcoord0.xy; vec4 offset = vec4(-1., 1., 1., 0.) / uResolution.xxyy; - vec4 s0 = texture2D(u_source, uv - offset.yz); - vec4 s1 = texture2D(u_source, uv - offset.wz); - vec4 s2 = texture2D(u_source, uv - offset.xz); + vec2 center = (floor(v_texcoord0.xy * uResolution.xy) + vec2_splat(0.5)) / uResolution.xy; + vec4 bounds = (floor(u_source_rect.xyzw) + vec4(1.,1.,-1.,-1.)) / uResolution.xyxy; + + vec4 s0 = texture2D(u_source, compute_texel(uv - offset.yz, center, bounds)); // -1,-1 + vec4 s1 = texture2D(u_source, compute_texel(uv - offset.wz, center, bounds)); // 0,-1 + vec4 s2 = texture2D(u_source, compute_texel(uv - offset.xz, center, bounds)); // 1,-1 - vec4 s3 = texture2D(u_source, uv + offset.xw); - vec4 s4 = texture2D(u_source, uv); - vec4 s5 = texture2D(u_source, uv + offset.yw); + vec4 s3 = texture2D(u_source, compute_texel(uv + offset.xw, center, bounds)); // -1, 0 + vec4 s4 = texture2D(u_source, compute_texel(uv, center, bounds)); // 0, 0 + vec4 s5 = texture2D(u_source, compute_texel(uv + offset.yw, center, bounds)); // 1, 0 - vec4 s6 = texture2D(u_source, uv + offset.xz); - vec4 s7 = texture2D(u_source, uv + offset.wz); - vec4 s8 = texture2D(u_source, uv + offset.yz); + vec4 s6 = texture2D(u_source, compute_texel(uv + offset.xz, center, bounds)); // -1, 1 + vec4 s7 = texture2D(u_source, compute_texel(uv + offset.wz, center, bounds)); // 0, 1 + vec4 s8 = texture2D(u_source, compute_texel(uv + offset.yz, center, bounds)); // 1, 1 offset = 0.5 * offset; - vec4 t0 = texture2D(u_source, uv - offset.yz); - vec4 t1 = texture2D(u_source, uv - offset.xz); - vec4 t2 = texture2D(u_source, uv + offset.xz); - vec4 t3 = texture2D(u_source, uv + offset.yz); + vec4 t0 = texture2D(u_source, compute_texel(uv - offset.yz, center, bounds)); // -1,-1 + vec4 t1 = texture2D(u_source, compute_texel(uv - offset.xz, center, bounds)); // 1,-1 + vec4 t2 = texture2D(u_source, compute_texel(uv + offset.xz, center, bounds)); // -1, 1 + vec4 t3 = texture2D(u_source, compute_texel(uv + offset.yz, center, bounds)); // 1, 1 vec4 v0 = s0 + s1 + s3 + s4; vec4 v1 = s1 + s2 + s4 + s5; diff --git a/resources/core/shader/bloom_upsample_fs.sc b/resources/core/shader/bloom_upsample_fs.sc index 5f7d69d..313f708 100644 --- a/resources/core/shader/bloom_upsample_fs.sc +++ b/resources/core/shader/bloom_upsample_fs.sc @@ -3,24 +3,32 @@ $input v_texcoord0 #include SAMPLER2D(u_source, 0); - +uniform vec4 u_source_rect; uniform vec4 u_params; +vec2 compute_texel(vec2 uv, vec2 center, vec4 bounds) { + vec4 w = vec4(step(bounds.xy, uv), step(uv, bounds.zw)); + return mix(center, uv, vec2(w.x*w.z, w.y*w.w)); +} + void main() { vec2 uv = v_texcoord0.xy; vec4 offset = vec4(-1., 1., 1., 0.) / uResolution.xxyy; - vec4 t0 = texture2D(u_source, uv - offset.yz); - vec4 t1 = texture2D(u_source, uv - offset.wz); - vec4 t2 = texture2D(u_source, uv - offset.xz); + vec2 center = (floor(v_texcoord0.xy * uResolution.xy) + vec2_splat(0.5)) / uResolution.xy; + vec4 bounds = (floor(u_source_rect.xyzw) + vec4(1.,1.,-1.,-1.)) / uResolution.xyxy; + + vec4 t0 = texture2D(u_source, compute_texel(uv - offset.yz, center, bounds)); // -1,-1 + vec4 t1 = texture2D(u_source, compute_texel(uv - offset.wz, center, bounds)); // 0,-1 + vec4 t2 = texture2D(u_source, compute_texel(uv - offset.xz, center, bounds)); // 1,-1 - vec4 t3 = texture2D(u_source, uv + offset.xw); - vec4 t4 = texture2D(u_source, uv); - vec4 t5 = texture2D(u_source, uv + offset.yw); + vec4 t3 = texture2D(u_source, compute_texel(uv + offset.xw, center, bounds)); // -1, 0 + vec4 t4 = texture2D(u_source, compute_texel(uv, center, bounds)); + vec4 t5 = texture2D(u_source, compute_texel(uv + offset.yw, center, bounds)); // 1, 0 - vec4 t6 = texture2D(u_source, uv + offset.xz); - vec4 t7 = texture2D(u_source, uv + offset.wz); - vec4 t8 = texture2D(u_source, uv + offset.yz); + vec4 t6 = texture2D(u_source, compute_texel(uv + offset.xz, center, bounds)); // -1, 1 + vec4 t7 = texture2D(u_source, compute_texel(uv + offset.wz, center, bounds)); // 0, 1 + vec4 t8 = texture2D(u_source, compute_texel(uv + offset.yz, center, bounds)); // 1, 1 gl_FragColor = u_params.z * (t0 + t2 + t6 + t8 + 2. * (t1 + t3 + t5 + t7) + 4. * t4) / 16.; } diff --git a/resources/core/shader/compositing_fs.sc b/resources/core/shader/compositing_fs.sc index f94b14f..d7b63fa 100644 --- a/resources/core/shader/compositing_fs.sc +++ b/resources/core/shader/compositing_fs.sc @@ -9,49 +9,42 @@ SAMPLER2D(u_copyDepth, 1); tone-mapping operators implementation taken from https://www.shadertoy.com/view/lslGzl */ -vec3 LinearToneMapping(vec3 color, float exposure) // 1. -{ +vec3 LinearToneMapping(vec3 color, float exposure) { // 1. color = clamp(exposure * color, 0., 1.); return color; } -vec3 SimpleReinhardToneMapping(vec3 color, float exposure) // 1.5 -{ +vec3 SimpleReinhardToneMapping(vec3 color, float exposure) { // 1.5 color *= exposure / (1. + color / exposure); return color; } -vec3 LumaBasedReinhardToneMapping(vec3 color) -{ +vec3 LumaBasedReinhardToneMapping(vec3 color) { float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); float toneMappedLuma = luma / (1. + luma); color *= toneMappedLuma / luma; return color; } -vec3 WhitePreservingLumaBasedReinhardToneMapping(vec3 color, float white) // 2. -{ +vec3 WhitePreservingLumaBasedReinhardToneMapping(vec3 color, float white) { // 2. float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); float toneMappedLuma = luma * (1. + luma / (white * white)) / (1. + luma); color *= toneMappedLuma / luma; return color; } -vec3 RomBinDaHouseToneMapping(vec3 color) -{ +vec3 RomBinDaHouseToneMapping(vec3 color) { color = exp(-1. / (2.72 * color + 0.15)); return color; } -vec3 FilmicToneMapping(vec3 color) -{ +vec3 FilmicToneMapping(vec3 color) { color = max(vec3(0., 0., 0.), color - vec3(0.004, 0.004, 0.004)); color = (color * (6.2 * color + .5)) / (color * (6.2 * color + 1.7) + 0.06); return color; } -vec3 Uncharted2ToneMapping(vec3 color, float exposure) -{ +vec3 Uncharted2ToneMapping(vec3 color, float exposure) { float A = 0.15; float B = 0.50; float C = 0.10; @@ -66,8 +59,31 @@ vec3 Uncharted2ToneMapping(vec3 color, float exposure) return color; } -void main() -{ +vec4 Sharpen(vec2 uv, float strength) { + vec4 up = texture2D(u_copyColor, uv + vec2(0, 1) / uResolution.xy); + vec4 left = texture2D(u_copyColor, uv + vec2(-1, 0) / uResolution.xy); + vec4 center = texture2D(u_copyColor, uv); + vec4 right = texture2D(u_copyColor, uv + vec2(1, 0) / uResolution.xy); + vec4 down = texture2D(u_copyColor, uv + vec2(0, -1) / uResolution.xy); + + float exposure = uAAAParams[1].x; + up.xyz = SimpleReinhardToneMapping(up.xyz, exposure); + left.xyz = SimpleReinhardToneMapping(left.xyz, exposure); + center.xyz = SimpleReinhardToneMapping(center.xyz, exposure); + right.xyz = SimpleReinhardToneMapping(right.xyz, exposure); + down.xyz = SimpleReinhardToneMapping(down.xyz, exposure); + + vec4 res = (1.0 + 4.0 * strength) * center - strength * (up + left + right + down); + return vec4(res.xyz, center.w); +} + +void main() { +#if 1 + vec4 in_sample = Sharpen(v_texcoord0, uAAAParams[2].y); + + vec3 color = in_sample.xyz; + float alpha = in_sample.w; +#else vec4 in_sample = texture2D(u_copyColor, v_texcoord0); vec3 color = in_sample.xyz; @@ -78,7 +94,9 @@ void main() //color = lumaBasedReinhardToneMapping(color); //color = FilmicToneMapping(color); //color = Uncharted2ToneMapping(color, exposure); +#endif + // gamma correction float inv_gamma = uAAAParams[1].y; color = pow(color, vec3_splat(inv_gamma)); diff --git a/resources/core/shader/default_fs.sc b/resources/core/shader/default_fs.sc index 0789743..1bc97ac 100644 --- a/resources/core/shader/default_fs.sc +++ b/resources/core/shader/default_fs.sc @@ -70,41 +70,54 @@ float SampleShadowPCF(sampler2DShadow map, vec4 coord, float inv_pixel_size, flo return k / 4.0; } -// Entry point of the forward pipeline default uber shader (Phong and PBR) +// Entry point of the forward pipeline default shader void main() { +#if DEPTH_ONLY != 1 #if USE_DIFFUSE_MAP +#if DIFFUSE_UV_CHANNEL == 1 + vec3 diff = texture2D(uDiffuseMap, vTexCoord1).xyz; +#else // DIFFUSE_UV_CHANNEL == 1 vec3 diff = texture2D(uDiffuseMap, vTexCoord0).xyz; -#else +#endif // DIFFUSE_UV_CHANNEL == 1 +#else // USE_DIFFUSE_MAP vec3 diff = uDiffuseColor.xyz; -#endif +#endif // USE_DIFFUSE_MAP #if USE_SPECULAR_MAP +#if SPECULAR_UV_CHANNEL == 1 + vec3 spec = texture2D(uSpecularMap, vTexCoord1).xyz; +#else // SPECULAR_UV_CHANNEL == 1 vec3 spec = texture2D(uSpecularMap, vTexCoord0).xyz; -#else +#endif // SPECULAR_UV_CHANNEL == 1 +#else // USE_SPECULAR_MAP vec3 spec = uSpecularColor.xyz; -#endif +#endif // USE_SPECULAR_MAP #if USE_SELF_MAP vec3 self = texture2D(uSelfMap, vTexCoord0).xyz; -#else +#else // USE_SELF_MAP vec3 self = uSelfColor.xyz; -#endif +#endif // USE_SELF_MAP #if USE_AMBIENT_MAP +#if AMBIENT_UV_CHANNEL == 1 vec3 ao = texture2D(uAmbientMap, vTexCoord1).xyz; -#else +#else // AMBIENT_UV_CHANNEL == 1 + vec3 ao = texture2D(uAmbientMap, vTexCoord0).xyz; +#endif // AMBIENT_UV_CHANNEL == 1 +#else // USE_AMBIENT_MAP vec3 ao = vec3_splat(1.0); -#endif +#endif // USE_AMBIENT_MAP #if USE_ADVANCED_BUFFERS ao *= texture2D(uAmbientOcclusion, gl_FragCoord.xy / uResolution.xy).x; -#endif +#endif // USE_ADVANCED_BUFFERS #if USE_LIGHT_MAP vec3 light = texture2D(uLightMap, vTexCoord1).xyz; -#else +#else // USE_LIGHT_MAP vec3 light = vec3_splat(0.0); -#endif +#endif // USE_LIGHT_MAP // vec3 view = mul(u_view, vec4(vWorldPos,1.0)).xyz; // fragment view space pos @@ -121,7 +134,7 @@ void main() { N.xy = texture2D(uNormalMap, vTexCoord0).xy * 2.0 - 1.0; N.z = sqrt(1.0 - dot(N.xy, N.xy)); N = normalize(mul(N, TBN)); -#endif +#endif // USE_NORMAL_MAP vec3 R = reflect(-V, N); // view reflection vector around normal @@ -150,7 +163,7 @@ void main() { float ramp_k = clamp((view.z - (uLinearShadowSlice.w - ramp_len)) / ramp_len, 0.0, 1.0); k *= pcf * (1.0 - ramp_k) + ramp_k; } -#endif +#endif // SLOT0_SHADOWS c_diff = uLightDiffuse[0].xyz * m.i_diff * k; c_spec = uLightSpecular[0].xyz * m.i_spec * k; } @@ -167,7 +180,7 @@ void main() { #if SLOT1_SHADOWS k *= SampleShadowPCF(uSpotShadowMap, vSpotShadowCoord, uShadowState.y, uShadowState.w); -#endif +#endif // SLOT1_SHADOWS c_diff += uLightDiffuse[1].xyz * m.i_diff * k; c_spec += uLightSpecular[1].xyz * m.i_spec * k; @@ -194,28 +207,38 @@ void main() { #if USE_REFLECTION_MAP vec4 reflection = texture2D(uReflectionMap, R.xy); color += reflection.xyz; -#endif +#endif // USE_REFLECTION_MAP color = DistanceFog(view, color); +#endif // DEPTH_ONLY != 1 #if USE_OPACITY_MAP float opacity = texture2D(uOpacityMap, vTexCoord0).x; -#else + +#if ENABLE_ALPHA_CUT + if (opacity < 0.8) + discard; +#endif // ENABLE_ALPHA_CUT +#else // USE_OPACITY_MAP float opacity = 1.0; -#endif +#endif // USE_OPACITY_MAP +#if DEPTH_ONLY + ; +#else // DEPTH_ONLY #if FORWARD_PIPELINE_AAA_PREPASS vec3 N_view = mul(u_view, vec4(N, 0)).xyz; vec2 velocity = vec2(vProjPos.xy / vProjPos.w - vPrevProjPos.xy / vPrevProjPos.w); gl_FragData[0] = vec4(N_view.xyz, vProjPos.z); gl_FragData[1] = vec4(velocity.xy, gloss, 0.); // -#else +#else // FORWARD_PIPELINE_AAA_PREPASS // incorrectly apply gamma correction at fragment shader level in the non-AAA pipeline -# if FORWARD_PIPELINE_AAA == 0 +#if FORWARD_PIPELINE_AAA != 1 float gamma = 2.2; color = pow(color, vec3_splat(1. / gamma)); -# endif +#endif // FORWARD_PIPELINE_AAA != 1 gl_FragColor = vec4(color, opacity); -#endif +#endif // FORWARD_PIPELINE_AAA_PREPASS +#endif // DEPTH_ONLY } diff --git a/resources/core/shader/default_vs.sc b/resources/core/shader/default_vs.sc index 7a46352..b509d2b 100644 --- a/resources/core/shader/default_vs.sc +++ b/resources/core/shader/default_vs.sc @@ -24,15 +24,16 @@ void main() { mul(uPreviousModel[int(a_indices.y * 256.0)], vtx) * a_weight.y + mul(uPreviousModel[int(a_indices.z * 256.0)], vtx) * a_weight.z + mul(uPreviousModel[int(a_indices.w * 256.0)], vtx) * a_weight.w; -#endif -#else +#endif // FORWARD_PIPELINE_AAA_PREPASS +#else // ENABLE_SKINNING vec4 world_pos = mul(u_model[0], vtx); #if FORWARD_PIPELINE_AAA_PREPASS vec4 prv_world_pos = mul(uPreviousModel[0], vtx); -#endif -#endif +#endif // FORWARD_PIPELINE_AAA_PREPASS +#endif // ENABLE_SKINNING +#if DEPTH_ONLY != 1 // normal vec4 normal = vec4(a_normal * 2. - 1., 0.); @@ -46,12 +47,12 @@ void main() { skinned_normal = normalize(skinned_normal); vNormal = skinned_normal.xyz; -#else +#else // ENABLE_SKINNING vNormal = mul(normal_mat(u_model[0]), normal.xyz); -#endif +#endif // ENABLE_SKINNING // tangent frame -#if (USE_NORMAL_MAP) // [EJ] FIXME this probably won't be the only condition to compute the tangent frame for long +#if USE_NORMAL_MAP vec4 tangent = vec4(a_tangent * 2.0 - 1.0, 0.0); vec4 binormal = vec4(a_bitangent * 2.0 - 1.0, 0.0); @@ -70,47 +71,40 @@ void main() { vTangent = skinned_tangent.xyz; vBinormal = skinned_binormal.xyz; -#else +#else // ENABLE_SKINNING vTangent = mul(u_model[0], tangent).xyz; vBinormal = mul(u_model[0], binormal).xyz; -#endif -#endif +#endif // ENABLE_SKINNING +#endif // USE_NORMAL_MAP // shadow data -#if (SLOT0_SHADOWS || SLOT1_SHADOWS) +#if SLOT0_SHADOWS || SLOT1_SHADOWS float shadowMapShrinkOffset = 0.01; vec3 shadowVertexShrinkOffset = vNormal * shadowMapShrinkOffset; -#endif +#endif // SLOT0_SHADOWS || SLOT1_SHADOWS -#if (SLOT0_SHADOWS) +#if SLOT0_SHADOWS vLinearShadowCoord0 = mul(uLinearShadowMatrix[0], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord1 = mul(uLinearShadowMatrix[1], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord2 = mul(uLinearShadowMatrix[2], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord3 = mul(uLinearShadowMatrix[3], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); -#endif +#endif // SLOT0_SHADOWS -#if (SLOT1_SHADOWS) +#if SLOT1_SHADOWS vSpotShadowCoord = mul(uSpotShadowMatrix, vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); -#endif +#endif // SLOT1_SHADOWS +#endif // DEPTH_ONLY != 1 - // vWorldPos = world_pos.xyz; -#if (USE_DIFFUSE_MAP || USE_SPECULAR_MAP|| USE_NORMAL_MAP || USE_SELF_MAP || USE_OPACITY_MAP) vTexCoord0 = a_texcoord0; -#endif - -#if (USE_LIGHT_MAP || USE_AMBIENT_MAP) vTexCoord1 = a_texcoord1; -#endif - // vec4 proj_pos = mul(uViewProjUnjittered, world_pos); #if FORWARD_PIPELINE_AAA_PREPASS vProjPos = proj_pos; vPrevProjPos = mul(uPreviousViewProjection, prv_world_pos); -#endif +#endif // FORWARD_PIPELINE_AAA_PREPASS - // gl_Position = mul(u_viewProj, world_pos); } diff --git a/resources/core/shader/forward_pipeline.sh b/resources/core/shader/forward_pipeline.sh index 4344696..e4bda47 100644 --- a/resources/core/shader/forward_pipeline.sh +++ b/resources/core/shader/forward_pipeline.sh @@ -29,19 +29,21 @@ uniform mat4 uMainInvProjection; // inverse projection for the main render (used uniform mat4 uPreviousViewProjection; uniform mat4 uPreviousModel[BGFX_CONFIG_MAX_BONES]; uniform mat4 uViewProjUnjittered; -uniform vec4 uAAAParams[2]; // [0].x: ssgi ratio, [0].y: ssr ratio, [0].z: temporal AA weight, [0].w: motion blur strength, [1].x: exposure, [1].y: 1/gamma, [1].z: sample count, [1].w: max radius +uniform vec4 uAAAParams[3]; // [0].x: ssgi ratio, [0].y: ssr ratio, [0].z: temporal AA weight, [0].w: motion blur strength, + // [1].x: exposure, [1].y: 1/gamma, [1].z: sample count, [1].w: screenspace ray max length + // [2].x: specular weight, [2].y: sharpen uniform mat4 uMainInvView; // inversion view matrix - -#if FORWARD_PIPELINE_AAA -SAMPLER2D(uIrradianceMap, 8); -SAMPLER2D(uRadianceMap, 9); -#else -SAMPLERCUBE(uIrradianceMap, 8); -SAMPLERCUBE(uRadianceMap, 9); -#endif -SAMPLER2D(uBrdfMap, 10); -SAMPLER2D(uNoiseMap, 11); +uniform mat4 uProbeMatrix; +uniform mat4 uInvProbeMatrix; +uniform vec4 uProbeData; + +SAMPLERCUBE(uIrradianceMap, 7); +SAMPLERCUBE(uRadianceMap, 8); +SAMPLER2D(uSSIrradianceMap, 9); +SAMPLER2D(uSSRadianceMap, 10); +SAMPLER2D(uBrdfMap, 11); +SAMPLER2D(uNoiseMap, 12); SAMPLER2D(uAmbientOcclusion, 13); SAMPLER2DSHADOW(uLinearShadowMap, 14); SAMPLER2DSHADOW(uSpotShadowMap, 15); @@ -79,8 +81,7 @@ vec3 Unproject(vec3 frag_coord) { return ndc.xyz / ndc.w; } -vec3 ComputeFragCoordViewRay(vec2 frag_coord) -{ +vec3 ComputeFragCoordViewRay(vec2 frag_coord) { vec2 sp = ((frag_coord - u_viewRect.xy) / u_viewRect.zw) * 2. - 1.; sp.y *= -1.; @@ -92,3 +93,65 @@ vec3 ComputeFragCoordViewRay(vec2 frag_coord) } bool isNan(float val) { return (val <= 0.0 || 0.0 <= val) ? false : true; } + +// +vec2 RaySphere(vec3 r0, vec3 rd, vec3 s0, float sr) { + float a = dot(rd, rd); + vec3 s0_r0 = r0 - s0; + + float b = 2.0 * dot(rd, s0_r0); + float c = dot(s0_r0, s0_r0) - (sr * sr); + float disc = b * b - 4.0 * a* c; + + if (disc < 0.0) + return vec2(-1.0, -1.0); + + return vec2(-b - sqrt(disc), -b + sqrt(disc)) / (2.0 * a); +} + +vec3 RayBox(vec3 ray_origin, vec3 ray_dir, vec3 minpos, vec3 maxpos) { + vec3 inverse_dir = 1.0 / ray_dir; + vec3 tbot = inverse_dir * (minpos - ray_origin); + vec3 ttop = inverse_dir * (maxpos - ray_origin); + vec3 tmin = min(ttop, tbot); + vec3 tmax = max(ttop, tbot); + vec2 traverse = max(tmin.xx, tmin.yz); + float traverselow = max(traverse.x, traverse.y); + traverse = min(tmax.xx, tmax.yz); + float traversehi = min(traverse.x, traverse.y); + return vec3(float(traversehi > max(traverselow, 0.0)), traversehi, traverselow); +} + +vec3 ReprojectProbe(vec3 O, vec3 V) { + vec3 W; + + if (uProbeData.x == 0.0) { + vec3 local_O = mul(uInvProbeMatrix, vec4(O, 1.0)).xyz; // move ray to probe volume space + vec3 local_V = mul(uInvProbeMatrix, vec4(V, 0.0)).xyz; + local_V = normalize(local_V); + + vec2 T = RaySphere(local_O, local_V, vec3(0.0, 0.0, 0.0), 0.5); + + if (T.y > -1.0) { + vec3 local_I = local_O + local_V * T.y; + W = normalize(mul(uProbeMatrix, vec4(local_I, 0.0)).xyz); + } else { + return V; + } + } else if (uProbeData.x == 1.0) { + vec3 local_O = mul(uInvProbeMatrix, vec4(O, 1.0)).xyz; // move ray to probe volume space + vec3 local_V = mul(uInvProbeMatrix, vec4(V, 0.0)).xyz; + local_V = normalize(local_V); + + vec3 T = RayBox(local_O, local_V, vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5)); // intersect with volume + + if (T.x == 0.0) { + return V; + } else { + vec3 local_I = local_O + local_V * T.y; + W = normalize(mul(uProbeMatrix, vec4(local_I, 0.0)).xyz); // move intersection back to world space + } + } + + return normalize(mix(V, W, uProbeData.y)); +} diff --git a/resources/core/shader/hiz_compute_cs.sc b/resources/core/shader/hiz_compute_cs.sc index e9bf7fe..184e56c 100644 --- a/resources/core/shader/hiz_compute_cs.sc +++ b/resources/core/shader/hiz_compute_cs.sc @@ -5,51 +5,49 @@ IMAGE2D_RO(u_depthTexIn, rg32f, 0); // input: level i of the min depth pyramid IMAGE2D_WR(u_depthTexOut, rg32f, 1); // output: level i+1 of the min depth pyramid -uniform vec4 u_zBounds; // near(x) far(y) unused(z,w) - NUM_THREADS(16, 16, 1) void main() { - ivec2 sizeOut = imageSize(u_depthTexOut); - ivec2 coordOut = ivec2(gl_GlobalInvocationID.xy); + ivec2 sizeOut = imageSize(u_depthTexOut); + ivec2 coordOut = ivec2(gl_GlobalInvocationID.xy); - ivec2 sizeIn = imageSize(u_depthTexIn); - ivec2 coordIn = coordOut * 2; + ivec2 sizeIn = imageSize(u_depthTexIn); + ivec2 coordIn = coordOut * 2; - vec2 z = vec2(1.0, 0.0); + vec2 z = vec2(1.0, 0.0); - // The computation is applied on all the texture area. - // It's not restricted to the actual viewport so we don't need to perform any extra check. - vec2 z0 = imageLoad(u_depthTexIn, coordIn).xy; + // The computation is applied on all the texture area. + // It's not restricted to the actual viewport so we don't need to perform any extra check. + vec2 z0 = imageLoad(u_depthTexIn, coordIn).xy; vec2 z2 = imageLoad(u_depthTexIn, coordIn + ivec2(0, 1)).xy; vec2 z1 = imageLoad(u_depthTexIn, coordIn + ivec2(1, 0)).xy; - vec2 z3 = imageLoad(u_depthTexIn, coordIn + ivec2(1, 1)).xy; - - z.x = min(min(z0.x, z1.x), min(z2.x, z3.x)); - z.y = max(max(z0.y, z1.y), max(z2.y, z3.y)); - - // Here we handle the case where the size of the previous level is odd and we are on the boundaries - // of the output texture. - // In this case, we will need to sample an extra row or column. - bvec4 odd_last = bvec4(greaterThan(sizeIn.xy, 2*sizeOut.xy), equal(coordOut, sizeOut-ivec2(1,1))); - bvec2 extra_fetch = bvec2(all(odd_last.xz), all(odd_last.yw)); - if(extra_fetch.x) { - vec2 z4 = imageLoad(u_depthTexIn, coordIn + ivec2(2,0)).xy; - vec2 z5 = imageLoad(u_depthTexIn, coordIn + ivec2(2,1)).xy; + vec2 z3 = imageLoad(u_depthTexIn, coordIn + ivec2(1, 1)).xy; + + z.x = min(min(z0.x, z1.x), min(z2.x, z3.x)); + z.y = max(max(z0.y, z1.y), max(z2.y, z3.y)); + + // Here we handle the case where the size of the previous level is odd and we are on the boundaries + // of the output texture. + // In this case, we will need to sample an extra row or column. + bvec4 odd_last = bvec4(greaterThan(sizeIn.xy, 2*sizeOut.xy), equal(coordOut, sizeOut-ivec2(1,1))); + bvec2 extra_fetch = bvec2(all(odd_last.xz), all(odd_last.yw)); + if(extra_fetch.x) { + vec2 z4 = imageLoad(u_depthTexIn, coordIn + ivec2(2,0)).xy; + vec2 z5 = imageLoad(u_depthTexIn, coordIn + ivec2(2,1)).xy; z.x = min(z.x, min(z4.x, z5.x)); z.y = max(z.y, max(z4.y, z5.y)); - } - if(extra_fetch.y) { - vec2 z6 = imageLoad(u_depthTexIn, coordIn + ivec2(0,2)).xy; - vec2 z7 = imageLoad(u_depthTexIn, coordIn + ivec2(1,2)).xy; + } + if(extra_fetch.y) { + vec2 z6 = imageLoad(u_depthTexIn, coordIn + ivec2(0,2)).xy; + vec2 z7 = imageLoad(u_depthTexIn, coordIn + ivec2(1,2)).xy; z.x = min(z.x, min(z6.x, z7.x)); z.y = max(z.y, max(z6.y, z7.y)); - - if(extra_fetch.x) { - vec2 z8 = imageLoad(u_depthTexIn, coordIn + ivec2(2,2)).xy; - z.x = min(z.x, z8.x); + + if(extra_fetch.x) { + vec2 z8 = imageLoad(u_depthTexIn, coordIn + ivec2(2,2)).xy; + z.x = min(z.x, z8.x); z.y = max(z.y, z8.y); } - } + } - imageStore(u_depthTexOut, coordOut, vec4(z.x, z.y,0,1) ); + imageStore(u_depthTexOut, coordOut, vec4(z.x, z.y, 0, 1)); } \ No newline at end of file diff --git a/resources/core/shader/hiz_copy_cs.sc b/resources/core/shader/hiz_copy_cs.sc index 3083f58..4a64ae8 100644 --- a/resources/core/shader/hiz_copy_cs.sc +++ b/resources/core/shader/hiz_copy_cs.sc @@ -6,6 +6,7 @@ SAMPLER2D(u_depth, 0); IMAGE2D_WR(u_depthTexOut, rg32f, 1); // output: level 0 of the min/max depth pyramid uniform mat4 u_projection; +uniform vec4 u_zThickness; NUM_THREADS(16, 16, 1) void main() { @@ -13,13 +14,13 @@ void main() { ivec2 coord = ivec2(gl_GlobalInvocationID.xy) + ivec2(u_viewRect.xy); #if BGFX_SHADER_LANGUAGE_GLSL - ivec2 tex_coord = ivec2(coord.x, textureSize(u_depth, 0).y - 1 - coord.y); + ivec2 tex_coord = ivec2(coord.x, textureSize(u_depth, 0).y - 1 - coord.y); #else - ivec2 tex_coord = coord; + ivec2 tex_coord = coord; #endif vec2 z; - if(all(bvec4(greaterThanEqual(coord, viewport.xy), lessThan(coord, viewport.xy + viewport.zw)))) { + if (all(bvec4(greaterThanEqual(coord, viewport.xy), lessThan(coord, viewport.xy + viewport.zw)))) { z = texelFetch(u_depth, tex_coord, 0).ww; #if BGFX_SHADER_LANGUAGE_GLSL @@ -27,7 +28,7 @@ void main() { #else vec2 q = u_projection[2].zw; #endif - z.y += 0.1 * q.x; + z.y += u_zThickness.x * q.x; // Store logarithmic depth z.xy = q.x * z.xy / (z.xy - q.yy); diff --git a/resources/core/shader/hiz_trace.sh b/resources/core/shader/hiz_trace.sh index 791d45f..c96f915 100644 --- a/resources/core/shader/hiz_trace.sh +++ b/resources/core/shader/hiz_trace.sh @@ -5,44 +5,37 @@ uniform vec4 u_depthTexInfos; // width(x) heigh(y) start mipmap level(z) max mipmap level(w) -vec3 intersect_cell_boundary(vec3 pos, vec3 dir, vec2 cell, vec2 cell_count, vec4 cross_step) { - vec2 planes = (cell + cross_step.xy) / cell_count; - vec3 intersection; - if(dir.x == 0.0) { - float delta = (planes.y - pos.y) / dir.y; - intersection = pos + dir * delta; - intersection.y += cross_step.w; +// +vec3 ray_step_cell(vec3 ray, vec3 dir, float step, vec2 z_range) { + float t_ = 100000000.0; // [EJ] any large value is ok + + if (dir.x > 0.0) + t_ = min(t_, (floor(ray.x / step + 1.0) * step - ray.x) / dir.x); + else if (dir.x < 0.0) + t_ = min(t_, (ceil(ray.x / step - 1.0) * step - ray.x) / dir.x); + + if (dir.y > 0.0) + t_ = min(t_, (floor(ray.y / step + 1.0) * step - ray.y) / dir.y); + else if (dir.y < 0.0) + t_ = min(t_, (ceil(ray.y / step - 1.0) * step - ray.y) / dir.y); + + if (dir.z > 0.0) { + if (ray.z < z_range.x) + t_ = min(t_, (z_range.x - ray.z) / dir.z); + } else if (dir.z < 0.0) { + if (ray.z > z_range.y) + t_ = min(t_, (z_range.y - ray.z) / dir.z); } - else if(dir.y == 0.0) { - float delta = (planes.x - pos.x) / dir.x; - intersection = pos + dir * delta; - intersection.x += cross_step.z; - } - else { - vec2 delta = (planes - pos.xy) / dir.xy; - intersection = pos + dir * min(delta.x, delta.y); - intersection.xy += (delta.x < delta.y) ? vec2(cross_step.z, 0.0) : vec2(0.0, cross_step.w); - } - return intersection; -} -// returns the texture size in pixels for a given pyramid level. -// textureSize(sampler, level) should be used. -// Unfortunately bgfx directx implementation always returns the size of level 0. -vec2 mip_size(int level) { - return floor(u_depthTexInfos.xy / ((level != 0) ? exp2(level) : 1.0)); + return ray + dir * t_; } -bool hiz_trace(vec3 ray_o, vec3 ray_d, mat4 proj, float z_near, int max_iterations, out vec3 ray) { - vec2 viewport_scale = uv_ratio.xy / u_depthTexInfos.xy; - vec3 viewport_min = vec3(u_viewRect.xy * viewport_scale, 0.); - vec3 viewport_max = vec3((u_viewRect.xy + u_viewRect.zw) * viewport_scale, 1.); +float hiz_trace(vec3 ray_o, vec3 ray_d, mat4 proj, float z_near, int max_iterations, out vec3 ray) { + vec3 viewport_min = vec3(u_viewRect.xy, 0.); + vec3 viewport_max = vec3(u_viewRect.xy + u_viewRect.zw, 1.); - int level_max = int(u_depthTexInfos.w); int level_min = int(u_depthTexInfos.z); - ivec2 iterations = ivec2(0, level_min); - - vec2 cell_count = mip_size(level_min); + int level_max = int(u_depthTexInfos.w); // clip to the near plane float ray_len = ((ray_o.z + ray_d.z * 1000.0) < z_near) ? (z_near - ray_o.z) / ray_d.z : 1000.0; @@ -52,97 +45,108 @@ bool hiz_trace(vec3 ray_o, vec3 ray_d, mat4 proj, float z_near, int max_iteratio vec4 h0 = mul(proj, vec4(ray_o, 1.)); vec4 h1 = mul(proj, vec4(end_point, 1.)); - // screen-space endpoints + // endpoints in screen space vec3 p0 = h0.xyz / h0.w; - p0.y *= -1.; - p0.xy = NDCToViewRect(p0.xy) / cell_count.xy; + p0.y *= -1.0; + p0.xy = NDCToViewRect(p0.xy); vec3 p1 = h1.xyz / h1.w; - p1.y *= -1.; - p1.xy = NDCToViewRect(p1.xy) / cell_count.xy; + p1.y *= -1.0; + p1.xy = NDCToViewRect(p1.xy); - // compute ray start position and direction in screen space - vec3 pos = p0; + // + ray = p0; vec3 dir = normalize(p1 - p0); - if(dir.z == 0) { - ray = vec3(-1., -1., 0.); - return false; - } + vec2 uv_offset = sign(dir.xy) * 0.0001; // slight nudge to sample the correct cell - vec4 cross_step; - cross_step.xy = step(vec2_splat(0.0), dir.xy); - cross_step.zw = (2.0 * cross_step.xy - vec2_splat(1.0)) * vec2_splat(0.5) / cell_count.xy; - - vec2 cell = floor(pos.xy * cell_count); - pos.xy = cell / cell_count + vec2_splat(0.25) / cell_count.xy; - - ray = intersect_cell_boundary(pos, dir, cell, cell_count, cross_step); - - for(iterations.x = 0; (iterations.x < max_iterations) && (iterations.y >= 0); ++iterations.x, --iterations.y) { - // check if the ray goes out of the viewport. - if(any(lessThan(ray, viewport_min))) { - return false; - } - if(any(greaterThanEqual(ray, viewport_max))) { - return false; - } - cell_count = mip_size(iterations.y); - cell = floor(ray.xy * cell_count); - - vec2 z_interval = texelFetch(u_depthTex, ivec2(cell), iterations.y).xy; - - vec3 tmp = ray; - float dz = z_interval.x - ray.z; - if (dz > 0) { - tmp = ray + dz * sign(dir.z) * dir / dir.z; - } - else { - dz = ray.z - z_interval.y; - if (dz > 0) { - tmp = ray + dz * sign(dir.z) * dir / dir.z; - } - } - vec2 new_cell = floor(tmp.xy * cell_count); - if (any(notEqual(new_cell, cell))) { - tmp = intersect_cell_boundary(ray, dir, cell, cell_count, cross_step); - iterations.y = min(level_max, iterations.y + 2); +#if 1 + int level = level_min; + + int iterations = 0; + + while (level > -1) { + if (++iterations == max_iterations) + return -1.0; + + if (any(lessThan(ray, viewport_min))) + return 0.0; // TODO ramp out + if (any(greaterThanEqual(ray, viewport_max))) + return 0.0; // TODO ramp out + + float step = pow(2.0, level); + vec2 z_range = texelFetch(u_depthTex, ivec2(ray.xy / step + uv_offset), level).xy; + + if (ray.z >= z_range.x && ray.z <= z_range.y) { + --level; + } else { + ray = ray_step_cell(ray, dir, step, z_range); + if (level < level_max - 2) + ++level; } - else if ((iterations.y == (level_min+1)) && (dz > 0.00001)) { - tmp = intersect_cell_boundary(ray, dir, cell, cell_count, cross_step); - iterations.y = 2; + } + + vec2 k_fade = saturate((ray.xy - viewport_min) / (u_viewRect.zw * 0.1)); + k_fade *= saturate(vec2(1.0, 1.0) - (ray.xy - viewport_max * 0.9) / (u_viewRect.zw * 0.1)); + + ray.xy /= u_depthTexInfos.xy; + + return k_fade.x * k_fade.y; // hit +#else + int level = 0; // reference implementation (works on any mip level) + + for (int i = 0; i < 4096; ++i) { + if (any(lessThan(ray, viewport_min))) + return false; + if (any(greaterThanEqual(ray, viewport_max))) + return false; + + float step = pow(2.0, level); + vec2 z_range = texelFetch(u_depthTex, ivec2(ray.xy / step), level).xy; + + if (ray.z >= z_range.x && ray.z <= z_range.y) { + ray.xy = ray.xy / u_depthTexInfos.xy; + return true; } - ray = tmp; + ray = ray_step_cell(ray, dir, step, z_range); } - return iterations.y < 0 && iterations.x < max_iterations; + return 0.0; +#endif } -bool TraceScreenRay(vec3 ray_o, vec3 ray_d, mat4 proj, float z_near, int max_iterations, out vec2 hit_pixel, out vec3 hit_point) { +float TraceScreenRay(vec3 ray_o, vec3 ray_d, mat4 proj, float z_near, int max_iterations, out vec2 hit_pixel, out vec3 hit_point) { vec3 ray; - if (hiz_trace(ray_o, ray_d, proj, z_near, max_iterations, ray)) { + float hit = hiz_trace(ray_o, ray_d, proj, z_near, max_iterations, ray); + + if (hit > 0.0) { // compute screen position of the hit pixel #if BGFX_SHADER_LANGUAGE_GLSL hit_pixel = vec2(ray.x, 1.0 - ray.y) * floor(uResolution.xy / uv_ratio); #else hit_pixel = ray.xy * floor(uResolution.xy / uv_ratio); #endif - - // and its world space coordinates hit_point = ray; - hit_point.xy = 2. * hit_point.xy - 1.; - hit_point.y *= -1.; - - vec4 p = mul(uMainInvProjection, vec4(hit_point, 1.)); - hit_point.xyz = p.xyz / p.w; - return true; } else { hit_pixel = vec2_splat(0.); hit_point = vec3_splat(0.); - return false; } + + return hit; +} + +float ComputeRayLogDepth(in mat4 projection, in vec3 ray) { + const float z_epsilon = 0.1; // [todo] parameter? + + float za = texelFetch(u_depthTex, ivec2(floor(ray.xy * u_depthTexInfos.xy)), 0).x; + float zb = z_epsilon * (za - projection[2].z); +#if BGFX_SHADER_LANGUAGE_GLSL + return (zb + projection[3].z * za) / (zb + projection[3].z); +#else + return (zb + projection[2].w * za) / (zb + projection[2].w); +#endif } #endif // HIZ_TRACE_SH_HEADER_GUARD diff --git a/resources/core/shader/pbr_fs.sc b/resources/core/shader/pbr_fs.sc index e337cd3..50c0549 100644 --- a/resources/core/shader/pbr_fs.sc +++ b/resources/core/shader/pbr_fs.sc @@ -39,16 +39,17 @@ float SampleShadowPCF(sampler2DShadow map, vec4 coord, float inv_pixel_size, flo #if FORWARD_PIPELINE_AAA #define PCF_SAMPLE_COUNT 2.0 // 3x3 +// float weights[9] = {0.024879, 0.107973, 0.024879, 0.107973, 0.468592, 0.107973, 0.024879, 0.107973, 0.024879}; + float weights[9] = {0.011147, 0.083286, 0.011147, 0.083286, 0.622269, 0.083286, 0.011147, 0.083286, 0.011147}; + for (float j = 0.0; j <= PCF_SAMPLE_COUNT; ++j) { - float v = (j + jitter.y) / PCF_SAMPLE_COUNT * 2.0 - 1.0; + float v = 6.0 * (j + jitter.y) / PCF_SAMPLE_COUNT - 1.0; for (float i = 0.0; i <= PCF_SAMPLE_COUNT; ++i) { - float u = (i + jitter.x) / PCF_SAMPLE_COUNT * 2.0 - 1.0; - k += SampleHardShadow(map, coord + vec4(vec2(u, v) * k_pixel_size, 0.0, 0.0), bias); + float u = 6.0 * (i + jitter.x) / PCF_SAMPLE_COUNT - 1.0; + k += SampleHardShadow(map, coord + vec4(vec2(u, v) * k_pixel_size, 0.0, 0.0), bias) * weights[j * 3 + i]; } } - - k /= (PCF_SAMPLE_COUNT + 1) * (PCF_SAMPLE_COUNT + 1); -#else +#else // FORWARD_PIPELINE_AAA // 2x2 k += SampleHardShadow(map, coord + vec4(vec2(-0.5, -0.5) * k_pixel_size, 0.0, 0.0), bias); k += SampleHardShadow(map, coord + vec4(vec2( 0.5, -0.5) * k_pixel_size, 0.0, 0.0), bias); @@ -56,7 +57,7 @@ float SampleShadowPCF(sampler2DShadow map, vec4 coord, float inv_pixel_size, flo k += SampleHardShadow(map, coord + vec4(vec2( 0.5, 0.5) * k_pixel_size, 0.0, 0.0), bias); k /= 4.0; -#endif +#endif // FORWARD_PIPELINE_AAA return k; } @@ -121,39 +122,33 @@ vec3 DistanceFog(vec3 pos, vec3 color) { // Entry point of the forward pipeline default uber shader (Phong and PBR) void main() { -#if FORWARD_PIPELINE_AAA - vec4 jitter = texture2D(uNoiseMap, mod(gl_FragCoord.xy, vec2(64, 64)) / vec2(64, 64)); -#else - vec4 jitter = vec4_splat(0.); -#endif - // #if USE_BASE_COLOR_OPACITY_MAP vec4 base_opacity = texture2D(uBaseOpacityMap, vTexCoord0); base_opacity.xyz = sRGB2linear(base_opacity.xyz); -#else +#else // USE_BASE_COLOR_OPACITY_MAP vec4 base_opacity = uBaseOpacityColor; -#endif +#endif // USE_BASE_COLOR_OPACITY_MAP - // +#if DEPTH_ONLY != 1 #if USE_OCCLUSION_ROUGHNESS_METALNESS_MAP vec4 occ_rough_metal = texture2D(uOcclusionRoughnessMetalnessMap, vTexCoord0); -#else +#else // USE_OCCLUSION_ROUGHNESS_METALNESS_MAP vec4 occ_rough_metal = uOcclusionRoughnessMetalnessColor; -#endif +#endif // USE_OCCLUSION_ROUGHNESS_METALNESS_MAP // #if USE_SELF_MAP vec4 self = texture2D(uSelfMap, vTexCoord0); -#else +#else // USE_SELF_MAP vec4 self = uSelfColor; -#endif +#endif // USE_SELF_MAP // vec3 view = mul(u_view, vec4(vWorldPos, 1.0)).xyz; vec3 P = vWorldPos; // fragment world pos - vec3 V = normalize(GetT(u_invView) - P); // view vector - vec3 N = normalize(vNormal); // geometry normal + vec3 V = normalize(GetT(u_invView) - P); // world space view vector + vec3 N = sign(dot(V, vNormal)) * normalize(vNormal); // geometry normal #if USE_NORMAL_MAP vec3 T = normalize(vTangent); @@ -164,7 +159,7 @@ void main() { N.xy = texture2D(uNormalMap, vTexCoord0).xy * 2.0 - 1.0; N.z = sqrt(1.0 - dot(N.xy, N.xy)); N = normalize(mul(N, TBN)); -#endif +#endif // USE_NORMAL_MAP vec3 R = reflect(-V, N); // view reflection vector around normal @@ -175,6 +170,13 @@ void main() { vec3 color = vec3(0.0, 0.0, 0.0); + // jitter +#if FORWARD_PIPELINE_AAA + vec4 jitter = texture2D(uNoiseMap, mod(gl_FragCoord.xy, vec2(64, 64)) / vec2(64, 64)); +#else // FORWARD_PIPELINE_AAA + vec4 jitter = vec4_splat(0.); +#endif // FORWARD_PIPELINE_AAA + // SLOT 0: linear light { float k_shadow = 1.0; @@ -188,16 +190,16 @@ void main() { } else if(view.z < uLinearShadowSlice.z * k_fade_split) { k_shadow *= SampleShadowPCF(uLinearShadowMap, vLinearShadowCoord2, uShadowState.y * 0.5, uShadowState.z, jitter); } else if(view.z < uLinearShadowSlice.w * k_fade_split) { -# if FORWARD_PIPELINE_AAA +#if FORWARD_PIPELINE_AAA k_shadow *= SampleShadowPCF(uLinearShadowMap, vLinearShadowCoord3, uShadowState.y * 0.5, uShadowState.z, jitter); -# else +#else // FORWARD_PIPELINE_AAA float pcf = SampleShadowPCF(uLinearShadowMap, vLinearShadowCoord3, uShadowState.y * 0.5, uShadowState.z, jitter); float ramp_len = (uLinearShadowSlice.w - uLinearShadowSlice.z) * 0.25; float ramp_k = clamp((view.z - (uLinearShadowSlice.w - ramp_len)) / max(ramp_len, 1e-8), 0.0, 1.0); k_shadow *= pcf * (1.0 - ramp_k) + ramp_k; -# endif +#endif // FORWARD_PIPELINE_AAA } -#endif +#endif // SLOT0_SHADOWS color += GGX(V, N, NdotV, uLightDir[0].xyz, base_opacity.xyz, occ_rough_metal.g, occ_rough_metal.b, F0, uLightDiffuse[0].xyz * k_shadow, uLightSpecular[0].xyz * k_shadow); } // SLOT 1: point/spot light (with optional shadows) @@ -209,7 +211,7 @@ void main() { #if SLOT1_SHADOWS attenuation *=SampleShadowPCF(uSpotShadowMap, vSpotShadowCoord, uShadowState.y, uShadowState.w, jitter); -#endif +#endif // SLOT1_SHADOWS color += GGX(V, N, NdotV, L, base_opacity.xyz, occ_rough_metal.g, occ_rough_metal.b, F0, uLightDiffuse[1].xyz * attenuation, uLightSpecular[1].xyz * attenuation); } // SLOT 2-N: point/spot light (no shadows) [todo] @@ -225,13 +227,7 @@ void main() { } // IBL -#if FORWARD_PIPELINE_AAA - vec4 irradiance_occlusion = texture2D(uIrradianceMap, gl_FragCoord.xy / uResolution.xy); - - vec3 irradiance = irradiance_occlusion.xyz; - vec3 radiance = texture2D(uRadianceMap, gl_FragCoord.xy / uResolution.xy).xyz; -#else - float MAX_REFLECTION_LOD = 6.; + float MAX_REFLECTION_LOD = 10.; #if 0 // LOD selection vec3 Ndx = normalize(N + ddx(N)); float dx = length(Ndx.xy / Ndx.z - N.xy / N.z) * 256.0; @@ -241,14 +237,25 @@ void main() { float dd = max(dx, dy); float lod_level = log2(dd); #endif - vec3 irradiance = textureCube(uIrradianceMap, N).xyz; - vec3 radiance = textureCubeLod(uRadianceMap, R, occ_rough_metal.y * MAX_REFLECTION_LOD).xyz; + + vec3 irradiance = textureCube(uIrradianceMap, ReprojectProbe(P, N)).xyz; + vec3 radiance = textureCubeLod(uRadianceMap, ReprojectProbe(P, R), occ_rough_metal.y * MAX_REFLECTION_LOD).xyz; + +#if FORWARD_PIPELINE_AAA + vec4 ss_irradiance = texture2D(uSSIrradianceMap, gl_FragCoord.xy / uResolution.xy); + vec4 ss_radiance = texture2D(uSSRadianceMap, gl_FragCoord.xy / uResolution.xy); + + irradiance = ss_irradiance.xyz; // mix(irradiance, ss_irradiance, ss_irradiance.w); + radiance = mix(radiance, ss_radiance, ss_radiance.w); #endif vec3 diffuse = irradiance * base_opacity.xyz; vec3 F = FresnelSchlickRoughness(NdotV, F0, occ_rough_metal.y); vec2 brdf = texture2D(uBrdfMap, vec2(NdotV, occ_rough_metal.y)).xy; vec3 specular = radiance * (F * brdf.x + brdf.y); +#if FORWARD_PIPELINE_AAA + specular *= uAAAParams[2].x; // * specular weight +#endif vec3 kS = specular; vec3 kD = vec3_splat(1.) - kS; @@ -261,21 +268,29 @@ void main() { color += self.xyz; color = DistanceFog(view, color); +#endif // DEPTH_ONLY != 1 float opacity = base_opacity.w; +#if ENABLE_ALPHA_CUT + if (opacity < 0.8) + discard; +#endif // ENABLE_ALPHA_CUT + +#if DEPTH_ONLY != 1 #if FORWARD_PIPELINE_AAA_PREPASS vec3 N_view = mul(u_view, vec4(N, 0)).xyz; vec2 velocity = vec2(vProjPos.xy / vProjPos.w - vPrevProjPos.xy / vPrevProjPos.w); gl_FragData[0] = vec4(N_view.xyz, vProjPos.z); gl_FragData[1] = vec4(velocity.xy, occ_rough_metal.y, 0.); -#else +#else // FORWARD_PIPELINE_AAA_PREPASS // incorrectly apply gamma correction at fragment shader level in the non-AAA pipeline -# if FORWARD_PIPELINE_AAA == 0 +#if FORWARD_PIPELINE_AAA != 1 float gamma = 2.2; color = pow(color, vec3_splat(1. / gamma)); -# endif +#endif // FORWARD_PIPELINE_AAA != 1 gl_FragColor = vec4(color, opacity); -#endif +#endif // FORWARD_PIPELINE_AAA_PREPASS +#endif // DEPTH_ONLY } diff --git a/resources/core/shader/pbr_vs.sc b/resources/core/shader/pbr_vs.sc index 8b9ecc9..f1b2ab9 100644 --- a/resources/core/shader/pbr_vs.sc +++ b/resources/core/shader/pbr_vs.sc @@ -34,15 +34,16 @@ void main() { mul(uPreviousModel[int(a_indices.y * 256.0)], vtx) * a_weight.y + mul(uPreviousModel[int(a_indices.z * 256.0)], vtx) * a_weight.z + mul(uPreviousModel[int(a_indices.w * 256.0)], vtx) * a_weight.w; -#endif -#else +#endif // FORWARD_PIPELINE_AAA_PREPASS +#else // ENABLE_SKINNING vec4 world_pos = mul(u_model[0], vtx); #if FORWARD_PIPELINE_AAA_PREPASS vec4 prv_world_pos = mul(uPreviousModel[0], vtx); -#endif -#endif +#endif // FORWARD_PIPELINE_AAA_PREPASS +#endif // ENABLE_SKINNING +#if DEPTH_ONLY != 1 // normal vec4 normal = vec4(a_normal * 2. - 1., 0.); @@ -56,12 +57,12 @@ void main() { skinned_normal = normalize(skinned_normal); vNormal = skinned_normal.xyz; -#else +#else // ENABLE_SKINNING vNormal = mul(normal_mat(u_model[0]), normal.xyz); -#endif +#endif // ENABLE_SKINNING // tangent frame -#if (USE_NORMAL_MAP) // [EJ] FIXME this probably won't be the only condition to compute the tangent frame for long +#if USE_NORMAL_MAP // [EJ] FIXME this probably won't be the only condition to compute the tangent frame for long vec4 tangent = vec4(a_tangent * 2.0 - 1.0, 0.0); vec4 binormal = vec4(a_bitangent * 2.0 - 1.0, 0.0); @@ -80,39 +81,42 @@ void main() { vTangent = skinned_tangent.xyz; vBinormal = skinned_binormal.xyz; -#else +#else // ENABLE_SKINNING vTangent = mul(u_model[0], tangent).xyz; vBinormal = mul(u_model[0], binormal).xyz; -#endif -#endif +#endif // ENABLE_SKINNING +#endif // USE_NORMAL_MAP // shadow data -#if (SLOT0_SHADOWS || SLOT1_SHADOWS) +#if SLOT0_SHADOWS || SLOT1_SHADOWS float shadowMapShrinkOffset = 0.01; vec3 shadowVertexShrinkOffset = vNormal * shadowMapShrinkOffset; #endif -#if (SLOT0_SHADOWS) +#if SLOT0_SHADOWS vLinearShadowCoord0 = mul(uLinearShadowMatrix[0], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord1 = mul(uLinearShadowMatrix[1], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord2 = mul(uLinearShadowMatrix[2], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); vLinearShadowCoord3 = mul(uLinearShadowMatrix[3], vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); #endif -#if (SLOT1_SHADOWS) +#if SLOT1_SHADOWS vSpotShadowCoord = mul(uSpotShadowMatrix, vec4(world_pos.xyz + shadowVertexShrinkOffset, 1.0)); #endif +#endif // DEPTH_ONLY // vWorldPos = world_pos.xyz; -#if (USE_BASE_COLOR_OPACITY_MAP || USE_OCCLUSION_ROUGHNESS_METALNESS_MAP || USE_DIFFUSE_MAP || USE_SPECULAR_MAP|| USE_NORMAL_MAP || USE_SELF_MAP || USE_OPACITY_MAP) +#if USE_BASE_COLOR_OPACITY_MAP || USE_OCCLUSION_ROUGHNESS_METALNESS_MAP || USE_DIFFUSE_MAP || USE_SPECULAR_MAP|| USE_NORMAL_MAP || USE_SELF_MAP || USE_OPACITY_MAP vTexCoord0 = a_texcoord0; #endif -#if (USE_LIGHT_MAP || USE_AMBIENT_MAP) +#if DEPTH_ONLY != 1 +#if USE_LIGHT_MAP || USE_AMBIENT_MAP vTexCoord1 = a_texcoord1; #endif +#endif // DEPTH_ONLY != 1 // vec4 proj_pos = mul(uViewProjUnjittered, world_pos); diff --git a/resources/core/shader/ssgi_fs.sc b/resources/core/shader/ssgi_fs.sc index 48d4e12..f8294fb 100644 --- a/resources/core/shader/ssgi_fs.sc +++ b/resources/core/shader/ssgi_fs.sc @@ -16,15 +16,17 @@ SAMPLER2D(u_depthTex, 5); // input: minimum depth pyramid #include #include - void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); vec4 jitter = texture2D(u_noise, mod(gl_FragCoord.xy, vec2(64, 64)) / vec2(64, 64)); // sample normal/depth vec2 uv = GetAttributeTexCoord(vTexCoord0, textureSize(u_attr0, 0).xy); vec4 attr0 = texture2D(u_attr0, uv); - vec3 n = attr0.xyz; + vec3 n = normalize(attr0.xyz); + if (isNan(n.x) || isNan(n.y) |isNan(n.z)) + n = vec3(0, 1, 0); // compute ray origin & direction vec3 ray_o = GetRayOrigin(uMainProjection, v_viewRay, attr0.w); @@ -34,7 +36,6 @@ void main() { vec3 up = cross(n, right); // - vec4 color = vec4_splat(0.); const float z_min = 0.1; for (int i = 0; i < int(sample_count); ++i) { @@ -49,30 +50,33 @@ void main() { vec2 hit_pixel; vec3 hit_point; - if (TraceScreenRay(ray_o - v_viewRay * 0.05, ray_d_spread, uMainProjection, z_min, /*jitter.z,*/ 64, hit_pixel, hit_point)) { + float k = TraceScreenRay(ray_o - v_viewRay * 0.05, ray_d_spread, uMainProjection, z_min, 192, hit_pixel, hit_point); + + if (k > 0.0) { // use hit pixel velocity to compensate the fact that we are sampling the previous frame - vec2 uv = hit_pixel * uv_ratio / uResolution.xy; + uv = hit_pixel * uv_ratio / uResolution.xy; vec2 vel = GetVelocityVector(uv); - vec4 attr0 = texelFetch(u_attr0, ivec2(hit_pixel), 0); + attr0 = texelFetch(u_attr0, ivec2(hit_pixel), 0); + + float log_depth = ComputeRayLogDepth(uMainProjection, hit_point); - if (dot(attr0.xyz, ray_d_spread) < 0.0) { // ray facing the collision + if ((dot(attr0.xyz, ray_d_spread) < 0.0) && (hit_point.z <= log_depth)) { // ray facing the collision vec3 irradiance = texture2D(u_color, uv - vel * uv_ratio).xyz; - color += vec4(irradiance * 1.0, 0.0); + color += vec4(irradiance, 1.0); } else { color += vec4(0.0, 0.0, 0.0, 0.0); // backface hit } } else { vec3 world_ray_d_spread = mul(uMainInvView, vec4(ray_d_spread, 0.0)).xyz; - color += vec4(textureCubeLod(u_probe, world_ray_d_spread, 0).xyz, 1.); + vec3 world_ray_o = mul(uMainInvView, vec4(ray_o, 1.0)).xyz; + color += vec4(textureCubeLod(u_probe, ReprojectProbe(world_ray_o, world_ray_d_spread), 0).xyz, 0.); } } } -#if 1 - if (isNan(color.x) || isNan(color.y) || isNan(color.z)) - color = vec4(1., 0., 0., 1.); -#endif + color /= sample_count * sample_count; + color = (saturate(color) / 32.0) * 32.0; - gl_FragColor = color / (sample_count * sample_count); + gl_FragColor = color; } diff --git a/resources/core/shader/ssr_fs.sc b/resources/core/shader/ssr_fs.sc index b8b0808..6c693e7 100644 --- a/resources/core/shader/ssr_fs.sc +++ b/resources/core/shader/ssr_fs.sc @@ -16,30 +16,49 @@ SAMPLER2D(u_depthTex, 5); // input: minimum depth pyramid #include #include - void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); vec4 jitter = texture2D(u_noise, mod(gl_FragCoord.xy, vec2(64, 64)) / vec2(64, 64)); // sample normal/depth vec2 uv = GetAttributeTexCoord(vTexCoord0, textureSize(u_attr0, 0).xy); vec4 attr0 = texture2D(u_attr0, uv); - vec3 n = attr0.xyz; + vec3 n = normalize(attr0.xyz); + if (isNan(n.x) || isNan(n.y) |isNan(n.z)) + n = vec3(0, 1, 0); // compute ray origin & direction vec3 ray_o = GetRayOrigin(uMainProjection, v_viewRay, attr0.w); vec3 ray_d = reflect(normalize(ray_o), n); - // roughness + const float z_min = 0.1; + +#if 0 + vec2 hit_pixel; + vec3 hit_point; + + float k = TraceScreenRay(ray_o - v_viewRay * 0.05, ray_d, uMainProjection, z_min, 4096, hit_pixel, hit_point); + + if (k > 0.0) { + uv = hit_pixel * uv_ratio / uResolution.xy; + vec2 vel = GetVelocityVector(uv); + color = mix(color, vec4(texture2D(u_color, uv - vel * uv_ratio).xyz, 1.0), k); + } else if (k == 0.0) { + color = vec4(1.0, 0.0, 0.0, 0.0); + } else if (k == -1) { + color = vec4(0.0, 1.0, 0.0, 0.0); // max iteration reached (most likely due to a bug) + } +#else float roughness = texture2D(u_attr1, uv).z; + roughness = pow(roughness, 2.5); vec3 right = cross(ray_d, vec3(0, 0, 1)); vec3 up = cross(ray_d, right); - - const float z_min = 0.1; // - vec4 color = vec4_splat(0.); + vec3 world_ray_o = mul(uMainInvView, vec4(ray_o, 1.0)).xyz; + for (int i = 0; i < int(sample_count); ++i) { float r = roughness * (float(i) + jitter.y) / sample_count; float spread = r * 3.141592 * 0.5 * 0.99; @@ -49,31 +68,35 @@ void main() { float angle = float(j + jitter.w) / sample_count * 2. * 3.141592; vec3 ray_d_spread = (right * cos(angle) + up * sin(angle)) * sin_spread + ray_d * cos_spread; + vec3 world_ray_d = mul(uMainInvView, vec4(ray_d_spread, 0.0)).xyz; + vec4 fallback = vec4(textureCubeLod(u_probe, ReprojectProbe(world_ray_o, world_ray_d), 0).xyz, 0.); + vec2 hit_pixel; vec3 hit_point; - if (TraceScreenRay(ray_o - v_viewRay * 0.05, ray_d_spread, uMainProjection, z_min, /*jitter.z,*/ 64, hit_pixel, hit_point)) { + float k = TraceScreenRay(ray_o - v_viewRay * 0.05, ray_d_spread, uMainProjection, z_min, 192, hit_pixel, hit_point); + + if (k > 0.0) { // use hit pixel velocity to compensate the fact that we are sampling the previous frame uv = hit_pixel * uv_ratio / uResolution.xy; vec2 vel = GetVelocityVector(uv); - - vec4 attr0 = texelFetch(u_attr0, ivec2(hit_pixel), 0); - - if (dot(attr0.xyz, ray_d_spread) < 0.0) { - color += vec4(texture2D(u_color, uv - vel * uv_ratio).xyz, 0.); - } else { - color += vec4(0.0, 0.0, 0.0, 0.0); // backface hit - } + + attr0 = texelFetch(u_attr0, ivec2(hit_pixel), 0); + + float log_depth = ComputeRayLogDepth(uMainProjection, hit_point); + + vec4 output = vec4(0.0, 0.0, 0.0, 1.0); // assume backface hit + if (dot(attr0.xyz, ray_d_spread) < 0.0 && hit_point.z <= log_depth) + output = vec4(texture2D(u_color, uv - vel * uv_ratio).xyz, 1.0); // front face hit + + color += mix(fallback, output, k); } else { - vec3 world_ray_d_spread = mul(uMainInvView, vec4(ray_d_spread, 0.0)).xyz; - color += vec4(textureCubeLod(u_probe, world_ray_d_spread, 0).xyz, 1.); + color += fallback; } } } -#if 1 - if (isNan(color.x) || isNan(color.y) || isNan(color.z)) - color = vec4(1., 0., 0., 1.); + color /= sample_count * sample_count; #endif - gl_FragColor = color / (sample_count * sample_count); + gl_FragColor = color; } diff --git a/resources/core/shader/taa_fs.sc b/resources/core/shader/taa_fs.sc index 4836804..8237b94 100644 --- a/resources/core/shader/taa_fs.sc +++ b/resources/core/shader/taa_fs.sc @@ -10,6 +10,7 @@ SAMPLER2D(u_attr1, 3); #define AABB_CLAMPING 0 #define AABB_CLAMPING_FAST 1 #define VARIANCE_CLIPPING_GAMMA 0 // https://community.arm.com/developer/tools-software/graphics/b/blog/posts/temporal-anti-aliasing +#define LUMINANCE_AJDUST 1 void main() { vec2 uv = gl_FragCoord.xy / uResolution.xy; @@ -81,7 +82,20 @@ void main() { prv_color = clamp(prv_color, box_min, box_max); #endif +#if LUMINANCE_AJDUST + const vec3 luminance = vec3(0.2127, 0.7152, 0.0722); + + float l0 = dot(luminance, prv_color); + float l1 = dot(luminance, color); + + float w1 = (uAAAParams[0].z) / (1.0 + l1); + float w0 = (1.0 - uAAAParams[0].z) / (1.0 + l0); + + vec3 taa_out = (w1 * color + w0 * prv_color) / max(w0 + w1, 0.00001); + gl_FragColor = vec4(taa_out, 1.); +#else // TAA vec3 taa_out = color * uAAAParams[0].z + prv_color * (1. - uAAAParams[0].z); gl_FragColor = vec4(taa_out, 1.); +#endif } diff --git a/resources/sounds/metro_announce.ogg b/resources/sounds/metro_announce.ogg new file mode 100644 index 0000000000000000000000000000000000000000..5edb891a826b06b038a33a9668abc93a0b9ba74b GIT binary patch literal 73374 zcmagG1ymhDvo5-Em*5rx1lzc~ySoH;cXxM!yC%5126uON_uxSi{B4r|+;iT2Z{0hq zcdwqV?yBypuWD+BVdTxslmXy?{~Y18{~AV+uC!o;V6JwKh89lmU0@;={~6*2_P6l^ zO#Xf5|2p1hzEe~bz80a1y#2q95r}_`SU~hI7WU?J@{VRi))t0pf7ugB5-~9_F)*+* zun^8Pz=C=hWrzm+r{h!s08{`# zpA?9kXetRTOvjZ@@5qg8iyDfoY$t(aut*wE0FZ(NB#1*Wl-?`DaNpvCT-+d5))*=LrYm6!c`_4WbSd``nV)buZL;!!#HUZ-C7?QOZW3>bmGoF8W5hlPn0p8aWlb*oN7RIO}2p?!E7S zhmQcrZU)OZgV}*ZJ~7aS7{o=yiGHz`r{u0sw+h z|DyQ6s=rYFhvI^SNUBlF`f;W)n)j-rZ<6b@rVmvZkr9Mq8deaBqZ<#uIaP5=SQR!d zYEl;_DMy`LU8{K z{9m5qfH4$LFcMEHT}LWAOEd9{MgE3m23=Hs>1t!_81U#fkk$X z1*F(y000*N_zW^~FlwY@oRBT=7g)waQm9-`=-hss;9^u}L{20U=Qu$M-X95^eTX;P zN|cx)GC|Q~>@h@0y+zuR2OO}Vum=fH1OWUQhZ*~E94945azrK>;j)v4IVlPgXC;BO zDLBkPvLZC2y{!CM$&rF29O=mH0yLwDO3)+#4CsfDI~|7!CV&9|7$Pdc14ELOCV_`> zN~4Si1WHnz#aYUeoUk8*M;W2BgHVBm1j>`ZA%fs>!Go-zX+cT=fT$Y`^b?|FF%|`& zQUJE?Nv9H7q%p{((M6{)OqS4Pmy%f~SXdQRRL9U&7gSW&uw+%6$YjxFWp7nf(M44; zRp(MJ1)yB8?7@dtEw2S zt1hpyJ_S*$O3LbJ%F1ZU>nfhgXil3-%33Q&>uNHq%1@gv5&CFOTkXrLs>t+uDF4&zNW1o>q@JE_KcYewrXPh065T!hz~ zzKT;JMO7c2WRop+9Sv;TE%q3#rRcn}pRH75oghJs>w|U#Z43lDYiuAwc3mZckJ0dN zxGAf(L5C-?9{OK#-&$Wl5YgPc=xS5+gaBm53%~4osR=g2^pZkh026T2!*CEt)qxCn~^_vY<_x z)wC=foY#!{V1&waRA4+Q30K(gP5`kw$|*n=k}52~kd~rV8wUY7e^yYUv;a+6va}Jz zs;~e{T8gm=L(7(NkpMKK08L90IEbNaLpzx9d5{RiYUEw`Ul|8!Ng!6~K?zb)mB|T8 z<5`LCtb$^sB!Nu{QZk@bNlDd)C}~4Rl6G*>u<~6vRc&$tNciEZW)x@AtRaY1f>IP{hL&|pQHmvqkfHRo z@o?42n)QBOv*KNbH0TQ96x(vA28ufV5C8#xIFp?B5c2M}g5gS&k`}0r)YzygYl1i{EugIPhyLa>;i zWq__#1|5v02TwRb|2<{1$P8rH0?s32GkY6?qL4t%ky zA}(AstYBRNfw=K7!%7N7u$pKA0M?;k05uT!qHGj6a8xYDe>dSz1X5!ZV);k>6Fpo{E#{t*QtfKCTN1kNLZ_bSUN=D!n@|8Bwk|7@ZZlxKZV16{2L zaqwaPW#wXMe@~69|C6Mr{X6|1Is1QS@BgQkmQ^JPa{sde2z0^20N!Bm)xS^S>d9RB}fVvY+=LvyQpPB31H!(=2u#f%SnMw4Wn2?l|VxVmJKTy3ucu-{)uB$ z0dgQsE6~!dXn~E3hEa_m{evRJqTyH8MNI_2wyizDU!uk#G`XO@cV_#qt%*U&w$NW~ zzuy=rfozfUjuv7ODG;jOF+v;+GQ+>V;JE+Qhst&I4_Y8V9sL`Zq#*PcTClmGKHx_X z$SD7|$UrT)e+k@QFb1Q6Qn@4$=YM@kXdoGX)4L?4cSM3z^G^5+)xRw`;&*lZ9ZdXh z?t3l>f+)m$6#jfnnm^8E5b2THJC+%zV?Zx4c0#5lO1e;<>v64PWfF-Z-$u>9yvJ^!_u$)jkSZzy}ix(J#rv96VvwA%G3r1lux;i za2p-$7zM08WD<2>qCx^!g`OF=yPH!HxujhJMyG-UOz_L?h_C&*TfpynNPCeIS!l0< zFSE|-=(S{vXOt}k7Z66L+N%Q3U>c3_4O>yN;?Mhzf`x`OKUyfVtlh7EmJ7f*i&7;k zk6MJS)+xUAAXV%<5mHP?f!~4|+|jfQ*<~|m8^JlxD(NvSi8t0!G?@*LI%&XCVoy<% z%XJ|ZQR1TCD4XGFK|H1Bu1Up^BA0bex`VzX5W!Xv#{fYvlL_$!4d*Q7ofR$;2;{knQsF6dTUR&rv^_Ky!3V z4nV_|f9lAcYQ6)gD^Sfj)e*OJNHA;#XFqp0TzkoGpyD`3w&5uFxZ6(qr4}ZAj?UTi z?88e&%5Vsfj#I{n&Vnl_W|?IgOt+QB5jh_)731I4)~UcSF9Sz<;fM~1tb^delAj<) z#^cJ_ryAm-%K7MbpkW&ArgTn!G$qm2uC_h#qb-eziaNa7PNOA$&>wb>gpQ@74{iQN1GEOzXM))Cmk|UFk|+_$Glr21MU%x9nY@(AU5MXFAHIguc<> zPjuJs!EEuelHMFah&TC=d1j}lvS&#A(J*Brlp1!PZC^beMe9wNnGs2EW~?xABUqtjo9T-;32d||EQ$D% z!=eq2sfOAnU5wZC$B_MK&XP~S!t$&1)q|m)a|%s}&5t^Bn?}Kmt%o+i1L5N>k3*DryHUOLIT4C7#LcNjE613TlXaDYuMo6^wAUQwAIucgoO zzX-yuVz~8)Mj?j}!ULU;R z>Xo2q#3^>GY(ymlJQVhC?^^t0pZ9RHesbzR4$p%(9@{MikvS0_a;}|Q0>PI+($|*v>8+?+NKPf>yFU#? zOmTeG5M$bp7kwdq83J#F5Xx<1Am;C4l`OZKfOJjbKfMk{u(MmLunsh zmu@gv`-DmF_>@V9cUCca!EOs*qvqX!Nw*xPsj`p=##3@xr?b5H*;PE{I^* zDxIQ^OZGdthV8KM2}}0luc_2(EC0lHblb$bZ9+cI#s_5XizI@g;)0K5D`m+RhI>*N zakvRZ-wQ@3$}q}iJCzl&-gJvYshU-A(KHQjAVbf&OYhV+$LSZ%5zCjqiRmiZLr~e! zI(@;l#9p~Mil%abVO<3CIW-0V2oV;FV1$X5A~3zI&(L)L)L|G!F8KjaO)#mILLSB;BM{hV*d?fQ=2)<|gf z_9=9}+2{easDY`VnA6r`bj2Vg;I-=O)8blTjeE#!K%wgzCPqpk+{az}UHgcDCJcCh zFE^`6)C7+3tMK;A68rB5fv|LBM4qb`sZ|Nj6rind*Rq!kH%}8h3|rMQO;^-W^zV@WX*|1{cwyYttqUuwI=3X6A5NC6^7`ZtNUa(%6XCTRG~H`}k8 z+$=b_F34@c36E5Yg&d|uqdv3j9z?d#zbkMs*&xWQ?JC8)k{fNIH}$kL z@(!|6JarTl%k9nTfAeB${U#%Qr7|&vQ>$_D3*UYVMUm%R?}FH|&rnfSnFPW!H*+Nr zzg*16F41j#>-u%SzoGRbF;hNKaJrP^)9f!bI|mj5{{1#|2lA%-tZ6#pMrhQ}BKFa| zcUHr5w5rQ|&5*~C-)${iyHa3HqO104`?Gm(?mxqv@ockHIwIlxK9m`|g2CoPBnr2q zn943BFtYI@*sj{utk_*R#Bi0eLM?r^A7MBrZ+Vmc-s&jONXdYQ3ke>Oz zaZyE((!4I%7YqYwKu)~4FrI%@+GDHHT@EQ6SeI@`lHt?T{{t3e#pqh707@R9=>EN- zVeJX>A)>y3(5e-BqC1#rWOSI4tqtq`;*FblY{z#dnCjP;thNUyhZr(t#D-$VanT~6 zqMe>xaYjYmubiy{Fw2Yx5}^?y>&Qc*zUmiWh%vddy9OZ1n6FH$+WuD_{n z17jtHNJ4&s8+{d22J_Vf$IQ|_Eosy;d+GFo$v%OPF;~?4nr547kx?C0jJ=32{VGA+ z{rp@GAot33sL`@N6blrE*A8PWnxLa0&AM-JvEoHV(lq#>meGJy{DRX&(x9QtG_#&V zg`kUIV~SYT4YpPKsFe#wrLC$ulMotk@Ckq8Mdsz|qA}A?Z;IV-dvdxxOIDUgV|Gr2 z?H8VE+D&Q7tZ@~RGc$d8?ps{@?j!!4!ZXa!`f)AZ&bH&SSo;ucukh!TuLP>a?1D9l zs|jcBxIL7^bQl&{#{HKf@iy>Ll;R{UvVujZA870X*RIqaomY{W%K2l`8aZs>)B0)k znFAIJPu)%xN-e%^n;&NIHJ!27n7@o#YWt|-oBq6Ksf^|9(D72;h-xcKOLd9@WOSm( zQju%kPs6)%vD(7vc0@xIjH(*J|Sx3zTx}EijVMmQSf-L zVuha<9S&?oXxGQ|iv+XLN{?xKMvr8v)%EnTwcW4;X{;QAy&U}HP+4`RoVe10rHEPm zY}lHn3)A}K%IcMn)j3pA>!YLF^wStVE7E-R!O!k)`AF$&o8@_Rkb|2DnFhZ8;~zsf zo$rsWGM)D0HOh=PobA^`KCxfFYJN>kDl`|7K*O4BtC8EUW#v^>Jk+_Wg}I;>cZ;-^ z^zh#n&LGBm4(x08cRAG6_(ijTa9sY24;&yOR=RBwd<*B|Q$LGiqvEuFu$?&2uX4Xyk{f8?9KldiB<&%p3Wc z6E$+CVbAtqe?LhcG-_{PE)|7S6)h1SO#;v_Qn7HVP!L+~-SUl{R@r^4<=jVbo|Ev$ z4y~!nUOb}$@@ajQ@tieGX%(G5TV zzG>Om%FsbZ*-4joK&7%N4tAo9M?3Qhnf2g)zdnXtcYw^i!hdKQql!G-|-c^6z#=Wn2D^ z%D2A&5SXre9*-^Lev;JC3ewVJPIRRDBDWKyn=6z;B;=-H%rMR4^NDF4lNTrDA@a|5 zX=%x+cakWB78h?^fumBswlt$PC*DSjIlpBsiTItdD1Pu^9e~#A}tFV`#im z;@6OUDa!V^NJR}vt{8CuU3^)?2-SfJq|HRU0+f^!Ro}yz!*kaDrjRV?h z2f^|d5A3Iz(O5E?rm^l^Lbh0TYD3krFGNxWi zTELS*pxf3gO))ET27RYpnS9INBgPXIcc+u+oWp5;L|{oGZm9C+fts57^(86kpfxlm z-Uq#1tHSKNl(*Z`gFy9Ks*m2x*Fbfj9x~8A+uBp$gpT z*j56HE))|PQ_Z*sh8Cxt03K#dyYbAky3NcpqFe$wy`kSWNE#*0a`rTNA)&N0@W7?U z<3!E-jn3xRNM|fyx=KsZyg^Ex^-<)rOMO5O?fHBxzd%%0LB!!hifFZ?6KT2T)CVl&SJiSYCn?3WS@kSc*@A60&BV5VSd~PP*pd;1y@ds~s7q zs+?VY16l($0Y~0LX5$`%%Rx(J(Ivtn7#}sQdxQwX!NlUjhATB?s7j&&eq110i;|fX z^FVC%>|2NFD1(!eE1&Ywlw0Dn*D1n7ky;~TjL%zzO3G-;QvvC1g?Ir{V4`c+1A?{p!|ih#wPZ;~ld53)fSoS8U;RcC6#Jvo_XHnqUy1LmOR2nvqy zPMWK$vu!~#tlqTJ#kcagiK@K;AoCuXOb*qLU`QSjZS1*=6teV;Plu|Qx^`6US^x|66cmhZ$vhx`;{N!`9b`-$x@~=$#6Wox7Qh+G#hL(_ZD=k!F8T z5J|Y8nR-8Sun2I8aT8C0e-N84k~!e+JL)&YF`n;;E)6wSbjG-y4$YnJ zz;p&ATeyW|G36?r^Tx(D0Z#PsY2^}kNuF;Z^a}a4qQ&P=Rlk@ZX2!DS4I|(>x$jJ4 zq@hnxK0I%E2d@%BG5bTH3J?J~sXm0|gZ zxB||`U501dh5eC)%uj!@|AHG{~XI-6u%^+0r~MQyv~qqnxl zMSQm(zjlX#$DFUN^xra>%CsmN!fDW5-9)|5z7ZAa98@2$nW+j=cBq{klru~>@+eEn zLS*-=kxie#F&CXHG7+80B?P-7W2`ASR!aw<8rRcpO@rJ`e4U zES&Vl`^{$AedGR$`V~jWoo7fS^r!%nxMFid4PkO=Evq(; z8w{G{?0Kn7)MFB}(*LOdy4@NErn}OA1#-bd1Mp%%tUQ-GEf>V9U#^!TX z^H#1m8@cV^tCGT*D3qOP%o2bxH!;%C>}%c1`V~Wa9>TDRiOe8``wA*Im~K=LV!gF) zU=j*``T}XABqt>ONn)zU8$pWNe%<%nZI=u-big<6yThn8{W#w=+2P;5HW7(lP7F0S?6l!sV#3gs{uXuG#sPQQ$rwk~*m(K{TodUZ8*r= zu^&%OZfd^NC~Hs#FEEsT2`dcFW)yKtBnWhL43zaKX%~E{S;jEkHt)q^j_%N(aDK^} zut|Y+-MWruDfZ}v|4lYrv?LF24BSEFY^rab^m^`OP!8Z1E*?P%sXL{Uf?FgP@tX$w z#x3iDQY7#yDDv$)R(KUPUMOb*F#sm571}~{1%>k*=TAFghL`iyLsaczds%<9bl(j< zAq;sa$|l2?Z%DIcG1XEx*7Nn*Szy#uz_VWp_+K{(=MD3gLQ3JgHLPe=VPX33k`^Mm zy?uy&-n^U<`-1b+WpO=*P|nXgXHLZHwv!n-d436e?i=Pz32miu0@_yK4(Ux5rngav zr^!Cb8s58QHdBa^JKQoIH4&kd>{D1pjN2sr&ML>e7VA7SFDQg$_IWc+7pdT4vqh|by0N}&Nyogfhi}Tedv9GR<#3_D%8VP~7Av=aTOrELo20t(T+Q#G}9=8+ovRehZL&$=|a<{NFw5hwl1 zMM0?kCBg&=RA-7|nU8jyb{J~Zb>H@YjBa+T_YFFt_jrd<=^YmlhaaUTw4Jpb(8KT? zD}A#YC3?{HWU)A6C2v{R1AJ#O0I-jY3j zBq33v8?dow+a+tIsmuD zN~P%ANSi+)eI6cb-X2dZ%SgKHlost?IP6Dq3nR!!w5|^rd_n~$caV9^A2n4zj(@rb zKt#F&bhTtn@f!sEx*N1OeGP-5az2&|Nfw$AK%AhMW+AVC=i*>DNjFL~XpRbDbgk>n zByn_^i-}I0Bk}0nW58EY2bVNWbxrn1r>KOO^iyAt^yn||X*GB|M7mNXP0|qVaJgJZ zh}20H*!D)I>UEoZEy4S^%0*AiWM|H*7c?Z+ku=_JX9nxVk>OsuUPT_OTN*~EHz;8{ zrZkrP6o%DRpA)iTV|N}vO?NuAnPg;q^y)kRG|SMPQ5@2Xc|bcr2K0*DABtQ^ zwnvhVOuty~CsiyS8!td*J0gleEpb}b-|)jf3YL5gj`*->D5U3^EWC#a`MTNLEZ&#J z;PDih?RK9`l563sX>snD5&El7L(%ZlNuOnrZF25>HE`_RP>yt35wI zAEDk5)`jE87B2r+mVtLj(mP@7I8nrt11}7C0*M z#F`})P7QHrzRT~9K8-PAruCdGovpmw<14jl{F#Z5PURhH%V-{Ox=)>gS{Vf{Uv7g) zP^ZDg#J-}cZ=)58ml!CFP)GMv~_{1_yiz(v)JQ5+~DN5Sjj)@%CFs5!3Pz}ix$ zBqesw*2j|DGXgbaFE|4SkPt_rE}UKUvaS;52b2ihJ5fUB>1y|U(v)$-=d zt+(Cuh`&&01JOb&Wz9(ix;Ym~6AuQ8pMad=+01P!zr2Ei8dxkVNlN;bvX0wi(~vqY zBJPDrHW5BPXolTv9<6yZD#88$mtEWlYfrI+P&$L~AYoTklwN4~t=dR=>@Enu2=|)| z=P9#G31)>93|A-dpg*mXIfc}a^cOJVBD2Kj$eav%>t?+)M7tuc0D^|Z`8SsGEbn_B zoLopT-@5S*qELV5rnSd9qd7th zG*D)x=qUHGYa!g69sW^Ry3=$Y3LAdow+mEt9Wg$P0HeBxVHIERGDmulx+@F|(kwI8 zZoxH5^)bV-r>sps3;L(;4Owq@)nEt_H@blz>}x;2So%ceMfrtU-+7n2U`~`OZWO@r zQAh8>774fuTXcerIMRU0y7|vR{;|2$Qm!*4I?thPW}C*2DY!VI$l7R!XO+2W33${`jYCT5qGq3^p2WepK&A z*OPO9?+fY}gJemS{pKls^A^X??#FcZ3h_OJ}pq@;E3-rTP1w*I5i zQ-$*{>2_i$x2^Ok{T&s3IH{w==pu(NgYB7`b&J3x8>Y?}Ay&&82sAz|R$F{#ns7h+ z{;zlvHXg_rbLp-*cq_8(@&pepME3UiF%>LEc3@Izr|{CPBk-*+$&c`UsDSs zM{rssIF&W9_I2W&2r=2*(A-htILl12qPcrkOvr@s3AH{iewp`Bw4|zf&S}C;rHojL z)|_^N`c5(zV>C_3yJ+;%5!!o=cbLruevrXS6^=ZUNd!lLm~SjB-v|~a!naTGF16ZfPPL-M67o*RV4(X?~)7-}7$Gm%2T=gu+zi09c}M z$fRs2nL(GBpUpjt&aIsG+)+a1?g_{cx9qGI0UN2dDkG#8rF;!OI`pvd=c}D5Y?~D! z@F!Xhn)rdWPUY*;38v00K0EfKiLCQ@A4*u>#FlbLUGXI#%Pw9tB2$J-DGec6Hy$Lr z(C={|X4(15=am-fycZV=Lah45fB-^k8y|e@0L8R=yVe0Yi;k}_7GJ7J;f8Y)fLGUZ zzjaQZsUDuD)r0&p-I{&xRu8_8FybHiNn|0{w#hX5VWXGHWAnX8#bxP17EF~WO)-sv zi+#rp<~J+LSY(Ee`$MPb)fJhkY9@sKw)dv=*pFz>5p|2E%j;-PVSvA(v8pN*sA;@< z@k8Yyw;5~zwdY61P1hwWu?@TWR77Vb?e{jQI|Zvq7|Nj^F428qy-8-w8-S{O(IEuK zFqK*sev&b$F)B!eii1x5#hDGbs^6aoh;H9Lhg1PjH7m!L|MOU*d;Py3YrMaLp^qx8 zOSry9#mKU~ygYvZWM*7j-kMulncJo&r=TFGW7%Bhi^?u49JWrE2sqESv;Tt@J>%HD z?D9~v^s^$+SpW4jDJtwOe&pp6A|oyN2Or;-uYI%kI9{==m%}>yc=vPGkq&z?bk(b& zeSxZ^&5^J0?L=iO`HZowxje#_F+X+90Z#IAEdy@-B>c)36Vc0^vAdVNMRE3su#k=u zL_gCcLdb-zW!)Dkr!O?=uwu(ozfHHt)$=5Nri>-)Rv6SziC8Et;~o#Jh;B8&r%WjR z`7X$BQg>IKWRD}Jk>;Q|N=?=Nc;ZF9SNfsV z1C>fco1Mkz9tVG~>1py@35k60R(4oQdLN85`<@cF6@5Chp?j_h>XToz7OM*+3U}7! zGFV*hUIvA*eupooOY3@$8`Ydc1_um$V4#49**RhQgt~qzUl5ZM((~fLexcuXq8_$j zwY#l|xGTQyWhoH^&#c@U3e==>dy{|4c^p%3`&S%OjeMMJB#tI2#qFUsbQ+>vpb}{Y znl~FD%kvr@Z82ohA^T%DYZ`1?-iOrT+=g-6H24?$nA)-Rj3@VprMT_dMfd`_gMxTo zbip!ppEcI9?d7ly3(roiwy%8YLVunu#q;>xQ}lAi;y=d1^f+wJ6+y8Fs~H%F%s49i z96(n1_$9^v$DhvERkp!~NjgD z>FOQQ7UpHehGHtKxE_s#8V)lcnMHL5IME$6(xcVe;_GR_-u%;`=emjdVeQ^;)AH){ z;VsPIGFmM4CV4~3*}DJ8TT;E-if}&#Z=fDsG=J)BH-}>({R@X2F$LZH6 zK;5Z|*;c0vPPjywmcBGnyp^cg>{762Khs@kdanUIcSlzr8?C*0VpEfAwBn-O^aR#Q zFI8D=QFucXa(3mMB$F+E@RTznbS-uYyysj5to`WJhd1;jY=%S7v{_Aouk#A)+ z9IJ0Q^#k&Q`E-0|cl4#T=md_ll>$DoYa)_>6<4MT>d)8kEKxe;oK+@{dy+0C6^1wN zt`##*oh^~H6($7g6U^h`C)19un+(NS?wcF!eySJ6xs3XSpVo4tN`&MRb~pKxI4oC9 z`rh{sI1b|kQ=1LLx zUd1{M{Tfn+_U(A7%-*gNlMTmQ=V)SbOziKMX^E;0gZG4)IJDrRu$15|BbnV8fDLRo6*lh;=7}8EoIQoAPNF z*}ggvbD33CG-fsV^zs}IzP3RtY?Px|19YivOp(|GB$D?ptg;iH5peqQ^(1#2uF`hr z-n1(8NPi*gW!4^6>TWF#jm+R%Owl<;8WCyk>xY+%vdQIHy1EdnRK-K2QhO(2PsT!W z;g$Li_AB@XnBy(T}6G zwcQpBZ+4oQ@&z_}{QR@h;T`*_{R7Lyz*=Uu<)UU`S{%5SNnHmUM`!CNg>TzD$GF`F zWOTkLU}VOW2YwI(zy{6q%Ml$XvaLoVQQK%_I6#W!ohMS0e>88$r#?*j*7`{`<|FK( zf>oTChAEENfQ^%>%xL_?hl%>b$MxOuyP&rT#wzdUqvCiqU&}T(Ld zU^E>jr~1=m18rdn)gZ}|CyO6-pU^l&Nm6S}7T>gC3Pv(kp!kM~yKT5JLzs2y41DTO zH9Ez4z-_bqevHHsIW=+9RO1sa>-{l&1cM{FfcZLe%Tr_L5DMhCpYwD z-4}K}4c%bv*R{*O!9f{vXhUpLW|voip<)o1&R3S;|3x%-K8FI`-g^?GrgN8U8Vmqr z>3Tr*%!_W7H$vdRnfQOEc+n&?iL!YP-3-3|xm>Gm@>?o8DMm@!D2mU4Rg8gGc*#I7 zS_oo*{(A?T+IZKh9{4Bm#bT4}XWIEi=nI|;q7dR~b?~3X{F=&uEb<5_+cHPUb0>I} zyx7q8drHKyO{d70(lmpzLaYU|o|0RK34r9N?(O20GxkcBf>r^JD)Zo$+CK=rK=ukp(3{*@A zXwpgRk1P%a9#%M~2uUM~*{HdQLMInaW6Y8CqQzrG$5rN1F676|GGP}IYiYHYi{i`_ z&xY_ujkF>RHOk}!+WDz!uKrRKBUc_ak&$er{k4ea>-^R`=xF!h`?cJKtx}6G>ko?Q zNF-di4Xb_^=-!5&g5kVj$q6I+Fd%{a*O0UWEZ@Vs8y#rY4NVP5yh@3WpnzuM zh)9TNT~m!hIVmL`B-xn!o0@Bd@k)Tn?B?^`InS0x#>6A@(~8ZF8q8{{ScbOl@Jz2o zl1C!Fzy9+hzM|s>s%8Ev{GXxUB`!=o@hg=snX(ZTeaIfPKOlMKXQ_!_i%?}ZLN1y^ zjf3Sq=ePSW?Nx#40OP^|JL9cUWN#zg_~G;~F2+3p*}>#L7vxgc!*Z@*9Dv``<%isL zXY%EGb!2n!ue{4cex9{D_K-qjM~qQ*rYv?EbV>buK@);96;r?19vyPCCi9a0s_*48 z##?M{4SFn|y^K*h&;xygYmB=Beypl2Stxt^83Mbx>d^H5UVbygo9+5ZesGAx+(?_h ziNldQ+xZ^3nFoQVHxb>L5 zN~^BU!~IlS>X=@=E}8x2J9&9#c>q){bJ4f>QxnBx+PB0TI*=&5snL(UB-4@g;ZjS; z{(P=UCh|+XM{U9xq3-U|jZ8guYbQ2=_lVWe8C(+H)6Hu|DXT`A z5GUtq_4Pm!#;ASf-D)f2PZ%t0AY$vZqwJ>6SNR$Yz&GlTvhV@jkZ%*l8a4TXstT)W zQG@V3?#~V9zp3{4QYX*0T)ob(8($VQY*utesw&utBTyQAyeIYbcrz@=W(dm|MoBUp z;&J{A2XMpZuF_i0^0h(JMs-ZSodqvF;X#z<%KkyD+h(=Fs*kx4~b85!`sw4Pl+5J3Q!6rg5DF?mA0`(+!26y zijW%dS}Nl~-$9)kW!dYkG(owa}!X?x>ZpHCRRj z42etiH5n{u0KvJJ%L{bdGvu|8a}$hZn4AJ+vg4{WN$mz@oL^Mg#{t)Tij zB?`+ImT5FPW{W_vp?6i0$OeBjb86(tti}w`VVKwass4483w}qt(>ykE3-cGP@9LQL z*b?hN?Kz(xM2d_?C5>YaL)0gYB8B0*d-9h!%h)T9(6N+jIdBa^QtI!p7dc>y5B>It zRNd(9?EB_pJIi|+JyIt~qt$GqMiKqMK`H5pt`4{=&gZA)T4tS0zZcQI*Cx?dQ~5+u zvm|h`>eAWFRs{#^EBm`y!w+98InUIHwT56#23sNy3%+bFe?rQ?$%rf@(A^q}eCVKE zmNQ937y1+!g+T>3d)GvpM=i#Dxd;_#MO>?mlB;~bh=0RSc@cAtT;ga6X=QYii=!k2Fd9 z(E6c4EFCKFzVDn$FG(OyzfbxSkt4z8U`&j|2r3n}qL};{s36$_*{-AtshTl{(UPtW zLD#c<@8t5Wx$F65+Qy@NTeZ`go>k7ta@^stT`UN6F^5Wu2IUd{@y>0cxpYc;UE(*b zlpRVvh4M>O?TzN-i;xvkl2DUo;>Le|L&YT=h7RP-dpc}0*voH?%x^suo#|nwLF-`w z+T~=Sud;T2`pI@<(O91sL@>)IZ_abLzzpG)1nsNBBbJMQ)Ryp^kK=&LAcM!AW#fAN7)FY%H^zE&TLO39E3mUL`V%!bE z6#%#DrrYtv<@)0zrbM|9UX-BCLnpqAaUTQDr9vvS*XA_cl3X*vQuSFn>x~@24f0^7 zF@Cj!aL_VaZsMN^`vLbbS9eK;-)#kE0y3)^MmcpqMZH|)-f)};^P?n5fZU!4Zk=(y z)QYMqk@VyRwf9F@FLe9eR5^F&9LIe1A;l>m_ z)H4vj=rqi>5b&xgYYi9K4ZZcXDPuz@hhw_Xh?-(XiYVdUGL@~ zPus4-p6oIyt(Qw-=3O_P##hbK$#GNZ`_Hy^Q7kB^h$%CZ%RY#T-tL&`Ut17psE20+ zEWrFI@Haz$p~5VDU%~s3_4z-aWAG>Y_j!i*e=-4mw*b&mAhb@nPQ%2wy)wHpzrC}* zy0AXI3#1^YU}R!iU#Fy`1gddiKEhTJA(7wzzBi(b=|469>;MiJEc;cSV9o*&zmlBi zg(MDhs@j?BpF)e&177`>RLvN?>uP8Ihrewt&XSDPyqfUF4vK9P1cW1tpI+HArn&#f%Ump{>&UcP4Ww?bl3qs`ryXvC_`giQQ_6v6@~!g zn34R|FErXKhqWR_t2Yu1#3se(ycV2ky2yg_7-(x<7v_&4=m@o}g#7;a902|AlOgp? zb%r`m!LHrJCoq#HBbDQDe3%LC0%!$lPHboJ${&j@d{246D9wb035&}O5?VI zVdi&A!HdN;=$mF~*u6FU5`!#8=|1`cF zweQ(M962eQ6zRw$BSd^h+HGqka5dX>kWAV1X&G|JvY7ihj;yoh_!FYOll;3w^7
?d4_V?e$*Z{CV6Atz&TzIuM3mVX5R}s-i1p>3h8xIY|0b2LM+pT z^+E<;i$`h3((52P4l1vtql6yJaL(Db()#7pyIj({JwGj{H^&s>_+2q(|HD4fF<0jAC3#Imw63 zJA^+Q@|A8OAClfm2$jdhcwJv0h|Sc9?1Ph*Suuo2_0=J*gcs&DPF=}xs1kl9=bEOt zw&>|v2+6i+n3toLWvajVe*lv}Y`-F!|3iG88CICcVvQrOqAX&roSFQjN)D&-@higo zO-FjW^qN*d*3%EK8hV}zXyz7%EF*3mN*e}OSnknhp@m?t=RNo*KkDL$kVVGR(ry4= zOC7}?fiWS&zWr9y4oQPC9EIPO(z+PXPj>gy^mcg6|BvN=f1di<_wPX$e|=#+NHQ_v z)|Ej?t`nq9i##rf!pk~6t+G=r$N}FeOa>^i^pEJwSyh5U%Ymbh`U+aA1V)CNL&ZSK zgwz-+_}^#3BpE***ib-4MLU^<{bG$)L`y%6yVo2v$}2%54xF*;$?yl1SZl3*`d}hb zyWCC&pxPcH>bA^;=ICnb!k4mC>X`HDuNd!K;oydWZVmHGAwDr92FzQ56uZZ*z$#Rt z06sfyW(pW^z0)02x1#{;{&fJ zbEh_le?WP7r;DGiFkq;g)Rwb2Ae4`^%tRDXIxUCvtD*aw|-sZuX zqyFioarWYO~+k|GL?R z{3OPfh%$EZF1_mtlZUcN!7Z}3`ei~KV>Y>hIRgGVb*6?ukPd74e|=6St%$`8)6-^9 zAWh1Z(OR*^$gOZWO$P4aXMuJtzsAYT#)Ho@f7)nX9X0cXjq)>B|6MjYDUCWdg7x|1 zKH-I^%E)o)_4vSnV^dDrwj{~;FtT8jcZeW8d`2ep7W{Lcf$49^(&fsPvUHj83O&6> zy3^cr@W{GwZ5sLWkfl}q1-Ico#BDl{F(@!slk7{xZB86@Z!-Ky&%Wd;G~|6NV!Z=Q z|4h(rRw%u#;d|S%uuB0UYRQY|We+dPDjgJER5ZecZ{zK*`<=**)`)B8>7`R0BVXWGu2)vF?5 zLnF?W1UR4zE7{0{OUJ~WCWnd(8N9_qkUmpbI z##&o*_{~t6(zW1nfzYGTzbpOHk}~pF-?+%Z^h+)g$Gt>r%gd4vRFrPht$uXfMZgnr{b^fY#YD8L*%5RoTDZ9pr!uaw-HnRNyPUn4v zXh$TLFG>QwTb=ZnPy=a#eK%qT1_EQR#?nny)C~N&`qiMpsePIC)rC1fn$5U>z}UW< zzhW~cdadv79W^Xb{7J2$IC-X_57MQAFUB(cp{^IaAi$ga7~ns)yMeM%NEet@nD3aP z4!E(DGo)E5vE^Q|!{EeRim5j-w#2pwwbkkg$^kR+V_fLkeI<7|>W9X|3l zcbj+S|2dd(>);@X>+m><+*fFzNZFH(m@p4i++E+x8d+|nXXoqv7us|#2i=Ptps=K! zPOSRVYda$r?ih>WvdL{7XB{5dNP{%#+Ai@ikxy~Ap$WSZzrE5{(ipytLHtsA#RHaF za!si&Pw#bCRyCSZuG{)uGj1I<$y(@nTdCCx`CH5lKqa+%1FcB|_I(_pE?6$YX`@vp z5|*<;6H^*eI}+;;UC@{GamIlH1pq#LUCSP#fC;AErb9!qEgHZ$j)_-o^mH+B?CbPv z?@y!EKF!1bTc%UPDz*+Zrl)>%#jF}N=qk2=+RMfj4Pf>mrf;Xyye!i9z^fSQZsU#2 z_LWDyWy%4}xX}f$^`b9^gd#5~9&qKXt>Dw)`;xUD9ye5lNqN*NQ7XBPdT4F}i1} zo856<*r{##gyYGW(;qNTB`b$5MF^-No%|dkfZf4$VlU~Gw9 z>GCaOK69G^039|wJs#gCV~JG0YrGiizRw2Nw3NC2aXN6!104q4|Yh$$?60RH$c1QNChgz?YcO(d(OS7^KCvrIC^2g+3sNTXuiG+68U0kx0Zd#DXSD(a zd@gZd#=-0rTkQWm7KBUC^xl?PQ7V!{k4v_R*}gfU7{u)%eWvg5EX-on0tBs{a_t#M z0GFJMq7A7Aq-HzQTu|eHwJ>2+2mjiOqHhARr*WyNypSHI zMLN5BaY%f$R!Vu$B>~K0Sp+Vw1H(^Fz;V!8J2%pcDlslavaV9XhUq7N5ro0zg1u8{ z0faK4vLP*>oZ|nlf~A9Hl{*}b4ig{BfeD_r!pKZ5uxOYI4$gOq@pUM}!jsveqzimP zDF>#M1vjLhWB}=6k=|?7N|E%&inRLI$y!WX|$TPu03Jx4w@1{=DBeLJ87lpawI zPVFoy_FW375`!^9VOr<-xZl_FMeDjD(kFf0vKuDe!Qz_MfLJ3@`bWBLh5-6W6*Try z34xQLee*Bh+NRjELhSZ(V4V0(TjeVbSqig>X;j+TI2_cu_PqNPd%pGmw?VHyp3Bg4 zt9e=4RQj;2qC_-AR`A$n$nUgv)d(sa#CcqJ|%Sp(hTbpoO0JBg*u>dF6guSo1 zU))nyd~*YK;P)>zGLzG`1$qG0tnnKk7o%PiOhPE{3iO;#KD0#0kdwCQ6C>#69NwQ2 zBI%M$42+J-vq3ItpREhk#sMz^UO*khWKpSWR?A}+!;RV+aL9cu1z|xA-{-NRX0GUR zkP7fOa|D(;^>R$64vfs*uixyQf_YXZv17m(F1h3i866q0u>Z38Is5CR^b-L4xml=0(s}BE_I^N>>-=K|{tXA;hr~KI*~8jAa-_ z+=;Cqi4ySwnn5mSS0^oD7680r)ua?D-%DQ4IX?j$2e#3F*E4d-rl@o%?gU1EA4a4 zxNv{UKr8p7m{>dRS`4P#K|QyLy-2k{SwGC}ptwdX?^)WT}m$y&bke%?}E(+7W-N zDmh`2?WbpGg*L6i$+Y7N4p%3qVy-XhE0T%|!bw3*N5kLelgt~7@%csjqhR56^N?V? zJEei&wkbE^VbmF5Mt9-AucD=P-}2U8H>tqJi2X7!^AsXkChMO;2zIZR`nDA0I$kti~BoI=2co<%oki*1@(l zzdsh`PyB61skIxO7m8}uOO`DFToN^G0QpsX8T&XJgiAK!uj3R=nj-t*yNyj(Y8j#Z zJkrMNVj?5#DtrHOJ?&WQyz>#fKB524>NS9$M4HF5!j8Zno4MqL?# z^49@K$s|e)gTUGao(G1`R6xHaQ%1n&h#l2@dJ&%hj6zAa;pC~5fcu01qOQIe{amcp z;^pr-?5g2p9pznw9+S(A&}Yd_4s<}H|GhJ$0|0y?5n~TTAS=o%B0#xP{4}2#jYQ6E z!E4W0`xa>?oM`$< z;|Np;-hb&MDE?X-v3Btdc(uuRW`g>2S9=9 zrZFa|wOT8Rue|8T$lq&ERD0~x=7GVZA3y9;pPrd|aY}7$GgDpNQ4M$TgEt?wt2f06 z@HF>JO}fOoQg#H`Y28tN!!jHH;43%MeI%I{0PZf*Jw!+6OU8 z`&}j%_g15{+t;P_ioE?Ja#cUiYDEXuX=}q#S&Ab5gjn`IN8~T@C=bCS0Q4;3yC5rU z)y2sTL!O#vgIt^agIDS@(m-4pY}!4mmB$mU{ zw2XyQS-kC%Tfcrk@Myf7J^ee6 z9&1;j3)b6Z^H~7eicIN}7*Fl*_8)O%&bd3&w%dG>5gagjmr3ZX6#Jr+eYvxw`-Jhg${*34g@ z3yL2Nhp4C_zctXyU{c+sO^w|~rW4LmFjt7XD+t_I`hcLyi)8=uv=u^i_2F2xh@>cZ zyM-zm33N8;NgE6^6(}1&6%zqclHYihij1^-2#!~!k-PS%<1a;Y=5<0z4Xj4di5$B0 zU0BAS*#JQ}Sjpp0;v%|GVVbK)FDcZHX?8P=`K>hXn*qd9`7DOp{sdHrbte;mYkb$u zvI?N|?NMq>MiE=NQK6kU<|{$lnXy{{j82hNq39w+_#XdP0HXwR2Qh6;+mhG#;p|(G zJmGe(xsLa6{>VEA0hBWJt38>yJLC4jz$M?NA52;L*t`}KOg_8zCz9_L9BRD24%N^N z4>V5oQ)0U&7J_=&b^s_nR;K}T+q}6OeeuuCE@VY<-i6uo)U6L)9e6)_`@h-m+SGp`a==YeUN{4dgCJUbJ`9oiSfSN8~+p>R8 zT_FG(S_Po>yfNrzZ=~>j^*tR`idn=oBkUhCrO;Xu*_j`kZL?+&81ZGtf`8Ko84Tosv&r{{MldZGeEkVQG>|ySUy^^d85583G*>n`RlwDZKDZI zT1pD70{xT`G1Os&=5^MvJ!i9O)koha{U-02s|dyzEf%WC?8dV;h-mWLlm7ChPIvLH zC$(mWWYIW%BZ$e#EHO@qRno-$Gg7R3K3Q6->?9O#` zQOXgqZ2CT8P$F>+vCViUlk(Z8TBK(kM)FdLZb~U<_j^g+F96)sU0YcqAL13`|J zfxOujCedJ5W3?msaQB#KliUWg=PQeqQz}xIQ^0-pm|A#r_i;) z~wT{>PXjiQHO z5I1Z7Q;oNT0!XW^lC{&MRuq!_^KV+a`M3EG&rkM=*XFk-Hs`U+cW{13Zu-pP=aEpO zyn-vP95({TNW)sZip=_|r}0-z_%mk!n{TXY3C8vNFqU^`FuKv&p)T)fXqF!X9i%B% z9Rcn~8M$XDO}%5Z2sK&lxX>Wo>PHKlYzR|t``$y(-5?_2mPdyg&s09TlHJ%!zb99K zJ$vI~$qQ=7l+5wq2S~$dxn8@~bY&^)F*yPSza>-y<@#IICs2cYR?v=IRd@kcQiG`O-ogrr7V+%;Dqhx_p{gCXze0soozO@1z|LW8UMY znS`6~WMOCnj;iZUb%tzb95(c8Ot^yTJ40lCG~x@G*>L)-M6&l?YQ=(0y(4KKWkTT{u96Vl+0?%=a8>jIXTXH=p;P*ta|IE&;O z@%c>sI<&xI9=^s;yD*aa?fMo<#;J49r74Nqcd<5dNy!fHKyir%2yMTb;ttYltNo#L zJ5e?OehY1S4nlwf-mM|&p+WgoB`1*-Mo@hCZ3Kza2X_RQL zxQi_*-Ww(qI8U3_g)v2Cr<6?|Z!W@awIl8kLLxgzDt@|TFco4P(i{5QMzot|(zz6? zqw6e-Fx826R<;~9z!e4w>uM2O(xqCX>vuAIk~kha@0AS7ASw9ETDmqoED!mb;~MWJ z$8Q24rBFagbh!+NcWmeYlrqHwDC0`#IQ?z}a7mM2VMM?<(OA11pKtBZS+O_N zO6zVc@g|A@v{sP|u#PIBMg8{z+;D?$k~b=;)jHS#p;}ya=6GaPD3OZNFJ7TD1)M5% zGij?EuW89-{;wLqYn+_SYU8PL*v}fUhgbH+-%m(bT9;vc_snPjtX9zt4Dv6Kz{>H7 zW+2=^VPWs2NhD-bsU~igWW&m$+ z)RZA60MuI11trNDXB`tb>w4{*kmkXddSS08ITV-;^*>F@m!fZ7iprpM7uY&R?D9 z^t{1m+tZx4x|P-|MvS0$?g;9s3DEzxb~v6Quoi)p3NRW2j~`KRKWPGV>lmp6?L84< zcRgS3$*aS=i0)KEH|H+ub%k$PXQ{k6Th?0PvA_^BZTDt~e0qVjX>waIrCQMr%E!Md z>`X92nZom|HrxX`8uL`NAr!2D8`;T>Plo>(35IKV)oG_39d0@DD+yt^1$!zvro3sX zW2m+;;0H1NUhA#!;N33@7z%bZaU3*&4Ss6{@fB8J@MLYc&$sRvd4MIr81Q4IvNM{2 z%P()G%{LC--_0_oWb^Q$vi)y)JaG7&`Y=o}y^7`okLtC$q7dJ$&d53pMX@0(hN1RL zK<+Z2%Dr0fuXsqyP2q?sKJ3Cik|jRFtF5>P5JoJ&aU13d-Ru4toq<$+WI+uhAH!4a z!#e_U%+X>P-&hpP1u$>1jsrWP7ogwZ@&@lYioK*cAvoEYZ8l}doWV}C~i1`#)JOr?V|$U{%C-mNI( zMaPKonR3;K*t!p9S|GY<{Pxerg&n<~nq+z^*F)o_a$8W_NbuW;n`LDWTDAwIjB$RR zpwrYAK5Yio9%Xp;qkF}&$g5);|#_eiN`HQ^l9Hvy%NYW2SNjP@h|UOUzL z5?2S|hsA`tZ{NdQ2%7+11|TS3v8s$=;MiU=+M0>Uy(5D&_OhI1-=+5TOXGBF-n5?O zCTY;!!%I?nfUyzeg*yF;z8=dBJAHm8FEq3sODa?ZnK4N>v{*GexhZ+?@6?t;^Nbp1 z^og5!j+Q2ewgw9x-`L_SDk>>5H8tDdejBZ2O_m$TPptRJjD;F(B%G0}!wT&Ub^0ZUq0j<+Xh0ABk{rbP?}9)~B> z`mPHfbRL(sRjsPTu~HPz=Vn;*e%)HH9zJ}?Ozn9G2W85{pM`SCyO6n@J$N^KraE+X z77mDgJT0lhWSF$P#r(ruv$C?{w9~mY^&>w(hB(+&+J}7oLGde-A6=a83ijWjFd~Ui zV2AE5{>nZx_l zCBk&e=FC8UN*aX%QZgpBKDAKYq%}_zX{Pd)F7DN2xcHH;&?VgvyHN1PWde+rFE$E@!1b;=J6?PS2$>lTI5xxjm==jHw|xyn<_!55hE z$(eVvgvJ-zi<}vF?yXLx+vMnZl*)!&T0PsW#wf@>0ke)I>5_85pmVe`J_q+JiLZ!J zT`7~o7XY+2$pFiCPw?Jl4ctIM-lZo2<7}aD)^X}{DjYN)Pv+3MAfvtvFq|r32k@Ga zVx#}31K^tFHS}XN!-D{HI$1D`YN#1%^!p3o5?0elmUY&5u1!g<*WHg;?cB^NF<-RIDjl3g zVFyy~M|O;#Ab^!z?_1skSY)cpqjQ3okQ>LB@=a_br%kGHMw1bi|@r`XQ+T-*rS9iWP34Gex^VxuTl zPsR6q*V=)}&j z%^VI?#J>&x1F`WtTCSuS&!}w)vk}mpOfMGzWG?+#LJ(8&Y8yZzvNjVmP}@Vr#Qcno zmvgvoRca5kP37h(*2N_9if_J~Zr3Ms&4nl^h*tvHq(2=SEDrnFug*(|avALXem4hl0H%vjHwIlJ_=!o#P_w zeK2@$7(XWn1E$vO*ew3Lf7{Wlc1@#YY9#*Hi_Ei32tr?cf)WuZ;#v^5PRA4y;dg4E z=rt79MP%|2z-6jJz)i_JV^Xz35U#utRR9vO0Uk^pQx9W-_(k!&?|YPjXgQ$`SCt>5 zc7iE34ySM3dgn!Wa(mE^SNG?auV?I!W;{MW7DJes^356`f{C~@ieCB?h!5=YN@Vv+ zOXr`U)99eExaFv|s<{BqeGoM1Mb5mfJ(1)e*1xsc64&QN53yLix_D`FI5SWXkXcb| zpfFrxdl2aEZSZ!7dGG1^t73|9dUL5#B>3LFc1K1KROa(>)G>D*#F-_Tyj`sYy4ZD7 zP|Ixx#L>%o1QV++wqsO%Ck4%zf*z|Ou6B#w!=Sy?>$b1T>>x}g0Ny%fpoSD6eglQ; zcgttJq_kB)I0KJr{n$lL6$3XfwDRRn*lgaCGvbfDjG1jI=NtK7O}e!!s|l?(4ru9W z_QBqqMJ%(|IrzHF1gsPp#SReuGpW^;XEVS?OnD--#Eq*?w}syZowLF!Ot5D)Jns3F z*+`E~{FYj5;x4CH5!+;{7%otgD)cI%R!9j#>&l7cfB)(Xv_6~am(hoav!A@(Vv4FDhbE6mfwcUkPHnv8dfifLy>WH@H;9FrVljA-%3o_`iZfh z$dNcpF!nxVMyj?hRo9o_zOB~{T;W%-56Q)ZuXKMQ!>rtP!9f%v&5@sa>qi+t5NgVj zlvGgR&VrtNjZp!u)6Oluuc<&PH@pmbF0Im9xR{u@lY?eNKpiN$M3b~k$9X@8TiLl= zXjtK2JDC;HGHFZEHE~a;Nb_|NFxUT5u#N&AORb=VO@jJ{Z{OT-t|Ng%qdAW5Gyq1_ zD<7n*gp{Me+|Bog`&$m)JbnE6G>rMPcUBpb`3pSFvq`a;iROH!R5sC`cI&YFjVkeZ zmErAVx*4K^Coa-;(B{U#rl+L+}+OrjjzJ$G)HbA{FIDyMQ$XVU3K zE~3R>0B&Ws+UwdRZfcv{zXEb6piwK%vXiid2DwzJqf|J>EWyJUw<@YS{ihdn()V+} zZT9s!S)Vq9e|I~>yUaAlN~wZHDyqnVSmtXnuZv+z_|@&)i=4mNCvy6ZfOka)l=7>u zZ(jS1Y>i)6CgFcI7Q1)0kv@>_!G?gaAEo4SDr!YBHT-z^lG+Y`&K<9=%rAz6?2o1H zZpQiAT=|Vej8mbvTBnVp?6ssQu4XUwDbOMhTV+_CQkpmcK}2Oxgx^|C%>MOO@&6Io7wY>ZE2<9X&HhuoMYafnU@{Oi-5MY3;QY z1fWG6@wVlix^+BZzQTc)0;V``>AsNiZJBVdNl(}%!g3$jQG?OTyXvd1q!6UMe0u4m z{Zds>0Z(0MU4RZA3pH*{x@Mpq)W_bNHCzxAj>1ztK>C{F9xM|;E+IocD)&0B@{zM&%p)=M|XuOU>)V9{M>1Vxs zKem^(y2+hXAQgJ>omLWmi}+Or^_&?%ZAGQv0dF_s+X)W7s^^X`iU#ZQYF)zw$E=|N zu95a>{YiDn0VghiQ99mnU%j{~{d1BtW&%D-O>R{HQeY;0^X_ianjU2x0GJGbE;&G~ zidq{6F5TX2t^2c{HoKv5d(XJ|x)}8vo!>L^mUT4^wT2oOl`1!v^WzUp-fi8KY+KnD zq1K?`m~pc%G#`=-#H_1uNdn!Aoe;TG$Q@&7 zLoqJLncYHn2-o7~XC31WW2nVfud=syHZAQomLBEPmP@5eq0f$|0Ddd&a#Rdx2jfKE zy#d9Xx&ut6fz*$(Ra%Nt*zdO0=9!HDUSmGRe0XzZ{@njzGxh88&F0GGPpI!NMj@X` zlw5nO{thyzjJz5zWqDizw1lNIQ9z#CQW1=jsiT`U^{p`U3#;qElOH*Idq9~~9chLc zp_522{kRApx^{ihw9F5yU`(^Jq(>%v%O0dhiajV@oJnhz*A4~86~*Y0P++9p2Di>n zxT$Br1If54)b8i*pC`(evx=5-+Xm>VSON?HT~aR5q{j>xS4|vM5T9aaFOM7M9&;>t zdpYdRx97{M@x;21uDe5xbJrst%?@vB-uCQ9S)2D&wj~hU5{}Y4)l3jmH?RZ0UNcB& zATrB&^0pG0hm7^~jK?0D_TTCpdkkLHD7o=3PaCf%hs?|1Ggmj^ZNDwR0H}LtCp4@@ z*)M6C(jX8qsNmCOou~mlwMrI+A1pC-cdS-O<9L;ic~w*w{-}dl#oWlsncsJ#Y-g`O zzGW{X_mnZw4KpiYCq)BnWA!}Ik*AICnxm7vA~s&fozv`g3)Z&zxAq8>5z(;?hu z2kWVKlj+wRaztw#mpa08q&(Up;-=dXZi&I2$? zs{95el}8)*1keTEQfl!T!CV3$y;a0F<6kCXJU z$+O=#9Ggz@2mX8_FB{~jKz0Q{3tMso2}mrO{({?#0>j8^d`1+J@8h#q~;yz99|mfU0;BYHo%O zJvvKt0k)352dAR_H__ebD-=vhPdhsHtqWUgNhJ00tt1c9rUxwN0B2lZueT0Okc&;d zteIwE!4)!n*i~7Vr)p@wUnbj+{3x`$U98dN92Hzi50+9eoDd>6CG49pnV{_MyD%2> zr)#KSEJ3_qsU#lnD2Rvk^uvAlkG<2({Ob50DN}@TrH?kjjR}5xmHHZ)73lw%7@i{+ z?9jv{&?TESkgfoEudRwkiUP&;8)KgV^D6p|B{M#+c<5$xfhpzS7#QyoKHYwZ814pMN z_j^?gZI9*&zNU#`xFFLy4&NI^b5#hF(C$LlK}SHd(2dTC+i7H_6z=-DX!L76p2av!Qn7sxbe? zLi^e~b$!{I&rA)kg*LUP$y~A9-JG6S|~Mpqhh`?Om9S86}{)k zU=R{wX1m)0pNL5l&r=Wn-n;r5 zUczKI@E6j1Q6rh|Sa~?3FjbRxI_|fEK`rgC4cJ}Y?TE1XhI^WAZx};v2R~<9;#GAa|lVXET$GGkeDk^lURWTR0ML#m z;Xa2&UCpadQYa$-_75xBVTcy2Nu6E&ktsmx?U_T9TWs$uh0Slc8T@Ek{}*Jmhm1$d zDf*A#O2^i=`9o;F8Vw}n)YT>%d2NbiMF%Gk`Hi>rFa=9p0-if{dK;Mn_+f~3_fxN$ zG{6*eOE|0md0(y=wV^=b{hX}boH#d4#`)xFBF}Odbmie7KdBjdT1u~5vJ@$3-s8Dp zZWaL7gk%0%8?MCZeO1ZfRu~+5&!5;A8YI0#o`bN3a^us%K%#gFCfXDo=h@Z3!57(x z77$Z0$g1`^XL7!uL25(_in6UpB9`pGJmx`EtI;tHX;IWaVGa zcbTHSe6rNK3Cr-hv-s!Ku)t6%fla~iGXeT|)EKcETe#%5bW7&6PFo4r^7>4a>5`rU z^0lP=JdJlqg#~(NQ#2fB!%;Ke`x;$%dFdes*0IW1Vl^MNk(+0;(d@&RS@aLO<|em; z+q?&E!NBOp5^zc7o|w94D`E21t^UGo;$7P2-ZNop}gLe*Ta-zqE>)5sX)m_dkkFM_PrXv zOyvJ^ZGC({Cym|48;e|+>$mS@43k=&Ty=x;a;dpfg!ik}iBHXF=?*HVX4QZq`Qh5@ zNGy@v7Cl^R4xRp|qd@~9C}?99;WyPc$>_uLsHeK5yhsSPvP>F^S}<%GOV9UPjyT;! zs^!E5wqjC#6H_j)9=ERq<#zS9mA({VNFEStZ8EoI%W^16jAI~5%)+V4#XI<1LUmvo z+6DRW1xg5 zgL+tTY%F<%sr_=9w-3Nw9j>0EMh(n+2jwr}5_w5;Dn?u@35m-975cg|(Ez?{)#9FM z3sw1J%o2lgJ=x~`cI#sSTxuBLK(lGR4c z+gIO$Rq|%C_Q$(33+m<(OfZxCFzU=F@iRP zd{!{GgZkIsXv*g{?QX#mfMQwruj+4e=_D zE|=ixaF0u+)0#voS}pX`QI46k?4+! zv@K$prXZ{i6Yzrp^I6gigIl&oDb|x81*O zLIm58&WL%E>mVVQn)NIpKY7jd~0dm{&397yE=(}Tzk0G=yNW(|3O@PmwfK46c|2KIKw&CF}vQ+O(}cHIZXOVs~S z=lwJ_JbYk~^JInAA_T$Z8sLlq`v+QJrdun?=iv!}7qaqm5WNu(IHfe1S|bTIGeR}Z zX;RD*GPZAQ7W5bK_V0hgLfRJf4Rl~jp< zScg;FuqVuKc{2`S=Z12*QGC9k00)#*EQRX;;2aPdG}>0KiqRPcI@_Dk@9q96uAu=v#;f(IXaD*U z`)!w=4;dUhb!i(#;1u%rlX#Gmu6YSXqqVDDcd^_!fMTJV}VlMG*V&+J}hs{n-wk+gfZUFz9iG zeHtjKst2X=4Qw)p`KVZ#SqD9Ll&(i7g#}y`jB`!V*%m-J@2>Navd_pIA7BYl(&?MKsgq?P6q%!TdizOx-8(YCs3!G z7S0osPPzsF*a03p#tuMNa+S*&DF$}-^BG5fudfUr%)s{7B`#*4+Qi{Z)?2^1wqcqd zGeyhR!%BlG+9$sDtPvW8laf}&F9$?NzyaROu>G27FFDOzYo{5|6Cmo1!%@Y4{tHtg z>1a&g^4Q}?YRCwTNnymy&hOJINR|Q*_FoLc=zBw@-Hn2)E{+n2esEV@$I>{1x7nWr zv{~}d{!b;qMHa2r`-3f`-MFHutaK&-OuQGGR_V%yXF7Pzo^KoFp$GxS(Ow%4%bZx6`!GziTXyIF#l|9lsM04CAC9XNJW`P+ zt{%fo;~TX6+3re1{A?LXO(szpmfln){|=Z@W2j^8tz=wu&VgdjII3AjWzN zWtTiTkWNokFcb=1A-U}~=@s(`t=!2BSz*^c@14#>RIW1Ji`C7sbDoBS{8Z*B^JX6h zI?)CvqwZ3%P_1Cpo;b)7$*e-g!0XZ~{2Mtigm@S6(Sh%18uSi6+e~Ju!zdv9%P7D5 zn>ytZI645&6&wjL#tJ((4?sGmxc)%?c#~mfQ?2&5#{XYqPMYbn$tFwMl74q>&&^@e zO`jOG&Lq*bB$iU9--E@>L0f|RlEnQ5Ugw)id2b2A{32AYK>@0uO6i-yAb_d?-&d!S zjc^VsEnjN0-?-v~yG^ilOQlzvk~I?#SJkC6ed5-i(3=CqUaQCho318wiDoca2`t0? z(lnc4Ple;k%br(FAgMwd-tz9+J&0~b1d)mcAuMXM?2qhOypWVx{OtaWdV=?^HBaqE zAe=p{uWZI@UWbeagjp^~bs@Q#HF}=-|Kxtv9@NNL%h+Sm-qNt}7 zHOHV&W|#EN(&F3@>q$g3CxCZz_{$!VF>0X*9hLIic4d0~+yPy_8XvD0c@4neGj8(U zcwjxG5xZ7*t@9zU-&tz0!EFo1oc;;pAkdSVBH`PSU$kO`FUw`3CBm4BG|1ihD7V5l z&@@a6Rx=+ap*l5WeIx+_^qdgahK>R`9I0R@_LL~!X?6i#E0yjRAsmA@lhVGvK1DGd z(!m48u>@YlauxF-&A`Q;`rr9?-XwS7`|6L?%jNT(%^B0bjlHatHFG$v3DCu<@$M!x zm(kJurpu||qOFM^n5%`MT92EAV3#ESHBS4GoKOYJ&z@g3ipK7)uI~<+084J$o+Q?H zBVC`MX8$g02o(aB$)VK^v&*2|AlWetZsgC(eo-l5dJ=aM+adl`+ZbDCJvI4dIjpPG zQDw^wU(oDA{soU9YG#_@PUaOwuO}**zDy<%_Gr2xksl#XV{Oq2rhxX{{9d;Wh^>v5 z1@HjgJ9X`mOeVH)cM42o)BRQi7WD)&0KICeqHc*Y1(w@i?B#8l98ULtjE@IWHNJl2 zTKWfTA0|!~-!@K*?99r)O<1JplbA$j)+>PjK(Lq7jnxL`v%0+3Y!!yA+g@nIaOD=5 zkGv}%s7ud` zewRkF5_)^T-?3C#XDa)Li?{)rf2C<5We18JkqJu-%ZQXINT{H|M9kg|MG|XWUylay zzHi|^WEAgcW$1;`8E+H`-kLRf8>|Oj6N-{!MSmP_3_f(!T9aTx00IG;Q_U(BS9UdUr z0w&9#tS``h4!M`w3(5IJ&-BPzyb0(uBE=}-@AWAR;Zsx^%(KI3PQZ9!!<{9tbdX<9N;D> zB_}8`F)`ZD)#2aX+Rdy;V36HH8x6>X6={4QP@4~w_pCLn#4SW#a2Gh_ElAt z$|a70zounR-g`{@edy-m+Nd5}o%QqWRhw&j`<}i`AaQpP1Nh^5r7J&qN@P=5c9+J; z4+xyWmVPg%@qCa}#&=WRw9%K7|DJALEN{~M-kQaO8*JLtQTQ+Ro+CzW=7!?48!Y$nBOtU_G9 zfay02T!LYvuK}!H6lS_T)75W??pgR>G9!DeN+PlWoMPQz1yVl8)~oYL;{la0VINAX z-8)r!EaZBVw`}2(=JvcUEMpc{RQ!_kOqKWI0c1+)%}8TThr9kSXi&+BVa&H-?&$8f zQq4mKC(Y`Pq!6MPkSkepnq6nYHe%&%2NSeP$qBOZb}9aDeoDY5_m}Y=ZlfXjK4LmM zj+>3G9C*p2pfl^3nWp%g<)?GnPf!8cTJ>h_V@i;oOiI)1PoF;OAe+wZ3!qowiiw}( zoD>Cq&n{1L67tzJBz(}$t$$v-v_3d9w9HcahUc1|Ae;0NY0a-X-hSAaE+7V|*O&05 zu=TD~KTWoDH>W;_S=$$-?4rjAFi4-Ko43wjx)`3SK%HCgU?0&%dlNY)7DSL35MPVd zGl8!vf9i}EK1q;N`%gSciC0?5FCXUQvv>Rb8EVk`40x z<_IX}a#skii2$6)ZK!eqdwtqF$sOXGoB#>MH3>XCUOR>U5pRLSle1D@Z#_!a!}6xU z*j32aZOrKa1B<-n?G2L&eRlKu?0kE79(?gO%(H1mz8FW}b$N{ZaE8RUc9xYgzIPj+H7fpZv5Hk^ z(KZuu!)_#rNdOstSw+GRR&9;=E3d@h_Ed=;fnde8`}hI6h^9#RD1%F%$Qz}>W5_bg zv_&B@A+&7aIXin$cqwSTL)%X%;od9dP|P(q0Ooez4koPkL(64 zq4fniHx^Up)eYqc@*g%ru1Y9F{21P4@y{~)bjOvIPe4Zzgdv4AIn?{dI;yK#cxJ3B z5DQ=S1WDwc9aCb|ps0G$AY!x)EiH|uo@J~pe1PxP-dy0E7%HQz7?fgx`0PqmZ z002Hqt#pkriLAT9bpI4}SAan=g2MrT@2eVJ6QvlqWB!=<{*MEvsV|3LovjSr@_(~G z{>Nad$>YOA>A@Z1BH++cTIJqT_1h3`TSCOn=WMw^H+*yG+YoBcH5gz?a$z47TwtMy~5`sZh2KIa9SMNui`A)&Pv@y|DiISke z6PtXmbYniRr#*gYwK;XXt~bE(7@lj1K{aZkt_RZ!XURsbi`WgMXCnQ3P2UV$PFuCi zB&yOVBvdocu{pKA7-zHm&A3MgJ`_n8zf(|lP3gQ3ehl-$H!$2_X%d`M1zr+rl2EIb zBBcJmVrqe;HqAn8_3qi&HkbO@>B=wLO*2~&BebuHlLUz#Y{vXY6{C~%uuQu!4s9~P zC#q2wH*B8JFIVM#=jei6DJqEu<(->o6kzy+|17Np6vc79;$)OTz% zq$PJU$43i!$EvNR0o&n@;B>haOP5goP@o*vx-^K8x3aTN$USNh!)Xtyj=HlV217p3^uZ8ZmE zJBVatM9qW>4^bdI(NiWs%w}^N4*Z6;kGTN=K5I4fh%gD_7b$z%^`98)$p{2)VE{NF zQNX0CjAkpvz{b6Md$aTHJX|pj%faWn^)ypIYGt~4*C@&!Uq3P-`!1++dP1nS1K*u+hw!g@)DQ_sB;%c=d+p- z%S^l2?QMG%*VWd?@JDO~`TL6HhefQnUB`9_KY=dZo6OCOY9AIE#|24`j;OU{7!B}{eIhUVJtzQ=GEMI&ODdeE@|5PpxU zW1Dl3)8!UY&bO5$orAU2E0au#1$(wS!?s#zf&t2&eeoII=Ul8@u){c+ISQ$kWa`O{ zSnemN+T-T@fqmU@Zm2+7W{m>gYBl=ECUemLP>zh(FDmy13!m^kMF5fkp>Y5v(56Y6 zluir5ay^g6`bBye6EPIyU(b0}L@pwqt3NI?Kved1q)|x3WSYe$h?9JnQ z_Ej}FPwVis`-ccPVrhlc7tnhqP`Ayc{`!o$YrBkPe@?7F8|@E#&b&p|ME!Sh7WD>X z*2u+N#2c06RMBmgQmtvs`(h1+X17-NYmaf$Xup!MyQ+dZB)s=ht&sERqQv-0UzL#t zmiX97pP(SSwM{#h(pkvz^?3mia?H2XLYF+u#uv0=s?f#DDECFGG;ND1dI7#F)nbns z9Q0pSl-Ezu!2ljuo}jV8vH%WH1h9fmVydes8w#*VfeBlC8y^z%M#Na+%&Fq5n{IDca|1I7nXDY2WX=Syi6ykE7Uv=d-74AE`Ndb$L7r!iU^Sun^ zQ~`YODqH4~d!L-UD|EzIHYd|ao^}d3JwkA^;nJ*aI)1HhAH{p4jAOE)ZmXxQHbE7><5oURO5ip7{yg&y@TD4Rr!NqcN-Mc~v zekU=XiDQz@wlp#V0D>_e|HXIWmWM0u@&32og8H5QzFz^)i@n0g)+wZItrN!v52ts)$Y-4`ufvR6odT)e` zRQ;}-J|jd4I1v?1*sMw8S{NfCLO|MT^wovR7PNpi=}cX@KQUw`WX@-1UMoR?iAMNjx z?!1*_KB^6FC^DYyHBWr?Yj=M8sXsp?SBLESP7-#ZR{F^}=Y}~?mKvG zYkt}~ADX}*p#iKi$qbC`7l^bv{Um`)XgJrH6>(zzNONPkbM|9}-fjx_5?O7s9u<62 zwG#-UPlEsZ6~I+6F*H|W9R~7?w$hZ0V+E#$CyOr4XovS#ve~OWftkpHRdtfAavH+AWp;bz-2P7zA*Et3y#tsp_%)R3dEXhX;<$;d6s%>^uGyDXJ=CY0Dy`D00000 z0EjIB015yA08ifcT@^Fd-`U{Z*4)|M*V)C2J5>Pw@6Hqxp@vyZdn~W6(Lb{y1O&(mtks*T8-lZ zCTmOOsC4EvOhQwkuS|jpxcv&W{fFqYks;yRiw6efeA`_U+n;FweoO6c4$^@qlk0jn zN*C+`NWRs&uKCO|4ob9pQ`@EoZQaG>~(1^NH zjaK)oHE+$k0U3%!JRitB89QBuv>07iHduj3<|HEnv90NQ3c z{AkSba}E@=Otv^j#o0>V6$HJIhTpiL)A^+Z*Uh^i4S3NiBHh;(ho+n8dGx(&N3d=F zfq*x9sv$nPs%!~U+)Sso$g@F04!UdF#G*pQH#WttYi0Qz0|5Ymn^Fm3c6D%Xjeat{ zRCoY>D=ldc(tw+-)BS{7vNTe^3IxCyZrWJB9P%U>D9<@RMDK3pEY}Tk-Nf+qyf4}| zce-Ag%0Tlr4)=hW;G_V2F4uFwaqsd~S|#>aRKd#ST!m>o&=(d|%-o19C?nnb$zw`J zvy|@{=}EGlzLNs=Jt0Mn5sfa24+WX(M;U;roBaxEfjiyM`wG3gsL^#{=Sps0xlXeE zy#=l6n{uef1n8fd|2uTOxP|p+JnoC9x_oq0gvHL(p>i|06x3j%4YT4&n=(%LZ^}|W z=noA64M-&S8ioe|zAGK$lC%*J{xk7zpF#U&0T=)d&^4gODwBA5DF$vo@X-F%uV*cX z>!HNp2Y+48`)JNOH0SSm7fmh~)q7vl&I<(IEkxG<8TGzH6=i2-^hpKU)KtZe^gUGXv;_t726p z8-NrAmY)N8E1TiHyPv1-Z)d$TGHqX(iuW_(X?oRMvPJV?2UR$o`dwv@Qk8P!Pk~?l z7{bvS1B@WB<}!JXG_ox!zsE{h$5x>R%+SVHblruMa74s+QWSt#od--z`%GF{F16nz z5vEPs-XDnu!_^aRWWO2KEM(BlO>;$@aKzf=U{*z~x|C*a>*LqXD-q9mBfO(Rvo2J( zdcLD=@}Z~@O$&~c)@A z={c@Py;Uec;afUH0kncOHwCV=k(0jlv#zM%jRs^3*l)2NaS}67U6xS%&6m&I*qN{c zC1G!3#R)xoF5Mz~InF6{X4&3t(XFkBh9Wzu48#bien?F$F9x2(4bQHkHkWE;S>Cfd z1<)>%EG2px0V;)CUX{%b+D$+8d)$*gtE+`pHG|uf6Q^`H4x(TfRJ|=rxDdVP^&xxLNpaFj4nG)ex?7Wb1}5ad98&T{YE48(g{IpbBTZ=e z>Yjs-wj$%+~59yz^7s7gT8wBApgGs9xkmIjVwY^Ym33R!&^ zt3x;)K`#p4TOH#ObKOCGvpB5Rx9E?B1Ejq`qpPCMI|@Ye%X`O+yg^TIf81^->DTOE zpB=90?2OdMnJ_iXt;u2&Nr>0>m?w#n=+iAU9&_7SWNonuP&v?6v&B(|#Wr4E47M;u z^#2dumWZ}%sx3*5N&>qgyhO(N%j6RNBtI+GDt%^0I%6~(;9ue4>eyW{M?coLn1DOn zWi-X`Vz>*u%r8F|vLY!HOuIfhf}I8s;=D7b-@w`)HX8TzZ=)bVSU{8`p#sRq@=eTE zGkWSTPC*?%k@&3Y_x3CR-Yacoj~E@qnav4z-`px%SV5pS0L2VIx4!dio@8KsY4F2W zDs{Xdsjm(CVn6Qw->|*>r@z_PzIj(nnO?2Brmly($j?*Gk>!E!-dr>bo%JveeLSU~ z$?y{}4n=6lIe{vBTV41{4FESj0T=_=zMLW|R8VT)G#OmxYG}__L>IlDBNLS;e6UQ% zRgF)vpfJv-(XH_9qK(m3Ug?X50>@&-vhy^YiFjqH87NO3C=n^e3I)B&8rsM>hecUR zJBR^KBmi|IAvqm)3cm;U05S)n*izfLyvwfP1AZ!HU{wHOU_PsP?>CJBSP1}KZ*1j? z(Q}4yioqGav|+wxZn@Bc<#h7(WwG=h@wX|BTtb)9Lb&RaXC&{z^`7cU9%WyL!wJ{9 z4TPPfdF~LzZj++f6ip-mfE!sH(aW{Rwh~U7z=wV#vgClQA zl&H8TCJOf(F%v=s#E+n->*)`2xPI1d2T1YAP!^faeY-!~W-)XDetUIks{jz7{IC|9Z&yr( z1B@AfiHTJ)HiiPVaVcl}oQ&k(>p#-$zW6lgm49|O&!^7Q#(A~4Y4$!Et9OeWjK(Ai zXd(mixztLH-_xXd^``QX&JFE50EWNbtq%ZeHR8C$EsTBce`1+t-*UA>l%hAX-lxFi zSnPp_On8PYdqhe8J<$LSi*r+Qf#`TB2RE<61OXC1z?p)cdCrhps_j&lyQ_sbicJM} zZHJ1sNl@w`X#7$DgwH{e$h!H%{akqGZ+2>6V3C_ZeB^CRSe zB>XTLV7)obn7z$`CQN%1?aLKiLsDSl!0{tlGoRh|cKwwb>;BpOOLl{X{rsWW6Y~i( zJP`tr$d}UaI>%OtY{;NF1<;WN^x067E`a(S!9QmqD!PGV7#K+`d)J{-+8Rzww1B?* zo$`Iv?7tv}G7SVQ3+=Q0F_ie>Xh_FFRTe2cMcMCRdBChkB(Ek};?kk%BR+Vo#e{M* z|Ecl8kJZ>3IR#E@^p2D1!b@HmzgqM5a^+Hv$zdm`M0AEmLzw8j(7q`PYsus#qyrfG z)&SnCE#(rC7Ia_0Q(FXkCNaFc$3_!4IJ^;PW2}mP$Y+2|J@jf1w{mVKtAho(jx|0Q zo-5fnKVTd&ElD*N6?Oatc2V98 zJo%F`A70#1s})2*Cvv}aJaiH_!xi3x1;)_SMduN|Kf6BcYNHw z10EXUm+Ipe&TO0BJwX#49o;Vr(`Ic92QUGCdv)#-DFQ_}P#&Jg(g%AM;XF73ph2&V zx+;mD6a})o!}o0TR~9|1*9MJ2ICpE>XZ@q?!Db^tHd-GSF|5`dCstt}x|#=Y~9SeZl1|bL6~J=M@I;cpye> z*&PtU-El`VG33iL?6R!=>UJByq%F55xb#&G5zap$sUCpA1Rh)Ucn>0&#G5e_$n|NB zG3NwM#kCHz5 zvRT-ZV~*Xw$M!rmxKU>0E2>5)5dEf~wW~I0zqIt`!aYdB&*C2UHhnjEar2@vhmLH^ z%&y;5_!J#ME4^Cp%IC&!Vmvri#sg$$m0W|_7<9r^Co zv&=Y@oq6cjw}&jP5R@n0kxXYjwM;fRER9?`9nI1S(aIxU3h$?cO6V-xYL>X8fI_LQ zEO6cI&lyl`=n`#`l+e5$jR2<1u_#yQ%mEk%i74m{@&)QjTkLwbAHx(3bQf1@i-&!{ zsYHaK_Ks||J}#yraOqj~(AUN^bZ_?uzU;#73^DB_Ey!zigCPk!63t0snyS^(DRB)@ z3pW#$&=`ivf?8+*K8tnYh}595-0UmQ4sn=tHo}zBr-kHqFBuONe?E$00)t(c>Yh#Cg6OKra*fYX$A{>_!iY~ zf@^I>=IcZL(S8j>Np@?~_|6*nPD*nhRk|7De&e}sht>?m(?F5=w{V$r$Lqd3><(9Yy-g>*2@-^j(mI1w z&+qW8LIaa?HwlY%@?p!(D$=9ZPBNVZ*rFww)ugDB&r@GgW7GD29ROBZh4d1HgLpcW zhaUg{4~%C51Q<(Blf)`%9-x5UmhR%RJ32VC`SX_7&u;nrI7&DTReW1jU)SaYAnc|3 z?tkIXNDvQ*llTCtP#shY7Cd45l0V1SJ96*rd}q3(`)GvU3z>uMf-%rH|6gJV)W>IJ zbyU%}6O^%md>iqORaZiL`P%{+d4Du25l1X`=KMe9uF>I?bYDt9I;kcns5a^T8i$z#= zGQP4F>Rb2DxdGf#UBfk0aV1prxy1sPaCmV;HF7Mga1{D=2_>~La}J}3NZxiI8v>Lr zbqn(5t`6SZS->T8_T!0FaiaKpgDaHGn=b0001p zEdT%u0002^^7ir}DKa(NDJm-~Cnqp7HRROL=PEQcGTAUO;o8;X=;_qf-^5nY6@<7$ zv6u|^^&bG2;5W6#7(?`3g(KpPq`OqQtKSyL8D;Bm834>yO{X+*B>l11&jC!7Q*T}g z*bz@RGcF`G|Au=wzMsmzSu5(4xXZ+vi6BXwmOlNzwvZ8$L? zDq9+tpCx26J)A>6KDgwRS9ZV2Ov0bXinpf^b#ht;0P=dJZi-RHuzov};`i!lIoMn0 z-3UoPpcfuvzKpfbD2jBVX?e@NK7QmB8?DKWv2qyn^876<@{EQEiL{+kgq*Pp32gq~aO^Wk*Rda}vf z>v>Bj2I}e0@XlzmvyRn0nrAQ2;Zk6@JUm2$`3IzasPCc`td;YtLHGDZNh~RlQS#gZ$D!Xl2%R&hS zrO);=+o@5!^JZwP3jmBl#ZwbcVor2GRL1wA;mY`I`_&nzFC%-N2wM0x`PgCB7U^>o zuPxXb0i0IRP4Q=D4yaK7w{Bo0Y#e@55z68rhY&T1I{NE+Y~xy7$mqpkv|hnP#xwak zP~WF|0T>D6=4&PeVXb*)cbZjhuUw*BW_L)oOD+VgTFL$f-3GKenX(r<`R~XKrnL}26`E%Kem#~B6`-oL&<6|BVOPU)q7*N?yt2Gv^0cbEg8$U0(EjDRv=WA#?!VYn|N` zgn|CR0QcU{Cy-E;Rw6fzbqNDg2hUe|Uw%F|4xheaJ ze)`hRxb@?`%N&(+L}6hI*OSyYkJV&-YyG~)GTqMLmlP0ulbI6W&me5%E<;d5F+Yv@ z{*suJ+xUz^v`BeV*k-vPfD5*)wzK4Sm$Fon;olhdNE7c*#J5)Vc~$qugqr#pSqful za&~@_btWg+#=}X?TaJK9Bx`iaNOfHC@~|5dr$5||P`wa?lob(OIR5MqtIJmf0X_-7 zOP$;rAV7xi=34R0Do~>~RpqQDGR4fX+jA1*(<|%!<=C>iwb`Hiu_iWjRwDMzUhVY1 z(*)70$^%F@b1^$k@YN&hZYbFu`eC|i4j2|XAVbPst-Z_g3ShPGf7k$@RYd{0f4UDM zqr4~o2GG<&)G0j0DV^WehVwg1Ji*D85>IlbC#j+DmoE2}do%_2yW`}l!Nc=a=!qE= zadss6*d?5=eUY8VMdGoGTwbdbw*9v^CrbcR~r%Itk?g0ulv zN|kynP|*+@>*2k=f3b6|K4l(_WtA8p^+_9+V^S^(qbN3vbGh@s%&0bg&btcRnrBD< z??=Py|K;t^Nd{Ki^^>UvY$IgFQW5O?T2_K1SSe>5Vi%-DegswS7N{Se?qjb=Jd66* z8hc6{hd<)@t}3ShK=MSUpM<@*DPk^hQC2u%dCnTvctC8=)H$WfO0kA+T;nlKJa~WE zf(#5Bb12S^j%x9qxFB*WF`aBGx+8WZLf?CKhXY8+sZGbr2jmiKhigLb$WadWX%~#K zp%NMJg_D(kB&^sNcM2KHD#f{(T>vy5{mFi4ZoxT)?W>i*sCW>YW0Ei^a{ScD7QfQ; z&3+=&DqT9)#2YFBoF-W^lw@g*;{@z)Re&n&CwdgciHqZ@S7T{kf5PRh{6eAhddghd zyAje4E!sO{LuM}kj21bwqnK(qh^Y@ix$g1TmSvk{?t(6w+PeeVQ$3B1#L8|N(uq{D zI5Sl-`03$ZqN{zO0F)jTwvluI@8<%Pi?^ZOG@(W|vhW|M8~;1;ZQa1j>Dgh1Trlzt zw|k_!WLxHJtGxkoN;STg*A!wfK>NJ&oHHN$8pIj~7}E;7s$ypq#hb9@YvyQoQ@?MF z8S(FC2E|vblf-trNo#G>S~i6m%vfAhS_AoJ^2V(FfK9JoQrHcS6N+U&O|`F$G$alV z#r=;7XGl4&ny5*}Y9;)gcb(9Sif#;4a{RIDEXpug@|?K&^`-13yj@t$LJJo(lduH& zS%r&+N0T$HI6N8v5kz6;55K=!pD_XK7;i_j^Qdpx{x0;(pR1Zq@?5D*O#Ix!ns$}8 zE(m1XsM@OjViofZEq{`18&>nQFb;<^GnbZy(cPo`$R(99jHW!}0^Ca%X z*vSvU4j9x3$^z@dYdf!#k%=%uza~tur*;QvV z1nR~>)4Nj9sZXd202&$vx*}4&0@6`grF|lY$>;NpM7HRlZOgA1qc%=3%J|(=@$@O2 zZQMCwiqQ-I5A!`6Ur%Af=M&Y;c5;^BojGfxgmb@m@;82j>d*79R$3di_B6z%3lHNb zybUa{K#x%~G7JZ-aomKyTO0n&_jPr_43Vjy&rKMriM3X`>#Om_o zVOyH}zIrX62r=-+x|d+iD#{q87;5$-1oKDs3e0}iwSj2>t!FJzu}e`3^azPzyV5+P zL%x=0VM7k^Z^%~wjdFnDlah@8uDBHI?U(5SL;>Df^|Du7fr0fsKYR<+ux*%3jMK2( za%m-Nrf4VJ(wv0ie({`2Ubc2=`L^4CeVmgrz4z#=`6i{*Iu&8IW{Dj%gL_y;#6NK@ zX1dG3sj@ZX7A$(dxy^>+vPMlbtyyDz&`ZtFs=Kx682P~rzq9HL6x)<16Z76Hn{#EBUdiJB1L0JLg7(SMrucYTNr#5{bdiIGGeFzx56Po=a_h4pKnA!M(-8 zCq1sgSd%1bSDw~Pao_ImrMZJL_vK@TZ6YD}r{C5G+UY=QSw2SRg(CL+SsoA-;PHYcuqs>qqG?m@o9a4(X8YJ8MY+pVQzz;avQB%KE z&Wx2_I)ImfjQM8(cx~$ImghtmFp_#%Xalzbb?v@s1|lj1R+(_9(H;Yo;iHIwB{h z|Hsup$RiWSON8!VQ2wy5L(F;LG5W4n=RyI zmZ@YrB>tr8=WW8ORZl={)W;7H2F+hq=s1#r#TQHhkUMF98h)G31sU2WvAV-T%~w?s zfCIW)G_?@ubkNPV9o!{s;!%yv;h@&mNHSrB)7pD{Jqom?(mpEUY@5~1+PZ!&LIDdh z!{0zlbWa)N;ySZi`Sv}N$}m@C*5fzl!fNKw`a@hh@0|5Y7OdT)wWnF@^$f8JA<`S<0*ZR7dr|^V6l% z{r{dOH{Z+}rU|Q<82aJ#>Q@?NU@U(d1R`9+)FJHm)^MuCZk zj^z^lYOaQ~)1b?@pjGB#kLIjKNsfr2Obl2S^4D0&Ua~*3056cQ9b`d$S4RAcINjI`b6Xm+@0><@ z7Dhd=d#YdR83vl-5PnOoW)G^s?BL#%Pa5LffnJ)laz$sgW{NnI`Qgaj{?VvqcFJVK zc(-+Cj(^_1Z5s1@G|lC@S5(VSjw1Qmtu@bfGfdIslOmvEdL4q5bXRQ z7Fhzy3fs}V(d}`tR;GA6Z$|ubPQU@@FbI4tzOzlJ>AuvQqo1fQ-B`J`kL@6ITT>8M z(_Nx0b>#ka%yTLBZdNz8)(8MTJ2hz!A;1RjiK|AYl+dO}udRIa+gLhbimZD7X51#{ zbw8vBVwg$H+RLu(eopI``F@Vw=xw!y6tNKU`!82#9Y)MhfNXBlubcXkY#Mns2W5c+ z2M5nMw|bEUJsCdj-C(^QmIj^OeF_Jg80@H{>)A}M(!1!5lF;UpfGswDC6Qlz@5p^C z#xeynV_3V{Y*7+6Cz%npk8ZOW*>I=K6i~zk`$MdUt;P4GmJZe8q!ZI0(A00{8<~zy z*@WZiDb!nNdic+7ErT+TUT&*}|73M^u+pMW|F~1)9{^8hXHx(Gz!n1l00000h%EpB z3;+NC8QhsN8`Ia~r? z01a%~`hLid6*7ha`RA)q|3CP6^`ECxE zsOhYbJqADEfP|3j#E7ibBP79o#Olzj9MsT0HVRL${LxU z0qa`VS@%nJ8$_r)dVH>5*u+h7o2Zel#v-$S&Wmsb9ZAuP6OhQ)9L+8$@PX@6Q~kl8 zv#T0rNr*N+Ycg0~j(5>-E1%>%jo~YVvIY^)_n}iMn7vR6~R2 zri7)YrJ|0$8f>VE)RmO(E5e5B30rAQOjc?Xxgob;B0MokT}}#+66?*1d?Yq8nzCA& zx;8|gl{GLoY(3Tl2yWnuWCN5I9;mlT+YJ(iqEH$hO5OpCtfkb=k?uI5|2Tzr$eXd# z8_qzw2%(i3UH6Vgw5i^3ERla^E-$2Jo4R4}7JnU<2Bu^dwnnvEBVdb3VFI3NCD|iT zXyzZ|ro+=yrFYg#hkp>%n*Yw{@&DBe3(nxIUbau6`ptvgcAUxTt+R`;D z(eXMn@kX<7G{^(HxVuZNSujiGDvVP4*mJ7MuHOeSyiTxlw;GzhTPk2~Ig>xr`m`mo zq#&dA#$;hmAalb5{#wQG9@hln$;bfn>kMclx7Y&?U;>ulXe(*`N}?16CauFs+@BqX z{PjaN6*Th2pRm6Fb9kP#%gNeQFk}2|z(qkM#+X64DOd}!w0WbQC6JR9ehBfr=+7zw z+vkqU0aMFy+X=+`0JhH$wPUdL02_{^e!>hkXb8RD5 zUO35nwkNqG=^MQ=0b3V#Khbbfd*Ip=N1Qd{-CTdt@aVB+s2|}YC^a64&wIjmn>J2) zy}Vlv+hT{hQq?^ONUi2(KCqhHgQQyi%n8v)U*a18J}Wi&Na0M{lgU8#L*QTsE+Aib z0031@OcU*-7+7n!M%HrmJo=j5ySy?O^k%!>VubX`{d}jC=~2YT%2LKe!(3tAS>L_} z^9e%X)xg!8ItNtZTYVznbnd7%k&;^OR9a)z7VxuPNCK9zr&3!P77|oI89JOjJ+Og; z>L+PrD5>;h6*_yRd_*5Q%I@|>nH3J@t+S#&xruVu7LXKvy>M%6?N&DIrnDzvYV#%n zk<~f0F2W6G5_;v*C7NVK(kfBuk@?;MBdcH@d#f0%jx4`c3Km%8;3*6E2YxGc<_wZS z__9{`9)^W@kRJ2M0L*m2ukw?UqsJ6<*7T=f|JlFR>IvU%3i2Nv!vVSK=A8IrI@SB2 z&?dfGI0*qtN`_Z4hAWYY#~5l}RafmBe=z*Ah%fY98?e(bW}#zI=W<4?2t+JYlER!RdIpR+d=%sVxZsUpj&dN2`i-uMB2~i(3@#OZ(A&JK zxe)vnCxj~DApqW6?ePevKz=eQ4A0O&J|zK54{gQ)mB@)ynbuH1vmj*N+ZNhUJ_Bt zL$SuF#i}%By{@@7;b|5%kqnD@h2O910IQ;{P6el(2)h@lr#uH^z zG-i`tPK88_v}`RMLQRLx+Wu#0>mrvO5R5quEqR%fmZju(w$1Y-$v;HD+b%e5H<_gQ z4GChc%;+CZ_QW(Vq$woodZok$wE42`VEt=|C5cep(8Zrw7=R3RN=0tmMz?*6_5<(L0TZGS?%1R+k z?%%!$hi(yMJI!5?i}U_=4Xj>~17w;fz9tu+kbp~A&v~vpCsyD<2(&vAt-Y+)W>5>9 zy^}B}Oe*!09hS+K)6r+#0q`oCdj_v$>B~wI_FJpD&i^Yz^3BCmMB4X-0F+9_1-O_Y z7Owb7!~&O4H_f#8Qjy`N(Da+6RSmw2z8-Ni$#(Nl3WQo&(#i%m8!cKpKqbon=iF=N z@hUR^_sKZ3ISoGf}p5?CqtWE=h(K1&i2Hk%lfog!gRL^le zKQ=G{1z+e(QY_fq>QaTjp09aC8QGYAr(7%Yi`p1MS;i>SKuA^xQN$ z9KCRwB-wJ5mB$prL-Fav5840z%=Yh)&%LBu9zQkt&hHog966Wa(R3etj|cX0w=Lz& zQcqh!#;iDXb$YQfX;Dc-b4&I9C4|Pn%vIDs9@8d zo4TN?mv;3GghK98|5zne5xXya85#(f&k?W-`0sGHs8^sAO}&`amvgbd2zYT(%HAUR zZYovT_iMct08D{rluRa>;2Fmx4eZrvVq&<8l3df=DbsMiphh^+7Xrs5o@;G#DY9+C zMQq)}^+ZiPY$ol+v2>ZdFbpK#{WI%@Pae?0pR-pV+&;N^pWB^XX;Zf2uUnbt(FwDS za+?mXUq|dSsi&) z5Rw=Nhe=bsernbuhaR(lKU|P6;`(%cLJ*s0218${pxjxT+S9$~yJbBKn#IDQg)Q-= zs%wlMakThV9Qy=xsG>XLDM(WdHR(E?Cipz27-`EV-Xt_^h`KBJy$pH7>^d|6K6~|S z8}c+T-!OrP=h+1iI&gNzaK)0VWY042w7b04%zPg4Olw*3IDJ2!9iPlvY#-+71#{fb zj?cy?t}Z4`RM>K#2C{sG)?NMQ=02r8#nttPt^}0AseNePIJQO?OBcRc>z>vD>GYoMv{% zPad%bbBcSxaIoGkrJ2iip_9FPcn@_KFyl@;14(5bQZ~zy+M!m#&vi`s7F*VH(-bK1ferw^OC8-BBNLbI3BxtqI@z(YluwvSZc9#kRkEhQ*{`o&8|K8a>2t*`TLvBe z_vw#VaxFGD`b=*V#&+!vA-E&es1XOAMz^pYK6%L8Oh8_MHSF@r@vo3)JJJ zGMYaXLDa@$zGa~kT~@0=FIWq~H!Y;r@Bp@I)qbr*G{}=A1DRe=K3>aI4`Im7SPqy> z#zdth6}3+RpFJv=`zlTyM{F}A_{gU254u_R2jk@SYG{~kLbSEl#{bjQMiXTS!8qr< zM*&sVbY&zmH13nOR}h(pWMhq#37aD?xuyT!h&4x%!h(ekR6RE@@MkhclXcU39(PjI}&6Rgm#X zcOh;@y2EG(GQ+5Tr+%sQSLKN+Y!+#5M9?KE-rogO zVdC9))-Uz!Z@<`DO!QmNd2N?Y%{NPRk|fV(?Hdu;+ZV_c1iBf8B4rE++!?wz`Bzt+ zdPtw8-uECJuD0@}jI5aA;IdK!!(M8eXv5s=VR!46YfrPsa+^D2m`+Hc;lg|bX=AZ7 z0o{2fWuq(({#&hWugZ_?YsxZ|QOl1cYn_t8(mcurD2~neoe(w0mJ+2Nvern%2;T_z z(_*1sl+TKAn2|(?f#3keFOIbq16o7vk@~DS-l<_^KNyUu-Cco_+H~+B1-BQlt}ccg zAvf@|8-U-n2&AshtZoz=&yb%jC_*J5s6&yxG9|NceGQJou6BG)UP8m!1fB|&Yzbw+ z1n)kvi&#p+w678^mvh1~#onyJc}bhpxao5L`mk?(dgw8wC)%My!}*$dzJDMj&%ruv z5CcT-&2(dG$u6~nE^10^si~7(cfct~?nd;CM$gpO?yeV5<@>nZb<5KvmcFmT%P)B! zcR)O%LzR+ZesxmIXCj7)lmg?_0N6k$zrN4#{SdE}Bmaguoy_-i1Cam8ZXS)LXh+!@oD}2M(3mZ)>d+s^SQaA>q6~?6*J^u-g z?lv0$PiJRS006*Z0{{R3004+B000dD003e$qgfN;-{RfW+2z;R(cRV1)YHw;)z-dC z?d}L#AWsGcm~Y*xFet9SV$wG$7i5eNIWD)NgIj$8>?pme0WTYoVyYOapR#c;P&Ha*oG>ywr z3-7FUE;I%&pIRx+{$|<${(2q3l$13dKAw~e_u(03A{yza_keKqwb5&n=p_^gV_Krz z{(AV;h?oU_<1I0#uP;OySLXwH(@%!OuIQ+5OBhdoV4rTXzuJm~j^%g_WC|-Y?`wx->MmF5xi=x|U40e(yE z@<_U98u4dEc{r4gdGva1T3+i|%M|#Yz2(2{^8WR~)=gWu^ZLh+U+wnB9~ZR0 zO-x{H9UXlr6_^qsHKB#(ZWzUO=X`7G6sTCMN4 z9sPg#F&{53?Ay+mj{(eVc}oq)tVJt*rIkblcI(FxM>El}r09GM6}ag9VTz6?OYVc( z$WqwL`L#qtVJVuvuIhY5D7z5swLx}CAdP`}(KeExXEP+xIa%1w zN0>)!i_A=MM~}oZK*%HILHaNuek9T~(2zn(;Tvurhxf39cO01p!J|l^b(^s?_jITS zdw6eaoS&7LrQVb~Dg@qJ9n&7hl8~q@i96qZ$2azyGN5g%qEuT6sTfFo;I)tIak{TR zfAx|4moM6-KXk`#Q~KWU!i=29YHdeHE<~^*B_A%d1z{_R#BJ|!DlClwr59`gyXW%P zgnj^iH6(HleW&hm*MzH&rT6$$fPWC22ODv>nmYTzN{&u0R5wDm7pVnSs*bd$({4)j>~SoQzd|t1Ajc@m2PD z?#%XDTJzd(o0R>h@0Rq3JmvR#a1Bf*X4hfUCQ{_fP%;j?P@VjHQlFnj&-QhYXKdt} z5|!MV^KD1)I599XC#*rVFN4f?C7L>O&pyeQ#jU_lo!Q!Yat%;>NuI3q91uzIWTaK5 z-8rIg$k6$T=Eo+r5_%$Khbhq;WOSlVE?a(U4K+A&EgI$2QQ`%318kla14;zM8MUCn&VJ&9M1$K-$g*{jJh zCC&cj>RVkA3$TGVT69XZ-R4!S*q|D1J&{=eon~SxZ|ypYF=umY8oL%aXESo^Cs*}g9(Dm#sG)U800lb(fs~rXO*FRS>FJv4Ki7O9Yd1|$N zC~mIU-mfxW+Fp$Adx@NY(|38Ak#k}?d-AZNZB+gvUxEs*%a=&2>*FIT|IxEN6V4>c zj6h)*iDBjIzBMu*C}l_$;S%mRg|jK07Q*#hJm<~dsZ6zmgUvM|yKga;O}&G)xOtG~ z;Z!~J<8EP_+jIfjF#mQ`=%wfve>)7|-Rz#V$ugw*MHPJUmP-}(w;x`SLfF>D8^7vI zArPjgfgv>jo?9K`oMa0K*z*lf7h9>DK-adZ>{n}aq``RvdN zRb;+`ny0 zIPi&tDPh5MB38dx$u>FIC({_=S$+SR%rf0aMG_y?Z^Imwh%PasxG>6*j?G%8_)eoY zx21~`CQ|g-eZQzutdjGBSTmB-h|{CnFn zapz=y(dOB*Ra!Tj$&%nLU*_OaV1>FPXV&G5tiB@MazI&*bTzvW zd*SHEx`RL7q__Ba)Etb=TcGnvjThyM^@gW*U3b|w1lskpu71~Sj#;s}%rMLC79z&C zNQQXFn~sfiv(Ufq{MK;QL(Thzc@kwRnZD%%I?4VO{Hs&C*W3D@Tbc=t^A_p>m%3W< zyoHk;OsDV5P-NC7R(Y@x;YeLMEPW~eApxFyZEg;&fP&5K*eYxUjAMC??AWYICNcr5xoO&+wOT0&dZx3xZ*8`rLZGI1NfDL#`2uXkh+Oe{$N{+@bFf$c< z$1@i;{e1OCAexOI-)g<_cRJOw>4~vc4#sLAruT6~;)reKkQ$Dog0@|0H`_?;0u4z* zuHzAHUO9g*NBj|H^j2pf*64@VYpjA&C#5u*UD8fH!jy8wW{v1ULHT zV3u#HL-$mF71}@MKaHia zs_$Fjf}=zLx#9f4StYW)J<8|y-y=&lC5}vy;A^XUfkqFyl1_3O-;wX@Q!QS5xK9SR zM9>H?xIm`;hnLaU*R8(T`Z6Ao@V}btCYc{*gnEOH4MV8pm=aRkF|& zc=ZeYGx(IVU*%6;W9DYlrM$!W*vMyVTH?Kol$%kp2MYab+gM26Jc8`E!!*ez1WQ<+ zF|>xBlF>y~NEAetx-2DKx+Dr)AZ$t;N$QBpBbVZ&sI^~#HLdqz%;!-*cBavrcDN@scofrp>@%W$mn?0Stk6R09GdO~fP??q4>C?#eJaPS9+Q$keB=oXXl2A(oF*Xs!enW;(&Owz*9X}SOV<>2v;TL z+SX}HfedHJ6E0b`AKRNztDofRk@c0$>+FHP$+{f2+^K8XmDnyw8y1n5sY^3wPjC&b z|CwvWhcjHzgwkYsRWR3OG)T$lT&}R6rJ-^@qqtKWj!BGVMXBXot&@K|U59FAhG(a*dsgdJm)A&phjJ9ILNdNAyq~*u-F#qu-*(XbSFKkkrZvonIqSQhrtes@?{c3Gwx-}HKuP=M)5^^0 zZ;^L9vdkq$gqU3)X)&aT?gdp$6zV*+^bPPcozR?_6%snX8-#2$tzae0Fx7at0mXHxpPA*(yPPLQ#}h?h2fCfX6$Z$fZBu#Q%bAv>Eirl-zeu#5G*o1nWnoVt<Ne zO0PfapT6*Eg`~PsMiXHx(Gz^(%T z00000h%EpB4gdfE>=&RY6W!L|+S=dU+t$_E;oIHW)z0A6K3h%t2$~>GX68Ff2@cSW zW66HoCNVOwJaBPsd3NmcE&KO2)6)-*7xoWuSCR+hkuK4&rs3e3L(=Qn7ZkiD{anc8 zC8l&HHzF;yE9^xwtMD!{ROdABPBdwHfya` =`|W$@c=_Saow7zmEgr;VAzqEw;2 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