Index: ps/trunk/source/ps/Util.cpp =================================================================== --- ps/trunk/source/ps/Util.cpp (revision 10547) +++ ps/trunk/source/ps/Util.cpp (revision 10548) @@ -1,369 +1,368 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ps/Util.h" #include "lib/posix/posix_utsname.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/bits.h" // round_up #include "lib/allocators/shared_ptr.h" #include "lib/sysdep/sysdep.h" // sys_OpenFile #include "lib/sysdep/gfx.h" #include "lib/sysdep/snd.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/arch/x86_x64/topology.h" #include "lib/sysdep/smbios.h" #include "lib/tex/tex.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "ps/Game.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/VideoMode.h" #include "renderer/Renderer.h" #include "maths/MathUtil.h" #include "graphics/GameView.h" extern CStrW g_CursorName; static std::string SplitExts(const char *exts) { std::string str = exts; std::string ret = ""; size_t idx = str.find_first_of(" "); while(idx != std::string::npos) { if(idx >= str.length() - 1) { ret += str; break; } ret += str.substr(0, idx); ret += "\n"; str = str.substr(idx + 1); idx = str.find_first_of(" "); } return ret; } void WriteSystemInfo() { TIMER(L"write_sys_info"); // get_cpu_info and gfx_detect already called during init - see call site snd_detect(); struct utsname un; uname(&un); OsPath pathname = psLogDir()/"system_info.txt"; FILE* f = sys_OpenFile(pathname, "w"); if(!f) return; // current timestamp (redundant WRT OS timestamp, but that is not // visible when people are posting this file's contents online) { wchar_t timestampBuf[100] = {'\0'}; time_t seconds; time(&seconds); struct tm* t = gmtime(&seconds); const size_t charsWritten = wcsftime(timestampBuf, ARRAY_SIZE(timestampBuf), L"(generated %Y-%m-%d %H:%M:%S UTC)", t); ENSURE(charsWritten != 0); fprintf(f, "%ls\n\n", timestampBuf); } // OS fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version); // CPU fprintf(f, "CPU : %s, %s (%dx%dx%d)", un.machine, cpu_IdentifierString(), (int)cpu_topology_NumPackages(), (int)cpu_topology_CoresPerPackage(), (int)cpu_topology_LogicalPerCore()); double cpuClock = os_cpu_ClockFrequency(); // query OS (may fail) if(cpuClock <= 0.0) cpuClock = x86_x64_ClockFrequency(); // measure (takes a few ms) if(cpuClock > 0.0) { if(cpuClock < 1e9) fprintf(f, ", %.2f MHz\n", cpuClock*1e-6); else fprintf(f, ", %.2f GHz\n", cpuClock*1e-9); } else fprintf(f, "\n"); // memory fprintf(f, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable()); // graphics const std::wstring cardName = gfx::CardName(); const std::wstring driverInfo = gfx::DriverInfo(); fprintf(f, "Graphics Card : %ls\n", cardName.c_str()); fprintf(f, "OpenGL Drivers : %s; %ls\n", glGetString(GL_VERSION), driverInfo.c_str()); fprintf(f, "Video Mode : %dx%d:%d\n", g_VideoMode.GetXRes(), g_VideoMode.GetYRes(), g_VideoMode.GetBPP()); // sound fprintf(f, "Sound Card : %ls\n", snd_card); fprintf(f, "Sound Drivers : %ls\n", snd_drv_ver); // OpenGL extensions (write them last, since it's a lot of text) const char* exts = ogl_ExtensionString(); if (!exts) exts = "{unknown}"; fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str()); // System Management BIOS (even more text than OpenGL extensions) std::string smbios = SMBIOS::StringizeStructures(SMBIOS::GetStructures()); fprintf(f, "\nSMBIOS: \n%s\n", smbios.c_str()); fclose(f); f = 0; } // not thread-safe! static const wchar_t* HardcodedErrorString(int err) { static wchar_t description[200]; StatusDescription((Status)err, description, ARRAY_SIZE(description)); return description; } // not thread-safe! const wchar_t* ErrorString(int err) { // language file not available (yet) return HardcodedErrorString(err); // TODO: load from language file } // write the specified texture to disk. // note: cannot be made const because the image may have to be // transformed to write it out in the format determined by 's extension. Status tex_write(Tex* t, const VfsPath& filename) { DynArray da; RETURN_STATUS_IF_ERR(tex_encode(t, filename.Extension(), &da)); // write to disk Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); else ret = (Status)bytes_written; } (void)da_free(&da); return ret; } static size_t s_nextScreenshotNumber; // identifies the file format that is to be written // (case-insensitive). examples: "bmp", "png", "jpg". // BMP is good for quick output at the expense of large files. void WriteScreenshot(const VfsPath& extension) { // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath basenameFormat(L"screenshots/screenshot%04d"); const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename); const size_t w = (size_t)g_xres, h = (size_t)g_yres; const size_t bpp = 24; GLenum fmt = GL_RGB; int flags = TEX_BOTTOM_UP; // we want writing BMP to be as fast as possible, // so read data from OpenGL in BMP format to obviate conversion. if(extension == L".bmp") { fmt = GL_BGR; flags |= TEX_BGR; } // Hide log messages and re-render RenderLogger(false); Render(); RenderLogger(true); const size_t img_size = w * h * bpp/8; const size_t hdr_size = tex_hdr_size(filename); shared_ptr buf; AllocateAligned(buf, hdr_size+img_size, maxSectorSize); GLvoid* img = buf.get() + hdr_size; Tex t; if(tex_wrap(w, h, bpp, flags, buf, hdr_size, &t) < 0) return; glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img); if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(L"Screenshot written to '%ls'", realPath.string().c_str()); } else LOGERROR(L"Error writing screenshot to '%ls'", filename.string().c_str()); tex_free(&t); } // Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles. void WriteBigScreenshot(const VfsPath& extension, int tiles) { // If the game hasn't started yet then use WriteScreenshot to generate the image. if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; } // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath basenameFormat(L"screenshots/screenshot%04d"); const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename); // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and // hope the screen is actually large enough for that. const int tile_w = 640, tile_h = 480; ENSURE(g_xres >= tile_w && g_yres >= tile_h); const int img_w = tile_w*tiles, img_h = tile_h*tiles; const int bpp = 24; GLenum fmt = GL_RGB; int flags = TEX_BOTTOM_UP; // we want writing BMP to be as fast as possible, // so read data from OpenGL in BMP format to obviate conversion. if(extension == L".bmp") { fmt = GL_BGR; flags |= TEX_BGR; } const size_t img_size = img_w * img_h * bpp/8; const size_t tile_size = tile_w * tile_h * bpp/8; const size_t hdr_size = tex_hdr_size(filename); void* tile_data = malloc(tile_size); if(!tile_data) { WARN_IF_ERR(ERR::NO_MEM); return; } shared_ptr img_buf; AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize); Tex t; GLvoid* img = img_buf.get() + hdr_size; if(tex_wrap(img_w, img_h, bpp, flags, img_buf, hdr_size, &t) < 0) { free(tile_data); return; } ogl_WarnIfError(); // Resize various things so that the sizes and aspect ratios are correct { g_Renderer.Resize(tile_w, tile_h); SViewPort vp = { 0, 0, tile_w, tile_h }; g_Game->GetView()->GetCamera()->SetViewPort(vp); - g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); + g_Game->GetView()->SetCameraProjection(); } // Temporarily move everything onto the front buffer, so the user can // see the exciting progress as it renders (and can tell when it's finished). // (It doesn't just use SwapBuffers, because it doesn't know whether to // call the SDL version or the Atlas version.) GLint oldReadBuffer, oldDrawBuffer; glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer); glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer); glDrawBuffer(GL_FRONT); glReadBuffer(GL_FRONT); // Hide the cursor CStrW oldCursor = g_CursorName; g_CursorName = L""; // Render each tile for (int tile_y = 0; tile_y < tiles; ++tile_y) { for (int tile_x = 0; tile_x < tiles; ++tile_x) { // Adjust the camera to render the appropriate region g_Game->GetView()->GetCamera()->SetProjectionTile(tiles, tile_x, tile_y); RenderLogger(false); RenderGui(false); Render(); RenderGui(true); RenderLogger(true); // Copy the tile pixels into the main image glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data); for (int y = 0; y < tile_h; ++y) { void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8; void* src = (char*)tile_data + y * tile_w * bpp/8; memcpy(dest, src, tile_w * bpp/8); } } } // Restore the old cursor g_CursorName = oldCursor; // Restore the buffer settings glDrawBuffer(oldDrawBuffer); glReadBuffer(oldReadBuffer); // Restore the viewport settings { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->GetCamera()->SetViewPort(vp); - g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); - + g_Game->GetView()->SetCameraProjection(); g_Game->GetView()->GetCamera()->SetProjectionTile(1, 0, 0); } if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(L"Screenshot written to '%ls'", realPath.string().c_str()); } else LOGERROR(L"Error writing screenshot to '%ls'", filename.string().c_str()); tex_free(&t); free(tile_data); } Index: ps/trunk/source/tools/atlas/GameInterface/View.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 10547) +++ ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 10548) @@ -1,391 +1,391 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "View.h" #include "ActorViewer.h" #include "GameLoop.h" #include "Messages.h" #include "SimState.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/ParticleManager.h" #include "graphics/SColor.h" #include "graphics/UnitManager.h" #include "lib/timer.h" #include "lib/utf8.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpPathfinder.h" extern void (*Atlas_GLSwapBuffers)(void* context); extern int g_xres, g_yres; ////////////////////////////////////////////////////////////////////////// void View::SetParam(const std::wstring& UNUSED(name), bool UNUSED(value)) { } void View::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Colour& UNUSED(value)) { } void View::SetParam(const std::wstring& UNUSED(name), const std::wstring& UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// ViewActor::ViewActor() : m_SpeedMultiplier(1.f), m_ActorViewer(new ActorViewer()) { } ViewActor::~ViewActor() { delete m_ActorViewer; } void ViewActor::Update(float frameLength) { m_ActorViewer->Update(frameLength * m_SpeedMultiplier); } void ViewActor::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjection(2.f, 512.f, DEGTORAD(20.f)); camera.UpdateFrustum(); m_ActorViewer->Render(); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); } CCamera& ViewActor::GetCamera() { return m_Camera; } CSimulation2* ViewActor::GetSimulation2() { return m_ActorViewer->GetSimulation2(); } entity_id_t ViewActor::GetEntityId(AtlasMessage::ObjectID UNUSED(obj)) { return m_ActorViewer->GetEntity(); } bool ViewActor::WantsHighFramerate() { if (m_SpeedMultiplier != 0.f) return true; return false; } void ViewActor::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } ActorViewer& ViewActor::GetActorViewer() { return *m_ActorViewer; } void ViewActor::SetParam(const std::wstring& name, bool value) { if (name == L"wireframe") g_Renderer.SetModelRenderMode(value ? WIREFRAME : SOLID); else if (name == L"walk") m_ActorViewer->SetWalkEnabled(value); else if (name == L"ground") m_ActorViewer->SetGroundEnabled(value); else if (name == L"shadows") m_ActorViewer->SetShadowsEnabled(value); else if (name == L"stats") m_ActorViewer->SetStatsEnabled(value); } void ViewActor::SetParam(const std::wstring& name, const AtlasMessage::Colour& value) { if (name == L"background") { m_ActorViewer->SetBackgroundColour(SColor4ub(value.r, value.g, value.b, 255)); } } ////////////////////////////////////////////////////////////////////////// template static void delete_pair_2nd(std::pair v) { delete v.second; } ViewGame::ViewGame() : m_SpeedMultiplier(0.f) { ENSURE(g_Game); } ViewGame::~ViewGame() { std::for_each(m_SavedStates.begin(), m_SavedStates.end(), delete_pair_2nd); } CSimulation2* ViewGame::GetSimulation2() { return g_Game->GetSimulation2(); } void ViewGame::Update(float frameLength) { float actualFrameLength = frameLength * m_SpeedMultiplier; // Clean up any entities destroyed during UI message processing g_Game->GetSimulation2()->FlushDestroyedEntities(); if (m_SpeedMultiplier == 0.f) { // Update unit interpolation g_Game->Interpolate(0.0); // Update particles even when the game is paused, so people can see // what they look like. (TODO: maybe it'd be nice if this only applied in // the not-testing-game editor state, not the testing-game-but-currently-paused // state. Or maybe display a static snapshot of the particles (at some time // later than 0 so they're actually visible) instead of animation, or something.) g_Renderer.GetParticleManager().Interpolate(frameLength); } else { // Update the whole world // (Tell the game update not to interpolate graphics - we'll do that // ourselves) bool ok = g_Game->Update(actualFrameLength, false); if (! ok) { // Whoops, we're trying to go faster than the simulation can manage. // It's probably better to run at the right sim rate, at the expense // of framerate, so let's try simulating a few more times. double t = timer_Time(); while (!ok && timer_Time() < t + 0.1) // don't go much worse than 10fps { ok = g_Game->Update(0.0, false); // don't add on any extra sim time } } // Interpolate the graphics - we only want to do this once per visual frame, // not in every call to g_Game->Update g_Game->Interpolate(actualFrameLength); } // Cinematic motion should be independent of simulation update, so we can // preview the cinematics by themselves if (g_Game->GetView()->GetCinema()->IsPlaying()) g_Game->GetView()->GetCinema()->Update(frameLength); } void ViewGame::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); - camera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); + camera.SetProjection(g_Game->GetView()->GetNear(), g_Game->GetView()->GetFar(), g_Game->GetView()->GetFOV()); camera.UpdateFrustum(); // Update the pathfinder display if necessary if (!m_DisplayPassability.empty()) { CmpPtr cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpObstructionMan.null()) { cmpObstructionMan->SetDebugOverlay(true); } CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpPathfinder.null()) { cmpPathfinder->SetDebugOverlay(true); // Kind of a hack to make it update the terrain grid ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, fixed::Zero(), fixed::Zero() }; ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability); ICmpPathfinder::cost_class_t costClass = cmpPathfinder->GetCostClass("default"); cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass, costClass); } } ::Render(); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); } void ViewGame::SetParam(const std::wstring& name, bool value) { if (name == L"priorities") g_Renderer.SetDisplayTerrainPriorities(value); } void ViewGame::SetParam(const std::wstring& name, const std::wstring& value) { if (name == L"passability") { m_DisplayPassability = CStrW(value).ToUTF8(); CmpPtr cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpObstructionMan.null()) cmpObstructionMan->SetDebugOverlay(!value.empty()); CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpPathfinder.null()) cmpPathfinder->SetDebugOverlay(!value.empty()); } else if (name == L"renderpath") { g_Renderer.SetRenderPath(g_Renderer.GetRenderPathByName(CStrW(value).ToUTF8())); } } CCamera& ViewGame::GetCamera() { return *g_Game->GetView()->GetCamera(); } bool ViewGame::WantsHighFramerate() { if (g_Game->GetView()->GetCinema()->IsPlaying()) return true; if (m_SpeedMultiplier != 0.f) return true; return false; } void ViewGame::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } void ViewGame::SaveState(const std::wstring& label) { delete m_SavedStates[label]; // in case it already exists m_SavedStates[label] = SimState::Freeze(); } void ViewGame::RestoreState(const std::wstring& label) { SimState* simState = m_SavedStates[label]; if (! simState) return; simState->Thaw(); } std::wstring ViewGame::DumpState(bool binary) { std::stringstream stream; if (binary) { if (! g_Game->GetSimulation2()->SerializeState(stream)) return L"(internal error)"; // We can't return raw binary data, because we want to handle it with wxJS which // doesn't like \0 bytes in strings, so return it as hex static const char digits[] = "0123456789abcdef"; std::string str = stream.str(); std::wstring ret; ret.reserve(str.length()*3); for (size_t i = 0; i < str.length(); ++i) { ret += digits[(unsigned char)str[i] >> 4]; ret += digits[(unsigned char)str[i] & 0x0f]; ret += ' '; } return ret; } else { if (! g_Game->GetSimulation2()->DumpDebugState(stream)) return L"(internal error)"; return wstring_from_utf8(stream.str()); } } ////////////////////////////////////////////////////////////////////////// ViewNone* view_None = NULL; ViewGame* view_Game = NULL; ViewActor* view_Actor = NULL; View::~View() { } View* View::GetView(int /*eRenderView*/ view) { switch (view) { case AtlasMessage::eRenderView::NONE: return View::GetView_None(); case AtlasMessage::eRenderView::GAME: return View::GetView_Game(); case AtlasMessage::eRenderView::ACTOR: return View::GetView_Actor(); default: debug_warn(L"Invalid view type"); return View::GetView_None(); } } View* View::GetView_None() { if (! view_None) view_None = new ViewNone(); return view_None; } ViewGame* View::GetView_Game() { if (! view_Game) view_Game = new ViewGame(); return view_Game; } ViewActor* View::GetView_Actor() { if (! view_Actor) view_Actor = new ViewActor(); return view_Actor; } void View::DestroyViews() { delete view_None; view_None = NULL; delete view_Game; view_Game = NULL; delete view_Actor; view_Actor = NULL; } Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 10547) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 10548) @@ -1,205 +1,205 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MessageHandler.h" #include "../MessagePasserImpl.h" #include "../GameLoop.h" #include "../View.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "gui/GUIManager.h" #include "lib/external_libraries/sdl.h" #include "lib/sysdep/cpu.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/Util.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "renderer/Renderer.h" extern void (*Atlas_GLSwapBuffers)(void* context); namespace AtlasMessage { MESSAGEHANDLER(MessageTrace) { ((MessagePasserImpl*)g_MessagePasser)->SetTrace(msg->enable); } MESSAGEHANDLER(Screenshot) { // TODO: allow non-big screenshots too WriteBigScreenshot(L".bmp", msg->tiles); } QUERYHANDLER(CinemaRecord) { CCinemaManager* manager = g_Game->GetView()->GetCinema(); manager->SetCurrentPath(*msg->path, false, false); const int w = msg->width, h = msg->height; { g_Renderer.Resize(w, h); SViewPort vp = { 0, 0, w, h }; g_Game->GetView()->GetCamera()->SetViewPort(vp); - g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); + g_Game->GetView()->SetCameraProjection(); } unsigned char* img = new unsigned char [w*h*3]; unsigned char* temp = new unsigned char[w*3]; int num_frames = msg->framerate * msg->duration; View::GetView_Game()->SaveState(L"cinema_record"); // Set it to update the simulation at normal speed View::GetView_Game()->SetSpeedMultiplier(1.f); for (int frame = 0; frame < num_frames; ++frame) { View::GetView_Game()->Update(1.f / msg->framerate); manager->MoveToPointAt((float)frame/msg->framerate); Render(); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img); // Swap the rows around, else the image will be upside down //* // TODO: BGR24 output doesn't need flipping, YUV420 and RGBA32 do for (int y = 0; y < h/2; ++y) { memcpy(temp, &img[y*w*3], w*3); memcpy(&img[y*w*3], &img[(h-1-y)*w*3], w*3); memcpy(&img[(h-1-y)*w*3], temp, w*3); } //*/ // Call the user-supplied function with this data, so they can // store it as a video sCinemaRecordCB cbdata = { img }; msg->cb.Call(cbdata); } // Pause the game once we've finished View::GetView_Game()->SetSpeedMultiplier(0.f); View::GetView_Game()->RestoreState(L"cinema_record"); // TODO: delete the saved state now that we don't need it any more delete[] img; delete[] temp; // Restore viewport { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->GetCamera()->SetViewPort(vp); - g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); + g_Game->GetView()->SetCameraProjection(); } } QUERYHANDLER(Ping) { UNUSED2(msg); } MESSAGEHANDLER(SimStateSave) { View::GetView_Game()->SaveState(*msg->label); } MESSAGEHANDLER(SimStateRestore) { View::GetView_Game()->RestoreState(*msg->label); } QUERYHANDLER(SimStateDebugDump) { msg->dump = View::GetView_Game()->DumpState(msg->binary); } MESSAGEHANDLER(SimPlay) { View::GetView_Game()->SetSpeedMultiplier(msg->speed); } MESSAGEHANDLER(JavaScript) { g_ScriptingHost.ExecuteScript(*msg->command, L"Atlas"); } MESSAGEHANDLER(GuiSwitchPage) { g_GUI->SwitchPage(*msg->page, CScriptVal()); } MESSAGEHANDLER(GuiMouseButtonEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = msg->pressed ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; ev.ev.button.button = msg->button; ev.ev.button.state = msg->pressed ? SDL_PRESSED : SDL_RELEASED; float x, y; msg->pos->GetScreenSpace(x, y); ev.ev.button.x = (u16)clamp((int)x, 0, g_xres); ev.ev.button.y = (u16)clamp((int)y, 0, g_yres); in_dispatch_event(&ev); } MESSAGEHANDLER(GuiMouseMotionEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = SDL_MOUSEMOTION; float x, y; msg->pos->GetScreenSpace(x, y); ev.ev.motion.x = (u16)clamp((int)x, 0, g_xres); ev.ev.motion.y = (u16)clamp((int)y, 0, g_yres); in_dispatch_event(&ev); } MESSAGEHANDLER(GuiKeyEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP; ev.ev.key.keysym.sym = (SDLKey)(int)msg->sdlkey; ev.ev.key.keysym.unicode = msg->unichar; in_dispatch_event(&ev); } MESSAGEHANDLER(GuiCharEvent) { // wxWidgets has special Char events but SDL doesn't, so convert it to // a keydown+keyup sequence. (We do the conversion here instead of on // the wx side to avoid nondeterministic behaviour caused by async messaging.) SDL_Event_ ev = { { 0 } }; ev.ev.type = SDL_KEYDOWN; ev.ev.key.keysym.sym = (SDLKey)(int)msg->sdlkey; ev.ev.key.keysym.unicode = msg->unichar; in_dispatch_event(&ev); ev.ev.type = SDL_KEYUP; in_dispatch_event(&ev); } } Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 10547) +++ ps/trunk/source/graphics/GameView.cpp (revision 10548) @@ -1,1043 +1,1073 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GameView.h" #include "graphics/Camera.h" #include "graphics/CinemaTrack.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/input.h" #include "lib/timer.h" #include "maths/Bound.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scripting/ScriptableObject.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" extern int g_xres, g_yres; -const float CGameView::defaultFOV = DEGTORAD(20.f); -const float CGameView::defaultNear = 16.f; -const float CGameView::defaultFar = 4096.f; -const float CGameView::defaultCullFOV = CGameView::defaultFOV + DEGTORAD(6.0f); //add 6 degrees to the default FOV for use with the culling frustum - // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f*CELL_SIZE; /** * A value with exponential decay towards the target value. */ class CSmoothedValue { public: CSmoothedValue(float value, float smoothness, float minDelta) : m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta) { } float GetSmoothedValue() { return m_Current; } void SetValueSmoothly(float value) { m_Target = value; } void AddSmoothly(float value) { m_Target += value; } void Add(float value) { m_Target += value; m_Current += value; } float GetValue() { return m_Target; } void SetValue(float value) { m_Target = value; m_Current = value; } float Update(float time) { if (fabs(m_Target - m_Current) < m_MinDelta) return 0.0f; double p = pow((double)m_Smoothness, 10.0 * (double)time); // (add the factor of 10 so that smoothnesses don't have to be tiny numbers) double delta = (m_Target - m_Current) * (1.0 - p); m_Current += delta; return (float)delta; } void ClampSmoothly(float min, float max) { m_Target = Clamp(m_Target, (double)min, (double)max); } // Wrap so 'target' is in the range [min, max] void Wrap(float min, float max) { double t = fmod(m_Target - min, (double)(max - min)); if (t < 0) t += max - min; t += min; m_Current += t - m_Target; m_Target = t; } private: double m_Target; // the value which m_Current is tending towards double m_Current; // (We use double because the extra precision is worthwhile here) float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects) public: float m_Smoothness; }; class CGameViewImpl : public CJSObject { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), ConstrainCamera(true), Culling(true), FollowEntity(INVALID_ENTITY), FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) ViewScrollSpeed(0), ViewRotateXSpeed(0), ViewRotateXMin(0), ViewRotateXMax(0), ViewRotateXDefault(0), ViewRotateYSpeed(0), ViewRotateYSpeedWheel(0), ViewRotateYDefault(0), ViewDragSpeed(0), ViewZoomSpeed(0), ViewZoomSpeedWheel(0), ViewZoomMin(0), ViewZoomMax(0), ViewZoomDefault(0), JoystickPanX(-1), JoystickPanY(-1), JoystickRotateX(-1), JoystickRotateY(-1), JoystickZoomIn(-1), JoystickZoomOut(-1), PosX(0, 0, 0.01f), PosY(0, 0, 0.01f), PosZ(0, 0, 0.01f), Zoom(0, 0, 0.1f), RotateX(0, 0, 0.001f), RotateY(0, 0, 0.001f) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Whether the camera movement should be constrained by min/max limits * and terrain avoidance. */ bool ConstrainCamera; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager TrackManager; /** * Entity for the camera to follow, or INVALID_ENTITY if none. */ entity_id_t FollowEntity; /** * Whether to follow FollowEntity in first-person mode. */ bool FollowFirstPerson; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewRotateXSpeed; float ViewRotateXMin; float ViewRotateXMax; float ViewRotateXDefault; float ViewRotateYSpeed; float ViewRotateYSpeedWheel; float ViewRotateYDefault; float ViewDragSpeed; float ViewZoomSpeed; float ViewZoomSpeedWheel; float ViewZoomMin; float ViewZoomMax; float ViewZoomDefault; + float ViewFOV; + float ViewNear; + float ViewFar; int JoystickPanX; int JoystickPanY; int JoystickRotateX; int JoystickRotateY; int JoystickZoomIn; int JoystickZoomOut; //////////////////////////////////////// // Camera Controls State CSmoothedValue PosX; CSmoothedValue PosY; CSmoothedValue PosZ; CSmoothedValue Zoom; CSmoothedValue RotateX; // inclination around x axis (relative to camera) CSmoothedValue RotateY; // rotation around y (vertical) axis static void ScriptingInit(); }; static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue()); } static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetValue()); orientation->RotateY(m->RotateY.GetValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m->ViewCamera.SetViewPort(vp); - m->ViewCamera.SetProjection(defaultNear, defaultFar, defaultFOV); + m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->ViewCamera.SetViewPort(vp); - m->ViewCamera.SetProjection(defaultNear, defaultFar, defaultFOV); + m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } CObjectManager& CGameView::GetObjectManager() const { return m->ObjectManager; } JSObject* CGameView::GetScript() { return m->GetScript(); } /*static*/ void CGameView::ScriptingInit() { return CGameViewImpl::ScriptingInit(); } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->TrackManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } void CGameViewImpl::ScriptingInit() { AddProperty(L"culling", &CGameViewImpl::Culling); AddProperty(L"lockCullCamera", &CGameViewImpl::LockCullCamera); AddProperty(L"constrainCamera", &CGameViewImpl::ConstrainCamera); CJSObject::ScriptingInit("GameView"); } int CGameView::Initialize() { CFG_GET_SYS_VAL("view.scroll.speed", Float, m->ViewScrollSpeed); CFG_GET_SYS_VAL("view.rotate.x.speed", Float, m->ViewRotateXSpeed); CFG_GET_SYS_VAL("view.rotate.x.min", Float, m->ViewRotateXMin); CFG_GET_SYS_VAL("view.rotate.x.max", Float, m->ViewRotateXMax); CFG_GET_SYS_VAL("view.rotate.x.default", Float, m->ViewRotateXDefault); CFG_GET_SYS_VAL("view.rotate.y.speed", Float, m->ViewRotateYSpeed); CFG_GET_SYS_VAL("view.rotate.y.speed.wheel", Float, m->ViewRotateYSpeedWheel); CFG_GET_SYS_VAL("view.rotate.y.default", Float, m->ViewRotateYDefault); CFG_GET_SYS_VAL("view.drag.speed", Float, m->ViewDragSpeed); CFG_GET_SYS_VAL("view.zoom.speed", Float, m->ViewZoomSpeed); CFG_GET_SYS_VAL("view.zoom.speed.wheel", Float, m->ViewZoomSpeedWheel); CFG_GET_SYS_VAL("view.zoom.min", Float, m->ViewZoomMin); CFG_GET_SYS_VAL("view.zoom.max", Float, m->ViewZoomMax); CFG_GET_SYS_VAL("view.zoom.default", Float, m->ViewZoomDefault); CFG_GET_SYS_VAL("joystick.camera.pan.x", Int, m->JoystickPanX); CFG_GET_SYS_VAL("joystick.camera.pan.y", Int, m->JoystickPanY); CFG_GET_SYS_VAL("joystick.camera.rotate.x", Int, m->JoystickRotateX); CFG_GET_SYS_VAL("joystick.camera.rotate.y", Int, m->JoystickRotateY); CFG_GET_SYS_VAL("joystick.camera.zoom.in", Int, m->JoystickZoomIn); CFG_GET_SYS_VAL("joystick.camera.zoom.out", Int, m->JoystickZoomOut); CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosX.m_Smoothness); CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosY.m_Smoothness); CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosZ.m_Smoothness); CFG_GET_SYS_VAL("view.zoom.smoothness", Float, m->Zoom.m_Smoothness); CFG_GET_SYS_VAL("view.rotate.x.smoothness", Float, m->RotateX.m_Smoothness); CFG_GET_SYS_VAL("view.rotate.y.smoothness", Float, m->RotateY.m_Smoothness); + CFG_GET_SYS_VAL("view.near", Float, m->ViewNear); + CFG_GET_SYS_VAL("view.far", Float, m->ViewFar); + CFG_GET_SYS_VAL("view.fov", Float, m->ViewFOV); + + // Convert to radians m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); + m->ViewFOV = DEGTORAD(m->ViewFOV); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; // One way to fix shadows popping in at the edge of the screen is to widen the culling frustum so that // objects aren't culled as early. The downside is that objects will get rendered even though they appear // off screen, which is somewhat inefficient. A better solution would be to decouple shadow map rendering // from model rendering; as it is now, a shadow map is only rendered if its associated model is to be // rendered. // (See http://trac.wildfiregames.com/ticket/504) - m->CullCamera.SetProjection(defaultNear, defaultFar, defaultCullFOV); + m->CullCamera.SetProjection(m->ViewNear, m->ViewFar, GetCullFOV()); m->CullCamera.UpdateFrustum(); } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColours(); } void CGameView::Render() { g_Renderer.RenderScene(*this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBound bounds = patch->GetBounds(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; if(bounds[1].Y < waterHeight) { bounds[1].Y = waterHeight; } if (!m->Culling || frustum.IsBoxVisible (CVector3D(0,0,0), bounds)) { //c->Submit(patch); // set the renderstate for this patch patch->setDrawState(true); // set the renderstate for the neighbors CPatch *nPatch; nPatch = pTerrain->GetPatch(i-1,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i-1,j); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i-1,j+1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i,j+1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j+1); if(nPatch) nPatch->setDrawState(true); } } } // draw the patches for (ssize_t j=0; jGetPatch(i,j); // can't fail if(patch->getDrawState() == true) { c->Submit(patch); patch->setDrawState(false); } } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) g_Renderer.MakeShadersDirty(); m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } static void ClampDistance(CGameViewImpl* m, bool smooth) { if (!m->ConstrainCamera) return; CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D delta = targetCam.GetFocus() - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); float clampedDist = Clamp(dist, m->ViewZoomMin, m->ViewZoomMax); float diff = clampedDist - dist; if (!diff) return; if (smooth) { m->PosX.AddSmoothly(forwards.X * -diff); m->PosY.AddSmoothly(forwards.Y * -diff); m->PosZ.AddSmoothly(forwards.Z * -diff); } else { m->PosX.Add(forwards.X * -diff); m->PosY.Add(forwards.Y * -diff); m->PosZ.Add(forwards.Z * -diff); } } void CGameView::Update(float DeltaTime) { if (!g_app_has_focus) return; // TODO: this is probably not an ideal place for this, it should probably go // in a CCmpWaterManager or some such thing (once such a thing exists) if (!m->Game->m_Paused) g_Renderer.GetWaterManager()->m_WaterTexTimer += DeltaTime; if (m->TrackManager.IsActive() && m->TrackManager.IsPlaying()) { if (! m->TrackManager.Update(DeltaTime)) { // ResetCamera(); } return; } // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * DeltaTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * DeltaTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * DeltaTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * DeltaTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * DeltaTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * DeltaTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * DeltaTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * DeltaTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * DeltaTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * DeltaTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * DeltaTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * DeltaTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * DeltaTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * DeltaTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * DeltaTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * DeltaTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * DeltaTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * DeltaTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); if (!cmpPosition.null() && cmpPosition->IsInWorld()) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset, false); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = targetCam.GetFocus(); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * DeltaTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * DeltaTime); float zoomDelta = m->Zoom.Update(DeltaTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); ClampDistance(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = targetCam.GetFocus(); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*m->Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpRangeManager.null() && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r + CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r + CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() - CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() + CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() - CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() + CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(DeltaTime); m->PosY.Update(DeltaTime); m->PosZ.Update(DeltaTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(DeltaTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = targetCam.GetFocus(); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(DeltaTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = m->ViewCamera.GetFocus(); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix + m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); } void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); CVector3D pivot = targetCam.GetFocus(); CVector3D delta = target - pivot; //If minimap movement, maintain previous zoom level by not changing Y position // - this prevents strange behavior when moving across changes in terrain height if (!minimap) m->PosY.SetValueSmoothly(delta.Y + m->PosY.GetValue()); m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); ClampDistance(m, false); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); ClampDistance(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraAngleZoom() { CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D delta = targetCam.GetFocus() - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); m->Zoom.AddSmoothly(dist - m->ViewZoomDefault); // Reset orientations to default m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault)); } void CGameView::CameraFollow(entity_id_t entity, bool firstPerson) { m->FollowEntity = entity; m->FollowFirstPerson = firstPerson; } entity_id_t CGameView::GetFollowedEntity() { return m->FollowEntity; } +float CGameView::GetNear() const +{ + return m->ViewNear; +} + +float CGameView::GetFar() const +{ + return m->ViewFar; +} + +float CGameView::GetFOV() const +{ + return m->ViewFOV; +} + +float CGameView::GetCullFOV() const +{ + return m->ViewFOV + DEGTORAD(6.0f); //add 6 degrees to the default FOV for use with the culling frustum; +} + +void CGameView::SetCameraProjection() +{ + m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); +} + InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if(!g_app_has_focus || !g_Game || !g_Game->IsGameStarted()) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return IN_HANDLED; } // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). else if (hotkey == "camera.zoom.wheel.in") { m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } } return IN_PASS; } Index: ps/trunk/source/graphics/GameView.h =================================================================== --- ps/trunk/source/graphics/GameView.h (revision 10547) +++ ps/trunk/source/graphics/GameView.h (revision 10548) @@ -1,99 +1,104 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_GAMEVIEW #define INCLUDED_GAMEVIEW #include "renderer/Scene.h" #include "simulation2/system/Entity.h" #include "lib/input.h" // InReaction - can't forward-declare enum class CGame; class CObjectManager; class CCamera; class CCinemaManager; class CVector3D; struct SViewPort; struct JSObject; class CGameViewImpl; class CGameView : private Scene { NONCOPYABLE(CGameView); -public: - static const float defaultFOV, defaultCullFOV, defaultNear, defaultFar; - private: CGameViewImpl* m; // Check whether lighting environment has changed and update vertex data if necessary void CheckLightEnv(); public: //BEGIN: Implementation of Scene virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c); virtual CLOSTexture& GetLOSTexture(); virtual CTerritoryTexture& GetTerritoryTexture(); //END: Implementation of Scene private: // InitResources(): Load all graphics resources (textures, actor objects and // alpha maps) required by the game //void InitResources(); // UnloadResources(): Unload all graphics resources loaded by InitResources void UnloadResources(); public: CGameView(CGame *pGame); ~CGameView(); void SetViewport(const SViewPort& vp); void RegisterInit(); int Initialize(); CObjectManager& GetObjectManager() const; // Update: Update all the view information (i.e. rotate camera, scroll, // whatever). This will *not* change any World information - only the // *presentation* void Update(float DeltaTime); void BeginFrame(); void Render(); InReaction HandleEvent(const SDL_Event_* ev); void MoveCameraTarget(const CVector3D& target, bool minimap = false); void ResetCameraTarget(const CVector3D& target); void ResetCameraAngleZoom(); void CameraFollow(entity_id_t entity, bool firstPerson); entity_id_t GetFollowedEntity(); + float GetNear() const; + float GetFar() const; + float GetFOV() const; + float GetCullFOV() const; + + // Set projection of current camera using near, far, and FOV values + void SetCameraProjection(); + CCamera *GetCamera(); CCinemaManager* GetCinema(); JSObject* GetScript(); static void ScriptingInit(); }; extern InReaction game_view_handler(const SDL_Event_* ev); #endif Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 10547) +++ ps/trunk/source/renderer/Renderer.cpp (revision 10548) @@ -1,2120 +1,2120 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include #include #include #include #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderTechnique.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/FixedFunctionModelRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PlayerRenderer.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TransparencyRenderer.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB bytes reserved"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesReserved()); return buf; case Row_VBAllocated: if (col == 0) return "VB bytes allocated"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesAllocated()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer* terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Shadow map ShadowMap* shadow; /// Various model renderers struct Models { // The following model renderers are aliases for the appropriate real_* // model renderers (depending on hardware availability and current settings) // and must be used for actual model submission and rendering ModelRenderer* Normal; ModelRenderer* NormalInstancing; ModelRenderer* Player; ModelRenderer* PlayerInstancing; ModelRenderer* Transp; // "Palette" of available ModelRenderers. Do not use these directly for // rendering and submission; use the aliases above instead. ModelRenderer* pal_NormalFF; ModelRenderer* pal_PlayerFF; ModelRenderer* pal_TranspFF; ModelRenderer* pal_TranspSortAll; ModelRenderer* pal_NormalShader; ModelRenderer* pal_NormalInstancingShader; ModelRenderer* pal_PlayerShader; ModelRenderer* pal_PlayerInstancingShader; ModelRenderer* pal_TranspShader; ModelVertexRendererPtr VertexFF; ModelVertexRendererPtr VertexPolygonSort; ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; // generic RenderModifiers that are supposed to be used directly RenderModifierPtr ModWireframe; RenderModifierPtr ModSolidColor; RenderModifierPtr ModSolidPlayerColor; RenderModifierPtr ModTransparentDepthShadow; // RenderModifiers that are selected from the palette below RenderModifierPtr ModNormal; RenderModifierPtr ModNormalInstancing; RenderModifierPtr ModPlayer; RenderModifierPtr ModPlayerInstancing; RenderModifierPtr ModSolid; RenderModifierPtr ModSolidInstancing; RenderModifierPtr ModSolidPlayer; RenderModifierPtr ModSolidPlayerInstancing; RenderModifierPtr ModTransparent; RenderModifierPtr ModTransparentOpaque; RenderModifierPtr ModTransparentBlend; // Palette of available RenderModifiers RenderModifierPtr ModPlainUnlit; RenderModifierPtr ModPlayerUnlit; RenderModifierPtr ModTransparentUnlit; RenderModifierPtr ModTransparentOpaqueUnlit; RenderModifierPtr ModTransparentBlendUnlit; RenderModifierPtr ModShaderSolidColor; RenderModifierPtr ModShaderSolidColorInstancing; RenderModifierPtr ModShaderSolidPlayerColor; RenderModifierPtr ModShaderSolidPlayerColorInstancing; RenderModifierPtr ModShaderSolidTex; LitRenderModifierPtr ModShaderNormal; LitRenderModifierPtr ModShaderNormalInstancing; LitRenderModifierPtr ModShaderPlayer; LitRenderModifierPtr ModShaderPlayerInstancing; LitRenderModifierPtr ModShaderTransparent; LitRenderModifierPtr ModShaderTransparentOpaque; LitRenderModifierPtr ModShaderTransparentBlend; RenderModifierPtr ModShaderTransparentShadow; } Model; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { terrainRenderer = new TerrainRenderer(); shadow = new ShadowMap(); Model.pal_NormalFF = 0; Model.pal_PlayerFF = 0; Model.pal_TranspFF = 0; Model.pal_TranspSortAll = 0; Model.pal_NormalShader = 0; Model.pal_NormalInstancingShader = 0; Model.pal_PlayerShader = 0; Model.pal_PlayerInstancingShader = 0; Model.pal_TranspShader = 0; Model.Normal = 0; Model.NormalInstancing = 0; Model.Player = 0; Model.PlayerInstancing = 0; Model.Transp = 0; } ~CRendererInternals() { delete shadow; delete terrainRenderer; } /** * Load the OpenGL projection and modelview matrices and the viewport according * to the given camera. */ void SetOpenGLCamera(const CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj = camera.GetProjection(); glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); const SViewPort &vp = camera.GetViewPort(); glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } /** * Renders all non-transparent models with the given modifiers. */ void CallModelRenderers( const RenderModifierPtr& modNormal, const RenderModifierPtr& modNormalInstancing, const RenderModifierPtr& modPlayer, const RenderModifierPtr& modPlayerInstancing, int flags) { Model.Normal->Render(modNormal, flags); if (Model.Normal != Model.NormalInstancing) Model.NormalInstancing->Render(modNormalInstancing, flags); Model.Player->Render(modPlayer, flags); if (Model.Player != Model.PlayerInstancing) Model.PlayerInstancing->Render(modPlayerInstancing, flags); } /** * Filters all non-transparent models with the given modifiers. */ void FilterModels(CModelFilter& filter, int passed, int flags = 0) { Model.Normal->Filter(filter, passed, flags); if (Model.Normal != Model.NormalInstancing) Model.NormalInstancing->Filter(filter, passed, flags); Model.Player->Filter(filter, passed, flags); if (Model.Player != Model.PlayerInstancing) Model.PlayerInstancing->Filter(filter, passed, flags); } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width=0; m_Height=0; m_TerrainRenderMode=SOLID; m_ModelRenderMode=SOLID; m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0; m_SortAllTransparent = false; m_DisplayFrustum = false; m_DisableCopyShadow = false; m_DisplayTerrainPriorities = false; m_FastPlayerColor = true; m_SkipSubmit = false; m_Options.m_NoVBO = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_FancyWater = false; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; m_hCompositeAlphaMap = 0; m_Stats.Reset(); AddLocalProperty(L"fancyWater", &m_Options.m_FancyWater, false); AddLocalProperty(L"horizonHeight", &m->skyManager.m_HorizonHeight, false); AddLocalProperty(L"waterMurkiness", &m->waterManager.m_Murkiness, false); AddLocalProperty(L"waterReflTintStrength", &m->waterManager.m_ReflectionTintStrength, false); AddLocalProperty(L"waterRepeatPeriod", &m->waterManager.m_RepeatPeriod, false); AddLocalProperty(L"waterShininess", &m->waterManager.m_Shininess, false); AddLocalProperty(L"waterSpecularStrength", &m->waterManager.m_SpecularStrength, false); AddLocalProperty(L"waterWaviness", &m->waterManager.m_Waviness, false); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // model rendering delete m->Model.pal_NormalFF; delete m->Model.pal_PlayerFF; delete m->Model.pal_TranspFF; delete m->Model.pal_TranspSortAll; delete m->Model.pal_NormalShader; delete m->Model.pal_NormalInstancingShader; delete m->Model.pal_PlayerShader; delete m->Model.pal_PlayerInstancingShader; delete m->Model.pal_TranspShader; // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; // now start querying extensions if (!m_Options.m_NoVBO) { if (ogl_HaveExtension("GL_ARB_vertex_buffer_object")) { m_Caps.m_VBO=true; } } if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); typedef std::map Defines; Defines defNull; Defines defBasic; if (m_Options.m_Shadows) { defBasic["USE_SHADOW"] = "1"; if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) defBasic["USE_FP_SHADOW"] = "1"; if (m_Options.m_ShadowPCF) defBasic["USE_SHADOW_PCF"] = "1"; } if (m_LightEnv) defBasic["LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()] = "1"; Defines defColored = defBasic; defColored["USE_OBJECTCOLOR"] = "1"; Defines defTransparent = defBasic; defTransparent["USE_TRANSPARENT"] = "1"; // TODO: it'd be nicer to load this technique from an XML file or something CShaderPass passTransparentOpaque(m->shaderManager.LoadProgram("model_common", defTransparent)); passTransparentOpaque.AlphaFunc(GL_GREATER, 0.9375f); passTransparentOpaque.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CShaderTechnique techTransparentOpaque(passTransparentOpaque); CShaderPass passTransparentBlend(m->shaderManager.LoadProgram("model_common", defTransparent)); passTransparentBlend.AlphaFunc(GL_GREATER, 0.0f); passTransparentBlend.DepthFunc(GL_LESS); passTransparentBlend.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); passTransparentBlend.DepthMask(0); CShaderTechnique techTransparentBlend(passTransparentBlend); CShaderTechnique techTransparent(passTransparentOpaque); techTransparent.AddPass(passTransparentBlend); CShaderPass passTransparentShadow(m->shaderManager.LoadProgram("solid_tex", defBasic)); passTransparentShadow.AlphaFunc(GL_GREATER, 0.4f); CShaderTechnique techTransparentShadow(passTransparentShadow); m->Model.ModShaderSolidColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid", defNull)))); m->Model.ModShaderSolidColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_instancing", defNull)))); m->Model.ModShaderSolidPlayerColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_player", defNull)))); m->Model.ModShaderSolidPlayerColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_player_instancing", defNull)))); m->Model.ModShaderSolidTex = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_tex", defNull)))); m->Model.ModShaderNormal = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common", defBasic)))); m->Model.ModShaderNormalInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common_instancing", defBasic)))); m->Model.ModShaderPlayer = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common", defColored)))); m->Model.ModShaderPlayerInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common_instancing", defColored)))); m->Model.ModShaderTransparent = LitRenderModifierPtr(new ShaderRenderModifier( techTransparent)); m->Model.ModShaderTransparentOpaque = LitRenderModifierPtr(new ShaderRenderModifier( techTransparentOpaque)); m->Model.ModShaderTransparentBlend = LitRenderModifierPtr(new ShaderRenderModifier( techTransparentBlend)); m->Model.ModShaderTransparentShadow = LitRenderModifierPtr(new ShaderRenderModifier( techTransparentShadow)); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // model rendering m->Model.VertexFF = ModelVertexRendererPtr(new FixedFunctionModelRenderer); m->Model.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelRenderer); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer); m->Model.pal_NormalFF = new BatchModelRenderer(m->Model.VertexFF); m->Model.pal_PlayerFF = new BatchModelRenderer(m->Model.VertexFF); m->Model.pal_TranspFF = new SortModelRenderer(m->Model.VertexFF); m->Model.pal_TranspSortAll = new SortModelRenderer(m->Model.VertexPolygonSort); m->Model.pal_NormalShader = new BatchModelRenderer(m->Model.VertexRendererShader); m->Model.pal_NormalInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); m->Model.pal_PlayerShader = new BatchModelRenderer(m->Model.VertexRendererShader); m->Model.pal_PlayerInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); m->Model.pal_TranspShader = new SortModelRenderer(m->Model.VertexRendererShader); m->Model.ModWireframe = RenderModifierPtr(new WireframeRenderModifier); m->Model.ModPlainUnlit = RenderModifierPtr(new PlainRenderModifier); SetFastPlayerColor(true); m->Model.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier); m->Model.ModSolidPlayerColor = RenderModifierPtr(new SolidPlayerColorRender); m->Model.ModTransparentUnlit = RenderModifierPtr(new TransparentRenderModifier); m->Model.ModTransparentOpaqueUnlit = RenderModifierPtr(new TransparentOpaqueRenderModifier); m->Model.ModTransparentBlendUnlit = RenderModifierPtr(new TransparentBlendRenderModifier); m->Model.ModTransparentDepthShadow = RenderModifierPtr(new TransparentDepthShadowModifier); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); return true; } // resize renderer view void CRenderer::Resize(int width,int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow->RecreateTexture(); m_Width = width; m_Height = height; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO=value; break; case OPT_SHADOWS: m_Options.m_Shadows=value; MakeShadersDirty(); break; case OPT_FANCYWATER: m_Options.m_FancyWater=value; break; case OPT_SHADOWPCF: m_Options.m_ShadowPCF=value; break; default: debug_warn(L"CRenderer::SetOptionBool: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_FANCYWATER: return m_Options.m_FancyWater; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } void CRenderer::SetOptionFloat(enum Option opt, float val) { switch(opt) { case OPT_LODBIAS: m_Options.m_LodBias = val; break; default: debug_warn(L"CRenderer::SetOptionFloat: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m_Caps.m_ARBProgram) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!m_Caps.m_ARBProgram) { LOGWARNING(L"Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.m_RenderPath = rp; // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_SHADER: return "shader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) { if (name == "fixed") return RP_FIXED; if (name == "shader") return RP_SHADER; if (name == "default") return RP_DEFAULT; LOGWARNING(L"Unknown render path name '%hs', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // SetFastPlayerColor void CRenderer::SetFastPlayerColor(bool fast) { m_FastPlayerColor = fast; if (m_FastPlayerColor) { if (!FastPlayerColorRender::IsAvailable()) { LOGWARNING(L"Falling back to slower player color rendering."); m_FastPlayerColor = false; } } if (m_FastPlayerColor) m->Model.ModPlayerUnlit = RenderModifierPtr(new FastPlayerColorRender); else m->Model.ModPlayerUnlit = RenderModifierPtr(new SlowPlayerColorRender); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m_Options.m_RenderPath == RP_SHADER) { if (m->ShadersDirty) ReloadShaders(); m->Model.ModShaderNormal->SetShadowMap(m->shadow); m->Model.ModShaderNormal->SetLightEnv(m_LightEnv); m->Model.ModShaderNormalInstancing->SetShadowMap(m->shadow); m->Model.ModShaderNormalInstancing->SetLightEnv(m_LightEnv); m->Model.ModShaderPlayer->SetShadowMap(m->shadow); m->Model.ModShaderPlayer->SetLightEnv(m_LightEnv); m->Model.ModShaderPlayerInstancing->SetShadowMap(m->shadow); m->Model.ModShaderPlayerInstancing->SetLightEnv(m_LightEnv); m->Model.ModShaderTransparent->SetShadowMap(m->shadow); m->Model.ModShaderTransparent->SetLightEnv(m_LightEnv); m->Model.ModShaderTransparentOpaque->SetShadowMap(m->shadow); m->Model.ModShaderTransparentOpaque->SetLightEnv(m_LightEnv); m->Model.ModShaderTransparentBlend->SetShadowMap(m->shadow); m->Model.ModShaderTransparentBlend->SetLightEnv(m_LightEnv); m->Model.ModNormal = m->Model.ModShaderNormal; m->Model.ModNormalInstancing = m->Model.ModShaderNormalInstancing; m->Model.ModPlayer = m->Model.ModShaderPlayer; m->Model.ModPlayerInstancing = m->Model.ModShaderPlayerInstancing; m->Model.ModSolid = m->Model.ModShaderSolidColor; m->Model.ModSolidInstancing = m->Model.ModShaderSolidColorInstancing; m->Model.ModSolidPlayer = m->Model.ModShaderSolidPlayerColor; m->Model.ModSolidPlayerInstancing = m->Model.ModShaderSolidPlayerColorInstancing; m->Model.ModTransparent = m->Model.ModShaderTransparent; m->Model.ModTransparentOpaque = m->Model.ModShaderTransparentOpaque; m->Model.ModTransparentBlend = m->Model.ModShaderTransparentBlend; m->Model.Normal = m->Model.pal_NormalShader; m->Model.NormalInstancing = m->Model.pal_NormalInstancingShader; m->Model.Player = m->Model.pal_PlayerShader; m->Model.PlayerInstancing = m->Model.pal_PlayerInstancingShader; m->Model.Transp = m->Model.pal_TranspShader; } else { m->Model.ModNormal = m->Model.ModPlainUnlit; m->Model.ModNormalInstancing = m->Model.ModPlainUnlit; m->Model.ModPlayer = m->Model.ModPlayerUnlit; m->Model.ModPlayerInstancing = m->Model.ModPlayerUnlit; m->Model.ModTransparent = m->Model.ModTransparentUnlit; m->Model.ModTransparentOpaque = m->Model.ModTransparentOpaqueUnlit; m->Model.ModTransparentBlend = m->Model.ModTransparentBlendUnlit; m->Model.NormalInstancing = m->Model.pal_NormalFF; m->Model.Normal = m->Model.pal_NormalFF; m->Model.PlayerInstancing = m->Model.pal_PlayerFF; m->Model.Player = m->Model.pal_PlayerFF; m->Model.ModSolid = m->Model.ModSolidColor; m->Model.ModSolidInstancing = m->Model.ModSolidColor; m->Model.ModSolidPlayer = m->Model.ModSolidPlayerColor; m->Model.ModSolidPlayerInstancing = m->Model.ModSolidPlayerColor; if (m_SortAllTransparent) m->Model.Transp = m->Model.pal_TranspSortAll; else m->Model.Transp = m->Model.pal_TranspFF; } } ////////////////////////////////////////////////////////////////////////////////////////// // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap() { PROFILE3_GPU("shadow map"); m->shadow->BeginRender(); float shadowTransp = m_LightEnv->GetTerrainShadowTransparency(); glColor3f(shadowTransp, shadowTransp, shadowTransp); // Figure out transparent rendering strategy RenderModifierPtr transparentShadows; if (GetRenderPath() == RP_SHADER) { transparentShadows = m->Model.ModShaderTransparentShadow; } else { transparentShadows = m->Model.ModTransparentDepthShadow; } { PROFILE("render patches"); m->terrainRenderer->RenderPatches(); } { PROFILE("render models"); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->Model.Transp->Render(transparentShadows, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } glColor3f(1.0, 1.0, 1.0); m->shadow->EndRender(); } void CRenderer::RenderPatches(const CFrustum* frustum) { PROFILE3_GPU("patches"); bool filtered = false; if (frustum) { if (!m->terrainRenderer->CullPatches(frustum)) return; filtered = true; } // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) m->terrainRenderer->RenderTerrainShader((m_Caps.m_Shadows && m_Options.m_Shadows) ? m->shadow : 0, filtered); else m->terrainRenderer->RenderTerrain(filtered); if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. glDisable(GL_TEXTURE_2D); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer->RenderPatches(filtered); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer->RenderOutlines(filtered); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } class CModelCuller : public CModelFilter { public: CModelCuller(const CFrustum& frustum) : m_Frustum(frustum) { } bool Filter(CModel *model) { return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetBoundsRec()); } private: const CFrustum& m_Frustum; }; void CRenderer::RenderModels(const CFrustum* frustum) { PROFILE3_GPU("models"); int flags = 0; if (frustum) { flags = MODELFLAG_FILTERED; CModelCuller culler(*frustum); m->FilterModels(culler, flags); } if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } m->CallModelRenderers(m->Model.ModNormal, m->Model.ModNormalInstancing, m->Model.ModPlayer, m->Model.ModPlayerInstancing, flags); if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 1.0f, 0.0f); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } void CRenderer::RenderTransparentModels(ETransparentMode transparentMode, const CFrustum* frustum) { PROFILE3_GPU("transparent models"); int flags = 0; if (frustum) { flags = MODELFLAG_FILTERED; CModelCuller culler(*frustum); m->Model.Transp->Filter(culler, flags); } // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } // disable face culling for two-sided models in sub-renders if (flags) glDisable(GL_CULL_FACE); if (transparentMode == TRANSPARENT_OPAQUE) m->Model.Transp->Render(m->Model.ModTransparentOpaque, flags); else if (transparentMode == TRANSPARENT_BLEND) m->Model.Transp->Render(m->Model.ModTransparentBlend, flags); else m->Model.Transp->Render(m->Model.ModTransparent, flags); if (flags) glEnable(GL_CULL_FACE); if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 0.0f, 0.0f); m->Model.Transp->Render(m->Model.ModSolid, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // GetModelViewProjectionMatrix: save the current OpenGL model-view-projection matrix CMatrix3D CRenderer::GetModelViewProjectionMatrix() { CMatrix3D proj; CMatrix3D view; glGetFloatv(GL_PROJECTION_MATRIX, &proj._11); glGetFloatv(GL_MODELVIEW_MATRIX, &view._11); return proj*view; } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(const CVector4D& worldPlane) { float matrix[16]; CVector4D q; // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D normalMatrix = m_ViewCamera.m_Orientation.GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); // Grab the current projection matrix from OpenGL { PROFILE3("get proj matrix (oblique clipping)"); // sometimes the vsync delay gets accounted here glGetFloatv(GL_PROJECTION_MATRIX, matrix); } // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix q.m_X = (sgn(camPlane.m_X) - matrix[8]/matrix[11]) / matrix[0]; q.m_Y = (sgn(camPlane.m_Y) - matrix[9]/matrix[11]) / matrix[5]; q.m_Z = 1.0f/matrix[11]; q.m_W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.m_X; matrix[6] = c.m_Y; matrix[10] = c.m_Z - matrix[11]; matrix[14] = c.m_W; // Load it back into OpenGL glMatrixMode(GL_PROJECTION); glLoadMatrixf(matrix); glMatrixMode(GL_MODELVIEW); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture SScreenRect CRenderer::RenderReflections(const CBound& scissor) { PROFILE3_GPU("water reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. m_ViewCamera.m_Orientation.Scale(1, -1, 1); m_ViewCamera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); m_ViewCamera.UpdateFrustum(scissor); m_ViewCamera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); SViewPort vp; vp.m_Height = wm.m_ReflectionTextureSize; vp.m_Width = wm.m_ReflectionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); - m_ViewCamera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV*1.05f); // Slightly higher than view FOV + m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = GetModelViewProjectionMatrix(); SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glFrontFace(GL_CW); // Render sky, terrain and models m->skyManager.RenderSky(); ogl_WarnIfError(); RenderPatches(&m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderModels(&m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderTransparentModels(TRANSPARENT_BLEND, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); glFrontFace(GL_CCW); glDisable(GL_SCISSOR_TEST); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_ReflectionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, screenScissor.x1, screenScissor.y1, screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); } // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); return screenScissor; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture SScreenRect CRenderer::RenderRefractions(const CBound &scissor) { PROFILE3_GPU("water refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. m_ViewCamera.UpdateFrustum(scissor); m_ViewCamera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight)); SViewPort vp; vp.m_Height = wm.m_RefractionTextureSize; vp.m_Width = wm.m_RefractionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); - m_ViewCamera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV*1.05f); // Slightly higher than view FOV + m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = GetModelViewProjectionMatrix(); SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // a neutral gray to blend in with shores glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Render terrain and models RenderPatches(&m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderModels(&m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderTransparentModels(TRANSPARENT_BLEND, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_RefractionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, screenScissor.x1, screenScissor.y1, screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); } // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); return screenScissor; } void CRenderer::RenderSilhouettes() { PROFILE3_GPU("silhouettes"); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // colour. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer->RenderPatches(); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_SILHOUETTE_OCCLUDER); } { PROFILE("render transparent occluders"); if (GetRenderPath() == RP_SHADER) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.4f); m->Model.Transp->Render(m->Model.ModShaderSolidTex, MODELFLAG_SILHOUETTE_OCCLUDER); glDisable(GL_ALPHA_TEST); } else { // Reuse the depth shadow modifier to get alpha-tested rendering m->Model.Transp->Render(m->Model.ModTransparentDepthShadow, MODELFLAG_SILHOUETTE_OCCLUDER); } } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the colour of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } // TODO: For performance, we probably ought to do a quick raycasting check // to see which units are likely blocked by occluders and not bother // rendering any of the others { PROFILE("render models"); m->CallModelRenderers(m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, MODELFLAG_SILHOUETTE_DISPLAY); // (This won't render transparent objects with SILHOUETTE_DISPLAY - will // we have any units that need that?) } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles() { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(); if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 0.5f, 0.0f); m->particleRenderer.RenderParticles(true); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 1.0f, 0.0f); m->particleRenderer.RenderBounds(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions() { PROFILE3("render submissions"); ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // Prepare model renderers { PROFILE3("prepare models"); m->Model.Normal->PrepareModels(); m->Model.Player->PrepareModels(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->PrepareModels(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->PrepareModels(); m->Model.Transp->PrepareModels(); } m->terrainRenderer->PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(); if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(); } { PROFILE3_GPU("clear buffers"); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } ogl_WarnIfError(); CBound waterScissor; if (m_WaterManager->m_RenderWater) { waterScissor = m->terrainRenderer->ScissorWater(m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { SScreenRect reflectionScissor = RenderReflections(waterScissor); SScreenRect refractionScissor = RenderRefractions(waterScissor); PROFILE3_GPU("water scissor"); SScreenRect dirty; dirty.x1 = std::min(reflectionScissor.x1, refractionScissor.x1); dirty.y1 = std::min(reflectionScissor.y1, refractionScissor.y1); dirty.x2 = std::max(reflectionScissor.x2, refractionScissor.x2); dirty.y2 = std::max(reflectionScissor.y2, refractionScissor.y2); if (dirty.x1 < dirty.x2 && dirty.y1 < dirty.y2) { glEnable(GL_SCISSOR_TEST); glScissor(dirty.x1, dirty.y1, dirty.x2 - dirty.x1, dirty.y2 - dirty.y1); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } } } // render submitted patches and models RenderPatches(); ogl_WarnIfError(); if (g_Game) { // g_Game->GetWorld()->GetTerritoryManager()->RenderTerritories(); // TODO: implement in new sim system ogl_WarnIfError(); } // render debug-related terrain overlays TerrainOverlay::RenderOverlays(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { // render transparent stuff, but only the solid parts that can occlude block water RenderTransparentModels(TRANSPARENT_OPAQUE); ogl_WarnIfError(); m->terrainRenderer->RenderWater(); ogl_WarnIfError(); // render transparent stuff again, but only the blended parts that overlap water RenderTransparentModels(TRANSPARENT_BLEND); ogl_WarnIfError(); } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(TRANSPARENT); ogl_WarnIfError(); } // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water RenderParticles(); ogl_WarnIfError(); RenderSilhouettes(); // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // render debug lines if (m_DisplayFrustum) { DisplayFrustum(); m->shadow->RenderDebugDisplay(); ogl_WarnIfError(); } // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer->EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); // Finish model renderers m->Model.Normal->EndFrame(); m->Model.Player->EndFrame(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->EndFrame(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->EndFrame(); m->Model.Transp->EndFrame(); ogl_tex_bind(0, 0); { PROFILE3("error check"); if (glGetError()) { ONCE(LOGERROR(L"CRenderer::EndFrame: GL errors occurred")); } } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { glDepthMask(0); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer->RenderPriorities(); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) m->shadow->SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } void CRenderer::Submit(CPatch* patch) { m->terrainRenderer->Submit(patch); } void CRenderer::Submit(SOverlayLine* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { m->terrainRenderer->Submit(decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (model->GetFlags() & MODELFLAG_CASTSHADOWS) { // PROFILE( "updating shadow bounds" ); m->shadow->AddShadowedBound(model->GetBounds()); } // Tricky: The call to GetBounds() above can invalidate the position model->ValidatePosition(); bool canUseInstancing = false; if (model->GetModelDef()->GetNumBones() == 0) canUseInstancing = true; if (model->GetMaterial().IsPlayer()) { if (canUseInstancing) m->Model.PlayerInstancing->Submit(model); else m->Model.Player->Submit(model); } else if (model->GetMaterial().UsesAlpha()) { m->Model.Transp->Submit(model); } else { if (canUseInstancing) m->Model.NormalInstancing->Submit(model); else m->Model.Normal->Submit(model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); ogl_WarnIfError(); RenderSubmissions(); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit,GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D,tex); if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; (void)ogl_tex_get_data(textures[i], &src); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) (void)ogl_tex_free(textures[i]); // upload the composite texture Tex t; (void)tex_wrap(total_w, total_h, 8, TEX_GREY, data, 0, &t); m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); int ret = ogl_tex_upload(m_hCompositeAlphaMap, 0, 0, GL_INTENSITY); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; } /////////////////////////////////////////////////////////////////////////////////////////////////// // Scripting Interface jsval CRenderer::JSI_GetFastPlayerColor(JSContext*) { return ToJSVal(m_FastPlayerColor); } void CRenderer::JSI_SetFastPlayerColor(JSContext* ctx, jsval newval) { bool fast; if (!ToPrimitive(ctx, newval, fast)) return; SetFastPlayerColor(fast); } jsval CRenderer::JSI_GetRenderPath(JSContext*) { return ToJSVal(GetRenderPathName(m_Options.m_RenderPath)); } void CRenderer::JSI_SetRenderPath(JSContext* ctx, jsval newval) { CStr name; if (!ToPrimitive(ctx, newval, name)) return; SetRenderPath(GetRenderPathByName(name)); } jsval CRenderer::JSI_GetDepthTextureBits(JSContext*) { return ToJSVal(m->shadow->GetDepthTextureBits()); } void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval) { int depthTextureBits; if (!ToPrimitive(ctx, newval, depthTextureBits)) return; m->shadow->SetDepthTextureBits(depthTextureBits); } jsval CRenderer::JSI_GetShadows(JSContext*) { return ToJSVal(m_Options.m_Shadows); } void CRenderer::JSI_SetShadows(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_Shadows)) return; ReloadShaders(); } jsval CRenderer::JSI_GetShadowAlphaFix(JSContext*) { return ToJSVal(m_Options.m_ShadowAlphaFix); } void CRenderer::JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_ShadowAlphaFix)) return; m->shadow->RecreateTexture(); } jsval CRenderer::JSI_GetShadowPCF(JSContext*) { return ToJSVal(m_Options.m_ShadowPCF); } void CRenderer::JSI_SetShadowPCF(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_ShadowPCF)) return; ReloadShaders(); } jsval CRenderer::JSI_GetSky(JSContext*) { return ToJSVal(m->skyManager.GetSkySet()); } void CRenderer::JSI_SetSky(JSContext* ctx, jsval newval) { CStrW skySet; if (!ToPrimitive(ctx, newval, skySet)) return; m->skyManager.SetSkySet(skySet); } void CRenderer::ScriptingInit() { AddProperty(L"fastPlayerColor", &CRenderer::JSI_GetFastPlayerColor, &CRenderer::JSI_SetFastPlayerColor); AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath); AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent); AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum); AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias); AddProperty(L"shadowMapSize", &CRenderer::m_ShadowMapSize); AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow); AddProperty(L"shadows", &CRenderer::JSI_GetShadows, &CRenderer::JSI_SetShadows); AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits); AddProperty(L"shadowAlphaFix", &CRenderer::JSI_GetShadowAlphaFix, &CRenderer::JSI_SetShadowAlphaFix); AddProperty(L"shadowPCF", &CRenderer::JSI_GetShadowPCF, &CRenderer::JSI_SetShadowPCF); AddProperty(L"skipSubmit", &CRenderer::m_SkipSubmit); AddProperty(L"skySet", &CRenderer::JSI_GetSky, &CRenderer::JSI_SetSky); CJSObject::ScriptingInit("Renderer"); } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 10547) +++ ps/trunk/binaries/data/config/default.cfg (revision 10548) @@ -1,224 +1,227 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * On Linux / OS X, create: * ; * ~/.config/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%/0ad/config/local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; System settings: fancywater = true shadows = true shadowpcf = true vsync = false nos3tc = false noautomipmap = true novbo = false noframebufferobject = false ; Linux only: Set the driconf force_s3tc_enable option at startup, ; for compressed texture support force_s3tc_enable = true ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities ; fixed Only use OpenGL fixed function pipeline ; shader Use vertex/fragment shaders for transform and lighting where possible ; Using 'fixed' instead of 'default' may work around some graphics-related problems, ; but will reduce performance and features when a modern graphics card is available. renderpath = default ; Adjusts how OpenGL calculates mipmap level of detail. 0.0f is the default (blurry) value. ; Lower values sharpen/extend, and higher values blur/decrease. Clamped at -3.0 to 3.0. ; -1.0 to -1.5 recommended for good results. lodbias = 0 ; Opt-in online user reporting system userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/" ; Font mappings: font.console = console font.default = palatino12 font.misc = verdana16 ; Colour of the sky (in "r g b" format) skycolor = "0 0 0" ; GENERAL PREFERENCES: sound.mastergain = 0.5 ; Camera control settings view.scroll.speed = 120.0 view.rotate.x.speed = 1.2 -view.rotate.x.min = 20 -view.rotate.x.max = 60 -view.rotate.x.default = 30 +view.rotate.x.min = 28.0 +view.rotate.x.max = 60.0 +view.rotate.x.default = 35.0 view.rotate.y.speed = 2.0 view.rotate.y.speed.wheel = 0.45 view.rotate.y.default = 0.0 view.drag.speed = 0.5 view.zoom.speed = 256.0 view.zoom.speed.wheel = 32.0 -view.zoom.min = 96.0 -view.zoom.max = 512.0 -view.zoom.default = 192.0 +view.zoom.min = 64.0 +view.zoom.max = 256.0 +view.zoom.default = 112.0 view.pos.smoothness = 0.1 view.zoom.smoothness = 0.4 view.rotate.x.smoothness = 0.5 view.rotate.y.smoothness = 0.3 +view.near = 16.0 ; Near plane distance +view.far = 4096.0 ; Far plane distance +view.fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide ; HOTKEY MAPPINGS: ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+' and enclose the entire thing ; in doublequotes. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS hotkey.exit = "Alt+F4", "Ctrl+Break" ; Exit to desktop hotkey.leave = Escape ; End current game or Exit hotkey.pause = Pause ; Pause/unpause game hotkey.screenshot = F2 ; Take PNG screenshot hotkey.bigscreenshot = "Shift+F2" ; Take large BMP screenshot hotkey.togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode hotkey.screenshot.watermark = "K" ; Toggle product/company watermark for official screenshots hotkey.wireframe = "Alt+W" ; Toggle wireframe mode ; > CAMERA SETTINGS hotkey.camera.reset = "H" ; Reset camera rotation to default. hotkey.camera.follow = "F" ; Follow the first unit in the selection hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control) hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) hotkey.camera.zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) hotkey.camera.zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) hotkey.camera.rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards hotkey.camera.rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards hotkey.camera.rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain hotkey.camera.rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain hotkey.camera.rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) hotkey.camera.rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) hotkey.camera.pan = MouseMiddle, ForwardSlash ; Enable scrolling by moving mouse hotkey.camera.left = A, LeftArrow ; Scroll or rotate left hotkey.camera.right = D, RightArrow ; Scroll or rotate right hotkey.camera.up = W, UpArrow ; Scroll or rotate up/forwards hotkey.camera.down = S, DownArrow ; Scroll or rotate down/backwards ; > CONSOLE SETTINGS hotkey.console.toggle = BackQuote, F9 ; Open/close console ; > CLIPBOARD CONTROLS hotkey.copy = "Ctrl+C" ; Copy to clipboard hotkey.paste = "Ctrl+V" ; Paste from clipboard hotkey.cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > ENTITY SELECTION hotkey.selection.add = Shift ; Add units to selection hotkey.selection.remove = Ctrl ; Remove units from selection hotkey.selection.idleworker = Period ; Select next idle worker hotkey.selection.idlewarrior = Comma ; Select next idle warrior hotkey.selection.offscreen = Alt ; Include offscreen units in selection hotkey.selection.group.select.0 = 0 hotkey.selection.group.save.0 = "Ctrl+0" hotkey.selection.group.add.0 = "Shift+0" hotkey.selection.group.select.1 = 1 hotkey.selection.group.save.1 = "Ctrl+1" hotkey.selection.group.add.1 = "Shift+1" hotkey.selection.group.select.2 = 2 hotkey.selection.group.save.2 = "Ctrl+2" hotkey.selection.group.add.2 = "Shift+2" hotkey.selection.group.select.3 = 3 hotkey.selection.group.save.3 = "Ctrl+3" hotkey.selection.group.add.3 = "Shift+3" hotkey.selection.group.select.4 = 4 hotkey.selection.group.save.4 = "Ctrl+4" hotkey.selection.group.add.4 = "Shift+4" hotkey.selection.group.select.5 = 5 hotkey.selection.group.save.5 = "Ctrl+5" hotkey.selection.group.add.5 = "Shift+5" hotkey.selection.group.select.6 = 6 hotkey.selection.group.save.6 = "Ctrl+6" hotkey.selection.group.add.6 = "Shift+6" hotkey.selection.group.select.7 = 7 hotkey.selection.group.save.7 = "Ctrl+7" hotkey.selection.group.add.7 = "Shift+7" hotkey.selection.group.select.8 = 8 hotkey.selection.group.save.8 = "Ctrl+8" hotkey.selection.group.add.8 = "Shift+8" hotkey.selection.group.select.9 = 9 hotkey.selection.group.save.9 = "Ctrl+9" hotkey.selection.group.add.9 = "Shift+9" ; > SESSION CONTROLS hotkey.session.kill = Delete ; Destroy selected units hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise hotkey.timewarp.fastforward = Space ; If timewarp mode enabled, speed up the game hotkey.timewarp.rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game ; > OVERLAY KEYS hotkey.fps.toggle = "Shift+F" ; Toggle frame counter hotkey.session.devcommands.toggle = "Shift+D" ; Toggle developer commands panel hotkey.session.gui.toggle = "G" ; Toggle visibility of session GUI hotkey.menu.toggle = "F10" ; Toggle in-game menu hotkey.timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter ; > HOTKEYS ONLY hotkey.chat = Return ; Toggle chat window ; > GUI TEXTBOX HOTKEYS hotkey.text.delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor hotkey.text.delete.right = "Ctrl+Del" ; Delete word to the right of cursor hotkey.text.move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor hotkey.text.move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor ; > PROFILER hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt hotkey.profile2.enable = "F11" ; Enable HTTP/GPU modes for new profiler profiler2.http.autoenable = false ; Enable HTTP server output at startup (default off for security/performance) profiler2.gpu.autoenable = false ; Enable GPU timing at startup (default off for performance/compatibility) profiler2.gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available profiler2.gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available profiler2.gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available ; > QUICKSAVE hotkey.quicksave = "Shift+F5" hotkey.quickload = "Shift+F8" ; EXPERIMENTAL: joystick/gamepad settings joystick.enable = false joystick.deadzone = 8192 joystick.camera.pan.x = 0 joystick.camera.pan.y = 1 joystick.camera.rotate.x = 3 joystick.camera.rotate.y = 2 joystick.camera.zoom.in = 5 joystick.camera.zoom.out = 4