diff options
Diffstat (limited to 'Source/Main/engine.cpp')
-rw-r--r-- | Source/Main/engine.cpp | 6808 |
1 files changed, 6808 insertions, 0 deletions
diff --git a/Source/Main/engine.cpp b/Source/Main/engine.cpp new file mode 100644 index 00000000..362b66d5 --- /dev/null +++ b/Source/Main/engine.cpp @@ -0,0 +1,6808 @@ +//----------------------------------------------------------------------------- +// Name: engine.cpp +// Developer: Wolfire Games LLC +// Description: +// License: Read below +//----------------------------------------------------------------------------- +// +// Copyright 2022 Wolfire Games LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- +#include "engine.h" + +#include <Game/hardcoded_assets.h> +#include <Game/level.h> +#include <Game/savefile.h> +#include <Game/avatar_control_manager.h> + +#include <GUI/gui.h> +#include <GUI/widgetframework.h> +#include <GUI/scriptable_ui.h> +#include <GUI/dimgui/dimgui.h> +#include <GUI/dimgui/modmenu.h> +#include <GUI/dimgui/imgui_impl_sdl_gl3.h> +#include <GUI/IMUI/im_element.h> +#include <GUI/IMUI/im_support.h> + +#include <Graphics/cubemap.h> +#include <Graphics/flares.h> +#include <Graphics/models.h> +#include <Graphics/particles.h> +#include <Graphics/pxdebugdraw.h> +#include <Graphics/retargetfile.h> +#include <Graphics/sky.h> +#include <Graphics/camera.h> +#include <Graphics/shaders.h> +#include <Graphics/font_renderer.h> +#include <Graphics/simplify.hpp> +#include <Graphics/text.h> +#include <Graphics/lightprobecollection.hpp> +#include <Graphics/halfedge.h> + +#include <XML/level_loader.h> +#include <XML/Parsers/manifestparser.h> + +#include <Objects/cameraobject.h> +#include <Objects/hotspot.h> +#include <Objects/terrainobject.h> +#include <Objects/movementobject.h> +#include <Objects/reflectioncaptureobject.h> +#include <Objects/placeholderobject.h> +#include <Objects/envobject.h> +#include <Objects/lightprobeobject.h> +#include <Objects/lightvolume.h> +#include <Objects/decalobject.h> +#include <Objects/itemobject.h> +#include <Objects/dynamiclightobject.h> +#include <Objects/prefab.h> + +#include <Editors/map_editor.h> +#include <Editors/sky_editor.h> +#include <Editors/actors_editor.h> + +#include <Internal/hardware_specs.h> +#include <Internal/config.h> +#include <Internal/timer.h> +#include <Internal/common.h> +#include <Internal/filesystem.h> +#include <Internal/memwrite.h> +#include <Internal/locale.h> +#include <Internal/filesystem.h> +#include <Internal/modloading.h> +#include <Internal/detect_settings.h> +#include <Internal/dialogues.h> +#include <Internal/stopwatch.h> +#include <Internal/spawneritem.h> +#include <Internal/assetpreload.h> +#include <Internal/referencecounter.h> +#include <Internal/profiler.h> +#include <Internal/zip_util.h> + +#include <Asset/Asset/levelinfo.h> +#include <Asset/Asset/levelset.h> +#include <Asset/Asset/syncedanimation.h> +#include <Asset/Asset/attacks.h> +#include <Asset/Asset/reactions.h> +#include <Asset/Asset/animation.h> + +#include <Compat/fileio.h> +#include <Compat/filepath.h> + +#include <Timing/timingevent.h> +#include <Timing/intel_gl_perf.h> + +#include <Memory/stack_allocator.h> +#include <Memory/allocation.h> + +#include <Physics/bulletworld.h> +#include <Physics/physics.h> + +#include <Math/vec2math.h> +#include <Math/vec3math.h> + +#include <Logging/logdata.h> +#include <Logging/ramhandler.h> + +#include <Utility/assert.h> +#include <Utility/strings.h> + +#include <Online/online.h> +#include <Online/online_utility.h> + +#include <UserInput/keyTranslator.h> +#include <UserInput/keycommands.h> + +#include <Wrappers/glm.h> +#include <Images/image_export.hpp> +#include <Threading/thread_name.h> +#include <Network/asnetwork.h> +#include <Version/version.h> +#include <Steam/steamworks.h> +#include <JSON/json.h> +#include <Scripting/scriptfile.h> +#include <Sound/sound.h> +#include <Main/debuglevelload.h> + + +#include <FreeImage.h> +#include <SDL.h> + +#include <algorithm> +#include <limits> + +extern char imgui_ini_path[kPathSize]; +extern bool asdebugger_enabled; +extern bool asprofiler_enabled; + +//#define OpenVR +#ifdef OpenVR +#include "openvr.h" +#endif + +//#define OculusVR +#ifdef OculusVR +#include "OVR_CAPI_GL.h" +#include "Extras/OVR_Math.h" +bool g_oculus_vr_activated = false; + +#ifndef UNREFERENCED_PARAMETER +#define UNREFERENCED_PARAMETER(k) +#endif + +namespace OVR { + +struct DepthBuffer +{ + GLuint texId; + + DepthBuffer(Sizei size, int sampleCount) + { + UNREFERENCED_PARAMETER(sampleCount); + + assert(sampleCount <= 1); // The code doesn't currently handle MSAA textures. + + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + GLenum internalFormat = GL_DEPTH_COMPONENT24; + GLenum type = GL_UNSIGNED_INT; + if (GLEW_ARB_depth_buffer_float) + { + internalFormat = GL_DEPTH_COMPONENT32F; + type = GL_FLOAT; + } + + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, size.w, size.h, 0, GL_DEPTH_COMPONENT, type, NULL); + } + ~DepthBuffer() + { + if (texId) + { + glDeleteTextures(1, &texId); + texId = 0; + } + } +}; + +struct TextureBuffer +{ + ovrSession Session; + ovrTextureSwapChain TextureChain; + GLuint texId; + GLuint fboId; + Sizei texSize; + + TextureBuffer(ovrSession session, bool rendertarget, bool displayableOnHmd, Sizei size, int mipLevels, unsigned char * data, int sampleCount) : + Session(session), + TextureChain(NULL), + texId(0), + fboId(0), + texSize(0, 0) + { + UNREFERENCED_PARAMETER(sampleCount); + + assert(sampleCount <= 1); // The code doesn't currently handle MSAA textures. + + texSize = size; + + if (displayableOnHmd) + { + // This texture isn't necessarily going to be a rendertarget, but it usually is. + assert(session); // No HMD? A little odd. + assert(sampleCount == 1); // ovr_CreateSwapTextureSetD3D11 doesn't support MSAA. + + ovrTextureSwapChainDesc desc = {}; + desc.Type = ovrTexture_2D; + desc.ArraySize = 1; + desc.Width = size.w; + desc.Height = size.h; + desc.MipLevels = 1; + desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + desc.SampleCount = 1; + desc.StaticImage = ovrFalse; + + ovrResult result = ovr_CreateTextureSwapChainGL(Session, &desc, &TextureChain); + + int length = 0; + ovr_GetTextureSwapChainLength(session, TextureChain, &length); + + if(OVR_SUCCESS(result)) + { + for (int i = 0; i < length; ++i) + { + GLuint chainTexId; + ovr_GetTextureSwapChainBufferGL(Session, TextureChain, i, &chainTexId); + glBindTexture(GL_TEXTURE_2D, chainTexId); + + if (rendertarget) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + } + } + } + else + { + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + + if (rendertarget) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, texSize.w, texSize.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + + if (mipLevels > 1) + { + glGenerateMipmap(GL_TEXTURE_2D); + } + + Graphics::Instance()->genFramebuffers(&fboId, "texture_buffer"); + } + + ~TextureBuffer() + { + if (TextureChain) + { + ovr_DestroyTextureSwapChain(Session, TextureChain); + TextureChain = NULL; + } + if (texId) + { + glDeleteTextures(1, &texId); + texId = 0; + } + if (fboId) + { + Graphics::Instance()->deleteFramebuffer(&fboId); + fboId = 0; + } + } + + Sizei GetSize() const + { + return texSize; + } + + void SetAndClearRenderSurface(DepthBuffer* dbuffer) + { + GLuint curTexId; + if (TextureChain) + { + int curIndex; + ovr_GetTextureSwapChainCurrentIndex(Session, TextureChain, &curIndex); + ovr_GetTextureSwapChainBufferGL(Session, TextureChain, curIndex, &curTexId); + } + else + { + curTexId = texId; + } + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dbuffer->texId, 0); + + glViewport(0, 0, texSize.w, texSize.h); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_FRAMEBUFFER_SRGB); + } + + void UnsetRenderSurface() + { + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + + void Commit() + { + if (TextureChain) + { + ovr_CommitTextureSwapChain(Session, TextureChain); + } + } +}; + +}; //namespace OVR + +OVR::TextureBuffer * eyeRenderTexture[2] = { NULL, NULL }; +OVR::DepthBuffer * eyeDepthBuffer[2] = { NULL, NULL }; +ovrMirrorTexture mirrorTexture = NULL; +GLuint mirrorFBO = 0; +long long frameIndex = 0; +ovrSession session; +ovrHmdDesc hmdDesc; + +#endif + +extern ASTextContext g_as_text_context; + +SceneLight* primary_light = NULL; + +//We cannot allow the loading screen to call anything in input unless input is mutex locked +//this is because the loading loops modify the state of the input class. +std::queue<SDL_Event> queued_events; + +extern Timer game_timer; +extern Timer ui_timer; +extern TextAtlas g_text_atlas[kNumTextAtlas]; +extern TextAtlasRenderer g_text_atlas_renderer; + +#define THREADED 1 + +#ifdef _DEBUG +const int _max_steps_per_frame = 4; +#else +const int _max_steps_per_frame = 4; +#endif + +extern const char* new_empty_level_path; +extern Config config; +extern Config default_config; +extern NativeLoadingText native_loading_text; +extern bool g_simple_water; +extern bool g_disable_fog; +extern bool g_simple_shadows; +extern bool g_level_shadows; +extern bool g_albedo_only; +extern bool g_no_reflection_capture; +extern bool g_no_detailmaps; +extern bool g_no_decals; +extern bool g_no_decal_elements; +extern bool g_single_pass_shadow_cascade; +extern bool g_draw_vr; +extern bool g_gamma_correct_final_output; +bool g_draw_collision = false; +bool g_make_invisible_visible = false; + +std::string script_dir_path; +static float prev_view_time; + +static const float shadow_sizes[] = {20.0f, 60.0f, 250.0f, 1100.0f}; // Dimensions in world space of square for each shadow cascade + +static bool runtime_level_load_stress = true; + +static bool interrupt_loading; + +const char* font_path = "Data/Fonts/Lato-Regular.ttf"; + +//----------------------------------------------------------------------------- +//Functions +//----------------------------------------------------------------------------- + +//#define USE_NVTX_PROFILER true + +#ifdef OpenVR +vr::IVRSystem *m_pHMD; + +std::string GetTrackedDeviceString( vr::IVRSystem *pHmd, vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = NULL ) +{ + uint32_t unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, NULL, 0, peError ); + if( unRequiredBufferLen == 0 ) + return ""; + + char *pchBuffer = new char[ unRequiredBufferLen ]; + unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, pchBuffer, unRequiredBufferLen, peError ); + std::string sResult = pchBuffer; + delete [] pchBuffer; + return sResult; +} + +void ProcessVREvent( const vr::VREvent_t & event ) +{ + switch( event.eventType ) + { + case vr::VREvent_TrackedDeviceActivated: + { + //SetupRenderModelForTrackedDevice( event.trackedDeviceIndex ); + LOGI << "Device "<< event.trackedDeviceIndex << " attached. Setting up render model." << std::endl; + } + break; + case vr::VREvent_TrackedDeviceDeactivated: + { + LOGI << "Device "<< event.trackedDeviceIndex << " detached." << std::endl; + } + break; + case vr::VREvent_TrackedDeviceUpdated: + { + LOGI << "Device "<< event.trackedDeviceIndex << " updated." << std::endl; + } + break; + } +} +#endif + + +VBOContainer quad_vert_vbo; +VBOContainer quad_index_vbo; + +void PushGPUProfileRange(const char* cstr){ +#ifdef USE_NVTX_PROFILER + nvtxRangePushA(cstr); +#endif + +#ifdef GLDEBUG + if (GLEW_KHR_debug) { + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION , 1, -1, cstr); + } +#endif // GLDEBUG +} + +void PopGPUProfileRange(){ +#ifdef USE_NVTX_PROFILER + nvtxRangePop(); +#endif + +#ifdef GLDEBUG + if (GLEW_KHR_debug) { + glPopDebugGroup(); + } +#endif // GLDEBUG +} + +int live_update_countdown = 0; + +static void RequestLiveUpdate() { + live_update_countdown = 10; +} + +static void LiveUpdate(SceneGraph* scenegraph, AssetManager* asset_manager, ScriptableUI* scriptable_ui) { + Input* input = Input::Instance(); + Graphics* graphics = Graphics::Instance(); + Engine* engine = Engine::Instance(); + + if(config.PrimarySourceModified()){ + config.Load(config.GetPrimaryPath()); + engine->SetGameSpeed(config["global_time_scale_mult"].toNumber<float>(), true); + input->SetFromConfig(config); + graphics->SetFromConfig(config, true); + ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>()); + graphics->InitScreen(); + DebugDrawAux::Instance()->visible_sound_spheres = config["visible_sound_spheres"].toNumber<bool>(); + engine->GetSound()->SetMusicVolume(config["music_volume"].toNumber<float>()); + engine->GetSound()->SetMasterVolume(config["master_volume"].toNumber<float>()); + } + + ModLoading::Instance().Reload(); + AssetPreload::Instance().Reload(); + + Textures::Instance()->Reload(); + if(scenegraph){ + if(scenegraph->sky){ + scenegraph->sky->Reload(); + scenegraph->sky->LightingChanged(scenegraph->terrain_object_ != NULL); + } + } + + asset_manager->Reload<Attack>(); + asset_manager->Reload<Reaction>(); + asset_manager->Reload<SyncedAnimationGroup>(); + asset_manager->Reload<Animation>(); + asset_manager->Reload<SoundGroup>(); + asset_manager->Reload<ParticleType>(); + asset_manager->Reload<Material>(); + asset_manager->Reload<Character>(); + asset_manager->Reload<Animation>(); + asset_manager->Reload<Item>(); + + if(scenegraph){ + scenegraph->SendMessageToAllObjects(OBJECT_MSG::RELOAD); + scenegraph->level->LiveUpdateCheck(); + scenegraph->particle_system->script_context_.Reload(); + } + if( scriptable_ui ) { + scriptable_ui->Reload(); + } + AnimationRetargeter::Instance()->Reload(); + Shaders::Instance()->Reload(); + + ReloadImGui(); +} + +const char* CStrEngineStateType( const EngineStateType& state ) { + switch( state ) { + case kEngineNoState: return "EngineNoState"; + case kEngineLevelState: return "EngineLevelState"; + case kEngineEditorLevelState: return "EngineEditorLevelState"; + case kEngineScriptableUIState: return "EngineScriptableUIState"; + case kEngineCampaignState: return "EngineCampaignState"; + } + return "Unknown"; +} + +EngineState::EngineState(std::string _id, EngineStateType _type, Path _path) : id(_id), type(_type), path(_path), pop_past(false) +{ + +} + +EngineState::EngineState(std::string _id, EngineStateType _type) : id(_id), type(_type), pop_past(false) +{ + +} + +EngineState::EngineState(std::string _id, ScriptableCampaign* _campaign, Path _path) : id(_id), type(kEngineCampaignState), path(_path), pop_past(true) +{ + campaign.Set(_campaign); +} + +EngineState::EngineState() : type(kEngineNoState), pop_past(true) +{ + +} + +std::ostream& operator<<( std::ostream& out, const EngineState& in ) +{ + out << "Type(" << in.type << "," << in.path << ")"; + return out; +} + +Engine* Engine::instance_ = NULL; + +void Engine::Dispose() { + ModLoading::Instance().DeRegisterCallback(this); + ModLoading::Instance().Dispose(); + + config.GetRef("menu_show_state") = show_state; + config.GetRef("menu_show_performance") = show_performance; + config.GetRef("menu_show_log") = show_log; + config.GetRef("menu_show_warnings") = show_warnings; + config.GetRef("menu_show_save") = show_save; + config.GetRef("menu_show_sound") = show_sound; + config.GetRef("menu_show_mod_menu") = show_mod_menu; + config.GetRef("menu_show_asdebugger_contexts") = show_asdebugger_contexts; + config.GetRef("menu_show_asprofiler") = show_asprofiler; + config.GetRef("menu_show_mp_debug") = show_mp_debug; + config.GetRef("menu_show_mp_info") = show_mp_info; + config.GetRef("menu_show_input_debug") = show_input_debug; + config.GetRef("menu_show_mp_settings") = show_mp_settings; + + if(scenegraph_){ + scenegraph_->map_editor->gui = NULL; + scenegraph_->Dispose(); + delete scenegraph_; + scenegraph_ = NULL; + } + if(scriptable_menu_) + delete scriptable_menu_; + + for (int i = 0; i < kNumTextAtlas; i++) { + g_text_atlas[i].Dispose(); + } + Models::Instance()->Dispose(); + ActiveCameras::Get()->SetCameraObject(NULL); + particle_types.Dispose(); + g_text_atlas_renderer.Dispose(); + Input::Instance()->Dispose(); + DecalTextures::Dispose(); + Textures::Instance()->Dispose(); + Shaders::Instance()->Dispose(); + DebugDraw::Instance()->Dispose(); + DisposeTextAtlases(); + ScriptFileCache::Instance()->Dispose(); + sound.Dispose(); + ActiveCameras::Instance()->Dispose(); + Graphics::Instance()->Dispose(); + asset_manager.Dispose(); + IMrefCountTracker.logSanityCheck(); + Online::Instance()->Dispose(); +#if ENABLE_STEAMWORKS + Steamworks::Instance()->Dispose(); +#endif + as_network.Dispose(); + DisposeImGui(); + + SDLNet_Quit(); + SDL_Quit(); + + //Save config on shutdown, in case we have some global data stored in the configuration. + std::string config_path = GetConfigPath(); + if( config.HasChangedSinceLastSave() ) + { + LOGI << "Saving the configuration because we're disposing the engine and there are unsaved changes" << std::endl; + config.Save(config_path); + } + Engine::instance_ = NULL; + + AssetPreload::Instance().Dispose(); + +#ifdef OculusVR + if(g_oculus_vr_activated){ + ovr_Shutdown(); + } +#endif +#ifdef OpenVR + if( m_pHMD ) + { + vr::VR_Shutdown(); + m_pHMD = NULL; + } +#endif +} + +Engine* Engine::Instance() +{ + return Engine::instance_; +} + +ThreadedSound* Engine::GetSound() +{ + return &sound; +} + +AssetManager* Engine::GetAssetManager() +{ + return &asset_manager; +} + +AnimationEffectSystem* Engine::GetAnimationEffectSystem() +{ + return &particle_types; +} + +LipSyncSystem* Engine::GetLipSyncSystem() +{ + return &lip_sync_system; +} + +ASNetwork* Engine::GetASNetwork() { + return &as_network; +} + +SceneGraph* Engine::GetSceneGraph() { + return scenegraph_; +} + +void Engine::UpdateControls(float timestep, bool loading_screen) { + if(scenegraph_ && scenegraph_->map_editor && scenegraph_->map_editor->state_ != MapEditor::kInGame && !paused) { + Input::Instance()->AllowControllerInput(false); + } else { + Input::Instance()->AllowControllerInput(true); + } + + if( loading_screen == true || waiting_for_input_ ) { + SDL_Event event; + // We want to poll events during load screen to allow SDL to handle basic window drag/resize events + while (SDL_PollEvent(&event)) { + if( event.type == SDL_QUIT ) { + { + loading_mutex_.lock(); + LOGE << "User requested interrupt, shutting down loading" << std::endl; + interrupt_loading = true; + loading_mutex_.unlock(); + } + } + + if(SDL_GetWindowFlags(Graphics::Instance()->sdl_window_) & SDL_WINDOW_INPUT_FOCUS) { + switch(event.type){ + case SDL_MOUSEBUTTONDOWN: + case SDL_KEYDOWN: + case SDL_JOYBUTTONDOWN: + if (Online::Instance()->IsActive()) { + if (Online::Instance()->IsHosting() && Online::Instance()->AllClientsReady()) { + last_loading_input_time = SDL_TS_GetTicks(); + Online::Instance()->SessionStarted(true); + } + } + else { + last_loading_input_time = SDL_TS_GetTicks(); + Online::Instance()->SessionStarted(true); + } + break; + } + } + + queued_events.push(event); + } + } else { + Input* input = Input::Instance(); + Graphics* graphics = Graphics::Instance(); + + // reset deltas on userinterfaces + input->getMouse().Update(); + + if(loading_screen == false){ + PROFILER_ZONE(g_profiler_ctx, "UpdateKeyboardFocus"); + input->UpdateKeyboardFocus(); + } + + input->getKeyboard().clearKeyPresses(); + KeyCommand::ClearKeyPresses(); + + // Process all the events queued during the loading screen if necessary. + // We never want to miss events because our keyboard/mouse state would get out of sync + if(!queued_events.empty()){ + PROFILER_ZONE(g_profiler_ctx, "Push queued events"); + SDL_Event event; + while (SDL_PollEvent(&event)) { + queued_events.push(event); + } + while(!queued_events.empty()){ + SDL_PushEvent(&queued_events.front()); + queued_events.pop(); + } + } + + { + PROFILER_ZONE(g_profiler_ctx, "Process SDL events"); + SDL_Event event; + while (SDL_PollEvent(&event)) { + { + PROFILER_ZONE(g_profiler_ctx, "ImGui_ImplSdlGL3_ProcessEvent"); + ProcessEventImGui(&event); + } + //Redirect input events to the controller + switch( event.type ) { + case SDL_WINDOWEVENT: + switch(event.window.event){ + case SDL_WINDOWEVENT_FOCUS_LOST: + graphics->SetWindowGrab(false); + cursor.SetVisible(true); + input->HandleEvent(event); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + #ifndef WIN32 + RequestLiveUpdate(); + #endif + input->HandleEvent(event); + break; + case SDL_WINDOWEVENT_RESIZED: + resize_event_frame_counter = 10; + resize_value = ivec2(event.window.data1, event.window.data2); + break; + } + break; + case SDL_QUIT: + Input::Instance()->RequestQuit(); + return; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEWHEEL: + if(!WantMouseImGui()){ + input->HandleEvent(event); + } + break; + case SDL_KEYDOWN: + case SDL_TEXTINPUT: + if(!WantKeyboardImGui()){ + input->HandleEvent(event); + } + break; + default: + input->HandleEvent(event); + } + } + } + input->ignore_mouse_frame = false; + + Input::Instance()->ProcessControllers(timestep); + PlayerInput *controller = Input::Instance()->GetController(0); + static const std::string kSlow = "slow"; + if(Input::Instance()->debug_keys && current_engine_state_.type == kEngineEditorLevelState){ + if(controller != NULL && controller->key_down[kSlow].count==1) { + slow_motion = !slow_motion; + CommitPause(); + } + + if( KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kPause, KIMF_LEVEL_EDITOR_GENERAL)){ + user_paused = !user_paused; + CommitPause(); + } + } + + bool file_menu_active = current_engine_state_.type == kEngineScriptableUIState || + current_engine_state_.type == kEngineCampaignState || + (current_engine_state_.type == kEngineEditorLevelState && Input::Instance()->debug_keys); + if(file_menu_active) { + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kNewLevel, KIMF_LEVEL_EDITOR_GENERAL)) { + NewLevel(); + } + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kOpenLevel, KIMF_LEVEL_EDITOR_GENERAL)) { + ::LoadLevel(false); + } + } + + static const std::string kShowTiming = "show_timing"; + if(controller != NULL && controller->key_down[kShowTiming].count==1) { + #ifdef SLIM_TIMING + slimTimingEvents->toggleVisualization(); + #endif //SLIM_TIMING + } + static const std::string kScreenshot = "screenshot"; + if(controller != NULL && controller->key_down[kScreenshot].count==1) { + graphics->queued_screenshot = 1; + graphics->screenshot_mode = Graphics::kGameplay; + graphics->pre_screenshot_media_mode_state = graphics->media_mode(); + graphics->SetMediaMode(true); + } + static const std::string kTransparentScreenshot = "transparent_screenshot"; + if(controller != NULL && controller->key_down[kTransparentScreenshot].count==1) { + graphics->queued_screenshot = 1; + graphics->screenshot_mode = Graphics::kTransparentGameplay; + graphics->pre_screenshot_media_mode_state = graphics->media_mode(); + graphics->SetMediaMode(true); + } + + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kQuit, KIMF_MENU)) { + input->RequestQuit(); + } + + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kBack, KIMF_MENU)) { + ScriptableUICallback("back"); + } + + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kPrintObjects, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL | KIMF_PLAYING )) { + if( scenegraph_ ) + scenegraph_->PrintCurrentObjects(); + } + + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kToggleLevelLoadStress, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL | KIMF_PLAYING )){ + if( config["level_load_stress"].toNumber<bool>()){ + if( runtime_level_load_stress ) { + LOGI << "Disabling level load stress." << std::endl; + runtime_level_load_stress = false; + } else { + LOGI << "Enabling level load stress." << std::endl; + runtime_level_load_stress = true; + } + } else { + LOGI << "level load stress has not been enabled." << std::endl; + } + } + + if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kLoadItemSearch, KIMF_LEVEL_EDITOR_GENERAL)) { + show_load_item_search = true; + } + + if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kOpenCustomEditor, KIMF_LEVEL_EDITOR_GENERAL)) { + LOGI << "Launch custom editor(s)" << std::endl; + scenegraph_->level->Message("edit_selected_dialogue"); + + std::vector<Object*> selected; + scenegraph_->ReturnSelected(&selected); + + for(unsigned selected_i = 0; selected_i < selected.size(); ++selected_i) { + if(selected[selected_i]->GetType() == _hotspot_object) { + Hotspot* hotspot = (Hotspot*)selected[selected_i]; + + if(hotspot->HasCustomGUI()) { + hotspot->LaunchCustomGUI(); + } + } + } + } + + if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kMakeSelectedCharacterSavedCorpse, KIMF_LEVEL_EDITOR_GENERAL)) { + LOGI << "Make currently selected characters into saved corpses" << std::endl; + scenegraph_->level->Message("make_selected_character_saved_corpse"); + } + + if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kReviveSelectedCharacterAndUnsaveCorpse, KIMF_LEVEL_EDITOR_GENERAL)) { + LOGI << "Revive currently selected character corpses, and remove saved corpse state" << std::endl; + scenegraph_->level->Message("revive_selected_character_and_unsave_corpse"); + } + + if(current_engine_state_.type == kEngineScriptableUIState) { + if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kRefresh, KIMF_ANY)) { + LOGI << "Request refresh of current menu script" << std::endl; + scriptable_menu_->Reload(true); + } + } + + if( resize_event_frame_counter >= 0 ) { + if( resize_event_frame_counter == 0 ) { + Graphics::Instance()->WindowResized(resize_value); + if(!loading_screen && scenegraph_){ + scenegraph_->level->WindowResized(resize_value); + ScriptableCampaign* campaign_script = GetCurrentCampaign(); + if( campaign_script ) { + campaign_script->WindowResized(resize_value); + } + } + } + resize_event_frame_counter--; + } + } + + #ifdef OpenVR + vr::VREvent_t event; + while( m_pHMD->PollNextEvent( &event, sizeof( event ) ) ) + { + ProcessVREvent( event ); + } + + // Process SteamVR controller state + for( vr::TrackedDeviceIndex_t unDevice = 0; unDevice < vr::k_unMaxTrackedDeviceCount; unDevice++ ) + { + vr::VRControllerState_t state; + if( m_pHMD->GetControllerState( unDevice, &state, sizeof(state) ) ) + { + //m_rbShowTrackedDevice[ unDevice ] = state.ulButtonPressed == 0; + } + } +#endif +} + +void Engine::HandleRabbotToggleControls() { + Keyboard &keyboard = Input::Instance()->getKeyboard(); + + bool rabbot_mode = scenegraph_->map_editor->state_ == MapEditor::kInGame; + if(!rabbot_mode && keyboard.wasScancodePressed(SDL_SCANCODE_8, KIMF_PLAYING | KIMF_LEVEL_EDITOR_GENERAL)) { + scenegraph_->map_editor->SendInRabbot(); + } + int menu_player = Input::Instance()->PlayerOpenedMenu(); + if(menu_player != -1 && (current_menu_player == -1 || current_menu_player == menu_player)) { + if(rabbot_mode) { + if(current_engine_state_.type == kEngineEditorLevelState){ + scenegraph_->map_editor->StopRabbot(); + } else { + current_menu_player = menu_player; + scenegraph_->level->Message("open_menu"); + char buffer[48]; + sprintf(buffer, "menu_player%i", menu_player); + scenegraph_->level->Message(buffer); + } + } else { + scenegraph_->level->Message("open_menu"); + current_menu_player = menu_player; + char buffer[48]; + sprintf(buffer, "menu_player%i", menu_player); + scenegraph_->level->Message(buffer); + } + Graphics::Instance()->SetMediaMode(false); + } + + if (Online::Instance()->IsClient() && Online::Instance()->GetHostAllowsClientEditor() == false) { + if (current_engine_state_.type == kEngineEditorLevelState) { + current_engine_state_.type = kEngineLevelState; + scenegraph_->map_editor->SendInRabbot(); + } + } + + if (keyboard.wasScancodePressed(SDL_SCANCODE_F1, KIMF_PLAYING | KIMF_LEVEL_EDITOR_GENERAL)) { + if (Online::Instance()->IsClient()) { + if (Online::Instance()->GetHostAllowsClientEditor() == false && false) { + + return; + } + } + + + if(current_engine_state_.type == kEngineLevelState){ + current_engine_state_.type = kEngineEditorLevelState; + scenegraph_->map_editor->StopRabbot(); + Shaders::Instance()->create_program_warning = false; + } else if(current_engine_state_.type == kEngineEditorLevelState){ + current_engine_state_.type = kEngineLevelState; + scenegraph_->map_editor->SendInRabbot(); + } + } +} + +static void ClipPolyToPlane(std::vector<vec3>* poly_verts, vec3 plane_normal, float plane_distance) { + std::vector<vec3> clipped_verts; + // Check each line segment against the plane + for(size_t i=0, len=poly_verts->size(); i<len; ++i){ + vec3 start = poly_verts->at(i); + vec3 end = poly_verts->at((i+1)%len); + float start_dot = dot(start, plane_normal); + float end_dot = dot(end, plane_normal); + if(start_dot >= plane_distance){ + // Preserve start point if it is on the correct side of the plane + clipped_verts.push_back(start); + } + if( (start_dot < plane_distance) != (end_dot < plane_distance) ){ + // Line segment intersects the plane, add intersection point + // Solve for t: start_dot + t * (end_dot - start_dot) = plane_distance + // We know (end_dot != start_dot) so no risk of divide by zero + float t = (plane_distance - start_dot) / (end_dot - start_dot); + vec3 intersection_point = start + (t * (end - start)); + clipped_verts.push_back(intersection_point); + } + } + *poly_verts = clipped_verts; +} + +struct VoxelSpan { + int height[2]; // start and end height (0 = top, 1 = bottom) +}; + +bool VoxelSpanHeightSort (const VoxelSpan& a, const VoxelSpan& b) { + return (a.height[0] > b.height[0]); +} + +typedef std::list < VoxelSpan > VoxelSpanList; +typedef std::vector< VoxelSpanList > VoxelSpanField; + +struct VoxelField { + VoxelSpanField spans; + int voxel_field_dims[3]; + vec3 voxel_field_bounds[2]; + float voxel_size; + VoxelField() { + for(int i=0; i<3; ++i){ + voxel_field_dims[i] = 0; + } + voxel_size = 0.5f; + } +}; + +static void RasterizeTrisToVoxelField(const std::vector<vec3>& tri_verts, VoxelField& field) { + vec3 intersect_bounds[2] = {vec3(FLT_MAX), vec3(-FLT_MAX)}; + for(size_t tri_index=0, num_tri_verts=tri_verts.size(); + tri_index < num_tri_verts; + ++tri_index) + { + for(int j=0; j<3; ++j){ + intersect_bounds[0][j] = min(intersect_bounds[0][j], tri_verts[tri_index][j]); + intersect_bounds[1][j] = max(intersect_bounds[1][j], tri_verts[tri_index][j]); + } + } + + static const bool kDrawBounds = false; + if(kDrawBounds){ + DebugDraw::Instance()->AddWireBox((intersect_bounds[0] + intersect_bounds[1])*0.5f, + (intersect_bounds[1] - intersect_bounds[0])*0.5f, + vec4(1.0f), _persistent); + } + + // Get dimensions of voxel field + for(int j=0; j<3; ++j){ + int voxel_min = (int)floorf(intersect_bounds[0][j]/field.voxel_size); + int voxel_max = (int)ceilf(intersect_bounds[1][j]/field.voxel_size); + field.voxel_field_bounds[0][j] = voxel_min * field.voxel_size; + field.voxel_field_bounds[1][j] = voxel_max * field.voxel_size; + field.voxel_field_dims[j] = voxel_max - voxel_min; + } + + // Create storage for voxel spans + field.spans.clear(); + field.spans.resize(field.voxel_field_dims[0] * field.voxel_field_dims[2]); + + // Rasterize triangles to voxel field + for(size_t tri_index=0, num_tri_verts=tri_verts.size(); + tri_index < num_tri_verts; + tri_index += 3) + { + // Get bounding box of triangle + vec3 bounds_min(FLT_MAX), bounds_max(-FLT_MAX); + for(int i=0; i<3; ++i){ + for(int j=0; j<3; ++j){ + bounds_min[j] = min(bounds_min[j], tri_verts[tri_index+i][j]); + bounds_max[j] = max(bounds_max[j], tri_verts[tri_index+i][j]); + } + } + static const bool kDrawTriBounds = false; + if(kDrawTriBounds){ + DebugDraw::Instance()->AddWireBox((bounds_max + bounds_min)*0.5f, (bounds_max-bounds_min)*0.5f, vec4(1.0f), _persistent); + } + + // Get bounding box of triangle in terms of voxels + vec3 voxel_bounds_min, voxel_bounds_max; + int voxel_size[3]; + for(int i=0; i<3; ++i){ + voxel_bounds_min[i] = floorf(bounds_min[i]/field.voxel_size); + voxel_bounds_max[i] = ceilf(bounds_max[i]/field.voxel_size); + voxel_size[i] = (int)voxel_bounds_max[i] - (int)voxel_bounds_min[i]; + voxel_bounds_min[i] *= field.voxel_size; + voxel_bounds_max[i] *= field.voxel_size; + } + + // Clip poly to each voxel slice + for(int voxel_x = 0; voxel_x < voxel_size[0]; ++voxel_x){ + // Start with initial triangle + std::vector<vec3> poly_verts; + for(int i=0; i<3; ++i){ + poly_verts.push_back(tri_verts[tri_index+i]); + } + // Clip to slice bounds + ClipPolyToPlane(&poly_verts, vec3(1,0,0), voxel_bounds_min[0] + voxel_x*field.voxel_size); + ClipPolyToPlane(&poly_verts, vec3(-1,0,0), -(voxel_bounds_min[0] + (voxel_x+1)*field.voxel_size)); + // Clip poly along other axis + for(int voxel_z = 0; voxel_z < voxel_size[2]; ++voxel_z){ + // Start with initial triangle + std::vector<vec3> new_poly_verts = poly_verts; + // Clip to slice bounds + ClipPolyToPlane(&new_poly_verts, vec3(0,0,1), voxel_bounds_min[2] + voxel_z*field.voxel_size); + ClipPolyToPlane(&new_poly_verts, vec3(0,0,-1), -(voxel_bounds_min[2] + (voxel_z+1)*field.voxel_size)); + // Rasterize clipped polygon to voxel field + if(!new_poly_verts.empty()){ + vec3 poly_bounds_min(FLT_MAX), poly_bounds_max(-FLT_MAX); + for(size_t i=0, len=new_poly_verts.size(); i<len; ++i){ + for(int j=0; j<3; ++j){ + poly_bounds_min[j] = min(poly_bounds_min[j], new_poly_verts[i][j]); + poly_bounds_max[j] = max(poly_bounds_max[j], new_poly_verts[i][j]); + } + } + int span_top = (int)ceilf(poly_bounds_max[1]/field.voxel_size); + int span_bottom = (int)floorf(poly_bounds_min[1]/field.voxel_size); + VoxelSpan span; + span.height[0] = span_top; + span.height[1] = span_bottom; + int abs_voxel_x = voxel_x + (int)((voxel_bounds_min[0] - field.voxel_field_bounds[0][0])/field.voxel_size); + int abs_voxel_z = voxel_z + (int)((voxel_bounds_min[2] - field.voxel_field_bounds[0][2])/field.voxel_size); + field.spans[abs_voxel_x*field.voxel_field_dims[2] + abs_voxel_z].push_back(span); + + static const bool kDrawSpans = false; + if(kDrawSpans){ + vec3 pos = vec3(voxel_bounds_min[0] + (voxel_x+0.5f)*field.voxel_size, + (span_top + span_bottom)*0.5f * field.voxel_size, + voxel_bounds_min[2] + (voxel_z+0.5f)*field.voxel_size); + vec3 box_size = vec3(field.voxel_size*0.5f, + (span_top - span_bottom)*0.5f * field.voxel_size, + field.voxel_size*0.5f); + DebugDraw::Instance()->AddWireBox(pos, box_size, vec4(1.0f), _persistent); + } + } + } + } + } + + // Merge overlapping voxel spans + for(size_t span_index=0, len=field.spans.size(); span_index<len; ++span_index){ + std::list<VoxelSpan>& span_list = field.spans[span_index]; + span_list.sort(VoxelSpanHeightSort); + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end(); + ++iter) + { + bool stop = false; + while(!stop){ + std::list<VoxelSpan>::iterator next_iter = iter; + ++next_iter; + if(next_iter != span_list.end()){ + VoxelSpan& span = *iter; + VoxelSpan& next_span = *next_iter; + if(span.height[1] <= next_span.height[0]+1 && + span.height[0] >= next_span.height[1]-1) + { + span.height[0] = max(span.height[0], next_span.height[0]); + span.height[1] = min(span.height[1], next_span.height[1]); + span_list.erase(next_iter); + } else { + stop = true; + } + } else { + stop = true; + } + } + } + } +} + +VoxelField g_voxel_field; + +void GetColumnNeighbors(int num_spans[2], VoxelSpan* spans[2], int span_start[2], std::vector< std::list<int> > *span_neighbors) { + if(num_spans[0] == 0 || num_spans[1] == 0){ + // If either column has no spans than we will find no new neighbors + return; + } + // Start with the top side of the top span of each column + int span_id[2] = {0, 0}; + int span_end[2] = {0, 0}; + int active_span[2] = {-1, -1}; + bool done = false; + while(!done){ + int next_height[2]; + for(int i=0; i<2; ++i){ + next_height[i] = spans[i][span_id[i]].height[span_end[i]]; + } + if(next_height[0] > next_height[1]){ + ++span_end[0]; + } else if(next_height[1] > next_height[0]){ + ++span_end[1]; + } else { + ++span_end[0]; + ++span_end[1]; + } + // If we go off the bottom of a span, move to the top of the next span + for(int i=0; i<2; ++i){ + if(span_end[i] > 1){ + active_span[i] = -1; // We left the span so it is no longer active + span_end[i] = 0; + ++span_id[i]; + if(span_id[i] >= num_spans[i]){ + done = true; + } + } else if(span_end[i] == 1) { + active_span[i] = span_start[i] + span_id[i]; // We entered a new span + if(active_span[0] != -1 && active_span[1] != -1){ + VoxelSpan test_spans[2]; + test_spans[0] = spans[0][span_id[0]]; + test_spans[1] = spans[1][span_id[1]]; + if(test_spans[0].height[0] < test_spans[1].height[1] || + test_spans[1].height[0] < test_spans[0].height[1]) + { + LOGE << "False positive!" << std::endl; + } + span_neighbors->at(active_span[0]).push_back(active_span[1]); + span_neighbors->at(active_span[1]).push_back(active_span[0]); + // We found a neighbor pair! + } + } + } + } +} + +void PlaceLightProbes(SceneGraph* scenegraph, vec3 translation, quaternion rotation, vec3 scale) { + // Rasterize triangles in scene to g_voxel_field + { + std::vector<vec3> tri_verts; + { // Get tris intersecting box + btTransform transform; + { // Calculate box transform matrix + mat4 rotation_mat = Mat4FromQuaternion(rotation); + mat4 translation_mat; + translation_mat.SetTranslation(translation); + mat4 transform_mat = translation_mat * rotation_mat; + transform.setFromOpenGLMatrix(transform_mat.entries); + } + // Set up box collision shape in Bullet + btCollisionObject temp_object; + btBoxShape shape(btVector3(scale[0], scale[1], scale[2])); + temp_object.setCollisionShape(&shape); + temp_object.setWorldTransform(transform); + // Check for collision with each object + for(SceneGraph::object_list::iterator iter = scenegraph->collide_objects_.begin(); + iter != scenegraph->collide_objects_.end(); + ++iter) + { + Object* object = (*iter); + EntityType object_type = object->GetType(); + if(object_type == _env_object || object_type == _terrain_type){ + // Extract model from object + const Model* model = NULL; + BulletObject *bullet_object = NULL; + switch(object_type){ + case _env_object: + bullet_object = ((EnvObject*)object)->bullet_object_; + model = &Models::Instance()->GetModel(((EnvObject*)object)->model_id_); + break; + case _terrain_type: + bullet_object = ((TerrainObject*)object)->bullet_object_; + model = &((TerrainObject*)object)->terrain_.GetModel(); + break; + default: + break; + } + if(bullet_object && bullet_object->shape.get() && + (bullet_object->shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE || bullet_object->shape->getShapeType() == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE)) + { + // Check for collision between box and object, with triangle list + TriListResults tlr; + TriListCallback cb(tlr); + object->scenegraph_->bullet_world_->GetPairCollisions(temp_object, *bullet_object->body, cb); + // Add transformed triangles to list + const std::vector<int>& tris = tlr[bullet_object]; + for(unsigned tri_index=0; tri_index<tris.size(); ++tri_index){ + vec3 vert[3]; + int face_index = tris[tri_index]*3; + for(int j=0; j<3; ++j){ + int vert_index = model->faces[face_index+j]*3; + for(int k=0; k<3; ++k){ + vert[j][k] = model->vertices[vert_index+k]; + } + tri_verts.push_back(object->GetTransform() * vert[j]); + } + } + } + } + } + } // done getting tris intersecting box + + RasterizeTrisToVoxelField(tri_verts, g_voxel_field); + } + + // Create virtual box mesh, and create a voxel field from that + VoxelField box_voxel_field; + { + std::vector<vec3> tri_verts; + // Add box faces to voxel field + mat4 box_transform; + { + mat4 rotation_mat = Mat4FromQuaternion(rotation); + mat4 translation_mat; + translation_mat.SetTranslation(translation); + mat4 scale_mat; + scale_mat.SetScale(scale); + box_transform = translation_mat * rotation_mat * scale_mat; + } + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1,-1)); + + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1,-1)); + tri_verts.push_back(box_transform * vec3(-1, 1,-1)); + + tri_verts.push_back(box_transform * vec3(-1,-1, 1)); + tri_verts.push_back(box_transform * vec3( 1,-1, 1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + + tri_verts.push_back(box_transform * vec3(-1,-1, 1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + tri_verts.push_back(box_transform * vec3(-1, 1, 1)); + + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3(-1, 1,-1)); + tri_verts.push_back(box_transform * vec3(-1, 1, 1)); + + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3(-1, 1, 1)); + tri_verts.push_back(box_transform * vec3(-1,-1, 1)); + + tri_verts.push_back(box_transform * vec3( 1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + + tri_verts.push_back(box_transform * vec3( 1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + tri_verts.push_back(box_transform * vec3( 1,-1, 1)); + + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1,-1, 1)); + + tri_verts.push_back(box_transform * vec3(-1,-1,-1)); + tri_verts.push_back(box_transform * vec3( 1,-1, 1)); + tri_verts.push_back(box_transform * vec3(-1,-1, 1)); + + tri_verts.push_back(box_transform * vec3(-1, 1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + + tri_verts.push_back(box_transform * vec3(-1, 1,-1)); + tri_verts.push_back(box_transform * vec3( 1, 1, 1)); + tri_verts.push_back(box_transform * vec3(-1, 1, 1)); + + RasterizeTrisToVoxelField(tri_verts, box_voxel_field); + } + + VoxelSpanField empty_box_spans; + empty_box_spans.resize(box_voxel_field.spans.size()); + + // Get empty spans from box field + for(size_t column_index=0, len=box_voxel_field.spans.size(); column_index<len; ++column_index){ + std::list<VoxelSpan>& span_list = box_voxel_field.spans[column_index]; + std::list<VoxelSpan>& empty_span_list = empty_box_spans[column_index]; + span_list.sort(VoxelSpanHeightSort); + VoxelSpan* prev_span = NULL; + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end(); + ++iter) + { + VoxelSpan& span = *iter; + if(prev_span){ + VoxelSpan empty_span; + empty_span.height[0] = prev_span->height[1]; + empty_span.height[1] = span.height[0]; + empty_span_list.push_back(empty_span); + } + prev_span = &span; + } + } + + int voxel_field_start[2]; + voxel_field_start[0] = (int)(g_voxel_field.voxel_field_bounds[0][0]/g_voxel_field.voxel_size); + voxel_field_start[1] = (int)(g_voxel_field.voxel_field_bounds[0][2]/g_voxel_field.voxel_size); + int box_voxel_field_start[2]; + box_voxel_field_start[0] = (int)(box_voxel_field.voxel_field_bounds[0][0]/box_voxel_field.voxel_size); + box_voxel_field_start[1] = (int)(box_voxel_field.voxel_field_bounds[0][2]/box_voxel_field.voxel_size); + + VoxelSpanField cropped_voxel_field_spans; + cropped_voxel_field_spans.resize(box_voxel_field.spans.size()); + // Adapt triangle voxel field to box voxel field + for(size_t column_index=0, len=box_voxel_field.spans.size(); column_index<len; ++column_index){ + int world_coord[2]; + world_coord[0] = box_voxel_field_start[0]; + world_coord[0] += (column_index / box_voxel_field.voxel_field_dims[2]); + world_coord[1] = box_voxel_field_start[1]; + world_coord[1] += column_index % box_voxel_field.voxel_field_dims[2]; + int voxel_field_coord[2]; + voxel_field_coord[0] = world_coord[0] - voxel_field_start[0]; + voxel_field_coord[1] = world_coord[1] - voxel_field_start[1]; + int voxel_field_span_index = voxel_field_coord[0] * g_voxel_field.voxel_field_dims[2] + voxel_field_coord[1]; + std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index]; + std::list<VoxelSpan>* box_span_list = NULL; + if(voxel_field_coord[0] >= 0 && voxel_field_coord[1] >= 0 && + voxel_field_coord[0] < g_voxel_field.voxel_field_dims[0] && + voxel_field_coord[1] < g_voxel_field.voxel_field_dims[2]) + { + box_span_list = &g_voxel_field.spans[voxel_field_span_index]; + } + if(box_span_list) { + span_list = *box_span_list; + } + } + + // Crop tri spans to empty box field spans + for(size_t column_index=0, len=cropped_voxel_field_spans.size(); column_index<len; ++column_index){ + std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index]; + std::list<VoxelSpan>* box_span_list = &empty_box_spans[column_index]; + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end();) + { + VoxelSpan& span = *iter; + if(!box_span_list || box_span_list->empty()){ + iter = span_list.erase(iter); + } else { + LOG_ASSERT(box_span_list->size() == 1); + VoxelSpan& empty_span = box_span_list->front(); + if(span.height[1] > empty_span.height[0] || span.height[0] < empty_span.height[1]){ + iter = span_list.erase(iter); + } else { + span.height[0] = min(span.height[0], empty_span.height[0]); + span.height[1] = max(span.height[1], empty_span.height[1]); + ++iter; + } + } + } + } + + + // Get empty spans from voxel field + VoxelSpanField empty_field_spans; + empty_field_spans.resize(cropped_voxel_field_spans.size()); + for(size_t column_index=0, len=cropped_voxel_field_spans.size(); column_index<len; ++column_index){ + std::list<VoxelSpan>& empty_box_span_list = empty_box_spans[column_index]; + std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index]; + std::list<VoxelSpan>& empty_span_list = empty_field_spans[column_index]; + VoxelSpan* prev_span = NULL; + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end(); + ++iter) + { + VoxelSpan& span = *iter; + if(prev_span){ + VoxelSpan empty_span; + empty_span.height[0] = prev_span->height[1]; + empty_span.height[1] = span.height[0]; + if(empty_span.height[0] != empty_span.height[1]){ + empty_span_list.push_back(empty_span); + } + } + prev_span = &span; + } + if(!empty_box_span_list.empty()){ + if(span_list.empty()){ + empty_span_list = empty_box_span_list; + } else { + VoxelSpan empty_span; + empty_span.height[0] = empty_box_span_list.front().height[0]; + empty_span.height[1] = span_list.front().height[0]; + if(empty_span.height[0] != empty_span.height[1]){ + empty_span_list.push_front(empty_span); + } + empty_span.height[0] = span_list.back().height[1]; + empty_span.height[1] = empty_box_span_list.front().height[1]; + if(empty_span.height[0] != empty_span.height[1]){ + empty_span_list.push_back(empty_span); + } + } + } + } + + + // Flatten empty spans to contiguous memory + std::vector<VoxelSpan> span_vector; + std::vector<int> column_start_index; + column_start_index.resize(empty_field_spans.size()); + for(size_t column_index=0, len=empty_field_spans.size(); + column_index<len; + ++column_index) + { + column_start_index[column_index] = span_vector.size(); + std::list<VoxelSpan>& span_list = empty_field_spans[column_index]; + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end(); + ++iter) + { + span_vector.push_back(*iter); + } + } + // Cap start index list to avoid special case when finding num spans per column + column_start_index.push_back((int)span_vector.size()); + + // Find neighbors to each span + std::vector< std::list<int> > span_neighbors; + span_neighbors.resize(span_vector.size()); + for(size_t column_index=0, len=empty_field_spans.size(); + column_index<len; + ++column_index) + { + int num_spans[2]; + num_spans[0] = column_start_index[column_index+1] - column_start_index[column_index]; + if(num_spans[0] == 0){ + // No spans in this column, so skip to next + continue; + } + int coord[2]; + coord[0] = (int) (column_index / box_voxel_field.voxel_field_dims[2]); + coord[1] = column_index % box_voxel_field.voxel_field_dims[2]; + // Check column x+1 + if(coord[0] < box_voxel_field.voxel_field_dims[0] - 1){ + size_t check_column_index = column_index + 1; + num_spans[1] = column_start_index[check_column_index+1] - column_start_index[check_column_index]; + if(num_spans[1] > 0){ + int span_start[2]; + span_start[0] = column_start_index[column_index]; + span_start[1] = column_start_index[check_column_index]; + VoxelSpan* spans[2]; + spans[0] = &span_vector[span_start[0]]; + spans[1] = &span_vector[span_start[1]]; + GetColumnNeighbors(num_spans, spans, span_start, &span_neighbors); + } + } + // Check column z+1 + if(coord[1] < box_voxel_field.voxel_field_dims[0] - 1){ + size_t check_column_index = column_index + box_voxel_field.voxel_field_dims[2]; + num_spans[1] = column_start_index[check_column_index+1] - column_start_index[check_column_index]; + if(num_spans[1] > 0){ + int span_start[2]; + span_start[0] = column_start_index[column_index]; + span_start[1] = column_start_index[check_column_index]; + VoxelSpan* spans[2]; + spans[0] = &span_vector[span_start[0]]; + spans[1] = &span_vector[span_start[1]]; + GetColumnNeighbors(num_spans, spans, span_start, &span_neighbors); + } + } + } + + // Fill the top middle span, and flood fill from there + std::vector<int> filled; + filled.resize(span_vector.size(), 0); + std::queue<int> to_fill; + to_fill.push( column_start_index [ + (box_voxel_field.voxel_field_dims[0] / 2 * box_voxel_field.voxel_field_dims[2]) + + box_voxel_field.voxel_field_dims[2] / 2 ] ); + filled[to_fill.front()] = 1; + while(!to_fill.empty()){ + int span = to_fill.front(); + to_fill.pop(); + for(std::list<int>::iterator iter = span_neighbors[span].begin(); + iter != span_neighbors[span].end(); + ++iter) + { + int neighbor = *iter; + if(!filled[neighbor]){ + to_fill.push(neighbor); + filled[neighbor] = filled[span]+1; + } + } + } + + int kVoxelThinAmount = 16; + // Spawn probes in voxels + for(size_t column_index=0, len=empty_field_spans.size(); + column_index<len; + ++column_index) + { + std::list<VoxelSpan>& span_list = empty_field_spans[column_index]; + for(int i=column_start_index[column_index], end=column_start_index[column_index+1]; + i<end; ++i) + { + int voxel_x = (int)(column_index / box_voxel_field.voxel_field_dims[2]); + int voxel_z = column_index % box_voxel_field.voxel_field_dims[2]; + if(voxel_x % kVoxelThinAmount == 0 && voxel_z % kVoxelThinAmount == 0) { + float world_x = box_voxel_field.voxel_field_bounds[0][0] + (voxel_x+0.5f)*box_voxel_field.voxel_size; + float world_z = box_voxel_field.voxel_field_bounds[0][2] + (voxel_z+0.5f)*box_voxel_field.voxel_size; + VoxelSpan& span = span_vector[i]; + for(int j=span.height[1]; j<span.height[0]; ++j){ + if(j % kVoxelThinAmount == 0){ + float world_y = (j+0.5f) * box_voxel_field.voxel_size; + vec3 pos(world_x, world_y, world_z); + Object* ppo = new LightProbeObject(); + if(!filled[i]){ + ((LightProbeObject*)ppo)->GetScriptParams()->ASSetInt("Negative", 1); + } + ppo->SetTranslation(pos); + if( ActorsEditor_AddEntity(scenegraph, ppo) ) { + + } else { + LOGE << "Failed at adding probe to scenegraph" << std::endl; + delete ppo; + } + } + } + } + } + } + + VoxelSpanField final_field; + final_field.resize(empty_field_spans.size()); + for(size_t column_index=0, len=final_field.size(); + column_index<len; + ++column_index) + { + std::list<VoxelSpan>& span_list = final_field[column_index]; + for(int i=column_start_index[column_index], end=column_start_index[column_index+1]; + i<end; ++i) + { + if(filled[i] == 0){ + span_list.push_back(span_vector[i]); + } + } + } + + g_voxel_field = box_voxel_field; + g_voxel_field.spans = final_field; +} + +static int shutdown_timer = 100; + +static uint64_t level_load_settle_frame_count = 0; + +const char* stress_levels[] = { + "Data/Levels/og_story/19b_Magma_Arena.xml", + "Data/Levels/og_story/lukas-test05.xml", + "Data/Levels/og_story/lukas-test13.xml", + "Data/Levels/og_story/23a_barracks.xml", + "Data/Levels/og_story/23b_Rock_Arena_ffa.xml", + "Data/Levels/og_story/27_Sky_Ark.xml", + "Data/Levels/og_story/test10.xml", + "Data/Levels/og_story/lukas-test23.xml", + "Data/Levels/og_story/test13.xml", + "Data/Levels/og_story/22c_Waterfall_Arena_1v3.xml", + "Data/Levels/og_story/lukas-test36.xml", + "Data/Levels/og_story/test26.xml", + "Data/Levels/og_story/test25.xml", + "Data/Levels/og_story/lukas-test09.xml", + "Data/Levels/og_story/22b_Waterfall_Arena_1v4.xml", + "Data/Levels/og_story/lukas-test12.xml", + "Data/Levels/og_story/19_MagmaBarracks_Jill.xml", + "Data/Levels/og_story/lukas-test33.xml", + "Data/Levels/og_story/25_Garden_Duel.xml", + "Data/Levels/og_story/test06.xml", + "Data/Levels/og_story/11_Rebel_Base.xml", + "Data/Levels/og_story/test18.xml", + "Data/Levels/og_story/19c_Magma_Arena_1v1.xml", + "Data/Levels/og_story/01_Slaver_Camp_Landing.xml", + "Data/Levels/og_story/lukas-test27.xml", + "Data/Levels/og_story/23c_Rock_Arena_Wolves.xml", + "Data/Levels/og_story/cave_artPass.xml", + "Data/Levels/og_story/lukas-test01.xml", + "Data/Levels/og_story/14_The_Rats_Cache.xml", + "Data/Levels/og_story/test02.xml", + "Data/Levels/og_story/lukas-test32.xml", + "Data/Levels/og_story/lukas-test10.xml", + "Data/Levels/og_story/lukas-test03.xml", + "Data/Levels/og_story/test24.xml", + "Data/Levels/og_story/19_Magma_Barracks.xml", + "Data/Levels/og_story/test03.xml", + "Data/Levels/og_story/18_RudeAwakening_Jill.xml", + "Data/Levels/og_story/lukas-test02.xml", + "Data/Levels/og_story/18_RudeAwakening.xml", + "Data/Levels/og_story/lukas-test24.xml", + "Data/Levels/og_story/07_Beach_Boat_Assault.xml", + "Data/Levels/og_story/23_Rock_Arena.xml", + "Data/Levels/og_story/lukas-test08.xml", + "Data/Levels/og_story/27_Sky_Ark_David.xml", + "Data/Levels/og_story/test05.xml", + "Data/Levels/og_story/test15.xml", + "Data/Levels/og_story/22_Waterfall_Arena.xml", + "Data/Levels/og_story/test11.xml", + "Data/Levels/og_story/16_Tree_Rescue.xml", + "Data/Levels/og_story/test23.xml", + "Data/Levels/og_story/lukas-test17.xml", + "Data/Levels/og_story/12_Canyon_Ambush.xml", + "Data/Levels/og_story/test37.xml", + "Data/Levels/og_story/test04.xml", + "Data/Levels/og_story/lukas-test31.xml", + "Data/Levels/og_story/lukas-test26.xml", + "Data/Levels/og_story/20_Volcano_Mine.xml", + "Data/Levels/og_story/lukas-test16.xml", + "Data/Levels/og_story/13_Rat_Slavers.xml", + "Data/Levels/og_story/lukas-test15.xml", + "Data/Levels/og_story/lukas-test20.xml", + "Data/Levels/og_story/test14.xml", + "Data/Levels/og_story/test17.xml", + "Data/Levels/og_story/test32.xml", + "Data/Levels/og_story/lukas-test25.xml", + "Data/Levels/og_story/test09.xml", + "Data/Levels/og_story/test13_terrain.xml", + "Data/Levels/og_story/test19.xml", + "Data/Levels/og_story/21_Waterfall_Barracks.xml", + "Data/Levels/og_story/02_Slaver_Camp.xml", + "Data/Levels/og_story/lukas-test11.xml", + "Data/Levels/og_story/test12.xml", + "Data/Levels/og_story/test30.xml", + "Data/Levels/og_story/test16.xml", + "Data/Levels/og_story/lukas-test35.xml", + "Data/Levels/og_story/lukas-test04.xml", + "Data/Levels/og_story/test20.xml", + "Data/Levels/og_story/test01Stucco.xml", + "Data/Levels/og_story/test27.xml", + "Data/Levels/og_story/19e_Magma_Arena_Wolf.xml", + "Data/Levels/og_story/22b_Waterfall_Barracks_return.xml", + "Data/Levels/og_story/lukas-test21.xml", + "Data/Levels/og_story/lukas-test18.xml", + "Data/Levels/og_story/26_Sky_Ark_Cutscene.xml", + "Data/Levels/og_story/17_Rat_HQ.xml", + "Data/Levels/og_story/lukas-test29.xml", + "Data/Levels/og_story/lukas-test19.xml", + "Data/Levels/og_story/test35.xml", + "Data/Levels/og_story/24_Forced_Fight.xml", + "Data/Levels/og_story/13c_Rat_Slavers.xml", + "Data/Levels/og_story/test21.xml", + "Data/Levels/og_story/lukas-test14.xml", + "Data/Levels/og_story/test08.xml", + "Data/Levels/og_story/test29.xml", + "Data/Levels/og_story/09_Dog_Fort_Rescue.xml", + "Data/Levels/og_story/22_Waterfall_Arena_1v1.xml", + "Data/Levels/og_story/05_Watchtower_Build_Site_Rebuild.xml", + "Data/Levels/og_story/19d_Magma_Arena_ffa.xml", + "Data/Levels/og_story/lukas-test30.xml", + "Data/Levels/og_story/test22.xml", + "Data/Levels/og_story/23_Rock_Arena_2v2.xml", + "Data/Levels/og_story/test28.xml", + "Data/Levels/og_story/08_Ice_Cliff_Landing.xml", + "Data/Levels/og_story/13b_Rat_Slavers.xml", + "Data/Levels/og_story/08_Ice_Cliff_Landing_Crete.xml", + "Data/Levels/og_story/lukas-test28.xml", + "Data/Levels/og_story/test33.xml", + "Data/Levels/og_story/lukas-test07.xml", + "Data/Levels/og_story/test07.xml", + "Data/Levels/og_story/lukas-test34.xml", + "Data/Levels/og_story/test31.xml", + "Data/Levels/og_story/10_Storm_Troopers.xml", + "Data/Levels/og_story/test36.xml", + "Data/Levels/og_story/19b_Magma_Arena_2v2.xml", + "Data/Levels/og_story/test01.xml", + "Data/Levels/og_story/06_Occupied_Farm.xml", + "Data/Levels/og_story/lukas-test06.xml", + "Data/Levels/og_story/test34.xml", + "Data/Levels/og_story/04_Main_Slaver_Camp.xml", + "Data/Levels/og_story/05_Watchtower_Build_Site.xml", + "Data/Levels/og_story/15_Bayou.xml", + NULL +}; + +//Update the simulation +void Engine::Update() { + game_timer.UpdateWallTime(); + + PROFILER_ZONE(g_profiler_ctx, "Update"); + + if(check_save_level_changes_dialog_is_showing) { + // Wait for dialog to be complete + UpdateControls(ui_timer.timestep, false); + return; + } + +#ifdef WIN32 + { + PROFILER_ZONE(g_profiler_ctx, "Wait for object updates"); + if(WaitForSingleObject(data_change_notification, 0) == WAIT_OBJECT_0 || WaitForSingleObject(write_change_notification, 0) == WAIT_OBJECT_0) + { + RequestLiveUpdate(); + while(WaitForSingleObject(data_change_notification, 0) == WAIT_OBJECT_0){ + FindNextChangeNotification(data_change_notification); + } + while(write_change_notification != INVALID_HANDLE_VALUE && WaitForSingleObject(write_change_notification, 0) == WAIT_OBJECT_0){ + FindNextChangeNotification(write_change_notification); + } + } + } +#endif + +#if ENABLE_STEAMWORKS + { + PROFILER_ZONE(g_profiler_ctx, "Steam update"); + Steamworks::Instance()->Update(current_engine_state_.type != kEngineLevelState && current_engine_state_.type != kEngineEditorLevelState); + } +#endif + + while( queued_engine_state_.size() > 0 ) { + bool continue_state_change = true; + + EngineState new_engine_state; + + bool found = false; + std::deque<EngineState> history_copy = state_history; + std::deque<EngineState> final_pop; + + LOGI << "Checking if we actually allow any state changes" << std::endl; + + switch(current_engine_state_.type) { + case kEngineLevelState: + case kEngineEditorLevelState: + if( scenegraph_ ) { + if(!check_save_level_changes_dialog_is_finished && !check_save_level_changes_dialog_is_showing) { + // Trigger save dialog + Input::Instance()->SetGrabMouse(false); + check_save_level_changes_dialog_is_showing = true; + check_save_level_changes_dialog_quit_if_not_cancelled = false; + check_save_level_changes_dialog_is_finished = false; + return; + } + + continue_state_change = check_save_level_changes_last_result; + + // Clear dialog state + check_save_level_changes_dialog_is_showing = false; + check_save_level_changes_dialog_quit_if_not_cancelled = false; + check_save_level_changes_dialog_is_finished = false; + check_save_level_changes_last_result = false; + } + break; + case kEngineScriptableUIState: + break; + case kEngineCampaignState: + break; + case kEngineNoState: + break; + } + bool pushed_current_to_history = false; + + if( continue_state_change ) { + LOGI << "Performing a state queue request " << std::endl; + + EngineStateAction esa = queued_engine_state_.front(); + queued_engine_state_.pop_front(); + std::deque<EngineState> local_popped_past; + + switch( esa.type ) { + case kEngineStateActionPopState: + while( found == false ) { + EngineState candidate; + if( !history_copy.empty() ) { + candidate = history_copy.front(); + history_copy.pop_front(); + if( candidate.pop_past == false ) { + found = true; + LOGI << "Popping state to " << candidate << std::endl; + new_engine_state = candidate; + state_history = history_copy; + final_pop = local_popped_past; + } else { + LOGI << "Popping and tossing state " << candidate << std::endl; + local_popped_past.push_front(candidate); + } + } else { + if( esa.allow_game_exit ) { + Input::Instance()->RequestQuit(); + } + found = true; + } + } + break; + case kEngineStateActionPopUntilType: + while( found == false ) { + EngineState candidate; + if( !history_copy.empty() ) { + candidate = history_copy.front(); + history_copy.pop_front(); + + if( candidate.type == esa.state.type && candidate.pop_past == false ) { + found = true; + LOGI << "Popping state to " << candidate << std::endl; + new_engine_state = candidate; + state_history = history_copy; + final_pop = local_popped_past; + } else { + LOGI << "Popping and tossing state " << candidate << std::endl; + local_popped_past.push_front(candidate); + } + } else { + if( esa.allow_game_exit ) { + Input::Instance()->RequestQuit(); + } + found = true; + } + } + break; + case kEngineStateActionPopUntilID: + while( found == false ) { + EngineState candidate; + if( !history_copy.empty() ) { + candidate = history_copy.front(); + history_copy.pop_front(); + + if( candidate.id == esa.state.id && candidate.pop_past == false ) { + found = true; + LOGI << "Popping state to " << candidate << std::endl; + new_engine_state = candidate; + state_history = history_copy; + final_pop = local_popped_past; + } else { + LOGI << "Popping and tossing state " << candidate << std::endl; + local_popped_past.push_front(candidate); + } + } else { + if( esa.allow_game_exit ) { + Input::Instance()->RequestQuit(); + } + found = true; + } + } + + break; + case kEngineStateActionPushState: + state_history.push_front( current_engine_state_ ); + pushed_current_to_history = true; + new_engine_state = esa.state; + break; + + } + + if(pushed_current_to_history == false) { + final_pop.push_front(current_engine_state_); + } + + //Trigger final destruction of engine state + while( final_pop.size() > 0 ) { + EngineState final_state_pop = final_pop.front(); + LOGI << "There are some final popping to do of " << final_state_pop << std::endl; + final_pop.pop_front(); + switch(final_state_pop.type) { + case kEngineLevelState: + case kEngineEditorLevelState: + Input::Instance()->SetUpForXPlayers(0); + current_menu_player = -1; + break; + case kEngineScriptableUIState: + break; + case kEngineCampaignState: + final_state_pop.campaign->LeaveCampaign(); + break; + case kEngineNoState: + LOGE << "No state" << std::endl; + break; + } + } + + //We want to load something new! + if( new_engine_state.type != kEngineNoState ) + { + cursor.SetCursor(DEFAULT_CURSOR); + LOGI << "Switching game state" << std::endl; + ScriptableCampaign* sc = GetCurrentCampaign(); + //Lets unload what we previously had loaded first. + switch(current_engine_state_.type) { + case kEngineLevelState: + case kEngineEditorLevelState: + if( sc ) { + sc->LeaveLevel(); + } + ClearLoadedLevel(); + Input::Instance()->SetGrabMouse(false); + break; + case kEngineScriptableUIState: + if( scriptable_menu_ != NULL ) { + delete scriptable_menu_; + scriptable_menu_ = NULL; + IMElement::DestroyQueuedIMElements(); + LOGI << "Disposing of scriptable menu" << std::endl; + IMrefCountTracker.logSanityCheck(); + } + break; + case kEngineCampaignState: + break; + case kEngineNoState: + LOGE << "No state" << std::endl; + break; + } + + current_engine_state_ = new_engine_state; + + //Now load/initialize what is requested. + switch(current_engine_state_.type) { + case kEngineLevelState: + case kEngineEditorLevelState: + Engine::Instance()->GetSound()->TransitionToSong("overgrowth_silence"); + if( current_engine_state_.path.isValid() ) { + QueueLevelToLoad( current_engine_state_.path ); + } else { + ScriptableCampaign *sc = GetCurrentCampaign(); + if( sc ) { + std::string campaign_id = sc->GetCampaignID(); + ModInstance::Campaign camp = ModLoading::Instance().GetCampaign(campaign_id); + + for( size_t i = 0; i < camp.levels.size(); i++ ) { + if( strmtch(camp.levels[i].id, current_engine_state_.id) ) { + std::string short_path = std::string("Data/Levels/") + std::string(camp.levels[i].path); + + if( FileExists( short_path, kAnyPath ) ) + { + Path path = FindFilePath(short_path,kAnyPath); + QueueLevelToLoad( path ); + } + else + { + LOGE << "Unable to find level: " << short_path << ". Will not change state" << std::endl; + EngineStateAction esa; + esa.type = kEngineStateActionPopState; + esa.allow_game_exit = true; + queued_engine_state_.push_back(esa); + } + } + } + } else { + LOGE << "Currently no campaign is active, can't load level with id: " << current_engine_state_.id<< std::endl; + EngineStateAction esa; + esa.type = kEngineStateActionPopState; + esa.allow_game_exit = true; + queued_engine_state_.push_back(esa); + } + } + Shaders::Instance()->create_program_warning = false; + break; + case kEngineScriptableUIState: { + UIShowCursor(0); + scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticScriptableUICallback); + ASData as_data; + as_data.scenegraph = scenegraph_; + as_data.gui = &gui; + Path script_path = current_engine_state_.path; + scriptable_menu_->Initialize(script_path, as_data, break_on_script_change); + latest_menu_path_ = script_path; + + // Display popups queued while we didn't have a menu + while(!popup_pueue.empty()) { + scriptable_menu_->QueueBasicPopup(std::get<0>(popup_pueue.front()), std::get<1>(popup_pueue.front())); + popup_pueue.pop_front(); + } + break;} + case kEngineCampaignState: + current_engine_state_.campaign->EnterCampaign(); + break; + case kEngineNoState: + LOGE << "No state" << std::endl; + break; + } + + } + } else { + queued_engine_state_.clear(); + } + } + + if( queued_level_.isValid() ) { + LoadLevel(queued_level_); + queued_level_ = Path(); + PROFILER_NAME_TIMELINE(g_profiler_ctx, "Gameplay"); + + if( level_loaded_ ) { + if(scenegraph_) { + if(scenegraph_->map_editor->state_ != MapEditor::kInGame){ + cursor.SetVisible(true); + } + } + + ScriptableCampaign* sc = GetCurrentCampaign(); + if( sc ) { + sc->EnterLevel(); + } + } else { + EngineStateAction esa; + esa.type = kEngineStateActionPopState; + esa.allow_game_exit = true; + queued_engine_state_.push_back(esa); + } + } + int num_ui_timesteps = ui_timer.GetStepsNeeded(); + num_ui_timesteps = min(num_ui_timesteps,_max_steps_per_frame); + ui_timer.updates_since_last_frame = num_ui_timesteps; + + if(Input::Instance()->WasQuitRequested()){ + if(scenegraph_){ + if (!check_save_level_changes_dialog_is_finished && !check_save_level_changes_dialog_is_showing) { + // Trigger save dialog + Input::Instance()->SetGrabMouse(false); + check_save_level_changes_dialog_is_showing = true; + check_save_level_changes_dialog_quit_if_not_cancelled = true; + check_save_level_changes_dialog_is_finished = false; + return; + } + + quitting_ = check_save_level_changes_last_result; + + // Clear dialog state + check_save_level_changes_dialog_is_showing = false; + check_save_level_changes_dialog_quit_if_not_cancelled = false; + check_save_level_changes_dialog_is_finished = false; + check_save_level_changes_last_result = false; + } else { + quitting_ = true; + } + Input::Instance()->ClearQuitRequested(); + } + sound.UpdateGameTimestep(game_timer.timestep); + sound.Update(); + + if (Online::Instance()->IsAwaitingShutdown()) { + Online::Instance()->StopMultiplayer(); + } + + if (Online::Instance()->IsActive()) { + Online::Instance()->CheckPendingMessages(); + } + + if(!level_loaded_ || waiting_for_input_) { + GetAssetManager()->SetLoadWarningMode(false,"",""); + // Main menu loop + for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){ + UpdateControls(ui_timer.timestep, false); + ui_timer.Update(); + if(scriptable_menu_ && !waiting_for_input_){ + scriptable_menu_->Update(); + if(scriptable_menu_->IsDeleteScheduled()){ + + delete scriptable_menu_; + scriptable_menu_ = NULL; + } + } + } + sound.UpdateGameTimescale(1.0f); + if(waiting_for_input_){ + waiting_for_input_ = false; + bool skip_loading_pause = config["skip_loading_pause"].toNumber<bool>(); + bool no_dialogues = config["no_dialogues"].toNumber<bool>(); + if (last_loading_input_time < finished_loading_time && + (load_screen_tip[0] != '\0' || Online::Instance()->IsActive()) && + !skip_loading_pause && + !no_dialogues && + !interrupt_loading) + { + waiting_for_input_ = true; + } + + // Clients should never be allowed to end the loading screen themselves + // Multiplayer->ForceMapStartOnLoad() is the authority on level loading in Multiplayer (for the client) + if(Online::Instance()->IsClient()) { + waiting_for_input_ = !Online::Instance()->ForceMapStartOnLoad(); + } + } + } else { + if( scenegraph_->map_editor->state_ == MapEditor::kInGame ) { + GetAssetManager()->SetLoadWarningMode(true,"Main Game Mode",scenegraph_->level_path_.GetOriginalPath()); + } else { + GetAssetManager()->SetLoadWarningMode(false,"",""); + } + // Fixed time steps + const std::vector<ASContext*>& active_contexts = ASProfiler::GetActiveContexts(); + if(paused) { + for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){ + PROFILER_ZONE(g_profiler_ctx, "Timestep"); + ui_timer.Update(); + avatar_control_manager.Update(); + DebugDraw::Instance()->Update(game_timer.timestep); + DebugDrawAux::Instance()->Update(game_timer.timestep); + UpdateControls(ui_timer.timestep, false); + ActiveCameras::Get()->getCameraObject()->Update(ui_timer.timestep); + if (scenegraph_->map_editor->state_ != MapEditor::kInGame) { + PROFILER_ZONE(g_profiler_ctx, "Map editor update"); + scenegraph_->map_editor->Update(&cursor); + } + { + PROFILER_ZONE(g_profiler_ctx, "Level update"); + scenegraph_->level->Update(paused); + } + + for(size_t i=0, len=scenegraph_->movement_objects_.size(); i<len; ++i) { + MovementObject* mo = (MovementObject*)scenegraph_->movement_objects_[i]; + if(mo->controlled){ + mo->UpdatePaused(); + } + } + HandleRabbotToggleControls(); + + for(size_t i = 0; i < active_contexts.size(); ++i) { + active_contexts[i]->profiler.Update(); + } + } + } else { + int num_game_timesteps = game_timer.GetStepsNeeded(); + num_game_timesteps = min(num_game_timesteps,_max_steps_per_frame); + game_timer.updates_since_last_frame = num_game_timesteps; + + for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){ + ui_timer.Update(); + } + for(int curr_step=0; curr_step<num_game_timesteps; curr_step++) { + PROFILER_ZONE(g_profiler_ctx, "Timestep"); + { + PROFILER_ZONE(g_profiler_ctx, "UpdateControlledModule"); + avatar_control_manager.Update(); + } + { + PROFILER_ZONE(g_profiler_ctx, "Update debugdraw"); + DebugDraw::Instance()->Update(game_timer.timestep); + DebugDrawAux::Instance()->Update(game_timer.timestep); + } + + if(!paused){ + PROFILER_ZONE(g_profiler_ctx, "Update timer"); + game_timer.Update(); + } + { + PROFILER_ZONE(g_profiler_ctx, "Update controls"); + UpdateControls(game_timer.timestep, false); + } + { + PROFILER_ZONE(g_profiler_ctx, "Update flares"); + scenegraph_->flares.Update(game_timer.timestep); + } + + + + if(!paused){ + PROFILER_ZONE(g_profiler_ctx, "Scenegraph update"); + scenegraph_->Update(game_timer.timestep, game_timer.game_time); + } else { + ActiveCameras::Get()->getCameraObject()->Update(game_timer.timestep); + } + + // if host, go over all changes and send over socket. + // client, go over all updates for env objects + if (Online::Instance()->IsActive()) { + Online::Instance()->UpdateObjects(scenegraph_); + } + + if (Online::Instance()->IsActive()) { + for (uint32_t i = 0; i < scenegraph_->visible_static_meshes_.size(); i++) { + EnvObject * eo = scenegraph_->visible_static_meshes_[i]; + + eo->Update(game_timer.timestep); + } + } + + + if (scenegraph_->map_editor->state_ != MapEditor::kInGame) { + PROFILER_ZONE(g_profiler_ctx, "Map editor update"); + scenegraph_->map_editor->Update(&cursor); + } + + if(kLightProbeEnabled){ + // TODO: move alongside other editor input processing? + if (KeyCommand::CheckPressed(Input::Instance()->getKeyboard(), KeyCommand::kBakeGI, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL)) { + scenegraph_->map_editor->BakeLightProbes(0); + } + } + if(!paused){ + PROFILER_ZONE(g_profiler_ctx, "Animation effects update"); + particle_types.Update(game_timer.timestep); + //AnimationEffects::Instance()->Update(game_timer.timestep); + } + { + PROFILER_ZONE(g_profiler_ctx, "Level update"); + scenegraph_->level->Update(paused); + } + HandleRabbotToggleControls(); + // Disposing of an object might in turn queue up more items + // to delete, so make we loop until we're at the end of + // object_ids_to_delete + for(size_t i = 0; i < scenegraph_->object_ids_to_delete.size(); ++i){ + scenegraph_->map_editor->DeleteID(scenegraph_->object_ids_to_delete[i]); + } + scenegraph_->object_ids_to_delete.clear(); + + for(size_t i = 0; i < active_contexts.size(); ++i) { + active_contexts[i]->profiler.Update(); + } + } + sound.UpdateGameTimescale(powf(game_timer.time_scale / current_global_scale_mult, 0.5f)); + } + + level_updated_ = min(10,level_updated_+1); + } + + ScriptableCampaign *sc = GetCurrentCampaign(); + if(sc) { + sc->Update(); + } + + if( !IsStateQueued() && config["quit_after_load"].toBool() ) + { + if( shutdown_timer < 0 ) + { + Input::Instance()->RequestQuit(); + } + + shutdown_timer--; + } + + asset_manager.Update(); + as_network.Update(); + + + UpdateImGui(); + if (Online::Instance() != nullptr) { + Online::Instance()->LateUpdate(GetSceneGraph()); + } + + static int next_load_stress_level = 0; + if(!cache_generation_paths.empty() && level_load_settle_frame_count == 0) { + back_to_menu = true; + + EngineState newState("IsThisUsed", kEngineLevelState, cache_generation_paths.front()); + QueueState(newState); + + cache_generation_paths.erase(cache_generation_paths.begin()); + level_load_settle_frame_count = 60 * 4; + } else if(level_load_settle_frame_count > 0) { + --level_load_settle_frame_count; + } else if(runtime_level_load_stress) { + if(!config["level_load_stress"].toNumber<bool>()) { + runtime_level_load_stress = false; + } else { + if( stress_levels[next_load_stress_level] == NULL ) { + next_load_stress_level = 0; + } + if( stress_levels[next_load_stress_level] != NULL ) { + if( queued_engine_state_.size() == 0 ) { + Path level = FindFilePath( stress_levels[next_load_stress_level] ); + if( level.isValid() ) { + QueueLevelCacheGeneration(level); + } + next_load_stress_level++; + } + } + } + } else if(back_to_menu) { + cache_generation_paths.clear(); + + EngineStateAction esa; + esa.type = kEngineStateActionPopUntilType; + esa.allow_game_exit = false; + esa.state.type = kEngineScriptableUIState; + QueueState(esa); + back_to_menu = false; + } + +#if MONITOR_MEMORY + if((frame_counter % (60)) == 0) { + LOGI.Format("malloc Calls This Frame: %d\n", GetAndResetMallocAllocationCount() ); + LOGI.Format("malloc Current Allocations: %d\n", GetCurrentNumberOfMallocAllocations() ); + for( int i = 0; i < OG_MALLOC_TYPE_COUNT; i++ ) { + uint64_t mem_cb = GetCurrentTotalMallocAllocation(i); + if( mem_cb >= 1024 ) { + LOGI.Format("malloc Memory Use (%s): %u KiB\n", OgMallocTypeString(i), (unsigned)(mem_cb / 1024) ); + } else { + LOGI.Format("malloc Memory Use (%s): %u B\n", OgMallocTypeString(i), (unsigned)mem_cb ); + } + } + } +#endif + + save_file_.ExecuteQueuedWrite(); + + if(current_engine_state_.type != kEngineLevelState) { + if(live_update_countdown > 0 && --live_update_countdown == 0) { + if( config["enable_live_update"].toBool() && frame_counter > 30) { + LiveUpdate(scenegraph_, &asset_manager, scriptable_menu_); + } + } + } + + IMElement::DestroyQueuedIMElements(); + + frame_counter++; + + DisplayLastQueuedError(); + + if( queued_engine_state_.size() > 0 ) { // Avoid drawing bad frames, e.g. when loading level from dialogue + Update(); + } +} + +std::vector<vec3> sphere_points; + +static TextureRef histogram_tex; +static const int kHistogramBuckets = 256; +static const int kHistogramRes = 128; +static const bool kDrawHistogram = false; + +struct FramebufferObject { + GLuint id; + TextureRef bound_tex; +}; + +static void CreateHistogram(const TextureRef& color_tex) { + Shaders* shaders = Shaders::Instance(); + Textures* textures = Textures::Instance(); + Graphics *graphics = Graphics::Instance(); + + static FramebufferObject framebuffer; + static int shader_id; + static int vert_attrib_id; + //static int buckets_uniform_id; + static VBOContainer pixel_vbo; + if(!histogram_tex.valid()){ + histogram_tex = textures->makeTexture(kHistogramBuckets, 1, GL_RGBA16F, GL_RGBA); + textures->SetTextureName(histogram_tex, "Histogram"); + graphics->genFramebuffers(&framebuffer.id, "histogram"); + CHECK_GL_ERROR(); + graphics->PushFramebuffer(); + graphics->RenderFramebufferToTexture(framebuffer.id, histogram_tex); + CHECK_GL_ERROR(); + framebuffer.bound_tex = histogram_tex; + graphics->PopFramebuffer(); + CHECK_GL_ERROR(); + shader_id = shaders->returnProgram("histogram_create"); + shaders->setProgram(shader_id); + vert_attrib_id = shaders->returnShaderAttrib("pixel_uv", shader_id); + CHECK_GL_ERROR(); + std::vector<GLfloat> pixel_data; + pixel_data.resize(kHistogramRes * kHistogramRes * 2); + for(int i=0, index=0; i<kHistogramRes; ++i){ + for(int j=0; j<kHistogramRes; ++j){ + pixel_data[index+0] = (j+0.5f)/(float)kHistogramRes; + pixel_data[index+1] = (i+0.5f)/(float)kHistogramRes; + index += 2; + } + } + pixel_vbo.Fill(kVBOFloat | kVBOStatic, pixel_data.size() * sizeof(GLfloat), &pixel_data[0]); + CHECK_GL_ERROR(); + } + CHECK_GL_ERROR(); + graphics->PushViewport(); + CHECK_GL_ERROR(); + graphics->setViewport(0, 0, kHistogramBuckets, 1); + GLState gl_state; + gl_state.blend= true; + gl_state.depth_test = false; + gl_state.depth_write = false; + gl_state.cull_face = false; + gl_state.blend_src = GL_SRC_ALPHA; + gl_state.blend_dst = GL_ONE; + graphics->setGLState(gl_state); + CHECK_GL_ERROR(); + graphics->PushFramebuffer(); + CHECK_GL_ERROR(); + graphics->bindFramebuffer(framebuffer.id); + CHECK_GL_ERROR(); + shaders->setProgram(shader_id); + textures->bindTexture(color_tex); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + CHECK_GL_ERROR(); + graphics->EnableVertexAttribArray(vert_attrib_id); + CHECK_GL_ERROR(); + pixel_vbo.Bind(); + glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + CHECK_GL_ERROR(); + glPointSize(1); + graphics->DrawArrays(GL_POINTS, 0, kHistogramRes*kHistogramRes); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + + graphics->BindArrayVBO(0); + graphics->PopFramebuffer(); + graphics->PopViewport(); +} + +static void DrawHistogram() { + Shaders* shaders = Shaders::Instance(); + Textures* textures = Textures::Instance(); + Graphics *graphics = Graphics::Instance(); + + static int shader_id = -1; + static VBOContainer vbo; + if(shader_id == -1){ + shader_id = shaders->returnProgram("histogram_draw"); + CHECK_GL_ERROR(); + std::vector<GLfloat> data; + data.resize(kHistogramBuckets * 4); + for(int i=0, index=0; i<kHistogramBuckets; ++i){ + data[index+0] = (float)i; + data[index+1] = 0.0f; + data[index+2] = (float)i; + data[index+3] = 1.0f; + index += 4; + } + vbo.Fill(kVBOFloat | kVBOStatic, data.size() * sizeof(GLfloat), &data[0]); + CHECK_GL_ERROR(); + } + + GLState gl_state; + gl_state.blend= true; + gl_state.depth_test = false; + gl_state.depth_write = false; + gl_state.cull_face = false; + graphics->setGLState(gl_state); + shaders->setProgram(shader_id); + int vert_attrib_id = shaders->returnShaderAttrib("lines", shader_id); + int uniform_mvp_id = shaders->returnShaderVariable("mvp", shader_id); + int uniform_buckets_id = shaders->returnShaderVariable("num_buckets", shader_id); + glm::mat4 proj_mat; + proj_mat = glm::ortho(0.0f, (float)graphics->viewport_dim[2], 0.0f, (float)graphics->viewport_dim[3]); + glm::mat4 mvp_mat = proj_mat; + shaders->SetUniformInt(uniform_buckets_id, kHistogramBuckets); + shaders->SetUniformMat4(uniform_mvp_id, (const GLfloat*)&mvp_mat); + textures->bindTexture(histogram_tex); + graphics->EnableVertexAttribArray(vert_attrib_id); + vbo.Bind(); + glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + glLineWidth(1); + graphics->DrawArrays(GL_LINES, 0, kHistogramBuckets*2); + graphics->ResetVertexAttribArrays(); + graphics->BindArrayVBO(0); +} + +static void DrawQuad(int shader_id) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Engine.cpp DrawQuad"); + Shaders* shaders = Shaders::Instance(); + Graphics *graphics = Graphics::Instance(); + int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id); + graphics->EnableVertexAttribArray(vert_attrib_id); + glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + graphics->ResetVertexAttribArrays(); +} + +void ApplyPostEffects(std::string post_shader, Engine::DrawingViewport drawing_viewport, vec2 active_screen_start, vec2 active_screen_end, Engine::PostEffectsType post_effects_type, const VBOContainer& quad_vert_vbo, const VBOContainer& quad_index_vbo) {; + Shaders* shaders = Shaders::Instance(); + Textures* textures = Textures::Instance(); + Graphics *graphics = Graphics::Instance(); + Camera* camera = ActiveCameras::Get(); + + PROFILER_GPU_ZONE(g_profiler_ctx, "Apply post effects"); + graphics->PopFramebuffer(); + + if (graphics->multisample_framebuffer_exists) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Blit MSAA color buffer"); + graphics->BlitColorBuffer(); + } + + if(kDrawHistogram){ + CreateHistogram(graphics->screen_color_tex); + } + + GLState gl_state; + gl_state.blend = false; + gl_state.cull_face = false; + gl_state.depth_test = false; + gl_state.depth_write = false; + graphics->setGLState(gl_state); + + // Blur screen + quad_vert_vbo.Bind(); + quad_index_vbo.Bind(); + CHECK_GL_ERROR(); + + if(post_effects_type == Engine::kStraight){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw unfiltered screen quad"); + // Draw unfiltered HDR scene (e.g. for reflection capture) + int shader_id = shaders->returnProgram(post_shader); + graphics->PushViewport(); + if(drawing_viewport != Engine::kViewport){ + graphics->setViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]); + } else { + glViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]); + } + shaders->setProgram(shader_id); + textures->bindTexture(graphics->screen_color_tex); + //glEnable(GL_FRAMEBUFFER_SRGB ); + DrawQuad(shader_id); + //glDisable(GL_FRAMEBUFFER_SRGB ); + graphics->PopViewport(); + graphics->PopViewport(); + } /*else if(post_effects_type == Engine::kFinal && drawing_viewport == Engine::kViewport){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Tone map only"); + int shader_id = shaders->returnProgram(post_shader + " #TONE_MAP"); + graphics->PushViewport(); + glViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]); + shaders->setProgram(shader_id); + textures->bindTexture(graphics->screen_color_tex); + glEnable(GL_FRAMEBUFFER_SRGB ); + DrawQuad(shader_id); + glDisable(GL_FRAMEBUFFER_SRGB ); + graphics->PopViewport(); + graphics->PopViewport(); + } */else if(post_effects_type == Engine::kFinal) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Full post effects"); + static const GLbitfield all_buffers = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + static const GLenum all_attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT }; + graphics->PushViewport(); + graphics->PushFramebuffer(); + + graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + + TextureRef final_texture = graphics->screen_color_tex; + TextureRef temp_texture = graphics->post_effects.temp_screen_tex; + + graphics->EnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + + float curr_time = game_timer.game_time + game_timer.GetInterpWeight() / 120.0f; + if(graphics->config_.motion_blur_amount_ > 0.01f) + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Motion blur"); + { + mat4 temp = camera->GetViewMatrix(); + if(curr_time != prev_view_time){ + temp.SetTranslationPart(temp.GetTranslationPart() + camera->GetViewMatrix().GetRotatedvec3(ActiveCameras::Get()->GetCenterVel() * (curr_time - prev_view_time))); + float rotation_blur_amount = 0.125f; + temp = mix(temp, ActiveCameras::Get()->prev_view_mat, rotation_blur_amount); + } + int shader_id = shaders->returnProgram(post_shader + " #CALC_MOTION_BLUR"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + shaders->SetUniformMat4("proj_mat",camera->GetProjMatrix()); + shaders->SetUniformMat4("prev_view_mat", temp);//ActiveCameras::Get()->prev_view_mat); + shaders->SetUniformMat4("view_mat",camera->GetViewMatrix()); + if(curr_time != prev_view_time){ + shaders->SetUniformFloat("time_offset",curr_time - prev_view_time); + } else { + shaders->SetUniformFloat("time_offset", game_timer.timestep); + } + + textures->bindTexture(final_texture); + textures->bindTexture(graphics->screen_vel_tex, 2); + textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE); + textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH); + graphics->framebufferColorTexture2D(temp_texture); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + { // Find dominant motion blur directions + int intermediate_width = graphics->render_dims[0] / 4; + int intermediate_height = graphics->render_dims[1] / 4; + graphics->setViewport(0,0,intermediate_width,intermediate_height); + { // Lower resolution of overbright for bloom + int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + shaders->SetUniformFloat("src_lod",0.0f); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(temp_texture, 2); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + // Downsample bloom again + int downsampled_width = graphics->render_dims[0] / 16; + int downsampled_height = graphics->render_dims[1] / 16; + graphics->setViewport(0,0,downsampled_width,downsampled_height); + { + int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",intermediate_width); + shaders->SetUniformInt("screen_height",intermediate_height); + shaders->SetUniformFloat("src_lod",2.0f); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(temp_texture, 4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + // Apply separable guassian blur to further down-sampled overbright image + { + int shader_id = shaders->returnProgram(post_shader + " #BLUR_HORZ"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",downsampled_width); + shaders->SetUniformInt("screen_height",downsampled_height); + shaders->SetUniformFloat("src_lod",4.0f); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(final_texture,4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + { + int shader_id = shaders->returnProgram(post_shader + " #BLUR_VERT"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",downsampled_width); + shaders->SetUniformInt("screen_height",downsampled_height); + shaders->SetUniformFloat("src_lod",4.0f); + textures->bindTexture(final_texture); + graphics->framebufferColorTexture2D(temp_texture,4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]); + } + + { + int shader_id = shaders->returnProgram(post_shader + " #APPLY_MOTION_BLUR"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + shaders->SetUniformFloat("time_offset",curr_time - prev_view_time); + shaders->SetUniformFloat("motion_blur_mult", graphics->config_.motion_blur_amount_); + + textures->bindTexture(final_texture); + textures->bindTexture(temp_texture, 2); + textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE); + textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH); + graphics->framebufferColorTexture2D(graphics->screen_vel_tex); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + final_texture = graphics->screen_vel_tex; + } + } + + // Depth of field + if(graphics->config_.depth_of_field() && (camera->far_blur_amount > 0.0f || camera->near_blur_amount > 0.0f)) + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Depth of field"); + int shader_id = graphics->config_.depth_of_field_reduced() + ? shaders->returnProgram(post_shader + " #DOF #DOF_LESS") + : shaders->returnProgram(post_shader + " #DOF"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + shaders->SetUniformFloat("time",game_timer.game_time); + + shaders->SetUniformFloat("near_blur_amount",camera->near_blur_amount); + shaders->SetUniformFloat("far_blur_amount",camera->far_blur_amount); + shaders->SetUniformFloat("near_sharp_dist",camera->near_sharp_dist); + shaders->SetUniformFloat("far_sharp_dist",camera->far_sharp_dist); + shaders->SetUniformFloat("near_blur_transition_size",camera->near_blur_transition_size); + shaders->SetUniformFloat("far_blur_transition_size",camera->far_blur_transition_size); + + textures->bindTexture(final_texture); + textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE); + textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH); + graphics->framebufferColorTexture2D(temp_texture); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + shader_id = shaders->returnProgram(post_shader + " #COPY"); + + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(final_texture); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + } + + + CHECK_GL_ERROR(); + { // Apply tone mapping -- compress dynamic range + PROFILER_GPU_ZONE(g_profiler_ctx, "Tone map"); + int shader_id = shaders->returnProgram(post_shader + " #TONE_MAP"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + shaders->SetUniformFloat("saturation",Engine::Instance()->GetSceneGraph()->level->script_params().ASGetFloat("Saturation")); + if(g_albedo_only){ + shaders->SetUniformFloat("black_point",0.0f); + shaders->SetUniformFloat("white_point",1.0f); + } else { + shaders->SetUniformFloat("black_point",graphics->hdr_black_point); + shaders->SetUniformFloat("white_point",graphics->hdr_white_point); + } + shaders->SetUniformVec3("tint",camera->tint); + shaders->SetUniformVec3("vignette_tint",camera->vignette_tint); + textures->bindTexture(final_texture); + graphics->framebufferColorTexture2D(graphics->post_effects.tone_mapped_tex); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + final_texture = graphics->post_effects.tone_mapped_tex; + } + + if(graphics->hdr_bloom_mult > 0.0f) + { // This is all bloom + PROFILER_GPU_ZONE(g_profiler_ctx, "Bloom"); + CHECK_GL_ERROR(); + + int intermediate_width = graphics->render_dims[0] / 4; + int intermediate_height = graphics->render_dims[1] / 4; + graphics->setViewport(0,0,intermediate_width,intermediate_height); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + + { // Isolate pixels that are brighter than pure white + int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE #OVERBRIGHT"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",graphics->render_dims[0]); + shaders->SetUniformInt("screen_height",graphics->render_dims[1]); + if(g_albedo_only){ + shaders->SetUniformFloat("black_point",0.0f); + shaders->SetUniformFloat("white_point",1.0f); + } else { + shaders->SetUniformFloat("black_point",graphics->hdr_black_point); + shaders->SetUniformFloat("white_point",graphics->hdr_white_point); + } + shaders->SetUniformFloat("bloom_mult",graphics->hdr_bloom_mult); + shaders->SetUniformFloat("src_lod",0.0f); + textures->bindTexture(final_texture); + graphics->framebufferColorTexture2D(temp_texture, 2); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + { + int shader_id = shaders->returnProgram(post_shader + " #BLUR_DIR"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",intermediate_width); + shaders->SetUniformInt("screen_height",intermediate_width); + shaders->SetUniformFloat("src_lod",2.0f); + + shaders->SetUniformInt("horz",1); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(final_texture, 2); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + shaders->SetUniformInt("horz",0); + textures->bindTexture(final_texture); + graphics->framebufferColorTexture2D(temp_texture, 2); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + // Downsample bloom again + int downsampled_width = graphics->render_dims[0] / 16; + int downsampled_height = graphics->render_dims[1] / 16; + graphics->setViewport(0,0,downsampled_width,downsampled_height); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + { + int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",intermediate_width); + shaders->SetUniformInt("screen_height",intermediate_height); + shaders->SetUniformFloat("src_lod",2.0f); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(temp_texture, 4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + // Apply separable guassian blur to further down-sampled overbright image + { + int shader_id = shaders->returnProgram(post_shader + " #BLUR_DIR"); + shaders->setProgram(shader_id); + shaders->SetUniformInt("screen_width",downsampled_width); + shaders->SetUniformInt("screen_height",downsampled_height); + shaders->SetUniformFloat("src_lod",4.0f); + + shaders->SetUniformInt("horz",1); + textures->bindTexture(temp_texture); + graphics->framebufferColorTexture2D(final_texture, 4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + shaders->SetUniformInt("horz",0); + textures->bindTexture(final_texture); + graphics->framebufferColorTexture2D(temp_texture, 4); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + + // Add downsampled and super downsampled bloom to the original tone-mapped image + graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]); + { + int shader_id = shaders->returnProgram(post_shader + " #ADD"); + shaders->setProgram(shader_id); + textures->bindTexture(final_texture, TEX_TONE_MAPPED); + textures->bindTexture(temp_texture, TEX_INTERMEDIATE); + graphics->framebufferColorTexture2D(final_texture); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + //final_texture = temp_texture; + } + const bool kMinZDepth = false; + if(kMinZDepth) + { + glColorMask(false, false, false, false); + graphics->setDepthWrite(true); + graphics->setDepthTest(true); + graphics->setDepthFunc(GL_ALWAYS); + int width = graphics->render_dims[0]; + int height = graphics->render_dims[1]; + int level = 0; + int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE_DEPTH"); + shaders->setProgram(shader_id); + textures->bindTexture(graphics->screen_depth_tex); + while(width > 1 && height > 1){ + width = max(1, width/2); + height = max(1, height/2); + ++level; + graphics->setViewport(0,0,width,height); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + shaders->SetUniformInt("screen_width",width); + shaders->SetUniformInt("screen_height",height); + shaders->SetUniformFloat("src_lod",(float)(level-1)); + graphics->framebufferColorTexture2D(final_texture, level); + graphics->framebufferDepthTexture2D(graphics->screen_depth_tex, level); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + } + glColorMask(true, true, true, true); + graphics->setDepthWrite(false); + graphics->setDepthTest(false); + graphics->setDepthFunc(GL_LEQUAL); + } + + graphics->PopFramebuffer(); + graphics->PopViewport(); + + //glEnable(GL_SCISSOR_TEST); + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Final pass"); + + int shader_id; + if(g_gamma_correct_final_output){ + shader_id = shaders->returnProgram(post_shader + " #BRIGHTNESS #GAMMA_CORRECT_OUTPUT"); + } else { + shader_id = shaders->returnProgram(post_shader + " #BRIGHTNESS"); + } + + shaders->setProgram(shader_id); + graphics->setViewport((GLint)(active_screen_start[0] * graphics->window_dims[0]) + , (GLint)(active_screen_start[1] * graphics->window_dims[1]) + , (GLint)(active_screen_end[0] * graphics->window_dims[0]) + , (GLint)(active_screen_end[1] * graphics->window_dims[1])); + int temp_viewport_dims[4]; + assert(sizeof(temp_viewport_dims) == sizeof(graphics->viewport_dim)); + memcpy(temp_viewport_dims, graphics->viewport_dim, sizeof(temp_viewport_dims)); + graphics->setViewport((GLint)0.0f + , (GLint)0.0f + , (GLint)graphics->window_dims[0] + , (GLint)graphics->window_dims[1]); + glEnable(GL_SCISSOR_TEST); + glScissor(temp_viewport_dims[0], temp_viewport_dims[1], temp_viewport_dims[2], temp_viewport_dims[3]); + shaders->SetUniformInt("screen_width",graphics->config_.screen_width()); + shaders->SetUniformInt("screen_height",graphics->config_.screen_height()); + if(g_albedo_only){ + shaders->SetUniformFloat("black_point",0.0f); + shaders->SetUniformFloat("white_point",1.0f); + } else { + shaders->SetUniformFloat("black_point",graphics->hdr_black_point); + shaders->SetUniformFloat("white_point",graphics->hdr_white_point); + } + shaders->SetUniformFloat("hdr_bloom_mult",graphics->hdr_bloom_mult); + shaders->SetUniformFloat("time",game_timer.game_time); + shaders->SetUniformVec3("tint",camera->tint); + shaders->SetUniformFloat("brightness",config["brightness"].toNumber<float>()); + + if(sphere_points.empty()){ + while(sphere_points.size() < 32){ + vec3 point(RangedRandomFloat(-1.0f,1.0f), + RangedRandomFloat(-1.0f,1.0f), + RangedRandomFloat(-1.0f,1.0f)); + if(length_squared(point)<=1.0f){ + sphere_points.push_back(normalize(point)); + } + } + } + shaders->SetUniformVec3Array("sphere_points",sphere_points); + textures->bindTexture(final_texture); + textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH); + textures->bindTexture(graphics->noise_ref, TEX_NOISE); + textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE); + //It appears this might trigger a crash on intel6XX, + //so for now, we do gamma correction in the shader + //via #GAMMA_CORRECT_OUTPUT + //glEnable(GL_FRAMEBUFFER_SRGB ); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + //glDisable(GL_FRAMEBUFFER_SRGB ); + } + graphics->PopViewport(); + graphics->ResetVertexAttribArrays(); + } +} + +void Engine::DrawScene(DrawingViewport drawing_viewport, Engine::PostEffectsType post_effects_type, SceneGraph::SceneDrawType scene_draw_type) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scene") + Graphics* graphics = Graphics::Instance(); + PushGPUProfileRange(__FUNCTION__); + GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; + + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Setup Post Effects") + graphics->PushFramebuffer(); + if (graphics->multisample_framebuffer_exists) { + graphics->bindFramebuffer(graphics->multisample_framebuffer); + } else { + graphics->bindFramebuffer(graphics->framebuffer); + } + graphics->PushViewport(); + } + + // Prepare for drawing + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Clear screen"); + glDrawBuffers(2, buffers); + glClearColor( 0.0f, 0.0f, 0.0f, 0 ); + graphics->drawing_shadow=false; + graphics->Clear(true); + glDrawBuffers(1, buffers); + } + + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Apply viewpoint"); + ActiveCameras::Get()->applyViewpoint(); + + std::vector<Camera>& virt_cam = ActiveCameras::Instance()->GetVirtualCameras(); + + for (auto& cam : virt_cam) { + cam.applyViewpoint(); + } + + } + + { // Perform per-frame, per-camera functions, like character LOD and spawning grass + PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera"); + ActiveCameras::Get()->tint = vec3(1.0); + ActiveCameras::Get()->vignette_tint = vec3(1.0); + float predraw_time = game_timer.GetRenderTime(); + for(int i=0; i<(int)scenegraph_->objects_.size(); ++i){ + Object* obj = scenegraph_->objects_[i]; + if(!obj->parent){ + obj->PreDrawCamera(predraw_time); + } + } + } + + if(!g_no_decals){ + scenegraph_->PrepareLightsAndDecals(active_screen_start, active_screen_end, vec2(graphics->render_dims[0], graphics->render_dims[1])); + } + + // Draw everything + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scenegraph"); + if(graphics->depth_prepass){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw depth-only pass"); + mat4 proj = ActiveCameras::Get()->GetProjMatrix(); + mat4 view = ActiveCameras::Get()->GetViewMatrix(); + scenegraph_->DrawDepthMap(proj * view, (const vec4*)ActiveCameras::Get()->frustumPlanes, 6, SceneGraph::kDepthPrePass, scene_draw_type); + } + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw color pass"); + scenegraph_->Draw(scene_draw_type); + } + + if (graphics->multisample_framebuffer_exists) { + graphics->BlitDepthBuffer(); + graphics->bindFramebuffer(graphics->multisample_framebuffer); + } + + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw misc"); + { + if(!(graphics->queued_screenshot && graphics->screenshot_mode == 1)) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw flares"); + scenegraph_->flares.Draw(Flares::kSharp); + } + } + if (scenegraph_->map_editor->state_ != MapEditor::kInGame && ActiveCameras::Instance()->Get()->GetFlags() != Camera::kPreviewCamera) { + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw map editor"); + scenegraph_->map_editor->Draw(); + } + scenegraph_->light_probe_collection.Draw(*scenegraph_->bullet_world_); + scenegraph_->dynamic_light_collection.Draw(*scenegraph_->bullet_world_); + } + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw debug geometry"); + GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; + glDrawBuffers(2, buffers); + DebugDraw::Instance()->Draw(); + } + } + + float temp_white_point = 0.0f, temp_black_point = 0.0f; + if(scenegraph_->IsCollisionNavMeshVisible() || g_draw_collision){ + temp_white_point = graphics->hdr_white_point; + temp_black_point = graphics->hdr_black_point; + graphics->hdr_white_point = 1.0f; + graphics->hdr_black_point = 0.0f; + } + + { + std::string post_shader_name = graphics->post_shader_name; + if(scenegraph_->level->script_params().HasParam("Custom Shader") && config.GetRef("custom_level_shaders").toNumber<bool>()){ + const std::string& custom_shader = scenegraph_->level->script_params().GetStringVal("Custom Shader"); + if(!custom_shader.empty()) { + post_shader_name += " "+ custom_shader; + } + } + ApplyPostEffects(post_shader_name, drawing_viewport, active_screen_start, active_screen_end, post_effects_type, quad_vert_vbo, quad_index_vbo); + } + if(scenegraph_->IsCollisionNavMeshVisible() || g_draw_collision){ + graphics->hdr_white_point = temp_white_point; + graphics->hdr_black_point = temp_black_point; + } + PopGPUProfileRange(); + +} + +void Engine::LoadScreenLoop(bool loading_in_progress) { + { + PROFILER_ZONE_IDLE(g_profiler_ctx, "Load screen 60hz wait"); + SDL_Delay(16); // Sleep for 16 ms, don't need more than 60 fps! + } + PROFILER_ZONE(g_profiler_ctx, "Load screen loop"); + int num_time_steps = game_timer.GetStepsNeeded(); + num_time_steps = min(num_time_steps,_max_steps_per_frame); + for(int curr_step=0; curr_step<num_time_steps; curr_step++){ + PROFILER_ZONE(g_profiler_ctx, "Load screen step"); + game_timer.game_time += game_timer.timestep; + UpdateControls(game_timer.timestep, true); + } + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Load screen draw"); + DrawLoadScreen(loading_in_progress); + } +} + +void Engine::DrawLoadScreen(bool loading_in_progress) { +#ifndef GLDEBUG + // don't draw loading screen on gl debug + // makes traces more useful + + Graphics* graphics = Graphics::Instance(); + Textures* textures = Textures::Instance(); + CHECK_GL_ERROR(); + graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]); + + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + graphics->Clear(true); + + vec3 base_color = vec3(min(1.0f, (SDL_TS_GetTicks() - started_loading_time)/1000.0f)); + + if(!level_has_screenshot && load_screen_tip[0] == '\0') { + textures->drawTexture(loading_screen_logo->GetTextureRef(), + vec3((float)graphics->window_dims[0]/2.0f,(float)graphics->window_dims[1]/2.0f+sinf(game_timer.game_time)*20.0f,0), + 512.0f+(sinf(game_timer.game_time*2.0f)-1.0f)*20); + CHECK_GL_ERROR(); + } else { + if(level_has_screenshot) { + CHECK_GL_ERROR(); + + // no coloring + Graphics* graphics = Graphics::Instance(); + Shaders* shaders = Shaders::Instance(); + + int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LEVEL_SCREENSHOT"); + shaders->createProgram(shader_id); + int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id); + int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id); + int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id); + int uniform_color = shaders->returnShaderVariable("color", shader_id); + shaders->setProgram(shader_id); + CHECK_GL_ERROR(); + + GLState gl_state; + gl_state.blend = true; + gl_state.cull_face = false; + gl_state.depth_write = false; + gl_state.depth_test = false; + graphics->setGLState(gl_state); + CHECK_GL_ERROR(); + + + glm::mat4 proj_mat; + proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]); + glm::mat4 modelview_mat(1.0f); + vec2 where; + vec2 size(Textures::Instance()->getWidth(level_screenshot->GetTextureRef()), + Textures::Instance()->getHeight(level_screenshot->GetTextureRef())); + size /= size[1]; + size *= (float) graphics->window_dims[1]; + where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.5f); + modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0)); + modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f)); + glm::mat4 mvp_mat = proj_mat * modelview_mat; + CHECK_GL_ERROR(); + + graphics->EnableVertexAttribArray(shader_attrib_vert_coord); + CHECK_GL_ERROR(); + graphics->EnableVertexAttribArray(shader_attrib_tex_coord); + CHECK_GL_ERROR(); + static const GLfloat verts[] = { + 0, 0, 0, 0, + 1, 0, 1, 0, + 1, 1, 1, 1, + 0, 1, 0, 1 + }; + static const GLuint indices[] = { + 0, 1, 2, + 0, 2, 3 + }; + static VBOContainer vert_vbo; + static VBOContainer index_vbo; + static bool vbo_filled = false; + if(!vbo_filled) { + vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts); + index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices); + vbo_filled = true; + } + vert_vbo.Bind(); + index_vbo.Bind(); + + glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0); + CHECK_GL_ERROR(); + glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2)); + CHECK_GL_ERROR(); + + int num_indices = 6; + + glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat); + CHECK_GL_ERROR(); + vec4 color(base_color, 1.0f); + glUniform4fv(uniform_color, 1, &color[0]); + CHECK_GL_ERROR(); + + Textures::Instance()->bindTexture(level_screenshot, 0); + CHECK_GL_ERROR(); + graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0); + CHECK_GL_ERROR(); + + graphics->BindElementVBO(0); + graphics->BindArrayVBO(0); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + } + + { // Draw pulsing Overgrowth logo + CHECK_GL_ERROR(); + + // no coloring + Graphics* graphics = Graphics::Instance(); + Shaders* shaders = Shaders::Instance(); + + int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LOADING_LOGO"); + shaders->createProgram(shader_id); + int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id); + int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id); + int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id); + int uniform_color = shaders->returnShaderVariable("color", shader_id); + int uniform_time = shaders->returnShaderVariable("time", shader_id); + shaders->setProgram(shader_id); + CHECK_GL_ERROR(); + + GLState gl_state; + gl_state.blend = true; + gl_state.cull_face = false; + gl_state.depth_write = false; + gl_state.depth_test = false; + graphics->setGLState(gl_state); + CHECK_GL_ERROR(); + + + glm::mat4 proj_mat; + proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]); + glm::mat4 modelview_mat(1.0f); + vec2 where; + vec2 size(graphics->window_dims[1]*0.1f); + where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.1f); + modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0)); + modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f)); + glm::mat4 mvp_mat = proj_mat * modelview_mat; + CHECK_GL_ERROR(); + + graphics->EnableVertexAttribArray(shader_attrib_vert_coord); + CHECK_GL_ERROR(); + graphics->EnableVertexAttribArray(shader_attrib_tex_coord); + CHECK_GL_ERROR(); + static const GLfloat verts[] = { + 0, 0, 0, 0, + 1, 0, 1, 0, + 1, 1, 1, 1, + 0, 1, 0, 1 + }; + static const GLuint indices[] = { + 0, 1, 2, + 0, 2, 3 + }; + static VBOContainer vert_vbo; + static VBOContainer index_vbo; + static bool vbo_filled = false; + if(!vbo_filled) { + vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts); + index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices); + vbo_filled = true; + } + vert_vbo.Bind(); + index_vbo.Bind(); + + glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0); + CHECK_GL_ERROR(); + glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2)); + CHECK_GL_ERROR(); + + int num_indices = 6; + + glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat); + CHECK_GL_ERROR(); + vec4 color(base_color, 1.0f); + glUniform4fv(uniform_color, 1, &color[0]); + glUniform1f(uniform_time, SDL_GetTicks()/1000.0f); + CHECK_GL_ERROR(); + + Textures::Instance()->bindTexture(loading_screen_og_logo->GetTextureRef(), 0); + CHECK_GL_ERROR(); + graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0); + CHECK_GL_ERROR(); + + graphics->BindElementVBO(0); + graphics->BindArrayVBO(0); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + } + } + + // Draw technical loading text + if(!level_has_screenshot && load_screen_tip[0] == '\0' || load_screen_tip[0] == '\0' && Online::Instance()->IsActive()) { + int font_height = font_renderer.GetFontInfo(0, FontRenderer::INFO_HEIGHT) / 64; + for(int i=0; i<NativeLoadingText::kMaxLines; ++i){ + int pos[] = {20, graphics->window_dims[1] - 20 - font_height*i}; + g_text_atlas_renderer.num_characters = 0; + CHECK_GL_ERROR(); + g_text_atlas_renderer.AddText(&g_text_atlas[kTextAtlasMono], + &native_loading_text.buf[NativeLoadingText::kMaxCharPerLine*i], + pos, &font_renderer, + UINT32MAX); + CHECK_GL_ERROR(); + g_text_atlas_renderer.Draw(&g_text_atlas[kTextAtlasMono], + graphics, TextAtlasRenderer::kTextShadow, vec4(1.0f)); + CHECK_GL_ERROR(); + Textures::Instance()->InvalidateBindCache(); + } + } + + { // Draw fun text + int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50))); + TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, load_screen_tip); + int pos[] = {graphics->window_dims[0] / 2 - metrics.bounds[2] / 2, graphics->window_dims[1] / 2 - font_size / 2}; + g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, load_screen_tip, pos[0], pos[1], vec4(base_color, 1.0f)); + Textures::Instance()->InvalidateBindCache(); + } + + CHECK_GL_ERROR(); + Textures::Instance()->InvalidateBindCache(); + graphics->SwapToScreen(); + graphics->ClearGLState(); + CHECK_GL_ERROR(); + +#endif // GLDEBUG +} + +void Engine::SetViewportForCamera(int which_cam, int num_screens, Graphics::ScreenType screen_type){ + Graphics *graphics = Graphics::Instance(); + switch(which_cam){ + case 0: // Draw the first screen, which might take up the whole viewport, or just half + if(num_screens == 1){ + active_screen_start = vec2(0.0f, 0.0f); + active_screen_end = vec2(1.0f, 1.0f); + } else if(num_screens == 2){ + active_screen_start = vec2(0.0f, 0.0f); + active_screen_end = vec2(1.0f, 0.5f); + } else if(num_screens == 3 || num_screens == 4){ + active_screen_start = vec2(0.0f, 0.0f); + active_screen_end = vec2(0.5f, 0.5f); + } + break; + case 1: // The second screen always just takes half the viewport + if(num_screens == 2){ + active_screen_start = vec2(0.0f, 0.5f); + active_screen_end = vec2(1.0f, 1.0f); + } else if(num_screens == 3 || num_screens == 4){ + active_screen_start = vec2(0.5f, 0.0f); + active_screen_end = vec2(1.0f, 0.5f); + } + break; + case 2: + active_screen_start = vec2(0.0f, 0.5f); + active_screen_end = vec2(0.5f, 1.0f); + break; + case 3: + active_screen_start = vec2(0.5f, 0.5f); + active_screen_end = vec2(1.0f, 1.0f); + break; + } + + graphics->startDraw(active_screen_start, active_screen_end, screen_type); +} + +void Engine::DrawCubeMap(TextureRef cube_map, const vec3 &pos, GLuint cube_map_fbo, SceneGraph::SceneDrawType scene_draw_type) { + GLuint cube_map_tex_id = Textures::Instance()->returnTexture(cube_map); + Graphics* graphics = Graphics::Instance(); + CHECK_GL_ERROR(); + // TODO: Why do we have to render the first one twice? Some state is messed up + for(int temp_i=-1; temp_i<6; ++temp_i){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw cubemap face") + int i = max(0,temp_i); + graphics->startDraw(vec2(0.0f,0.0f), vec2(128.0f,128.0f), Graphics::kTexture); + ActiveCameras::Set(1); + CHECK_GL_ERROR(); + ActiveCameras::Get()->SetFlags(Camera::kPreviewCamera); + // Apply camera object settings to camera + Camera* camera = ActiveCameras::Instance()->Get(); + CHECK_GL_ERROR(); + // Set camera position + camera->SetPos(pos); + // Set camera euler angles from rotation matrix + mat4 rot = GetCubeMapRotation(i); + vec3 front = rot * vec3(0,0,1); + vec3 up = rot * vec3(0,1,0); + vec3 expected_right = normalize(cross(front, vec3(0,1,0))); + vec3 expected_up = normalize(cross(expected_right, front)); + float cam[3]; + cam[0] = asinf(front[1])*-180.0f/PI_f; + cam[1] = atan2f(front[0], front[2])*180.0f/PI_f; + cam[2] = atan2f(dot(up,expected_right), dot(up, expected_up))*180.0f/PI_f; + // Set rotation twice so we don't have to worry about interpolation + camera->SetXRotation(cam[0]); + camera->SetXRotation(cam[0]); + camera->SetYRotation(cam[1]); + camera->SetYRotation(cam[1]); + camera->SetZRotation(cam[2]); + camera->SetZRotation(cam[2]); + // Force true 90 degree fov + camera->flexible_fov = false; + camera->SetFOV(90.0f); + camera->SetDistance(0.0f); + // Draw view + CHECK_GL_ERROR(); + DrawScene(kViewport, Engine::kStraight, SceneGraph::kStaticOnly); + CHECK_GL_ERROR(); + scenegraph_->level->Draw(); + + graphics->PushFramebuffer(); + glBindFramebuffer(GL_FRAMEBUFFER, cube_map_fbo); + CHECK_GL_ERROR(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, cube_map_tex_id, 0); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, graphics->framebuffer); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cube_map_fbo); + CHECK_GL_ERROR(); + glBlitFramebuffer(0, 0, 128, 128, 0, 0, 128, 128, GL_COLOR_BUFFER_BIT, GL_NEAREST); + CHECK_GL_ERROR(); + graphics->PopFramebuffer(); + + camera->flexible_fov = true; + } + CHECK_GL_ERROR(); +} + + +std::vector<GLfloat> shadow_cache_verts; +std::vector<GLuint> shadow_cache_indices; +extern const bool kUseShadowCache = false; +bool shadow_cache_dirty = true; +bool shadow_cache_dirty_sun_moved = true; +bool shadow_cache_dirty_level_loaded = true; +VBOContainer shadow_cache_verts_vbo; +VBOContainer shadow_cache_indices_vbo; + +static inline vec2 CalculateMulMat4Vec3(const mat4& transform, const vec3& vertex) { + return { + transform[0 + 4 * 0] * vertex[0] + transform[0 + 4 * 1] * vertex[1] + transform[0 + 4 * 2] * vertex[2] + transform[0 + 4 * 3] * 1.0f, + transform[1 + 4 * 0] * vertex[0] + transform[1 + 4 * 1] * vertex[1] + transform[1 + 4 * 2] * vertex[2] + transform[1 + 4 * 3] * 1.0f, + }; +} + +static void CalculateMinMax(const mat4& model_to_light_transform, const Model& model, float out_min_max_bounds[4]) { + float result_min_max_bounds[4] = { FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX, }; // min_x, min_y, max_x, max_y + + vec3 bounding_corners[8] = { + model.min_coords, + vec3(model.max_coords[0], model.min_coords[1], model.min_coords[2]), + vec3(model.min_coords[0], model.max_coords[1], model.min_coords[2]), + vec3(model.max_coords[0], model.max_coords[1], model.min_coords[2]), + vec3(model.min_coords[0], model.min_coords[1], model.max_coords[2]), + vec3(model.max_coords[0], model.min_coords[1], model.max_coords[2]), + vec3(model.min_coords[0], model.max_coords[1], model.max_coords[2]), + model.max_coords, + }; + + for(int i = 0; i < 8; ++i) { + vec2 light_space_vert = CalculateMulMat4Vec3(model_to_light_transform, bounding_corners[i]); + result_min_max_bounds[0] = std::min(light_space_vert[0], result_min_max_bounds[0]); + result_min_max_bounds[1] = std::min(light_space_vert[1], result_min_max_bounds[1]); + result_min_max_bounds[2] = std::max(light_space_vert[0], result_min_max_bounds[2]); + result_min_max_bounds[3] = std::max(light_space_vert[1], result_min_max_bounds[3]); + } + + memcpy(out_min_max_bounds, result_min_max_bounds, sizeof(result_min_max_bounds)); +} + +void DrawShadowCache(const mat4& proj_view_matrix, const char* special){ + Graphics* graphics = Graphics::Instance(); + Shaders* shaders = Shaders::Instance(); + + GLState gl_state; + gl_state.depth_test = true; + gl_state.cull_face = true; + gl_state.depth_write = true; + gl_state.blend = false; + + graphics->setGLState(gl_state); + + const int kBufSize = 512; + char shader_name[kBufSize]; + FormatString(shader_name, kBufSize, "shadow%s", special); + int shader_id = shaders->returnProgram(shader_name); + shaders->setProgram(shader_id); + shaders->SetUniformMat4("projection_view_mat", proj_view_matrix); + int vert_attrib_id = shaders->returnShaderAttrib("vert", shader_id); + + shadow_cache_verts_vbo.Bind(); + graphics->EnableVertexAttribArray(vert_attrib_id); + glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0); + shadow_cache_indices_vbo.Bind(); + graphics->DrawElements(GL_TRIANGLES, shadow_cache_indices.size(), GL_UNSIGNED_INT, 0); + graphics->ResetVertexAttribArrays(); +} + +static bool UpdateShadowCache(SceneGraph* scenegraph) { + Graphics* graphics = Graphics::Instance(); + Textures* textures = Textures::Instance(); + Shaders* shaders = Shaders::Instance(); + PROFILER_ENTER(g_profiler_ctx, "Get bounds"); + + // Get basis from light perspective + vec3 light_dir = scenegraph->primary_light.pos; + vec3 light_to_world_basis[3]; + light_to_world_basis[2] = light_dir; + light_to_world_basis[0] = normalize(cross(vec3(0,1,0), light_to_world_basis[2])); + light_to_world_basis[1] = normalize(cross(light_to_world_basis[0], light_to_world_basis[2])); + mat4 light_to_world_transform = { + light_to_world_basis[0][0], light_to_world_basis[0][1], light_to_world_basis[0][2], 0.0f, + light_to_world_basis[1][0], light_to_world_basis[1][1], light_to_world_basis[1][2], 0.0f, + light_to_world_basis[2][0], light_to_world_basis[2][1], light_to_world_basis[2][2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + mat4 world_to_light_transform = transpose(light_to_world_transform); + + // Get bounds of scene in light space + const int kMaxUpdatesPerFrame = 32; + bool max_updates_hit = false; + int current_update_count = 0; + if(shadow_cache_dirty_sun_moved) + { + // Dirty all objects once, so iterative processing can clean them up + for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->visible_static_meshes_shadow_cache_bounds_.begin(); + it != scenegraph->visible_static_meshes_shadow_cache_bounds_.end(); + ++it) + { + ShadowCacheObjectLightBounds& lb = *it; + if(!lb.is_ignored) { + lb.is_calculated = false; + } + } + for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->terrain_objects_shadow_cache_bounds_.begin(); + it != scenegraph->terrain_objects_shadow_cache_bounds_.end(); + ++it) + { + ShadowCacheObjectLightBounds& lb = *it; + lb.is_calculated = false; + } + } + float bounds[4] = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; // min_x, min_y, max_x, max_y + for(size_t i = 0, len = scenegraph->visible_static_meshes_.size(); i < len && !max_updates_hit; ++i) + { + EnvObject& eo = *scenegraph->visible_static_meshes_[i]; + ShadowCacheObjectLightBounds& lb = scenegraph->visible_static_meshes_shadow_cache_bounds_[i]; + lb.is_ignored = eo.attached_ != NULL; + if(!lb.is_ignored) { + if(!lb.is_calculated || shadow_cache_dirty_level_loaded) { + const Model &env_model = *eo.GetModel(); + const mat4& model_to_world_transform = eo.GetTransform(); + CalculateMinMax(world_to_light_transform * model_to_world_transform, env_model, &lb.min_bounds[0]); + lb.is_calculated = true; + ++current_update_count; + } + } + + if(!shadow_cache_dirty_level_loaded && current_update_count == kMaxUpdatesPerFrame && i + 1 < len) + { + max_updates_hit = true; + break; + } + } + for(size_t i = 0, len = scenegraph->terrain_objects_.size(); i < len && !max_updates_hit; ++i) + { + TerrainObject& to = *scenegraph->terrain_objects_[i]; + ShadowCacheObjectLightBounds& lb = scenegraph->terrain_objects_shadow_cache_bounds_[i]; + if(!lb.is_calculated || shadow_cache_dirty_level_loaded) { + const Model &env_model = *to.GetModel(); + const mat4& model_to_world_transform = to.GetTransform(); + CalculateMinMax(world_to_light_transform * model_to_world_transform, env_model, &lb.min_bounds[0]); + lb.is_calculated = true; + ++current_update_count; + } + + if(!shadow_cache_dirty_level_loaded && current_update_count == kMaxUpdatesPerFrame && i + 1 < len) + { + max_updates_hit = true; + break; + } + } + if(shadow_cache_dirty_level_loaded || !max_updates_hit) + { + for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->visible_static_meshes_shadow_cache_bounds_.begin(); + it != scenegraph->visible_static_meshes_shadow_cache_bounds_.end(); + ++it) + { + ShadowCacheObjectLightBounds& lb = *it; + if(!lb.is_ignored) { + bounds[0] = std::min(lb.min_bounds[0], bounds[0]); + bounds[1] = std::min(lb.min_bounds[1], bounds[1]); + bounds[2] = std::max(lb.max_bounds[0], bounds[2]); + bounds[3] = std::max(lb.max_bounds[1], bounds[3]); + } + } + for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->terrain_objects_shadow_cache_bounds_.begin(); + it != scenegraph->terrain_objects_shadow_cache_bounds_.end(); + ++it) + { + ShadowCacheObjectLightBounds& lb = *it; + bounds[0] = std::min(lb.min_bounds[0], bounds[0]); + bounds[1] = std::min(lb.min_bounds[1], bounds[1]); + bounds[2] = std::max(lb.max_bounds[0], bounds[2]); + bounds[3] = std::max(lb.max_bounds[1], bounds[3]); + } + } + PROFILER_LEAVE(g_profiler_ctx); + + if(max_updates_hit) + { + return false; + } + + { // Draw depth map of the whole level + Camera* camera = ActiveCameras::Get(); + int num_shadow_view_frustum_planes = 6; + vec4 shadow_view_frustum_planes[6]; + CHECK_GL_ERROR(); + glColorMask(0,0,0,0); + graphics->drawing_shadow=true; + graphics->PushFramebuffer(); + graphics->bindFramebuffer(graphics->static_shadow_fb); + graphics->PushViewport(); + glEnable(GL_SCISSOR_TEST); + float shadow_size = max(bounds[2] - bounds[0], bounds[3]-bounds[1]); + //float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size' + vec3 shadow_center = (bounds[0]+bounds[2])*0.5f * light_to_world_basis[0] + (bounds[1]+bounds[3])*0.5f * light_to_world_basis[1]; + vec3 light_space_center(dot(shadow_center, light_to_world_basis[0]), + dot(shadow_center, light_to_world_basis[1]), + dot(shadow_center, light_to_world_basis[2])); // Note: Dot of vector vs columns is like transpose(mat) * vec. And transpose(rot_only_mat) == inverse(rot_only_mat). + // Snap center to nearest texel to improve frame-to-frame coherency + float texel_size = shadow_size * 2.0f / graphics->shadow_res; + for(int j=0; j<3; ++j){ + light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size; + } + // Update shadow center to new texel-snapped position + shadow_center = light_space_center[0] * light_to_world_basis[0] + + light_space_center[1] * light_to_world_basis[1]; + + graphics->setViewport(0,0,graphics->cascade_shadow_res,graphics->cascade_shadow_res); + graphics->Clear(true); + mat4 proj, view; + camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view); + mat4 proj_view_matrix = proj * view; + graphics->simple_shadow_mat = proj_view_matrix; + + for(int i=0; i<6; ++i){ + for(int j=0; j<4; ++j){ + shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = camera->frustumPlanes[i][j]; + } + } + scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticOnly); + glDisable(GL_SCISSOR_TEST); + graphics->PopViewport(); + graphics->PopFramebuffer(); + graphics->drawing_shadow=false; + glColorMask(1,1,1,1); + } + + if(false){ // Create shadow cache VBO of only triangles that affect shadows + PROFILER_LEAVE(g_profiler_ctx); + PROFILER_ENTER(g_profiler_ctx, "Fill VBO"); + shadow_cache_verts_vbo.Fill(kVBOStatic | kVBOFloat, shadow_cache_verts.size()*sizeof(GLfloat), &shadow_cache_verts[0]); + shadow_cache_indices_vbo.Fill(kVBOStatic | kVBOElement, shadow_cache_indices.size()*sizeof(GLuint), &shadow_cache_indices[0]); + + PROFILER_LEAVE(g_profiler_ctx); + PROFILER_ENTER(g_profiler_ctx, "Render all cascade tiles"); + const int render_size = 2048; + graphics->PushFramebuffer(); + graphics->PushViewport(); + graphics->SetBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_DST_COLOR); // Should be equivalent to Photoshop 'lighten' mode + std::vector<bool> visible_gpu(shadow_cache_indices.size()/3, false); + int tri_visible_shader_id = shaders->returnProgram("tri_visible"); + static GLuint cascade_tri_visible_fb = UINT_MAX; + static TextureRef cascade_tri_visible_texture_ref; + // Make sure framebuffer exists for accumulating visible triangle ids + if(cascade_tri_visible_fb == UINT_MAX){ + graphics->genFramebuffers(&cascade_tri_visible_fb, "cascade_tri_visible_fb"); + graphics->bindFramebuffer(cascade_tri_visible_fb); + cascade_tri_visible_texture_ref = textures->makeTextureColor(render_size, render_size); + Textures::Instance()->SetTextureName(cascade_tri_visible_texture_ref, "Cascade Triangle Visible"); + graphics->framebufferColorTexture2D(cascade_tri_visible_texture_ref); + } + graphics->bindFramebuffer(cascade_tri_visible_fb); + graphics->setViewport(0,0,render_size,render_size); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + graphics->Clear(true); + // Create VBO that just sends one vec2 for each texel in a render_size*render_size image + static VBOContainer pixel_vbo; + if(!pixel_vbo.valid()){ + std::vector<GLfloat> pixel_data; + pixel_data.resize(render_size * render_size * 2); + for(int i=0, index=0; i<render_size; ++i){ + for(int j=0; j<render_size; ++j){ + pixel_data[index+0] = (j+0.5f)/(float)render_size; + pixel_data[index+1] = (i+0.5f)/(float)render_size; + index += 2; + } + } + pixel_vbo.Fill(kVBOFloat | kVBOStatic, pixel_data.size() * sizeof(GLfloat), &pixel_data[0]); + } + glPointSize(1); + CHECK_GL_ERROR(); + float size[2] = {bounds[2] - bounds[0], bounds[3] - bounds[1]}; + for(int cascade=0; cascade<1; ++cascade){ + // Convert light-space bounds to texels + int texel_dim[2]; + for(int j=0; j<2; ++j){ + texel_dim[j] = (int)ceilf(size[j] / (shadow_sizes[cascade]) * graphics->shadow_res); + } + // How many tiles we need to render in each direction + int render_dim[2]; + for(int j=0; j<2; ++j){ + render_dim[j] = (texel_dim[j] + render_size - 1) / render_size; + } + Camera* camera = ActiveCameras::Get(); + for(int x=0; x<render_dim[0]; ++x){ + for(int y=0; y<render_dim[1]; ++y){ + graphics->bindFramebuffer(graphics->cascade_shadow_color_fb); + graphics->setViewport(0,0,render_size,render_size); + PROFILER_GPU_ZONE(g_profiler_ctx, "Cascade tile"); + graphics->Clear(true); + vec3 light_space_center = vec3(bounds[0] + (x+0.5f)*shadow_sizes[cascade], + bounds[1] + (y+0.5f)*shadow_sizes[cascade], + 0.0f); + // Snap center to nearest texel to improve frame-to-frame coherency + float texel_size = shadow_sizes[cascade] * 2.0f / graphics->shadow_res; + for(int j=0; j<2; ++j){ + light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size; + } + // Update shadow center to new texel-snapped position + vec3 shadow_center = light_space_center[0] * light_to_world_basis[0] + + light_space_center[1] * light_to_world_basis[1]; + mat4 proj, view; + camera->applyShadowViewpoint(light_dir, shadow_center, shadow_sizes[cascade], &proj, &view); + mat4 proj_view_matrix = proj * view; + // Draw triangle ids as color to RGB channels + glColorMask(1,1,1,0); + graphics->setDepthFunc(GL_LESS); + DrawShadowCache(proj_view_matrix, " #TRI_COLOR"); + // Draw to alpha channel if there is another fragment behind the front one + glColorMask(0,0,0,1); + graphics->setDepthFunc(GL_GREATER); + DrawShadowCache(proj_view_matrix, ""); + // Calculate visible tris on GPU + glColorMask(1,1,1,1); + graphics->setBlend(true); + graphics->bindFramebuffer(cascade_tri_visible_fb); + graphics->setViewport(0,0,render_size,render_size); + shaders->setProgram(tri_visible_shader_id); + shaders->SetUniformInt("output_width", render_size); + shaders->SetUniformInt("output_height", render_size); + textures->bindTexture(graphics->cascade_shadow_color_ref); + pixel_vbo.Bind(); + int vert_attrib_id = shaders->returnShaderAttrib("pixel_uv", tri_visible_shader_id); + graphics->EnableVertexAttribArray(vert_attrib_id); + glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + glDrawArrays(GL_POINTS, 0, render_size * render_size); + graphics->ResetVertexAttribArrays(); + } + } + { + int size = render_size * render_size; + unsigned char *pixels = new unsigned char[size * 4]; + ::memset(pixels, 0, size * 4); + { + PROFILER_ZONE_STALL(g_profiler_ctx, "glReadPixels"); + glReadPixels(0, 0, render_size, render_size, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)pixels); + } + //std::string path = ImageExport::FindEmptySequentialFile("Screenshots/TriVisible",".png"); + //ImageExport::SavePNGTransparent(path.c_str(), pixels, render_size, render_size); + int check_size = min(size, (int)visible_gpu.size()); + int num_tris = 0; + for(int pixel=0, index=0; pixel<check_size; ++pixel, index+=4){ + //unsigned char* pixel_ptr = &pixels[index]; + if(pixels[index+0] != 0){ + ++num_tris; + int id = index/4; + visible_gpu[id] = true; + } + } + delete [] pixels; + LOGI << "NUM GPU TRIS: " << num_tris << std::endl; + } + glColorMask(1,1,1,1); + std::vector<GLuint> new_shadow_cache_indices; + for(size_t i=0, index=0, len=visible_gpu.size(); i<len; ++i, index+=3){ + if(visible_gpu[i]){ + for(size_t j=0; j<3; ++j){ + new_shadow_cache_indices.push_back(shadow_cache_indices[index+j]); + } + } + } + + if(false){ // Debug wireframe + std::vector<GLuint> line_indices; + for(size_t i=0, len=new_shadow_cache_indices.size(); i<len; i+=3){ + for(size_t j=0; j<3; ++j){ + line_indices.push_back(new_shadow_cache_indices[i+j]); + line_indices.push_back(new_shadow_cache_indices[i+(j+1)%3]); + } + } + DebugDraw::Instance()->AddLines(shadow_cache_verts, line_indices, vec4(1.0), _persistent, _DD_XRAY); + } + shadow_cache_indices = new_shadow_cache_indices; + } + PROFILER_LEAVE(g_profiler_ctx); + PROFILER_ENTER(g_profiler_ctx, "Finalize"); + graphics->setDepthFunc(GL_LEQUAL); + graphics->PopViewport(); + graphics->PopFramebuffer(); + + if(shadow_cache_indices.empty()){ + // Add degenerate triangle if shadow cache is empty, so + // that we don't need a separate branch to deal with this + for(int i=0; i<3; ++i){ + shadow_cache_indices.push_back(0); + } + } + + shadow_cache_verts_vbo.Fill(kVBOStatic | kVBOFloat, shadow_cache_verts.size()*sizeof(GLfloat), &shadow_cache_verts[0]); + shadow_cache_indices_vbo.Fill(kVBOStatic | kVBOElement, shadow_cache_indices.size()*sizeof(GLuint), &shadow_cache_indices[0]); + + LOGI << "Shadow cache verts: " << shadow_cache_verts.size()/3 << std::endl; + LOGI << "Shadow cache tris: " << shadow_cache_indices.size()/3 << std::endl; + PROFILER_LEAVE(g_profiler_ctx); + } + + return true; +} + + +#define NUM_SHADOW_CASCADES 4 + + +struct uvec4 { + union { + unsigned int v[4]; + struct { + unsigned int x, y, z, w; + }; + }; + + uvec4() {} + + uvec4(unsigned int x_, unsigned int y_, unsigned int z_, unsigned int w_) + : x(x_) + , y(y_) + , z(z_) + , w(w_) + { + } +}; + + +struct ShadowCascadeParams { + mat4 proj_view_matrix[NUM_SHADOW_CASCADES]; + vec4 frustum_planes[NUM_SHADOW_CASCADES][6]; + uvec4 viewport[NUM_SHADOW_CASCADES]; +}; + + +void GetShadowableTris(SceneGraph* scenegraph) { + // For each cascade size + // Divide scene into tiles + // Draw tiles using shader that renders the triangle and object id to a texture + // Read the texture and add triangle/object pair to a set + // Create model for each tile + + Graphics* graphics = Graphics::Instance(); + Camera *camera = ActiveCameras::Get(); + + vec3 light_dir = scenegraph->primary_light.pos; + vec3 basis[3]; + basis[2] = light_dir; + basis[0] = normalize(cross(vec3(0,1,0), basis[2])); + basis[1] = normalize(cross(basis[0], basis[2])); + + vec3 camera_pos; + + ShadowCascadeParams shadow_params; + for (int i = 0; i < NUM_SHADOW_CASCADES; ++i) { + float shadow_size = shadow_sizes[i]; + float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size' + vec3 shadow_center = camera_pos; + vec3 light_space_center( + dot(shadow_center, basis[0]), + dot(shadow_center, basis[1]), + dot(shadow_center, basis[2])); + // Snap center to nearest texel to improve frame-to-frame coherency + float texel_size = shadow_size * 2.0f / graphics->shadow_res; + for(int j=0; j<3; ++j){ + light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size; + } + // Update shadow center to new texel-snapped position + shadow_center = light_space_center[0] * basis[0] + + light_space_center[1] * basis[1]; + + GLuint res = graphics->cascade_shadow_res / 2; + uvec4 &viewport = shadow_params.viewport[i]; + switch(i){ + case 0: viewport = uvec4(0 , 0, res , res ); break; + case 1: viewport = uvec4(res, 0, res*2 , res ); break; + case 2: viewport = uvec4(0 , res, res , res*2); break; + case 3: viewport = uvec4(res, res, res*2 , res*2); break; + } + mat4 proj, view; + camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view); + mat4 proj_view_matrix = proj * view; + graphics->cascade_shadow_mat[i] = proj_view_matrix; + shadow_params.proj_view_matrix[i] = proj_view_matrix; + for (int j = 0; j < 6; ++j) { + memcpy(&shadow_params.frustum_planes[i][j], &camera->frustumPlanes[j][0], 4 * sizeof(float)); + } + } + + // Make room for the shadow frustum planes for each cascade + int num_shadow_view_frustum_planes = 6; + vec4 shadow_view_frustum_planes[6]; + CHECK_GL_ERROR(); + glColorMask(1,1,1,1); + graphics->drawing_shadow=true; + graphics->PushFramebuffer(); + graphics->bindFramebuffer(graphics->cascade_shadow_fb); + graphics->PushViewport(); + graphics->setViewport(0, 0, graphics->cascade_shadow_res, graphics->cascade_shadow_res); + glEnable(GL_SCISSOR_TEST); + + for(int i=0; i < NUM_SHADOW_CASCADES; ++i){ + const uvec4 &viewport = shadow_params.viewport[i]; + graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w); + graphics->Clear(true); + mat4 proj_view_matrix = shadow_params.proj_view_matrix[i]; + + if(kUseShadowCache){ + DrawShadowCache(proj_view_matrix, ""); + } + for(int i=0; i<6; ++i){ + for(int j=0; j<4; ++j){ + shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = camera->frustumPlanes[i][j]; + } + } + if(i==0){ + scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic); + } else { + scenegraph->DrawDepthMap(proj_view_matrix, &shadow_view_frustum_planes[num_shadow_view_frustum_planes-6], 6, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic); + } + } + + glDisable(GL_SCISSOR_TEST); + graphics->PopViewport(); + graphics->PopFramebuffer(); + graphics->drawing_shadow=false; + glColorMask(1,1,1,1); +} + +float last_shadow_update_time = 0.0f; + +static void UpdateShadowCascades(SceneGraph* scenegraph) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw shadow cascades"); + bool update_all = false; + if(last_shadow_update_time > game_timer.game_time - 1.0f){ + } else { + update_all = true; + last_shadow_update_time = game_timer.game_time; + } + Graphics* graphics = Graphics::Instance(); + Camera *camera = ActiveCameras::Get(); + CHECK_GL_ERROR(); + // Get orthographic basis from perspective of directional light source + vec3 light_dir = scenegraph->primary_light.pos; + vec3 basis[3]; + basis[2] = light_dir; + basis[0] = normalize(cross(vec3(0,1,0), basis[2])); + basis[1] = normalize(cross(basis[0], basis[2])); + // Get player view camera information + camera->applyViewpoint(); + vec3 camera_facing = camera->GetFacing(); + vec3 camera_pos = camera->GetPos(); + mat4 camera_proj_mat = camera->GetProjMatrix(); + mat4 camera_view_mat = camera->GetViewMatrix(); + mat4 inv_camera_mat = invert(camera_proj_mat * camera_view_mat); + vec3 camera_corner_dir[4]; + camera_corner_dir[0] = inv_camera_mat * vec3(-1.0f, -1.0f, 1.0f); + camera_corner_dir[1] = inv_camera_mat * vec3( 1.0f, -1.0f, 1.0f); + camera_corner_dir[2] = inv_camera_mat * vec3( 1.0f, 1.0f, 1.0f); + camera_corner_dir[3] = inv_camera_mat * vec3(-1.0f, 1.0f, 1.0f); + vec4 view_frustum_planes[6]; + for(int i=0; i<6; ++i){ + for(int j=0; j<4; ++j){ + view_frustum_planes[i][j] = camera->frustumPlanes[i][j]; + } + } + int num_shadow_view_frustum_planes = 0; + vec4 shadow_view_frustum_planes[30]; + // Add camera frustum panes if they are facing the direction the light is coming from + for(int i=0; i<6; ++i) { + if(dot(light_dir, view_frustum_planes[i].xyz()) >= 0.0f){ + shadow_view_frustum_planes[num_shadow_view_frustum_planes] = view_frustum_planes[i]; + ++num_shadow_view_frustum_planes; + } + } + // Get convex hull of camera frustum in light space + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Convex hull"); + vec2 light_space_points[5]; + //bool points_used[5] = {false}; + for(int i=0; i<2; ++i){ + light_space_points[0][i] = dot(camera_pos, basis[i]); + } + const float kDist = 10000.0f; + for(int point=0; point<4; ++point){ + for(int i=0; i<2; ++i){ + light_space_points[point+1][i] = dot(camera_pos + normalize(camera_corner_dir[point])*kDist, basis[i]); + } + } + // Get convex hull using 'string wrapping' counter-clockwise + int hull_points[5]; + int num_hull_points=1; + // Start with leftmost point + float lowest_x = FLT_MAX; + for(int i=0; i<5; ++i){ + if(light_space_points[i][0] < lowest_x){ + lowest_x = light_space_points[i][0]; + hull_points[0] = i; + } + } + //points_used[hull_points[0]] = true; + float last_angle = -PI_f*0.5f; // Start with 'string' pointing straight down + bool done = false; + while(!done){ + float best_angle = 0.0f; + int last_point = hull_points[num_hull_points-1]; + float lowest_diff = FLT_MAX; + int best_guess = -1; + for(int i=0; i<5; ++i){ + if(i != last_point){ + vec2 vec = light_space_points[i] - light_space_points[last_point]; + float angle = atan2(vec[1], vec[0]); + while(angle < last_angle){ + angle += PI_f * 2.0f; + } + float diff = angle - last_angle; + if(diff < lowest_diff){ + best_guess = i; + lowest_diff = diff; + best_angle = angle; + } + } + } + if(best_guess == hull_points[0]){ + done = true; + } else { + LOG_ASSERT(num_hull_points < 5); + hull_points[num_hull_points] = best_guess; + ++num_hull_points; + last_angle = best_angle; + last_point = best_guess; + } + } + // Extrude convex hull lines into 3D planes + for(int i=0; i<num_hull_points; ++i){ + int points[2] = {hull_points[i], hull_points[(i+1)%num_hull_points]}; + vec2 dir = light_space_points[points[1]] - light_space_points[points[0]]; + vec2 normal(-dir[1], dir[0]); + vec3 world_normal = normalize(basis[0] * normal[0] + basis[1] * normal[1]); + vec3 pos = basis[0] * light_space_points[points[0]][0] + basis[1] * light_space_points[points[0]][1]; + float d = -dot(world_normal, pos); + shadow_view_frustum_planes[num_shadow_view_frustum_planes] = vec4(world_normal, d); + ++num_shadow_view_frustum_planes; + } + } + + ShadowCascadeParams shadow_params; + for (int i = 0; i < NUM_SHADOW_CASCADES; ++i) { + if(i!=0 && !update_all){ + continue; + } + PROFILER_GPU_ZONE(g_profiler_ctx, "Get shadow cascade params"); + float shadow_size = shadow_sizes[i]; + float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size' + vec3 shadow_center = camera_pos; + if(i==0){ + shadow_center += camera_facing * shadow_radius; + } + vec3 light_space_center(dot(shadow_center, basis[0]), + dot(shadow_center, basis[1]), + dot(shadow_center, basis[2])); + // Snap center to nearest texel to improve frame-to-frame coherency + float texel_size = shadow_size * 2.0f / graphics->shadow_res; + for(int j=0; j<3; ++j){ + light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size; + } + // Update shadow center to new texel-snapped position + shadow_center = light_space_center[0] * basis[0] + + light_space_center[1] * basis[1]; + + GLuint res = graphics->cascade_shadow_res / 2; + uvec4 &viewport = shadow_params.viewport[i]; + switch(i){ + case 0: viewport = uvec4(0 , 0, res , res ); break; + case 1: viewport = uvec4(res, 0, res*2 , res ); break; + case 2: viewport = uvec4(0 , res, res , res*2); break; + case 3: viewport = uvec4(res, res, res*2 , res*2); break; + } + mat4 proj, view; + camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view); + mat4 proj_view_matrix = proj * view; + graphics->cascade_shadow_mat[i] = proj_view_matrix; + shadow_params.proj_view_matrix[i] = proj_view_matrix; + for (int j = 0; j < 6; ++j) { + memcpy(&shadow_params.frustum_planes[i][j], &camera->frustumPlanes[j][0], 4 * sizeof(float)); + } + } + + // Make room for the shadow frustum planes for each cascade + num_shadow_view_frustum_planes += 6; + CHECK_GL_ERROR(); + glColorMask(0,0,0,0); + graphics->drawing_shadow=true; + graphics->PushFramebuffer(); + graphics->bindFramebuffer(graphics->cascade_shadow_fb); + graphics->PushViewport(); + graphics->setViewport(0, 0, graphics->cascade_shadow_res, graphics->cascade_shadow_res); + glEnable(GL_SCISSOR_TEST); + + if (g_single_pass_shadow_cascade) { + static GLuint shadow_cascade_ubo_id = 0; + if (shadow_cascade_ubo_id == 0) { + glGenBuffers(1, &shadow_cascade_ubo_id); + } + glBindBufferBase(GL_UNIFORM_BUFFER, UBO_SHADOW_CASCADES, shadow_cascade_ubo_id); + glBufferData(GL_UNIFORM_BUFFER, sizeof(ShadowCascadeParams), &shadow_params, GL_DYNAMIC_DRAW); + + { // Perform per-frame, per-camera functions, like character LOD and spawning grass + PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera"); + float predraw_time = game_timer.GetRenderTime(); + for(int i=0; i<(int)scenegraph->objects_.size(); ++i){ + Object* obj = scenegraph->objects_[i]; + if(!obj->parent){ + obj->PreDrawCamera(predraw_time); + } + } + } + + { + const uvec4 &viewport = shadow_params.viewport[0]; + graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w); + } + + int viewport_dim[4]; + for (int i = 1; i < NUM_SHADOW_CASCADES; ++i) { + const uvec4 &viewport = shadow_params.viewport[i]; + viewport_dim[0] = viewport.x; + viewport_dim[1] = viewport.y; + viewport_dim[2] = viewport.z - viewport.x; + viewport_dim[3] = viewport.w - viewport.y; + glScissorIndexed(i, viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]); + glViewportIndexedf(i, (float) viewport_dim[0], (float) viewport_dim[1], (float) viewport_dim[2], (float) viewport_dim[3]); + } + + mat4 proj_view_matrix = shadow_params.proj_view_matrix[0]; + + if(kUseShadowCache){ + DrawShadowCache(proj_view_matrix, ""); + } + + scenegraph->DrawDepthMap(proj_view_matrix, NULL, 0, SceneGraph::kDepthAllShadowCascades, SceneGraph::kStaticAndDynamic); + } else { + { // Perform per-frame, per-camera functions, like character LOD and spawning grass + PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera"); + float predraw_time = game_timer.GetRenderTime(); + for(int i=0; i<(int)scenegraph->objects_.size(); ++i){ + Object* obj = scenegraph->objects_[i]; + if(!obj->parent){ + obj->PreDrawCamera(predraw_time); + } + } + } + for(int i=0; i < NUM_SHADOW_CASCADES; ++i){ + if(i!=0 && !update_all){ + continue; + } + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw shadow cascade"); + const uvec4 &viewport = shadow_params.viewport[i]; + graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w); + graphics->Clear(true); + mat4 proj_view_matrix = shadow_params.proj_view_matrix[i]; + + if(kUseShadowCache){ + DrawShadowCache(proj_view_matrix, ""); + } + int cascade = i; + for(int i=0; i<6; ++i){ + for(int j=0; j<4; ++j){ + shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = shadow_params.frustum_planes[cascade][i][j]; + } + } + if(i==0) { + scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic); + } else { // Don't apply camera frustum to larger cascades unless we're updating it every frame + scenegraph->DrawDepthMap(proj_view_matrix, &shadow_view_frustum_planes[num_shadow_view_frustum_planes-6], 6, SceneGraph::kDepthShadow, SceneGraph::kStaticOnly); + } + } + } + + glDisable(GL_SCISSOR_TEST); + graphics->PopViewport(); + graphics->PopFramebuffer(); + graphics->drawing_shadow=false; + glColorMask(1,1,1,1); +} + +void ReadAverageColorsFromCubemap(vec3* avg_color, TextureRef cubemap) { + Graphics* graphics = Graphics::Instance(); + for(int i=0; i<6; ++i){ + static GLuint cube_map_read_fbo = UINT_MAX; + graphics->PushFramebuffer(); + if(cube_map_read_fbo == UINT_MAX){ + graphics->genFramebuffers(&cube_map_read_fbo,"cube_map_read_fbo"); + } + graphics->bindFramebuffer(cube_map_read_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 5); + GLfloat pixels[4*4*3]; + glReadBuffer(GL_COLOR_ATTACHMENT0); + { + PROFILER_ZONE_STALL(g_profiler_ctx, "Read pixels") + glReadPixels(0,0,4,4,GL_RGB,GL_FLOAT,(GLfloat*)pixels); + } + if(ISNAN(pixels[0])){ + LOGE << "Light probe read fail" << std::endl; + LOGE << "cube_map_read_fbo: " << cube_map_read_fbo << std::endl; + LOGE << "scenegraph_->light_probe_collection.cube_map.id: " << cubemap.id << std::endl; + LOGE << "pixels[0]: " << pixels[0] << std::endl; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 5); + glReadBuffer(GL_COLOR_ATTACHMENT0); + { + PROFILER_ZONE_STALL(g_profiler_ctx, "Read pixels") + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 4); + glReadPixels(0,0,4,4,GL_RGB,GL_FLOAT,(GLfloat*)pixels); + } + LOGE << "pixels[0]: " << pixels[0] << std::endl; + } + LOG_ASSERT(!ISNAN(pixels[0])); + // Get average of centermost pixels + avg_color[i] = vec3(0.0f); + for(int j=0, index=0; j<16; ++j, index+=3){ + if(j/4>0 && j/4<3 && j%4>0 && j%4<3){ + avg_color[i] += vec3(pixels[index+0], pixels[index+1], pixels[index+2]) / 4.0f; + } + } + graphics->PopFramebuffer(); + } +} + +extern int num_detail_object_draw_calls; + +void Engine::NewLevel() { + Path p = FindFilePath(new_empty_level_path,kAnyPath); + Engine::Instance()->QueueState(EngineState("",kEngineEditorLevelState, p)); +} + +bool Engine::GetSplitScreen() const +{ + return forced_split_screen_mode == kForcedModeSplit || (forced_split_screen_mode == kForcedModeNone && Graphics::Instance()->config_.split_screen()); +} + +static const int kCollisionNormalVersion = 2; +static const char* kCollisionNormalIdentifier = "overgrowth_level_collision_normals"; + +void SaveCollisionNormals(const SceneGraph* scenegraph) { + std::string path = scenegraph->level_path_.GetOriginalPathStr()+".col_norm"; + if(scenegraph->level_path_.source != kAbsPath) { + if(!config["allow_game_dir_save"].toBool()){ + path = GetWritePath(scenegraph->level_path_.mod_source)+path; + } + } + std::string temp_path = GetWritePath(CoreGameModID).c_str(); + temp_path += "temp_collision_normals"; + FILE *file = my_fopen(temp_path.c_str(), "wb"); + if(file){ + fwrite(kCollisionNormalIdentifier, strlen(kCollisionNormalIdentifier), 1, file); + fwrite(&kCollisionNormalVersion, sizeof(int), 1, file); + int num_objects = 0; + for(size_t i=0, len=scenegraph->visible_static_meshes_.size(); i<len; ++i){ + EnvObject* eo = scenegraph->visible_static_meshes_[i]; + if(eo->GetCollisionModelID() != -1){ + ++num_objects; + } + } + fwrite(&num_objects, sizeof(int), 1, file); + for(size_t i=0, len=scenegraph->visible_static_meshes_.size(); i<len; ++i){ + EnvObject* eo = scenegraph->visible_static_meshes_[i]; + if(eo->GetCollisionModelID() != -1){ + int id = eo->GetID(); + fwrite(&id, sizeof(int), 1, file); + int num_normals = (int)eo->normal_override_custom.size(); + fwrite(&num_normals, sizeof(int), 1, file); + if(num_normals > 0){ + fwrite(&eo->normal_override_custom[0], sizeof(vec4) * num_normals, 1, file); + } + int num_ledge_lines = (int)eo->ledge_lines.size(); + fwrite(&num_ledge_lines, sizeof(int), 1, file); + if(num_ledge_lines > 0){ + fwrite(&eo->ledge_lines[0], sizeof(int) * num_ledge_lines, 1, file); + } + } + } + fclose(file); + + Zip(temp_path, temp_path+".zip", "data", _YES_OVERWRITE); + //void UnZip(const std::string &zip_file_path, ExpandedZipFile &expanded_zip_file); + + std::string src = temp_path+".zip"; + std::string dst = path+".zip"; + if(movefile(src.c_str(), dst.c_str())){ + LOGI << "Creating parent dirs before trying to move file again" << std::endl; + CreateParentDirs(dst); + if(movefile(src.c_str(), dst.c_str())){ + #ifndef NO_ERR + DisplayError("Error", + (std::string("Problem moving file \"") + strerror(errno) + "\". From " + + src + + " to " + dst).c_str()); + #endif + } + } + } +} + +static int ParseCollisionNormalsFile(SceneGraph* scenegraph, const std::vector<char>& file){ + PROFILER_ZONE(g_profiler_ctx, "ParseCollisionNormalsFile"); + char header_buf[64] = {'\0'}; + int index = 0; + memread(header_buf, strlen(kCollisionNormalIdentifier), 1, file, index); + if(strcmp(header_buf, kCollisionNormalIdentifier) != 0){ + const int kBufSize = 512; + char buf[kBufSize]; + FormatString(buf, kBufSize, "Collision normal header '%s' does not match expected '%s'", header_buf, kCollisionNormalIdentifier); + DisplayError("Error", buf); + return -1; + } + int version; + memread(&version, sizeof(int), 1, file, index); + if(version <= 0 || version > kCollisionNormalVersion){ + const int kBufSize = 512; + char buf[kBufSize]; + FormatString(buf, kBufSize, "Collision normal version '%d' does not match expected '%d'", version, kCollisionNormalVersion); + DisplayError("Error", buf); + return -2; + } + int num_objects; + memread(&num_objects, sizeof(int), 1, file, index); + std::vector<vec4> normal_buf; + std::vector<int> ledge_line_buf; + for(int i=0; i<num_objects; ++i){ + int id; + memread(&id, sizeof(int), 1, file, index); + Object* obj = scenegraph->GetObjectFromID(id); + int num_normals; + memread(&num_normals, sizeof(int), 1, file, index); + normal_buf.resize(num_normals); + if(num_normals > 0){ + memread(&normal_buf[0], sizeof(vec4) * num_normals, 1, file, index); + } + if(version >= 2){ + int num_ledge_lines; + memread(&num_ledge_lines, sizeof(int), 1, file, index); + if(num_ledge_lines > 0){ + ledge_line_buf.resize(num_ledge_lines); + memread(&ledge_line_buf[0], sizeof(int) * num_ledge_lines, 1, file, index); + } + } + if(obj && obj->GetType() == _env_object){ + EnvObject* eo = (EnvObject*)obj; + eo->normal_override_custom = normal_buf; + eo->normal_override_buffer_dirty = true; + if(version >= 2){ + eo->ledge_lines = ledge_line_buf; + } + } + } + return 0; +} + +void LoadCollisionNormals(SceneGraph* scenegraph) { + PROFILER_ZONE(g_profiler_ctx, "LoadCollisionNormals"); + std::string path = scenegraph->level_path_.GetOriginalPathStr()+".col_norm.zip"; + if(FileExists(path, kAnyPath)){ + char temp_path[kPathSize]; + FindFilePath(path.c_str(), temp_path, kPathSize, kAnyPath); + ExpandedZipFile expanded_zip_file; + { + PROFILER_ZONE(g_profiler_ctx, "Unzipping file"); + UnZip(temp_path, expanded_zip_file); + } + std::vector<char> buffer; + const char* data; + const char* filename; + unsigned size; + expanded_zip_file.GetEntry(0, filename, data, size); + buffer.resize(size); + memcpy(&buffer[0], data, size); + ParseCollisionNormalsFile(scenegraph, buffer); + } +} + +//Draw the current frame +void Engine::Draw() { + Graphics* graphics = Graphics::Instance(); + Keyboard &keyboard = Input::Instance()->getKeyboard(); + + ++draw_frame; + num_detail_object_draw_calls = 0; + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw frame %d", draw_frame); + + if(user_paused){ + const int kBufSize = 512; + char buf[kBufSize]; + FormatString(buf, kBufSize, "Time is currently paused, press %s to unpause", config["bind[pause]"].str().c_str()); + gui.AddDebugText("paused", buf, 0.0f); + } + + if(slow_motion){ + const int kBufSize = 512; + char buf[kBufSize]; + FormatString(buf, kBufSize, "Time is currently slowed down, press %s to speed back up", config["key[slow]"].str().c_str()); + gui.AddDebugText("slow", buf, 0.0f); + } + + DrawImGui(graphics, scenegraph_, &gui, GetAssetManager(), Engine::Instance(), cursor.visible); + + if(check_save_level_changes_dialog_is_showing || !scenegraph_){ + // No level loaded, so draw menu instead + graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]); + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + graphics->Clear(true); + if(scriptable_menu_){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scriptable menu", draw_frame); + scriptable_menu_->Draw(); + } + RenderImGui(); + ImGui_ImplSdlGL3_RenderDrawLists(ImGui::GetDrawData()); + if(!WantMouseImGui()){ + cursor.Draw(); + } + + // Limit FPS in menus, even if limit_fps_in_game is not set to true (and thus timing code in GameMain isn't being called) + if(!graphics->config_.vSync() && !graphics->config_.limit_fps_in_game()){ + PROFILER_ZONE_IDLE(g_profiler_ctx, "SDL_Sleep"); + if(!g_draw_vr){ + static uint32_t last_time = 0; + + int max_frame_rate = graphics->config_.max_frame_rate(); + if(max_frame_rate < 15){ + max_frame_rate = 15; + } + if(max_frame_rate > 500){ + max_frame_rate = 500; + } + + uint32_t ticks_to_wait = (uint32_t)(1000.0f / max_frame_rate); + uint32_t diff = ticks_to_wait - (SDL_TS_GetTicks() - last_time) - 1; + if(diff > ticks_to_wait - 1){ + diff = ticks_to_wait - 1; + } + + SDL_Delay(diff); + last_time = SDL_TS_GetTicks(); + } + } + } else { + game_timer.ReportFrameForFPSCount(); + PushGPUProfileRange(__FUNCTION__); + + // Get number of screens that we need + const GraphicsConfig &graphics_config = graphics->config_; + scenegraph_->GetPlayerCharacterIDs(&num_avatars, avatar_ids, kMaxAvatars); + unsigned num_screens = clamp(num_avatars, 1, 4); + if(!Engine::Instance()->GetSplitScreen() || scenegraph_->map_editor->state_ != MapEditor::kInGame){ + num_screens = 1; + } + bool old_simple_water = g_simple_water; + if(num_screens > 1){ + g_simple_water = true; + } + + { // Perform per-frame calculations (like character shadows or LOD) + PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw frame"); + float predraw_time = game_timer.GetRenderTime(); + for(int i=0; i<(int)scenegraph_->objects_.size(); ++i){ + Object* obj = scenegraph_->objects_[i]; + if(!obj->parent){ + obj->PreDrawFrame(predraw_time); + } + } + } + + { + if(scenegraph_->reflection_data_loaded == false ) { + scenegraph_->LoadReflectionCaptureCubemaps(); + } + + PROFILER_GPU_ZONE(g_profiler_ctx, "Updating reflection capture cubemaps"); + std::vector<TextureRef> textures; + for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){ + Object* obj = scenegraph_->objects_[i]; + ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj; + if(obj->GetType() == _reflection_capture_object){ + if(reflection_obj->cube_map_ref.valid()){ + if(reflection_obj->GetScriptParams()->ASGetInt("Global") == 1){ + scenegraph_->sky->SetSpecularCubeMapTexture(reflection_obj->cube_map_ref); + } + } + } + } + textures.push_back(scenegraph_->sky->GetSpecularCubeMapTexture()); + scenegraph_->ref_cap_matrix.clear(); + scenegraph_->ref_cap_matrix_inverse.clear(); + for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){ + Object* obj = scenegraph_->objects_[i]; + ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj; + if(obj->GetType() == _reflection_capture_object){ + if(reflection_obj->cube_map_ref.valid()){ + if(reflection_obj->GetScriptParams()->ASGetInt("Global") != 1){ + textures.push_back(reflection_obj->cube_map_ref); + const mat4 &refmat = obj->GetTransform(); + scenegraph_->ref_cap_matrix.push_back(refmat); + scenegraph_->ref_cap_matrix_inverse.push_back(invert(refmat)); + } + } + } + } + + if(g_no_reflection_capture == false) + { + if(textures.size() > 6){ + textures.resize(6); + if(scenegraph_->ref_cap_matrix.size() > 6){ + scenegraph_->ref_cap_matrix.resize(6); + scenegraph_->ref_cap_matrix_inverse.resize(6); + } + LOGI << "Clamping number of cubemaps to 6" << std::endl; + } + + bool different = false; + if(textures.size() != scenegraph_->sub_cubemaps.size()){ + different = true; + } else { + for(size_t i=0, len=textures.size(); i<len; ++i){ + if(textures[i] != scenegraph_->sub_cubemaps[i]) { + different = true; + } + } + } + if(different || scenegraph_->cubemaps_need_refresh){ + scenegraph_->cubemaps_need_refresh = false; + { + LOGI << "Creating cubemaps texture" << std::endl; + PROFILER_GPU_ZONE(g_profiler_ctx, "Create cubemaps texture"); + //cubemaps = Textures::Instance()->makeCubemapArrayTexture(2, 128, 128, GL_RGBA16F, GL_RGBA, 0); + Textures::Instance()->setFilters(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); + Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + scenegraph_->cubemaps = Textures::Instance()->makeFlatCubemapArrayTexture(6, 128, 128, GL_RGBA16F, GL_RGBA, 0); + Textures::Instance()->SetTextureName(scenegraph_->cubemaps, "Reflection Capture Cubemap Array"); + Textures::Instance()->DeleteUnusedTextures(); + } + + Graphics* graphics = Graphics::Instance(); + graphics->PushFramebuffer(); + static bool initialized_framebuffers = false; + static GLuint framebuffers[2] = {0,0}; + if(initialized_framebuffers == false){ + Graphics::Instance()->genFramebuffers(&framebuffers[0], "cubemap_1"); + Graphics::Instance()->genFramebuffers(&framebuffers[1], "cubemap_2"); + initialized_framebuffers = true; + } + glColorMask(true, true, true, true); + graphics->setViewport(0, 0, 128*6, 128); + for(size_t tex_index=0, num_tex = textures.size(); tex_index<num_tex; ++tex_index){ + TextureRef tex = textures[tex_index]; + Textures::Instance()->EnsureInVRAM(tex); + CHECK_GL_ERROR(); + int width = 128, height = 128, level = 0; + while(width >= 1 && height >= 1){ + for(int i=0; i<6; ++i){ + glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[0]); + CHECK_GL_ERROR(); + int tex_id = Textures::Instance()->returnTexture(tex); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex_id, level); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[1]); + CHECK_GL_ERROR(); + tex_id = Textures::Instance()->returnTexture(scenegraph_->cubemaps); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_id, level, tex_index); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffers[0]); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffers[1]); + CHECK_GL_ERROR(); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer(0, 0, width, height, width*i, 0, width*i+width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + CHECK_GL_ERROR(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + CHECK_GL_ERROR(); + } + ++level; + width/=2; + height/=2; + } + } + graphics->PopFramebuffer(); + scenegraph_->sub_cubemaps = textures; + } + } else { + scenegraph_->cubemaps.clear(); + scenegraph_->cubemaps_need_refresh = true; + } + } + + if(shadow_cache_dirty){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Update shadow cache"); + if(UpdateShadowCache(scenegraph_)) + { + shadow_cache_dirty = false; + } + shadow_cache_dirty_sun_moved = false; // Always cleared. All the objects get their internal dirty flags set, so iterative processing can handle just a few per frame + shadow_cache_dirty_level_loaded = false; + } + + if(!g_simple_shadows && g_level_shadows) { + UpdateShadowCascades(scenegraph_); + } + + // Updated dynamic character cubemap if that is enabled + if(num_avatars != 0) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Update dynamic cubemap"); + MovementObject* player_obj = (MovementObject*)scenegraph_->GetObjectFromID(avatar_ids[0]); + if(graphics_config.dynamic_character_cubemap_ ){ + if(player_obj){ + bool temp_visible = player_obj->visible; + player_obj->visible = false; + bool media_mode = graphics->media_mode(); + graphics->SetMediaMode(true); + static TextureRef cube_map; + static GLuint cube_map_fbo; + if(!cube_map.valid()){ + CHECK_GL_ERROR(); + cube_map = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS); + Textures::Instance()->SetTextureName(cube_map, "Dynamic Character Cubemap"); + graphics->genFramebuffers(&cube_map_fbo,"cube_map_fbo"); + CHECK_GL_ERROR(); + } + player_obj->rigged_object()->cube_map = cube_map; + DrawCubeMap(cube_map, player_obj->position, cube_map_fbo, SceneGraph::kStaticAndDynamic); + CubemapMipChain(cube_map, Cubemap::SPHERE, NULL); + graphics->SetMediaMode(media_mode); + player_obj->visible = temp_visible; + } + } else { + player_obj->rigged_object()->cube_map.clear(); + } + } + + if(kLightProbeEnabled && !scenegraph_->light_probe_collection.to_process.empty()){ + LightProbeUpdateEntry entry = scenegraph_->light_probe_collection.to_process.front(); + scenegraph_->light_probe_collection.to_process.pop(); + LightProbe* probe = scenegraph_->light_probe_collection.GetProbeFromID(entry.id); + + if(probe){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Render light probe cubemap") + bool media_mode = graphics->media_mode(); + graphics->SetMediaMode(true); + // Increase light intensity to make shadows pure black + float temp_intensity = scenegraph_->primary_light.intensity; + if(entry.pass == 0){ + scenegraph_->primary_light.intensity = 3.0f; + } else { + scenegraph_->primary_light.intensity = 1.5f; + } + // Draw scene from light probe perspective + DrawCubeMap(scenegraph_->light_probe_collection.cube_map, probe->pos, + scenegraph_->light_probe_collection.cube_map_fbo, SceneGraph::kStaticOnly); + // Blur cubemap to represent different levels of roughness + CubemapMipChain(scenegraph_->light_probe_collection.cube_map, Cubemap::SPHERE, NULL); + // Reset the light intensity + scenegraph_->primary_light.intensity = temp_intensity; + graphics->SetMediaMode(media_mode); + + // Read pixels from roughest level of cubemap to get ambient cube + vec3 avg_color[6]; + ReadAverageColorsFromCubemap(avg_color, scenegraph_->light_probe_collection.cube_map); + for(int i=0; i<6; ++i){ + if(entry.pass == 0){ + probe->ambient_cube_color[i] = avg_color[i]; + } else if(entry.pass == 1){ + probe->ambient_cube_color_buf[i] = avg_color[i]; + } + } + } + // Finalize second bounce light probes + if (kLightProbe2pass && scenegraph_->light_probe_collection.to_process.empty()){ + for(size_t i=0, len=scenegraph_->light_probe_collection.light_probes.size(); i<len; ++i){ + for(int j=0; j<6; ++j){ + scenegraph_->light_probe_collection.light_probes[i].ambient_cube_color[j] = + scenegraph_->light_probe_collection.light_probes[i].ambient_cube_color_buf[j]; + } + } + kLightProbe2pass = false; + } + + scenegraph_->light_probe_collection.UpdateTextureBuffer(*scenegraph_->bullet_world_); + } + + // Clear background if we have more than one screen, since the screens might not + // completely cover everything + if(num_screens > 1 || graphics->render_output_dims[0] != graphics->window_dims[0] || graphics->render_output_dims[1] != graphics->window_dims[1]){ + graphics->startDraw(vec2(0.0f, 0.0f), vec2(1.0f, 1.0f), Graphics::kWindow); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f ); + graphics->Clear(true); + } + + // Draw voxel spans + static const bool kDrawSpans = false; + if(kDrawSpans){ + for(int span_index=0, len=(int)g_voxel_field.spans.size(); span_index<len; ++span_index){ + std::list<VoxelSpan>& span_list = g_voxel_field.spans[span_index]; + for(std::list<VoxelSpan>::iterator iter = span_list.begin(); + iter != span_list.end(); + ++iter) + { + VoxelSpan span = *iter; + + int voxel_x = span_index / g_voxel_field.voxel_field_dims[2]; + int voxel_z = span_index % g_voxel_field.voxel_field_dims[2]; + vec3 pos = vec3( + g_voxel_field.voxel_field_bounds[0][0] + (voxel_x+0.5f)*g_voxel_field.voxel_size, + (span.height[0] + span.height[1])*0.5f * g_voxel_field.voxel_size, + g_voxel_field.voxel_field_bounds[0][2] + (voxel_z+0.5f)*g_voxel_field.voxel_size); + vec3 box_size = vec3( + g_voxel_field.voxel_size*0.5f, + (span.height[0] - span.height[1])*0.5f * g_voxel_field.voxel_size, + g_voxel_field.voxel_size*0.5f); + DebugDraw::Instance()->AddWireBox(pos, box_size, vec4(1.0f), _delete_on_draw); + } + } + } + + if(scenegraph_->light_probe_collection.light_volume_enabled) + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Update light volume objects"); + LightVolumeObject* lvo = NULL; + for(size_t i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){ + lvo = scenegraph_->light_volume_objects_[i]; + } + + if(lvo && lvo->dirty && !IsBeingMoved(scenegraph_->map_editor, lvo)){ + int dims[3] = {600, 100, 100}; + float* pixels = (float*)alloc.stack.Alloc(dims[0] * dims[1] * dims[2] * 4 * sizeof(float)); + for(int x=0; x<dims[0]/6; ++x){ + for(int y=0; y<dims[1]; ++y){ + for(int z=0; z<dims[2]; ++z){ + vec3 pos = vec3(x/((float)dims[0] / 6.0f), y/(float)dims[1], z/(float)dims[2]); + pos -= vec3(0.5f); + pos *= 2.0f; + pos = lvo->GetTransform() * pos; + vec3 color[6]; + int tet = scenegraph_->light_probe_collection.GetTetrahedron(pos, color, -1); + for(int i=0; i<6; ++i){ + vec3 avg; + float opac = 1.0f; + if(tet != -1){ + avg = color[i]; + } else { + avg = vec3(0.0f,0.0f,0.0f); + opac = 0.0f; + } + //avg = pos * 0.01f; + int index = (z * (dims[0] * dims[1]) + y * (dims[0]) + x + i * dims[0]/6) * 4; + pixels[index + 0] = avg[2]; + pixels[index + 1] = avg[1]; + pixels[index + 2] = avg[0]; + pixels[index + 3] = opac; + } + } + } + } + scenegraph_->light_probe_collection.ambient_3d_tex = Textures::Instance()->make3DTexture(dims[0], dims[1], dims[2], GL_RGBA16F, GL_BGRA, false, &(pixels[0])); + Textures::Instance()->SetTextureName(scenegraph_->light_probe_collection.ambient_3d_tex, "Ambient Light Volume"); + alloc.stack.Free(pixels); + Textures::Instance()->DeleteUnusedTextures(); + lvo->dirty = false; + } + } + { + if( g_no_reflection_capture == false ) { + PROFILER_GPU_ZONE(g_profiler_ctx, "Update reflection capture objects"); + for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){ + Object* obj = scenegraph_->objects_[i]; + ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj; + if(obj->GetType() == _reflection_capture_object && reflection_obj->dirty){ + bool media_mode = graphics->media_mode(); + graphics->SetMediaMode(true); + // Draw scene from light probe perspective + if(!reflection_obj->cube_map_ref.valid()){ + reflection_obj->cube_map_ref = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS); + Textures::Instance()->SetTextureName(reflection_obj->cube_map_ref, "Reflection Capture Cubemap"); + } + DrawCubeMap(reflection_obj->cube_map_ref, scenegraph_->objects_[i]->GetTranslation(), + scenegraph_->light_probe_collection.cube_map_fbo, SceneGraph::kStaticOnly); + // Blur cubemap to represent different levels of roughness + CubemapMipChain(reflection_obj->cube_map_ref, Cubemap::SPHERE, NULL); + graphics->SetMediaMode(media_mode); + ReadAverageColorsFromCubemap(reflection_obj->avg_color, reflection_obj->cube_map_ref); + ((ReflectionCaptureObject*)(scenegraph_->objects_[i]))->dirty = false; + scenegraph_->cubemaps_need_refresh = true; + } + } + } + } + + CHECK_GL_ERROR(); + // Iterate through screens and render them + for(unsigned i=0; i<num_screens; ++i){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw screen"); + PushGPUProfileRange("Draw Screen"); + SetViewportForCamera(i, num_screens, Graphics::kRender); + ActiveCameras::Set(i); + ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>()); + if(level_loaded_) { // Draw the in-game scene if level is loaded + CHECK_GL_ERROR(); + DrawScene(kScreen, Engine::kFinal, SceneGraph::kStaticAndDynamic); + } + PopGPUProfileRange(); + } + CHECK_GL_ERROR(); + // Draw GUI layer + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw GUI screen"); + PushGPUProfileRange("Draw GUI Screen"); + CHECK_GL_ERROR(); + graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]); + CHECK_GL_ERROR(); + { + scenegraph_->level->Draw(); + CHECK_GL_ERROR(); + } + if(!graphics->media_mode()){ + CHECK_GL_ERROR(); + { // Draw alert string + if(scenegraph_->map_editor->state_ != MapEditor::kInGame){ + if(scenegraph_->level_path_.isValid()){ + gui.AddDebugText("_level",scenegraph_->level_path_.GetOriginalPath(),0.5); + } + if(graphics->nav_mesh_out_of_date){ + gui.AddDebugText("_nav_update","Nav mesh needs update",0.5); + } + } + } + } +#ifdef SLIM_TIMING + slimTimingEvents->drawVisualization( g_text_atlas_renderer, font_renderer, g_text_atlas[kTextAtlasMono] ); +#endif //SLIM_TIMING + if(config["fps_label"].toNumber<bool>() && !graphics->media_mode() || Online::Instance()->IsActive()) { + static float fps_updated_time = 0.0f; + if(fps_updated_time < ui_timer.game_time - 1.0f) { + float current = game_timer.GetFrameTime(); + float fastest = game_timer.GetFastestFrameTime(); + float slowest = game_timer.GetSlowestFrameTime(); + + int current_fps = (int) (current == 0.0f ? 0 : (1000.0f / current)); + int fastest_fps = (int) (fastest < 0.00001 ? 0 : (1000.0f / fastest)); + int slowest_fps = (int) (slowest < 0.00001 ? 0 : (1000.0f / slowest)); + + FormatString(fps_label_str, kFPSLabelMaxLen, "FPS: [%d, %d] current: %d", slowest_fps, fastest_fps, current_fps); + FormatString(frame_time_label_str, kFPSLabelMaxLen, "Frame time: [%.3f, %.3f] ms current: %.3f ms", fastest, slowest, current); + fps_updated_time = ui_timer.game_time; + } + gui.AddDebugText("_framerate",fps_label_str,0.5); + gui.AddDebugText("_frametime",frame_time_label_str,0.5); + } + + // Make sure glActiveTexture is synced up -- not sure why this should be necessary but it seems to be + // Otherwise character shadows break in game mode + Textures::Instance()->InvalidateBindCache(); + + if(kDrawHistogram){ + DrawHistogram(); + } + + PopGPUProfileRange(); + + } + + // Draw camera preview + if(level_loaded_) { // Draw the in-game scene if level is loaded + DrawImGuiCameraPreview(this,scenegraph_, graphics); + } + + if(!graphics->media_mode()){ + graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]); + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Render Dear ImGui screen"); + RenderImGui(); + ImGui_ImplSdlGL3_RenderDrawLists(ImGui::GetDrawData()); + } + if(!WantMouseImGui()){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw cursor"); + cursor.Draw(); + } + } + + const float fade_ticks = 300.0f; + if(level_has_screenshot && (first_level_drawn == UINT_MAX || SDL_TS_GetTicks() < first_level_drawn + fade_ticks)) { + CHECK_GL_ERROR(); + + if(first_level_drawn == UINT_MAX && !waiting_for_input_){ + first_level_drawn = SDL_TS_GetTicks(); + } + + // no coloring + Graphics* graphics = Graphics::Instance(); + Shaders* shaders = Shaders::Instance(); + + int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LEVEL_SCREENSHOT"); + shaders->createProgram(shader_id); + int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id); + int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id); + int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id); + int uniform_color = shaders->returnShaderVariable("color", shader_id); + shaders->setProgram(shader_id); + CHECK_GL_ERROR(); + + GLState gl_state; + gl_state.blend = true; + gl_state.cull_face = false; + gl_state.depth_write = false; + gl_state.depth_test = false; + graphics->setGLState(gl_state); + CHECK_GL_ERROR(); + + + glm::mat4 proj_mat; + proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]); + glm::mat4 modelview_mat(1.0f); + vec2 where; + vec2 size(Textures::Instance()->getWidth(level_screenshot->GetTextureRef()), + Textures::Instance()->getHeight(level_screenshot->GetTextureRef())); + size /= size[1]; + size *= (float) graphics->window_dims[1]; + where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.5f); + modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0)); + modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f)); + modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f)); + glm::mat4 mvp_mat = proj_mat * modelview_mat; + CHECK_GL_ERROR(); + + graphics->EnableVertexAttribArray(shader_attrib_vert_coord); + CHECK_GL_ERROR(); + graphics->EnableVertexAttribArray(shader_attrib_tex_coord); + CHECK_GL_ERROR(); + static const GLfloat verts[] = { + 0, 0, 0, 0, + 1, 0, 1, 0, + 1, 1, 1, 1, + 0, 1, 0, 1 + }; + static const GLuint indices[] = { + 0, 1, 2, + 0, 2, 3 + }; + static VBOContainer vert_vbo; + static VBOContainer index_vbo; + static bool vbo_filled = false; + if(!vbo_filled) { + vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts); + index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices); + vbo_filled = true; + } + vert_vbo.Bind(); + index_vbo.Bind(); + + glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0); + CHECK_GL_ERROR(); + glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2)); + CHECK_GL_ERROR(); + + int num_indices = 6; + + glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat); + CHECK_GL_ERROR(); + vec4 color(1.0f); + if(!waiting_for_input_){ + color[3] = 1.0f - (SDL_TS_GetTicks()-first_level_drawn) / fade_ticks; + } + glUniform4fv(uniform_color, 1, &color[0]); + CHECK_GL_ERROR(); + + Textures::Instance()->bindTexture(level_screenshot, 0); + CHECK_GL_ERROR(); + graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0); + CHECK_GL_ERROR(); + + graphics->BindElementVBO(0); + graphics->BindArrayVBO(0); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + + if(waiting_for_input_) { // Draw fun text + // Draw loading screen tip + int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50))); + TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, load_screen_tip); + int pos[] = {graphics->window_dims[0] / 2 - metrics.bounds[2] / 2, graphics->window_dims[1] / 2 - font_size / 2}; + g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, load_screen_tip, pos[0], pos[1], vec4(1.0f)); + + // Add spacing + pos[1] += metrics.bounds[3] + font_size; + + // Draw button prompt text + float pulse_while_waiting_for_input = powf(sinf((SDL_TS_GetTicks() - finished_loading_time) / (0.5f * 1000.0f)), 2.0f) / 2.0f + 0.5f; + std::string button_prompt_text = "Press any key to continue"; + if (Online::Instance()->IsActive()) { + if (Online::Instance()->IsHosting()) { + if (Online::Instance()->GetPlayerCount() > 0) { + if (Online::Instance()->AllClientsReady()) { + button_prompt_text = "All clients are ready. \nPress any key to continue"; + } else { + button_prompt_text = "Waiting for all clients to load"; + } + } else { + button_prompt_text = "Press any key to continue"; + } + } else { + button_prompt_text = "Waiting for the host to start the game"; + } + } + + if (load_screen_tip[0] == '\0') { + TextMetrics prompt_metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, button_prompt_text); + pos[0] = graphics->window_dims[0] / 2 - prompt_metrics.bounds[2] / 2; + } + g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, button_prompt_text, pos[0], pos[1], vec4(vec3(pulse_while_waiting_for_input), 1.0f)); + + Textures::Instance()->InvalidateBindCache(); + } + } + + //scenegraph_->decals->PostDraw(); + ActiveCameras::Set(0); + + PopGPUProfileRange(); + g_simple_water = old_simple_water; + } + + DecalTextures::Instance()->Draw(); + + ActiveCameras::Instance()->UpdatePrevViewMats(); + prev_view_time = game_timer.game_time + game_timer.GetInterpWeight() / 120.0f; + + { + PROFILER_GPU_ZONE(g_profiler_ctx, "Error check"); + if( printed_rendering_error_message == false ) { + if( CheckGLError(0,"", "General error in OpenGL rendering context. Please report with logfile to bugs@wolfire.com") ) { + printed_rendering_error_message = true; + } + } else { + char err[512]; + if( CheckGLErrorStr(err,512) ) { + LOGI << "OpenGL Error: " << err << std::endl; + } + } + } + + #ifdef OpenVR + if ( m_pHMD ) + { + vr::TrackedDevicePose_t m_rTrackedDevicePose[ vr::k_unMaxTrackedDeviceCount ]; + vr::EVRCompositorError err = vr::VRCompositor()->WaitGetPoses(m_rTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, NULL, 0 ); + if(err != vr::VRCompositorError_None){ + LOGE << "VR WaitGetPoses error: " << err << std::endl; + } + vr::TrackedDevicePose_t* hmd_pose = &m_rTrackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd]; + if(vr::VRCompositor()->CanRenderScene() && hmd_pose->bPoseIsValid){ + uint32_t dims[2]; + + m_pHMD->GetRecommendedRenderTargetSize( &dims[0], &dims[1] ); + static TextureRef resolve_tex = Textures::Instance()->makeRectangularTexture(dims[0],dims[1],GL_RGBA,GL_RGBA); + { + Graphics::Instance()->PushFramebuffer(); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + graphics->framebufferColorTexture2D(graphics->post_effects.temp_screen_tex); + graphics->bindFramebuffer(0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, graphics->post_effects.post_framebuffer); + GLint w = graphics->render_dims[0]; + GLint h = graphics->render_dims[1]; + glBlitFramebuffer(0, 0, Graphics::Instance()->window_dims[0], Graphics::Instance()->window_dims[1], + 0, 0, w, h, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + Graphics::Instance()->PopFramebuffer(); + } + //RenderControllerAxes(); + //RenderStereoTargets(); + //RenderCompanionWindow(); + + for(int eye=0; eye<2; ++eye){ + vr::EVREye vr_eye = eye ? vr::Eye_Left : vr::Eye_Right; + vr::HmdMatrix44_t vr_proj_mat = m_pHMD->GetProjectionMatrix( vr_eye, 0.2f, 20000.0f ); + vr::HmdMatrix34_t vr_eye_mat = m_pHMD->GetEyeToHeadTransform( vr_eye ); + vr::HmdMatrix34_t vr_head_mat = hmd_pose->mDeviceToAbsoluteTracking; + + mat4 proj_mat (vr_proj_mat.m[0][0], vr_proj_mat.m[1][0], vr_proj_mat.m[2][0], vr_proj_mat.m[3][0], + vr_proj_mat.m[0][1], vr_proj_mat.m[1][1], vr_proj_mat.m[2][1], vr_proj_mat.m[3][1], + vr_proj_mat.m[0][2], vr_proj_mat.m[1][2], vr_proj_mat.m[2][2], vr_proj_mat.m[3][2], + vr_proj_mat.m[0][3], vr_proj_mat.m[1][3], vr_proj_mat.m[2][3], vr_proj_mat.m[3][3]); + + mat4 eye_mat (vr_eye_mat.m[0][0], vr_eye_mat.m[1][0], vr_eye_mat.m[2][0], 0.0, + vr_eye_mat.m[0][1], vr_eye_mat.m[1][1], vr_eye_mat.m[2][1], 0.0, + vr_eye_mat.m[0][2], vr_eye_mat.m[1][2], vr_eye_mat.m[2][2], 0.0, + vr_eye_mat.m[0][3], vr_eye_mat.m[1][3], vr_eye_mat.m[2][3], 1.0f); + + mat4 head_mat (vr_head_mat.m[0][0], vr_head_mat.m[1][0], vr_head_mat.m[2][0], 0.0, + vr_head_mat.m[0][1], vr_head_mat.m[1][1], vr_head_mat.m[2][1], 0.0, + vr_head_mat.m[0][2], vr_head_mat.m[1][2], vr_head_mat.m[2][2], 0.0, + vr_head_mat.m[0][3], vr_head_mat.m[1][3], vr_head_mat.m[2][3], 1.0f); + + const bool kDrawImaxDisplay = true; + if(kDrawImaxDisplay) + { + Graphics::Instance()->PushFramebuffer(); + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + graphics->framebufferColorTexture2D(resolve_tex); + graphics->setViewport(0,0,dims[0],dims[1]); + graphics->Clear(true); + + Shaders* shaders = Shaders::Instance(); + int shader_id = Shaders::Instance()->returnProgram("simple_tex_3d #VR_DISPLAY"); + shaders->setProgram(shader_id); + shaders->SetUniformVec4("color", vec4(1.0f)); + int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id); + int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id); + graphics->BindArrayVBO(0); + CHECK_GL_ERROR(); + + GLState temp_gl_state; + temp_gl_state.blend = false; + temp_gl_state.cull_face = false; + temp_gl_state.depth_test = false; + temp_gl_state.depth_write = false; + graphics->setGLState(temp_gl_state); + CHECK_GL_ERROR(); + + float aspect = graphics->render_dims[0] / (float)graphics->render_dims[1]; + + mat4 translate_screen; + mat4 scale; + mat4 translate_offset; + + translate_screen.SetTranslation(vec3(0,0,-10.0f)); + scale.SetScale(vec3(10.0f*aspect, 10.0f, 10.0f)); + translate_offset.SetTranslation(vec3(-0.5f,-0.5f, 0.0f)); + + mat4 model = translate_screen * scale * translate_offset; + + mat4 m_mvp = proj_mat * invert(head_mat) * invert(eye_mat) * model; + shaders->SetUniformMat4("mvp_mat", m_mvp); + Textures::Instance()->bindTexture(graphics->post_effects.temp_screen_tex); + + graphics->EnableVertexAttribArray(vert_coord_id); + graphics->EnableVertexAttribArray(tex_coord_id); + quad_vert_vbo.Bind(); + quad_index_vbo.Bind(); + glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + DebugDraw::Instance()->Draw(); + Graphics::Instance()->PopFramebuffer(); + } + + vr::Texture_t vr_tex; + vr_tex.eType = vr::TextureType_OpenGL; + vr_tex.eColorSpace = vr::ColorSpace_Auto; + vr_tex.handle = (void*)Textures::Instance()->returnTexture(resolve_tex); + + vr::IVRCompositor* compositor = vr::VRCompositor(); + err = vr::VRCompositor()->Submit(vr_eye, &vr_tex ); + if(err != vr::VRCompositorError_None){ + LOGE << "VR Submit error: " << err << std::endl; + } + glFlush(); + } + glFlush(); + } + } + #endif + + #ifdef OculusVR + if(g_oculus_vr_activated){ + bool old_g_simple_water = g_simple_water; + g_simple_water = true; + PROFILER_ZONE(g_profiler_ctx, "VR"); + Graphics::Instance()->PushFramebuffer(); + ovrSessionStatus sessionStatus; + ovr_GetSessionStatus(session, &sessionStatus); + if (sessionStatus.ShouldQuit) + { + Input::Instance()->RequestQuit(); + } + if (sessionStatus.ShouldRecenter) + ovr_RecenterTrackingOrigin(session); + + + if (sessionStatus.IsVisible) + { + PROFILER_ENTER(g_profiler_ctx, "VR Setup"); + { + graphics->bindFramebuffer(graphics->post_effects.post_framebuffer); + graphics->framebufferColorTexture2D(Graphics::Instance()->post_effects.temp_screen_tex); + graphics->bindFramebuffer(0); + Graphics::Instance()->PushFramebuffer(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, graphics->post_effects.post_framebuffer); + GLint w = graphics->render_dims[0]; + GLint h = graphics->render_dims[1]; + glBlitFramebuffer(0, 0, Graphics::Instance()->window_dims[0], Graphics::Instance()->window_dims[1], + 0, 0, w, h, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + Graphics::Instance()->PopFramebuffer(); + } + + // Call ovr_GetRenderDesc each frame to get the ovrEyeRenderDesc, as the returned values (e.g. HmdToEyeOffset) may change at runtime. + ovrEyeRenderDesc eyeRenderDesc[2]; + eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]); + eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]); + + // Get eye poses, feeding in correct IPD offset + ovrPosef EyeRenderPose[2]; + ovrVector3f HmdToEyeOffset[2] = { eyeRenderDesc[0].HmdToEyeOffset, + eyeRenderDesc[1].HmdToEyeOffset }; + + double sensorSampleTime; // sensorSampleTime is fed into the layer later + ovr_GetEyePoses(session, frameIndex, ovrTrue, HmdToEyeOffset, EyeRenderPose, &sensorSampleTime); + + PROFILER_LEAVE(g_profiler_ctx); // VR SETUP + + ovrInputState touchState; + ovr_GetInputState(session, ovrControllerType_Active, &touchState); + ovrTrackingState trackingState = ovr_GetTrackingState(session, 0.0, false); + + static std::vector<int> debug_draw_vr; + for(int i=0, len=debug_draw_vr.size(); i<len; ++i){ + DebugDraw::Instance()->Remove(debug_draw_vr[i]); + } + debug_draw_vr.clear(); + + { + const int kBufSize = 512; + char key[kBufSize], val[kBufSize]; + FormatString(val, kBufSize, "touchState.Buttons: %u", touchState.Buttons); + gui.AddDebugText("Buttons", val, 0.5f); + FormatString(val, kBufSize, "touchState.Touches: %u", touchState.Touches); + gui.AddDebugText("Touches", val, 0.5f); + FormatString(val, kBufSize, "touchState.ControllerType: %#010x", (unsigned)touchState.ControllerType); + gui.AddDebugText("ControllerType", val, 0.5f); + + for(int hand=0; hand<2; ++hand){ + if(trackingState.HandStatusFlags[hand] & ovrStatus_PositionTracked){ + vec3 pos = *(vec3*)&trackingState.HandPoses[hand].ThePose.Position; + FormatString(key, kBufSize, "handpos%d", hand); + FormatString(val, kBufSize, "trackingState.HandPoses[%d].ThePose.Position: %f %f %f", hand, pos[0], pos[1], pos[2]); + quaternion quat = *(quaternion*)&trackingState.HandPoses[hand].ThePose.Orientation; + + mat4 scale; + scale.SetUniformScale(0.05f); + mat4 transform = Mat4FromQuaternion(quat) * scale; + transform.SetTranslationPart(ActiveCameras::Get()->GetPos() + pos); + debug_draw_vr.push_back( + DebugDraw::Instance()->AddTransformedWireScaledSphere(transform, + vec4(1.0f), + _fade)); + vec3 points[2]; + points[0] = transform * vec3(0,0,0); + points[1] = transform * vec3(0,0,0) + (transform * vec4(0,0,-100000,0)).xyz(); + debug_draw_vr.push_back( + DebugDraw::Instance()->AddLine(points[0], points[1], + vec4(1.0f), + _fade)); + + float dist = -10.0; + if(points[1][2] < dist){ + float t = (dist - points[0][2]) / (points[1][2] - points[0][2]); + vec3 pos = points[0] * (1.0-t) + points[1] * t; + vec2 pixel ((pos[0])*graphics->window_dims[1]*0.1f+graphics->window_dims[0]*0.5f, + (5.0f-pos[1])*graphics->window_dims[1]*0.1f); + if(pixel[0] > 0.0f && pixel[1] > 0.0f && + pixel[0] <= graphics->window_dims[0] && + pixel[1] <= graphics->window_dims[1]) + { + debug_draw_vr.push_back( + DebugDraw::Instance()->AddWireSphere(pos, + 0.1, + vec4(1.0f, 0.0f, 0.0f, 1.0f), + _fade)); + SDL_WarpMouseInWindow(Graphics::Instance()->sdl_window_, pixel[0], pixel[1]); + if(touchState.IndexTrigger[hand] > 0.5f){ + SDL_MouseButtonEvent event; + event.type = SDL_MOUSEBUTTONDOWN; + event.button = 1; + event.clicks = 1; + queued_events.push(*(SDL_Event*)&event); + } else { + SDL_MouseButtonEvent event; + event.type = SDL_MOUSEBUTTONUP; + event.button = 1; + queued_events.push(*(SDL_Event*)&event); + } + } + } + } + + } + } + + + // Render Scene to Eye Buffers + for (int eye = 0; eye < 2; ++eye) + { + PROFILER_ZONE(g_profiler_ctx, "VR draw eye screen"); + glDisable(GL_SCISSOR_TEST); + // Switch to eye render target + graphics->setDepthTest(true); + graphics->setDepthWrite(true); + glEnable(GL_DEPTH_TEST); + eyeRenderTexture[eye]->SetAndClearRenderSurface(eyeDepthBuffer[eye]); + float Yaw = 0.0f; + + // Get view and projection matrices + OVR::Matrix4f rollPitchYaw = OVR::Matrix4f::RotationY(Yaw); + OVR::Matrix4f finalRollPitchYaw = rollPitchYaw * OVR::Matrix4f(EyeRenderPose[eye].Orientation); + OVR::Vector3f finalUp = finalRollPitchYaw.Transform(OVR::Vector3f(0, 1, 0)); + OVR::Vector3f finalForward = finalRollPitchYaw.Transform(OVR::Vector3f(0, 0, -1)); + OVR::Vector3f shiftedEyePos = rollPitchYaw.Transform(*(OVR::Vector3f*)&((*(vec3*)&EyeRenderPose[eye].Position) + ActiveCameras::Get()->GetPos())); + + + OVR::Matrix4f view = OVR::Matrix4f::LookAtRH(shiftedEyePos, shiftedEyePos + finalForward, finalUp); + OVR::Matrix4f proj = ovrMatrix4f_Projection(hmdDesc.DefaultEyeFov[eye], 0.2f, 20000.0f, ovrProjection_None); + + const bool kDrawImaxDisplay = (!scenegraph_); + if(kDrawImaxDisplay) + { + Shaders* shaders = Shaders::Instance(); + int shader_id = Shaders::Instance()->returnProgram("simple_tex_3d #VR_DISPLAY"); + shaders->setProgram(shader_id); + shaders->SetUniformVec4("color", vec4(1.0f)); + int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id); + int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id); + graphics->BindArrayVBO(0); + CHECK_GL_ERROR(); + + GLState temp_gl_state; + temp_gl_state.blend = false; + temp_gl_state.cull_face = false; + temp_gl_state.depth_test = true; + temp_gl_state.depth_write = true; + graphics->setGLState(temp_gl_state); + CHECK_GL_ERROR(); + + float aspect = graphics->render_dims[0] / (float)graphics->render_dims[1]; + OVR::Matrix4f model = OVR::Matrix4f::Translation(0,0,-10) * OVR::Matrix4f::Scaling(10.0f*aspect, 10.0f, 10.0f) * OVR::Matrix4f::Translation(-0.5,-0.5,0); + + mat4 m_proj, m_view, m_model; + memcpy(&m_proj,& proj, sizeof(mat4)); + memcpy(&m_view, &view, sizeof(mat4)); + memcpy(&m_model, &model, sizeof(mat4)); + mat4 m_mvp = transpose(m_proj) * transpose(m_view) * transpose(m_model); + shaders->SetUniformMat4("mvp_mat", m_mvp); + Textures::Instance()->bindTexture(graphics->post_effects.temp_screen_tex); + + graphics->EnableVertexAttribArray(vert_coord_id); + graphics->EnableVertexAttribArray(tex_coord_id); + quad_vert_vbo.Bind(); + quad_index_vbo.Bind(); + glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + + memcpy(&m_proj, &proj, sizeof(mat4)); + memcpy(&m_view, &view, sizeof(mat4)); + m_view = transpose(m_view); + m_proj = transpose(m_proj); + ActiveCameras::Get()->SetMatrices((float*)&m_view, (float*)&m_proj); + DebugDraw::Instance()->Draw(); + } + + const bool kDrawVRScene = (scenegraph_); + if(kDrawVRScene){ + PROFILER_GPU_ZONE(g_profiler_ctx, "Draw screen"); + PushGPUProfileRange("Draw Screen"); + if(level_loaded_) { // Draw the in-game scene if level is loaded + mat4 m_proj, m_view, m_model; + memcpy(&m_proj, &proj, sizeof(mat4)); + memcpy(&m_view, &view, sizeof(mat4)); + m_view = transpose(m_view); + m_proj = transpose(m_proj); + auto cam = ActiveCameras::Get(); + vec3 cam_pos; + memcpy(&cam_pos, &shiftedEyePos, sizeof(cam_pos)); + auto old_interp = cam->interp_pos; + cam->interp_pos = cam_pos; + cam->SetMatrices((float*)&m_view, (float*)&m_proj); + + graphics->setDepthFunc(GL_LEQUAL); + graphics->setDepthTest(true); + graphics->setDepthWrite(true); + + cam->calcFrustumPlanes(m_proj, m_view); + + if(!g_no_decals){ + scenegraph_->PrepareLightsAndDecals(vec2(0, 0), vec2(1, 1), vec2(eyeRenderTexture[eye]->texSize.w, eyeRenderTexture[eye]->texSize.h)); + } + scenegraph_->DrawDepthMap(m_proj * m_view, + (const vec4*)ActiveCameras::Get()->frustumPlanes, + 6, + SceneGraph::kDepthPrePass, + SceneGraph::kStaticAndDynamic); + scenegraph_->Draw(SceneGraph::kStaticAndDynamic); + DebugDraw::Instance()->Draw(); + cam->interp_pos = old_interp; + } + PopGPUProfileRange(); + } + + // Avoids an error when calling SetAndClearRenderSurface during next iteration. + // Without this, during the next while loop iteration SetAndClearRenderSurface + // would bind a framebuffer with an invalid COLOR_ATTACHMENT0 because the texture ID + // associated with COLOR_ATTACHMENT0 had been unlocked by calling wglDXUnlockObjectsNV. + eyeRenderTexture[eye]->UnsetRenderSurface(); + + // Commit changes to the textures so they get picked up frame + eyeRenderTexture[eye]->Commit(); + } + + // Do distortion rendering, Present and flush/sync + + ovrLayerEyeFov ld; + ld.Header.Type = ovrLayerType_EyeFov; + ld.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; // Because OpenGL. + + for (int eye = 0; eye < 2; ++eye) + { + ld.ColorTexture[eye] = eyeRenderTexture[eye]->TextureChain; + ld.Viewport[eye] = OVR::Recti(eyeRenderTexture[eye]->GetSize()); + ld.Fov[eye] = hmdDesc.DefaultEyeFov[eye]; + ld.RenderPose[eye] = EyeRenderPose[eye]; + ld.SensorSampleTime = sensorSampleTime; + } + + ovrLayerHeader* layers = &ld.Header; + { + PROFILER_ZONE(g_profiler_ctx, "VR submit frame"); + ovrResult result = ovr_SubmitFrame(session, frameIndex, NULL, &layers, 1); + // exit the rendering loop if submit returns an error, will retry on ovrError_DisplayLost + if (!OVR_SUCCESS(result)) { + ovrErrorInfo errorInfo; + ovr_GetLastErrorInfo(&errorInfo); + FatalError("Error", "ovr_SubmitFrame failed: %s", errorInfo.ErrorString); + } + } + + frameIndex++; + } + + // Blit mirror texture to back buffer + const bool kMirror = false; + if(kMirror){ + glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + GLint w = Graphics::Instance()->window_dims[0]; + GLint h = Graphics::Instance()->window_dims[1]; + glBlitFramebuffer(0, h, w, 0, + 0, 0, w, h, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } + Graphics::Instance()->PopFramebuffer(); + + g_simple_water = old_g_simple_water; + } + #endif // OculusVR +} + +void Engine::LoadConfigFile() { + static const int kBufSize = 512; + char defaults_path[kBufSize]; + if(FindFilePath("Data/defaults.txt", defaults_path, kBufSize, kDataPaths | kModPaths) == -1){ + FatalError("Error", "Working directory is probably not set correctly -- could not find defaults.txt in Data folder"); + } + default_config.Load(defaults_path); + + std::string config_path = GetConfigPath(); + if(!config.Load(config_path)){ + // If no config file, then load defaults file, and create config file from that + if(!config.Load(defaults_path)){ + std::string abs_path = AbsPathFromRel("/Data"); + FatalError("Error", "Working directory is probably not set correctly -- could not find config.txt or defaults.txt in Data folder: %s", abs_path.c_str()); + } + config.Save(config_path); + } else { + // If config file is found, then fill in missing values with the defaults + Config old_config = config; + config.Load(defaults_path, true); + if(old_config != config){ + config.Save(config_path); + } + } + + config.GetRef("menu_player_config") = 0; + + Graphics::Instance()->SetFromConfig(config); + Input::Instance()->SetFromConfig(config); + //Textures::Instance()->SetProcessPoolsEnabled(config["background_process_pool"].toNumber<bool>()); + ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>()); + DebugDrawAux::Instance()->visible_sound_spheres = config["visible_sound_spheres"].toNumber<bool>(); + g_albedo_only = config["albedo_only"].toNumber<bool>(); + g_disable_fog = config["disable_fog"].toNumber<int>(); + g_no_reflection_capture = config["no_reflection_capture"].toNumber<bool>(); + g_no_decals = config["no_decals"].toNumber<bool>(); + g_no_decal_elements = config["no_decal_elements"].toNumber<bool>(); + g_no_detailmaps = config["no_detailmaps"].toNumber<bool>(); + g_single_pass_shadow_cascade = config["single_pass_shadow_cascade"].toNumber<bool>(); + sound.SetMusicVolume(config["music_volume"].toNumber<float>()); + sound.SetMasterVolume(config["master_volume"].toNumber<float>()); + std::string extra_data_path = config["extra_data_path"].str(); + + //Only care about data path if it's specified. + if( extra_data_path.length() > 0 ) + { + if( CheckFileAccess(extra_data_path.c_str()) ) + { + AddPath(extra_data_path.c_str(), kDataPaths); + } + else + { + std::stringstream ss; + ss << "Could not find: " << extra_data_path << std::endl << " Check extra_data_path in config file"; + DisplayError("Warning", ss.str().c_str(), _ok, false); + } + } + else + { + LOGE << "No extra_data_path was specified." << std::endl; + } + + Shaders::Instance()->shader_dir_path = "Data/GLSL/"; + script_dir_path = "Data/Scripts/"; + std::string mod_path = config["mod_path0"].str(); + if( !mod_path.empty()){ + if(CheckFileAccess(mod_path.c_str()) ){ + AddPath(mod_path.c_str(), kModPaths); + } else { + const int kBufSize = 512; + char err[kBufSize]; + FormatString(err, kBufSize, "Could not open path: %s\n", mod_path.c_str()); + DisplayError("Error", err); + } + } +} + +void Engine::PreloadAssets(const Path &level_path) { + //Release currently hold preloaded from previous level so they may get retagged. + asset_manager.ReleaseAssetHoldLoad(HOLD_LOAD_MASK_PRELOAD); + + std::vector<LevelAssetPreloadParser::Asset> &preload_files = AssetPreload::Instance().GetPreloadFiles(); + LOGI << "Starting Preloading for: " << level_path << std::endl; + for( unsigned i = 0; i < preload_files.size(); i++ ) { + if( preload_files[i].all_levels || preload_files[i].level_name == level_path.GetOriginalPathStr() ) { + //LOGI << "Preloading: " << preload_files[i].path << " " << preload_files[i].asset_type << " " << preload_files[i].load_flags << std::endl; + Path p = FindFilePath( preload_files[i].path, kAnyPath, false ); + if( p.isValid() ) { + GetAssetManager()->LoadSync(preload_files[i].asset_type, preload_files[i].path, preload_files[i].load_flags, HOLD_LOAD_MASK_PRELOAD); + } + } + } +} + +void Engine::LoadLevelData(const Path& level_path) { + LOGI << "Load Level: \"" << level_path << "\"" << std::endl; +#if THREADED + PROFILER_NAME_THREAD(g_profiler_ctx, "Level loading thread"); + NameCurrentThread("Level loading thread"); +#endif + PROFILER_ZONE(g_profiler_ctx, "Loading level"); + + LOG_ASSERT(scenegraph_->bullet_world_ == NULL); + scenegraph_->bullet_world_ = new BulletWorld(); + scenegraph_->bullet_world_->Init(); + scenegraph_->bullet_world_->SetGravity(Physics::Instance()->gravity); + scenegraph_->bullet_world_->CreatePlane(vec3(0.0f,1.0f,0.0f),-100.0f); + + scenegraph_->abstract_bullet_world_ = new BulletWorld(); + scenegraph_->abstract_bullet_world_->Init(); + + scenegraph_->plant_bullet_world_ = new BulletWorld(); + scenegraph_->plant_bullet_world_->Init(); + + //sound.AttachBulletWorld(scenegraph_->bullet_world_); + + AddLoadingText("Loading level..."); + + if( LevelLoader::LoadLevel(level_path, *scenegraph_) == false ) { + loading_mutex_.lock(); + finished_loading_time = (float) SDL_TS_GetTicks(); + loading_in_progress_ = false; + waiting_for_input_ = false; + loading_mutex_.unlock(); + return; + } + + AddLoadingText("Preloading assets..."); + + PreloadAssets(level_path); + + //Clear all unused assets, after loading new level. + while( asset_manager.UnloadUnreferenced(0,0) ){} + + loading_mutex_.lock(); + + LoadCollisionNormals(scenegraph_); + + { + PROFILER_ZONE(g_profiler_ctx, "UpdateControlledModule"); + avatar_control_manager.Update(); + } + + finished_loading_time = (float) SDL_TS_GetTicks(); + loading_in_progress_ = false; + waiting_for_input_ = true; + scenegraph_->map_editor->QueueSaveHistoryState(); + + loading_mutex_.unlock(); +} + + +void Engine::ClearLoadedLevel() { + //We want music in menues now, levels have to clear their own sounds. + sound.ClearTransient(); + sound.UpdateGameTimestep(game_timer.timestep); + sound.UpdateGameTimescale(1.0f); + sound.Update(); + if(scenegraph_){ + scenegraph_->Dispose(); + delete scenegraph_; + scenegraph_ = NULL; + } + Models::Instance()->Dispose(); + Online::Instance()->ClearIDTranslations(); + + asset_manager.Update(); + asset_manager.DumpLoadWarningData("asset_manager_warnings.xml"); + ActiveCameras::Get()->SetCameraObject(NULL); + level_loaded_ = false; + DebugDraw::Instance()->Dispose(); + + if( config["full_level_unload"].toBool() ) { + LOGI << "Unloading all unreferenced assets after clearing level [full_level_unload: true]..." << std::endl; + size_t pre_count = asset_manager.GetLoadedAssetCount(); + size_t tex_pre_count = asset_manager.GetAssetTypeCount(TEXTURE_ASSET); + while( asset_manager.UnloadUnreferenced(0,0) ){} + Textures::Instance()->DeleteUnusedTextures(); + LOGI << (pre_count - asset_manager.GetLoadedAssetCount()) << " Assets were unloaded" << std::endl; + LOGI << (tex_pre_count - asset_manager.GetAssetTypeCount(TEXTURE_ASSET)) << " Texture assets were unloaded" << std::endl; + } else { + LOGE << "Skipping post-clear level unload (Increases peak memory usage)[full_level_unload: false]" << std::endl; + } +} + +void Engine::LoadLevel(Path queued_level) { + GetAssetManager()->SetLoadWarningMode(false,"",""); + Shaders::Instance()->level_path = queued_level; + Shaders::Instance()->create_program_warning = false; + + PROFILER_ZONE(g_profiler_ctx, "Loading queued level"); + PROFILER_NAME_TIMELINE(g_profiler_ctx, "Loading level"); + + Path level_path = queued_level; + + latest_level_path_ = level_path; + + std::string difficulty = config.GetClosestDifficulty(); + + if( difficulty == "Casual" ) { + loading_screen_og_logo = loading_screen_og_logo_casual; + } else if( difficulty == "Hardcore" ) { + loading_screen_og_logo = loading_screen_og_logo_hardcore; + } else if( difficulty == "Expert" ) { + loading_screen_og_logo = loading_screen_og_logo_expert; + } + + + if( level_path.isValid() ) { + LevelInfoAssetRef lia = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(level_path.GetOriginalPath()); + if( lia.valid() ) { + Path shortest_path = FindShortestPath2(level_path.GetFullPath()); + const char* shortest_path_orig_folder = shortest_path.GetOriginalPath(); + + if( beginswith(shortest_path_orig_folder, "Data/Levels/") ) { + shortest_path_orig_folder = &shortest_path_orig_folder[strlen("Data/Levels/")]; + } + + level_has_screenshot = false; + level_screenshot.clear(); + char screenshot_path[kPathSize]; + + if( lia->GetLoadingScreenImage().empty() == false ) { + strscpy(screenshot_path, lia->GetLoadingScreenImage().c_str(), kPathSize); + } else { + LOGI << "No loading screen image, loading normal." << std::endl; + + FormatString(screenshot_path, kPathSize, "Data/LevelLoading/%s_loading.jpg", shortest_path_orig_folder); + } + + if(FileExists(screenshot_path, kModPaths | kDataPaths)){ + level_screenshot = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(screenshot_path, PX_NOCONVERT | PX_NOREDUCE | PX_NOMIPMAP, 0x0); + } else if (Online::Instance()->IsActive()) { + // Multiplayer needs an image for the "waiting for all clients" dialogue! + strscpy(screenshot_path, "Data/Images/full_fallback.png", kPathSize); + if(FileExists(screenshot_path, kModPaths | kDataPaths)) { + level_screenshot = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(screenshot_path, PX_NOCONVERT | PX_NOREDUCE | PX_NOMIPMAP, 0x0); + } else { + level_screenshot = loading_screen_logo; // Won't render correctly! Still better than nothing! + } + } + + if( level_screenshot.valid() ) { + level_has_screenshot = true; + first_level_drawn = UINT_MAX; + } + + AddLevelPathToRecentLevels(level_path); + + ClearLoadedLevel(); + forced_split_screen_mode = kForcedModeNone; + paused = false; + menu_paused = false; + user_paused = false; + slow_motion = false; + check_save_level_changes_dialog_is_showing = false; + check_save_level_changes_dialog_quit_if_not_cancelled = false; + check_save_level_changes_dialog_is_finished = false; + check_save_level_changes_last_result = false; + shadow_cache_dirty = true; + shadow_cache_dirty_sun_moved = true; + shadow_cache_dirty_level_loaded = true; + LOG_ASSERT(scenegraph_ == NULL); + scenegraph_ = new SceneGraph(); + scenegraph_->cubemaps_need_refresh = true; + scenegraph_->light_probe_collection.probe_lighting_enabled = config["tet_mesh_lighting"].toBool(); + scenegraph_->light_probe_collection.light_volume_enabled = config["light_volume_lighting"].toBool(); + ASData as_data; + as_data.scenegraph = scenegraph_; + as_data.gui = &gui; + scenegraph_->particle_system = new ParticleSystem(as_data); + scenegraph_->particle_system->particle_types = &particle_types; + scenegraph_->sky = new Sky(); + scenegraph_->flares.scenegraph = scenegraph_; + scenegraph_->map_editor = new MapEditor(); + scenegraph_->map_editor->gui = &gui; + scenegraph_->map_editor->Initialize(scenegraph_); + scenegraph_->map_editor->state_ = MapEditor::kIdle; + scenegraph_->map_editor->SetTypeEnabled(_light_probe_object, scenegraph_->light_probe_collection.show_probes); + scenegraph_->level = new Level(); + scenegraph_->level->Initialize(scenegraph_, &gui); + scenegraph_->level->script_params().SetObjectID(std::numeric_limits<uint32_t>::max()); + primary_light = &scenegraph_->primary_light; + + AddLoadingText("Starting to load \""+level_path.GetFullPathStr()+"\""); + + Input::Instance()->SetGrabMouse(false); + UIShowCursor(true); + + started_loading_time = SDL_TS_GetTicks(); + load_screen_tip[0] = '\0'; + LoadScreenLoop(true); + + interrupt_loading = false; + + #if THREADED + loading_in_progress_ = true; + std::thread load_thread(std::bind(&Engine::LoadLevelData, this, level_path)); + bool keep_looping = loading_in_progress_; + last_loading_input_time = SDL_TS_GetTicks(); + while(keep_looping){ + LoadScreenLoop(loading_in_progress_); + DisplayLastQueuedError(); + loading_mutex_.lock(); + keep_looping = loading_in_progress_; + loading_mutex_.unlock(); + } + load_thread.join(); + #else + LoadLevelData(level_path); + #endif + Graphics::Instance()->setSimpleShadows(config["simple_shadows"].toNumber<bool>()); + + if( interrupt_loading ) { + ClearLoadedLevel(); + return; + } + + if(g_no_reflection_capture == false ) + { + scenegraph_->LoadReflectionCaptureCubemaps(); + } + + if( kLightProbeEnabled ){ + PROFILER_ZONE(g_profiler_ctx, "Init light probe collection"); + scenegraph_->light_probe_collection.Init(); + } + { + PROFILER_ZONE(g_profiler_ctx, "Init dynamic light collection"); + scenegraph_->dynamic_light_collection.Init(); + } + { + PROFILER_ZONE(g_profiler_ctx, "Initialize level"); + scenegraph_->level->Initialize(scenegraph_, &gui); + } + + { + PROFILER_ZONE(g_profiler_ctx, "Send 'added_object' messages"); + for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){ + Object* obj = scenegraph_->objects_[i]; + static const int kBufSize = 256; + char msg[kBufSize]; + FormatString(msg, kBufSize, "added_object %d", obj->GetID()); + scenegraph_->level->Message(msg); + } + } + + AddLoadingText("Loading sky..."); + DrawLoadScreen(true); + + { + PROFILER_ZONE(g_profiler_ctx, "Load sky"); + scenegraph_->sky->QueueLoadResources(); + scenegraph_->sky->BakeFirstPass(); + if(!scenegraph_->terrain_object_){ + scenegraph_->sky->BakeSecondPass(NULL); + } + } + + AddLoadingText("Reinitializing prefabs..."); + scenegraph_->map_editor->ReloadAllPrefabs(); + + scenegraph_->map_editor->QueueSaveHistoryState(); + + AddLoadingText("Level load completed, initiating game..."); + AddLoadingText("Preloading shaders..."); + { + PROFILER_ZONE(g_profiler_ctx, "Preload shaders"); + scenegraph_->PreloadShaders(); + } + + DrawLoadScreen(true); + + if( kLightProbeEnabled ){ + PROFILER_ZONE(g_profiler_ctx, "Update light probe texture buffer"); + scenegraph_->light_probe_collection.UpdateTextureBuffer(*scenegraph_->bullet_world_); + } + + if ((config["editor_mode"].toNumber<bool>() == false && !scenegraph_->movement_objects_.empty()) || current_engine_state_.type != kEngineEditorLevelState) { + scenegraph_->map_editor->SendInRabbot(); + } + + native_loading_text.Clear(); + + level_loaded_ = true; + UIShowCursor(0); + level_updated_ = 0; + + if(level_path.GetOriginalPathStr() == new_empty_level_path){ + scenegraph_->level_path_ = Path(); + } + + if( level_has_screenshot && scenegraph_->level->loading_screen_.image.empty() ) { + scenegraph_->level->loading_screen_.image = screenshot_path; + } + scenegraph_->map_editor->UpdateEnabledObjects(); + //level_screenshot.clear(); + } else { + LOGE << "Unable to load LevelInfoAsset for " << level_path << ". Level file is likely corrupt or missing in some way." << std::endl; + } + } + + + Online::Instance()->SetLevelLoaded(); + Shaders::Instance()->create_program_warning = true; +} + +void Engine::AddLevelPathToRecentLevels(const Path& level_path) { + std::vector<std::string> level_history; + level_history.push_back(level_path.GetFullPath()); + + for(int i=0; i < kMaxLevelHistory; ++i) { + const int kConfigKeyNameSize = 128; + char configKeyName[kConfigKeyNameSize]; + FormatString(configKeyName, kConfigKeyNameSize, "level_history%d", i + 1); + + if(config.HasKey(configKeyName) && config[configKeyName].str() != level_path.GetFullPathStr() && config[configKeyName].str() != "") { + level_history.push_back(config[configKeyName].str()); + } + } + + for(int i = 0; i < kMaxLevelHistory; ++i) { + const int kConfigKeyNameSize = 128; + char configKeyName[kConfigKeyNameSize]; + FormatString(configKeyName, kConfigKeyNameSize, "level_history%d", i + 1); + + if(i < (int)level_history.size()) { + config.GetRef(configKeyName) = level_history[i]; + } else { + config.GetRef(configKeyName) = ""; + } + } + + config.Save(GetConfigPath()); +} + +void Engine::GetAvatarIds(std::vector<ObjectID> &avatars) { + scenegraph_->GetPlayerCharacterIDs(&num_avatars, avatar_ids, kMaxAvatars); + avatars.clear(); + for (int i = 0; i < num_avatars; ++i) { + avatars.push_back(avatar_ids[i]); + } +} + +void Engine::QueueLevelToLoad(const Path& level) { + if (Online::Instance()->IsHosting()) { + Online::Instance()->PerformLevelChangeCleanUp(); + Online::Instance()->ChangeLevel(level.GetOriginalPathStr()); + } + + queued_level_ = level; + UIShowCursor(1); + cursor.SetVisible(false); +} + +void Engine::QueueLevelCacheGeneration(const Path& path) { + cache_generation_paths.push_back(path); +} + +void Engine::GenerateLevelCache(ModInstance* mod_instance) { + for(size_t i = 0; i < mod_instance->levels.size(); ++i) { + std::string path = "Data/levels/"; + path += mod_instance->levels[i].path; + + QueueLevelCacheGeneration(FindFilePath(path, kAnyPath)); + } + for(size_t i = 0; i < mod_instance->campaigns.size(); ++i) { + for(size_t j = 0; j < mod_instance->campaigns[i].levels.size(); ++j) { + std::string path = "Data/levels/"; + path += mod_instance->campaigns[i].levels[j].path; + + QueueLevelCacheGeneration(FindFilePath(path, kAnyPath)); + } + } +} + +/** +FreeImage error handler +@param fif Format / Plugin responsible for the error +@param message Error message +*/ +void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) { + printf("\n*** "); + if(fif != FIF_UNKNOWN) { + printf("%s Format\n", FreeImage_GetFormatFromFIF(fif)); + } + printf(message); + printf(" ***\n"); +} + + +void Engine::Initialize() { + current_menu_player = -1; + waiting_for_input_ = false; + back_to_menu = false; + #ifdef OpenVR + // Loading the SteamVR Runtime + vr::EVRInitError eError = vr::VRInitError_None; + m_pHMD = vr::VR_Init( &eError, vr::VRApplication_Scene ); + + if ( eError != vr::VRInitError_None ) + { + m_pHMD = NULL; + char buf[1024]; + sprintf_s( buf, sizeof( buf ), "Unable to init VR runtime: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) ); + FatalError( "OpenVR Error", buf); + return; + } + + vr::IVRRenderModels *m_pRenderModels; + m_pRenderModels = (vr::IVRRenderModels *)vr::VR_GetGenericInterface( vr::IVRRenderModels_Version, &eError ); + if( !m_pRenderModels ) + { + m_pHMD = NULL; + vr::VR_Shutdown(); + + char buf[1024]; + sprintf_s( buf, sizeof( buf ), "Unable to get render model interface: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) ); + FatalError( "OpenVR Error", buf); + } + + if ( !vr::VRCompositor() ) + { + FatalError( "OpenVR Error", "vr::VRCompositor() failed"); + } + + std::string m_strDriver; + std::string m_strDisplay; + m_strDriver = "No Driver"; + m_strDisplay = "No Display"; + + m_strDriver = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String ); + m_strDisplay = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String ); + #endif + #ifdef OculusVR + ovrInitParams initParams = { ovrInit_RequestVersion, OVR_MINOR_VERSION, NULL, 0, 0 }; + ovrResult result = ovr_Initialize(&initParams); + if(OVR_FAILURE(result)) { + ovrErrorInfo errorInfo; + ovr_GetLastErrorInfo(&errorInfo); + LOGI << "ovr_Initialize failed: " << errorInfo.ErrorString << std::endl; + } else { + LOGE << "ovr_Initialize succeeded." << std::endl; + + ovrGraphicsLuid luid; + result = ovr_Create(&session, &luid); + if(OVR_FAILURE(result)) { + ovrErrorInfo errorInfo; + ovr_GetLastErrorInfo(&errorInfo); + LOGI << "ovr_Create failed: " << errorInfo.ErrorString << std::endl; + } else { + LOGE << "ovr_Create succeeded." << std::endl; + g_oculus_vr_activated = true; + g_draw_vr = true; + hmdDesc = ovr_GetHmdDesc(session); + } + } + #endif + AssetPreload::Instance().Initialize(); +#if ENABLE_STEAMWORKS + Steamworks::Instance()->Initialize(); +#endif + //multiplayer.Initialize(); + FreeImage_SetOutputMessage(FreeImageErrorHandler); + + if( Engine::instance_ != NULL ) + { + LOGF << "Engine only supports one instance at any time, if this need changes, refactoring will be needed for all situations that refer to Engine::Instance()" << std::endl; + } + else + { + Engine::instance_ = this; + } + + LOG_ASSERT( Engine::instance_ != NULL ); + paused = false; + scriptable_menu_ = NULL; + level_loaded_ = false; + quitting_ = false; + draw_frame = 0; + scenegraph_ = NULL; + resize_event_frame_counter = -1; + printed_rendering_error_message = false; + forced_split_screen_mode = kForcedModeNone; + + active_screen_start = vec2(0.0f, 0.0f); + active_screen_end = vec2(1.0f, 1.0f); + + // Check if we can find Data folder + char data_path[kPathSize]; + if(FindFilePath("Data", data_path, kPathSize, kDataPaths)){ + #ifdef _DEPLOY + FatalError("Error", "Could not find Data folder; please make sure Overgrowth is fully extracted and installed."); + #else + FatalError("Error", "Could not find Data folder; please make sure working directory is set correctly."); + #endif + } + + LOGI << "Initializing SDL" << std::endl; + if(SDL_Init(0) == 0) { + LOGI << "SDL Initialized successfully" << std::endl; + } else { + LOGE << "SDL Initialization failed" << std::endl; + } + + if( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0 ) { + LOGE << "Failed when initializing SDL video subsystem" << std::endl; + } + if( SDL_InitSubSystem(SDL_INIT_EVENTS) < 0 ) { + LOGE << "Failed when initializing SDL events subsystem" << std::endl; + } + if( SDL_InitSubSystem(SDL_INIT_TIMER) < 0 ) { + LOGE << "Failed when initializing SDL timer subsystem" << std::endl; + } + + LOGI << "Initializing SDL_net" << std::endl; + SDLNet_Init(); + + as_network.Initialize(); + asset_manager.Initialize(); + + KeyCommand::Initialize(); + ActiveCameras::Set(0); + Input::Instance()->Initialize(); + Input::Instance()->cursor = &cursor; + LoadConfigFile(); + AnimationRetargeter::Instance()->Load("Data/Animations/retarget.xml"); + + ModLoading::Instance().Initialize(); + ModLoading::Instance().InitMods(); + + std::string preferred_audio_device = config["preferred_audio_device"].str(); + sound.Initialize(preferred_audio_device.c_str()); + sound.EnableLayeredSoundtrackLimiter(config["use_soundtrack_limiter"].toBool()); + Graphics::Instance()->Initialize(); + Graphics::Instance()->InitScreen(); + + if( config["has_detected_settings"].toBool() == false ) { + DetectAndSetSettings(); + config.GetRef("has_detected_settings") = true; + } + + // Set initial default playername + if(!config.HasKey("playername") || !OnlineUtility::IsValidPlayerName(config.GetRef("playername").str())) { + config.GetRef("playername") = OnlineUtility::GetDefaultPlayerName(); + } + +#ifdef OculusVR + if(g_oculus_vr_activated){ + for (int eye = 0; eye < 2; ++eye) + { + ovrSizei idealTextureSize = ovr_GetFovTextureSize(session, ovrEyeType(eye), hmdDesc.DefaultEyeFov[eye], 1); + eyeRenderTexture[eye] = new OVR::TextureBuffer(session, true, true, idealTextureSize, 1, NULL, 1); + eyeDepthBuffer[eye] = new OVR::DepthBuffer(eyeRenderTexture[eye]->GetSize(), 0); + + if (!eyeRenderTexture[eye]->TextureChain) + { + FatalError("Error", "Could not create eye texture"); + } + } + + ovrMirrorTextureDesc desc; + memset(&desc, 0, sizeof(desc)); + desc.Width = hmdDesc.Resolution.w / 2; + desc.Height = hmdDesc.Resolution.h / 2; + desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; + ovrMirrorTexture mirrorTexture = NULL; + result = ovr_CreateMirrorTextureGL(session, &desc, &mirrorTexture); + if(OVR_FAILURE(result)) { + ovrErrorInfo errorInfo; + ovr_GetLastErrorInfo(&errorInfo); + LOGI << "ovr_CreateMirrorTextureGL failed: " << errorInfo.ErrorString << std::endl; + } else { + LOGE << "ovr_CreateMirrorTextureGL succeeded." << std::endl; + } + + // Configure the mirror read buffer + GLuint texId; + ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &texId); + + Graphics::Instance()->genFramebuffers(&mirrorFBO, "mirror_fbo"); + glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0); + glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); + } + #endif + + show_state = config["menu_show_state"].toBool(); + show_performance = config["menu_show_performance"].toBool(); + show_log = config["menu_show_log"].toBool(); + show_warnings = config["menu_show_warnings"].toBool(); + show_save = config["menu_show_save"].toBool(); + show_sound = config["menu_show_sound"].toBool(); + show_mod_menu = config["menu_show_mod_menu"].toBool(); + asdebugger_enabled = config["asdebugger_enabled"].toBool(); + asprofiler_enabled = config["asprofiler_enabled"].toBool(); + show_asdebugger_contexts = config["menu_show_asdebugger_contexts"].toBool(); + show_asprofiler = config["menu_show_asprofiler"].toBool(); + show_mp_debug = config["menu_show_mp_debug"].toBool(); + show_input_debug = config["menu_show_input_debug"].toBool(); + show_mp_info = config["menu_show_mp_info"].toBool(); + show_mp_settings = config["menu_show_mp_settings"].toBool(); + break_on_script_change = config["asdebugger_break_on_script_change"].toBool(); + + if (!GLEW_VERSION_4_0 && g_single_pass_shadow_cascade) { + LOGW << "OpenGL 4.0 not found, disabling single-pass shadow cascade" << std::endl; + g_single_pass_shadow_cascade = false; + config.GetRef("single_pass_shadow_cascade") = false; + } + + game_timer.SetStepFrequency(120); + ui_timer.SetStepFrequency(120); + { + char save[kPathSize]; + for( int i = SaveFile::kCurrentVersion; i >= 1; i--) { + if( i == 1 ) { + FormatString(save, kPathSize, "%ssavefile.sav", GetWritePath(CoreGameModID).c_str()); + } else { + FormatString(save, kPathSize, "%ssavefile.sav%d", GetWritePath(CoreGameModID).c_str(),i); + } + + if(FileExists(save,kAbsPath)){ + if( save_file_.ReadFromFile(save) ) { + LOGI << "Successfully loaded v" << i << " save file" << std::endl; + break; + } else { + LOGE << "Failed to load v" << i << " save file" << std::endl; + } + } + } + FormatString(save, kPathSize, "%ssavefile.sav%d", GetWritePath(CoreGameModID).c_str(),SaveFile::kCurrentVersion); + save_file_.SetWriteFile(save); + } + + CHECK_GL_ERROR(); + + cursor.SetCursor(DEFAULT_CURSOR); + + CHECK_GL_ERROR(); + PrintSpecs(); + CHECK_GL_ERROR(); + + CHECK_GL_ERROR(); + FontRenderer::Instance(&font_renderer); + + CHECK_GL_ERROR(); + if(config["load_all_levels"].toNumber<bool>()) { + ManifestXMLParser mp; + + if( !mp.Load( FindFilePath(config["ogda_manifest"].str(), kAnyPath).GetFullPathStr() ) ) + { + LOGF << "Unable to parse ogda manifest for load-all-levels" << std::endl; + } + + std::vector<ManifestXMLParser::BuilderResult>::iterator resultit; + + for( resultit = mp.manifest.builder_results.begin(); + resultit != mp.manifest.builder_results.end(); + resultit++ ) + { + if( resultit->type == "level" ) + { + LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(std::string("Data/") + resultit->dest); + Path level_path = FindFilePath(std::string("Data/") + resultit->dest, kAnyPath); + QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,level_path)); + } + } + } else if(config["main_menu"].toNumber<bool>()) { + CHECK_GL_ERROR(); + Path path = FindFilePath(script_dir_path + std::string("mainmenu.as"), kAnyPath); + QueueState( EngineState( "main_menu", kEngineScriptableUIState,path ) ); + CHECK_GL_ERROR(); + } else { + CHECK_GL_ERROR(); + Path path = FindFilePath(script_dir_path + std::string("mainmenu.as"), kAnyPath); + current_engine_state_ = EngineState("main_menu", kEngineScriptableUIState, path); // So that "back" takes us back to the main menu + + if( FileExists( DebugLoadLevel(), kAnyPath ) ) + { + Path path = FindFilePath(DebugLoadLevel(),kAnyPath); + LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath()); + QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState, path)); + } + else if( FileExists( std::string("Data/") + DebugLoadLevel(), kAnyPath ) ) + { + Path path = FindFilePath(std::string("Data/") + DebugLoadLevel(),kAnyPath); + LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath()); + QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,path)); + } + else + { + Path path = FindFilePath(std::string("Data/Levels/") + DebugLoadLevel(),kAnyPath); + LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath()); + QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,path)); + } + + CHECK_GL_ERROR(); + } + + if( config["global_time_scale_mult"].toNumber<float>() < 0.1f ) { + config.GetRef("global_time_scale_mult") = 0.1f; + } + game_timer.target_time_scale = config["global_time_scale_mult"].toNumber<float>(); + current_global_scale_mult = config["global_time_scale_mult"].toNumber<float>(); + + CHECK_GL_ERROR(); + g_text_atlas[kTextAtlasMono].Create(HardcodedPaths::paths[HardcodedPaths::kMonoFontPath], 18, &font_renderer, 0); + CHECK_GL_ERROR(); + g_text_atlas[kTextAtlasDynamic].Create(HardcodedPaths::paths[HardcodedPaths::kDynamicFontPath], 18, &font_renderer, 0); + CHECK_GL_ERROR(); + g_text_atlas_renderer.Init(); + + static const GLfloat quad_data[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + static const GLuint quad_index[] = { + 0, 1, 2, 0, 3, 2 + }; + + quad_vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(quad_data), (void*)quad_data); + quad_index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(quad_index), (void*)quad_index); + + InitImGui(); + + ModLoading::Instance().RegisterCallback(this); +#ifdef WIN32 + data_change_notification = FindFirstChangeNotificationW(UTF16fromUTF8(data_path).c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE); + char write_path[kPathSize]; + FindFilePath("Data/Mods/", write_path, kPathSize, kWriteDir); + write_change_notification = FindFirstChangeNotificationW(UTF16fromUTF8(write_path).c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE); +#endif + + loading_screen_logo = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/logo.tga", PX_SRGB | PX_NOREDUCE, 0x0); + + loading_screen_og_logo_casual = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_casual.png", PX_SRGB | PX_NOREDUCE, 0x0); + loading_screen_og_logo_hardcore = loading_screen_og_logo_casual;//= Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_hardcore.png", PX_SRGB | PX_NOREDUCE, 0x0); + loading_screen_og_logo_expert = loading_screen_og_logo_casual;//= Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_expert.png", PX_SRGB | PX_NOREDUCE, 0x0); + + loading_screen_og_logo = loading_screen_og_logo_casual; +} + +void Engine::GetShaderNames(std::map<std::string, int>& preload_shaders) { + std::string post_shader_name = Graphics::Instance()->post_shader_name; + if(scenegraph_->level->script_params().HasParam("Custom Shader") && config.GetRef("custom_level_shaders").toNumber<bool>()){ + const std::string& custom_shader = scenegraph_->level->script_params().GetStringVal("Custom Shader"); + if(!custom_shader.empty()) { + post_shader_name += " "+ custom_shader; + } + } + + preload_shaders[post_shader_name] = 0; + preload_shaders[post_shader_name + " #TONE_MAP"] = 0; + preload_shaders[post_shader_name + " #CALC_MOTION_BLUR"] = 0; + preload_shaders[post_shader_name + " #DOWNSAMPLE"] = 0; + preload_shaders[post_shader_name + " #BLUR_HORZ"] = 0; + preload_shaders[post_shader_name + " #BLUR_VERT"] = 0; + preload_shaders[post_shader_name + " #APPLY_MOTION_BLUR"] = 0; + preload_shaders[post_shader_name + " #DOF"] = 0; + preload_shaders[post_shader_name + " #COPY"] = 0; + preload_shaders[post_shader_name + " #DOWNSAMPLE #OVERBRIGHT"] = 0; + preload_shaders[post_shader_name + " #BLUR_DIR"] = 0; + preload_shaders[post_shader_name + " #ADD"] = 0; + preload_shaders[post_shader_name + " #DOWNSAMPLE_DEPTH"] = 0; + if(g_gamma_correct_final_output){ + preload_shaders[post_shader_name + " #BRIGHTNESS #GAMMA_CORRECT_OUTPUT"] = 0; + } else { + preload_shaders[post_shader_name + " #BRIGHTNESS"] = 0; + } +} + + +void Engine::ScriptableUICallback(const std::string &level) +{ + LOGW << "Got a callback to switch to: " << level << std::endl; + + if( config["global_time_scale_mult"].toNumber<float>() < 0.1f ) { + config.GetRef("global_time_scale_mult") = 0.1f; + } + game_timer.target_time_scale = config["global_time_scale_mult"].toNumber<float>(); + current_global_scale_mult = config["global_time_scale_mult"].toNumber<float>(); + + if( hasEnding( level, ".as" ) ) + { + std::string short_path = script_dir_path + level; + std::string long_path = level; + + //First check if a full path was entered. + if( FileExists( long_path, kAnyPath ) ) + { + LOGI << "L " << long_path << std::endl; + Path path = FindFilePath(long_path,kAnyPath); + QueueState(EngineState("",kEngineScriptableUIState,path)); + } + //Then check if it a path relative to script_dir_path as root. + else if( FileExists( short_path, kAnyPath ) ) + { + LOGI << "S " << long_path << std::endl; + Path path = FindFilePath(short_path,kAnyPath); + QueueState(EngineState("",kEngineScriptableUIState,path)); + } + else + { + std::string message = "Unable to find script: " + short_path + " or " + long_path + ". Will not change state"; + + DisplayError( "Unable to find script", message.c_str(), _ok, true ); + } + } + else if( hasEnding( level, ".xml" ) ) + { + std::string short_path = "Data/Levels/" + level; + std::string long_path = level; + + EngineStateType target_state_type = kEngineLevelState; + if(current_engine_state_.type == kEngineEditorLevelState){ + target_state_type = kEngineEditorLevelState; + } + + Path level_path; + //First check if a full path was entered. + if( FileExists( long_path, kAnyPath ) ) + { + level_path = FindFilePath(long_path,kAnyPath); + } + //Then check if it a path relative to Data/Levels as root. + else if( FileExists( short_path, kAnyPath ) ) + { + level_path = FindFilePath(short_path,kAnyPath); + } + + if( level_path.isValid() ) { + LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(level_path.GetFullPath()); + std::string level_id; + if(levelinfo.valid()){ + level_id = levelinfo->GetName(); + } + + ScriptableCampaign *sc = GetCurrentCampaign(); + if(sc) { + std::string campaign_id = sc->GetCampaignID(); + ModInstance::Campaign campaign = ModLoading::Instance().GetCampaign(campaign_id); + + for( size_t i = 0; i < campaign.levels.size(); i++ ) { + if( campaign.levels[i].path == long_path || campaign.levels[i].path == short_path ) { + level_id = campaign.levels[i].id; + } + } + } + QueueState(EngineState(level_id, target_state_type, level_path)); + } + else + { + std::string message = "Unable to find level: " + level + ". Will not change state"; + + DisplayError( "Unable to find level", message.c_str(), _ok, true ); + } + } + else if( hasBeginning( level, "set_campaign" ) ) + { + LOGI << "Setting campaign " << std::endl; + LOGI << level << std::endl; + if( level.size() > strlen("set_campaign ")) { + std::string campaign_id = level.substr(strlen("set_campaign ")); + if(strlen(ModLoading::Instance().GetCampaign(campaign_id).id) > 0) { + ModInstance::Campaign mi_campaign = ModLoading::Instance().GetCampaign(campaign_id); + ScriptableCampaign* campaign = new ScriptableCampaign(); + + Path campaign_path = FindFilePath(std::string("Data/Scripts/campaign/") + std::string(mi_campaign.main_script)); + if( campaign_path.isValid() == false ) { + if( strlen(mi_campaign.main_script) > 0 ) { + LOGE << "Given campaign script path \"" << mi_campaign.main_script << "\"is invalid, using fallback default_campaign.as" << std::endl; + } + campaign_path = FindFilePath("Data/Scripts/campaign/default_campaign.as"); + } + + if( campaign_path.isValid() ) { + LOGI << "Initializing campaign state with script " << campaign_path << std::endl; + campaign->Initialize( campaign_path, campaign_id ); + LOGI << "Campaign accepted, loading as state" << std::endl; + QueueState(EngineState("",campaign,campaign_path)); + } else { + LOGE << "Path to campaign script \"" << campaign_path << "\"" << " for campaign_id: " << campaign_id << std::endl; + } + } else { + LOGE << "Campaign id: " << campaign_id << " does not belong to any existing (or active) campaign " << std::endl; + } + } + } + else if( hasBeginning( level, "load_campaign_level" ) ) { + std::string level_id = level.substr(strlen("load_campaign_level ")); + QueueState(EngineState(level_id, kEngineLevelState)); + } + else if( level == "back" ) + { + //These rules should probably be removed from here and moved into scripts. + bool perform_exit = false; + switch( current_engine_state_.type ) { + case kEngineNoState: + LOGW << "Current state is no state, invalid state" << std::endl; + perform_exit = false; + break; + + case kEngineLevelState: + case kEngineEditorLevelState: + //Ingame, the esc button has very different use, + //instead spawning the main menu. + perform_exit = false; + break; + + case kEngineScriptableUIState: + if( scriptable_menu_ ) { + perform_exit = scriptable_menu_->CanGoBack(); + } else { + LOGE << "No current scriptable_ui_ when there should be." << std::endl; + perform_exit = true; + } + break; + case kEngineCampaignState: + perform_exit = true; + break; + default: + LOGE << "Unknown current state in ask pop queue state stack" << std::endl; + break; + } + + if( perform_exit ) { + EngineStateAction esa; + esa.type = kEngineStateActionPopState; + esa.allow_game_exit = false; + queued_engine_state_.push_back(esa); + } else { + LOGI << "Ignoring request to back" << std::endl; + } + } + else if( level == "back_to_menu" ) + { + EngineStateAction esa; + esa.type = kEngineStateActionPopUntilType; + esa.allow_game_exit = false; + esa.state.type = kEngineScriptableUIState; + queued_engine_state_.push_back(esa); + + Online::Instance()->StopMultiplayer(); + } + else if( level == "back_to_main_menu" ) + { + EngineStateAction esa; + esa.type = kEngineStateActionPopUntilID; + esa.allow_game_exit = false; + esa.state.id = "main_menu"; + queued_engine_state_.push_back(esa); + + Online::Instance()->StopMultiplayer(); + } + else if( level == "exit" ) + { + EngineStateAction esa; + esa.type = kEngineStateActionPopState; + esa.allow_game_exit = true; + queued_engine_state_.push_back(esa); + } + else if( level == "mods" ) + { + Path path = FindFilePath(script_dir_path + std::string("modmenu/main.as"),kAnyPath); + QueueState( EngineState("", kEngineScriptableUIState, path )); + } + else + { + LOGE << "Unknown message: " << level << std::endl; + } +} + +bool Engine::IsStateQueued() +{ + return (queued_engine_state_.size() > 0); +} + +void Engine::QueueState( const EngineState& state ) +{ + EngineStateAction esa; + esa.type = kEngineStateActionPushState; + esa.state = state; + queued_engine_state_.push_back(esa); +} + +void Engine::QueueState( const EngineStateAction& action ) +{ + queued_engine_state_.push_back(action); +} + +void Engine::QueueErrorMessage(const std::string& title, const std::string& message) +{ + // Send error directly to scriptable menus if possible (Main menu) + if(scriptable_menu_ != nullptr) { + scriptable_menu_->QueueBasicPopup(title, message); + } else { // If we don't have a menu, queue it up instead + popup_pueue.push_back(std::tuple<std::string, std::string>(title, message)); + } +} + +/* +void Engine::OpenChallengeMenu() { + LOG_ASSERT(!scriptable_menu_); + scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticHandleChallengeLevelSelect); + ASData as_data; + as_data.scenegraph = scenegraph_; + as_data.gui = &gui; + scriptable_menu_->Initialize(script_dir_path + "campaignchallengemenu.as", "void SetChallengeListMode()", as_data); +} +*/ + +void Engine::StaticScriptableUICallback( void* instance, const std::string &level ) { + ((Engine*)instance)->ScriptableUICallback(level); +} + +/* +void Engine::OpenCampaignMenu() +{ + LOG_ASSERT(!scriptable_menu_); + scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticHandleChallengeLevelSelect); + ASData as_data; + as_data.scenegraph = scenegraph_; + as_data.gui = &gui; + scriptable_menu_->Initialize(script_dir_path + "campaignmenu.as", "void SetCampaignListMode()", as_data); +} + +class NewMenuScenegraph: public SceneGraph{ + public: + NewMenuScenegraph():SceneGraph(){} + Sound * sound; +}; +*/ + + +void Engine::ModActivationChange( const ModInstance* mod ) { + ReloadImGui(); +} + +Path Engine::GetLatestMenuPath() { + return latest_menu_path_; +} + +Path Engine::GetLatestLevelPath() { + return latest_level_path_; +} + +void Engine::CommitPause() { + bool was_paused = paused; + if (!Online::Instance()->IsActive()) { + paused = user_paused || menu_paused; + } + else { + paused = false; + } + + game_timer.target_time_scale = (paused || slow_motion) ? 0.1f : current_global_scale_mult; + if (was_paused != paused) { + game_timer.time_scale = paused ? current_global_scale_mult : 0.1f; + } + if (!paused) { + Engine::Instance()->current_menu_player = -1; + } + +} + +ScriptableCampaign* Engine::GetCurrentCampaign() { + if(current_engine_state_.type == kEngineCampaignState ) { + if( current_engine_state_.campaign.Valid() ) { + return current_engine_state_.campaign.GetPtr(); + } + } else { + for( unsigned int i = 0; i < state_history.size(); i++ ) { + if( state_history[i].type == kEngineCampaignState ){ + if( state_history[i].campaign.Valid() ) { + return state_history[i].campaign.GetPtr(); + } + } + } + } + + return NULL; +} + +std::string Engine::GetCurrentLevelID() { + if(current_engine_state_.type == kEngineLevelState || current_engine_state_.type == kEngineEditorLevelState ) { + return current_engine_state_.id; + } else { + for( unsigned int i = 0; i < state_history.size(); i++ ) { + if( state_history[i].type == kEngineLevelState || state_history[i].type == kEngineEditorLevelState){ + return state_history[i].id; + } + } + } + return ""; +} + +void Engine::InjectWindowResizeEvent(ivec2 size) { + resize_event_frame_counter = 10; + resize_value = size; +} + +void DumpScenegraphState() { + SceneGraph* s = Engine::Instance()->GetSceneGraph(); + if( s) { + s->DumpState(); + } +} + +void Engine::SetGameSpeed(float val, bool hard) { + if( val >= 0.1f ) { + if(hard) { + game_timer.time_scale /= current_global_scale_mult; + } + game_timer.target_time_scale /= current_global_scale_mult; + config.GetRef("global_time_scale_mult") = val; + current_global_scale_mult = val; + game_timer.target_time_scale *= val; + if(hard) { + game_timer.time_scale *= val; + } + } else { + LOGE << "Disallowing setting gamespeed below 0.1" << std::endl; + } +} + +bool Engine::RequestedInterruptLoading() { + loading_mutex_.lock(); + bool val = interrupt_loading; + loading_mutex_.unlock(); + return val; +} |