Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 11555)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 11556)
@@ -1,634 +1,644 @@
/* Copyright (C) 2012 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 "scriptinterface/ScriptInterface.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "graphics/MapReader.h"
#include "gui/GUIManager.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/sysdep/sysdep.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CLogger.h"
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/ProfileViewer.h"
#include "ps/Pyrogenesis.h"
#include "ps/SavedGame.h"
#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/helpers/Selection.h"
#include "js/jsapi.h"
/*
* This file defines a set of functions that are available to GUI scripts, to allow
* interaction with the rest of the engine.
* Functions are exposed to scripts within the global object 'Engine', so
* scripts should call "Engine.FunctionName(...)" etc.
*/
extern void restart_mainloop_in_atlas(); // from main.cpp
namespace {
CScriptVal GetActiveGui(void* UNUSED(cbdata))
{
return OBJECT_TO_JSVAL(g_GUI->GetScriptObject());
}
void PushGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData)
{
g_GUI->PushPage(name, initData);
}
void SwitchGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData)
{
g_GUI->SwitchPage(name, initData);
}
void PopGuiPage(void* UNUSED(cbdata))
{
g_GUI->PopPage();
}
CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_Game)
return JSVAL_VOID;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY);
if (!cmpGuiInterface)
return JSVAL_VOID;
int player = -1;
if (g_Game)
player = g_Game->GetPlayerID();
CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get()));
CScriptVal ret (cmpGuiInterface->ScriptCall(player, name, arg.get()));
return guiManager->GetScriptInterface().CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get());
}
void PostNetworkCommand(void* cbdata, CScriptVal cmd)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get());
cmpCommandQueue->PostNetworkCommand(cmd2);
}
std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false);
}
std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player)
{
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
}
std::vector PickFriendlyEntitiesOnScreen(void* cbdata, int player)
{
return PickFriendlyEntitiesInRect(cbdata, 0, 0, g_xres, g_yres, player);
}
std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false);
}
CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)
{
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true);
return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z));
}
std::wstring SetCursor(void* UNUSED(cbdata), std::wstring name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
int GetPlayerID(void* UNUSED(cbdata))
{
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
{
// TODO: this should come from a config file or something
std::wstring name = sys_get_user_name();
if (name.empty())
name = L"anonymous";
return name;
}
void StartNetworkGame(void* UNUSED(cbdata))
{
ENSURE(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(void* cbdata, CScriptVal attribs, int playerID)
{
CGUIManager* guiManager = static_cast (cbdata);
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameAttribs, "");
}
CScriptVal StartSavedGame(void* cbdata, std::wstring name)
{
CGUIManager* guiManager = static_cast (cbdata);
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
// Load the saved game data from disk
CScriptValRooted metadata;
std::string savedState;
Status err = SavedGames::Load(name, guiManager->GetScriptInterface(), metadata, savedState);
if (err < 0)
return CScriptVal();
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameMetadata (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), metadata.get()));
CScriptValRooted gameInitAttributes;
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "initAttributes", gameInitAttributes);
int playerID;
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "player", playerID);
// Start the game
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameInitAttributes, savedState);
return metadata.get();
}
void SaveGame(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
if (SavedGames::Save(L"quicksave", *g_Game->GetSimulation2(), guiManager, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs)
{
CGUIManager* guiManager = static_cast (cbdata);
ENSURE(g_NetServer);
g_NetServer->UpdateGameAttributes(attribs, guiManager->GetScriptInterface());
}
void StartNetworkHost(void* cbdata, std::wstring playerName)
{
CGUIManager* guiManager = static_cast (cbdata);
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
{
guiManager->GetScriptInterface().ReportError("Failed to start server");
SAFE_DELETE(g_NetServer);
return;
}
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection("127.0.0.1"))
{
guiManager->GetScriptInterface().ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void StartNetworkJoin(void* cbdata, std::wstring playerName, std::string serverAddress)
{
CGUIManager* guiManager = static_cast (cbdata);
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
{
guiManager->GetScriptInterface().ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void DisconnectNetworkGame(void* UNUSED(cbdata))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
CScriptVal PollNetworkClient(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_NetClient)
return CScriptVal();
CScriptValRooted poll = g_NetClient->GuiPoll();
// Convert from net client context to GUI script context
return guiManager->GetScriptInterface().CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get());
}
void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid)
{
ENSURE(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SendNetworkChat(void* UNUSED(cbdata), std::wstring message)
{
ENSURE(g_NetClient);
g_NetClient->SendChatMessage(message);
}
std::vector GetAIs(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
return ICmpAIManager::GetAIs(guiManager->GetScriptInterface());
}
std::vector GetSavedGames(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
return SavedGames::GetSavedGames(guiManager->GetScriptInterface());
}
bool DeleteSavedGame(void* UNUSED(cbdata), std::wstring name)
{
return SavedGames::DeleteSavedGame(name);
}
void OpenURL(void* UNUSED(cbdata), std::string url)
{
sys_open_url(url);
}
void RestartInAtlas(void* UNUSED(cbdata))
{
restart_mainloop_in_atlas();
}
bool AtlasIsAvailable(void* UNUSED(cbdata))
{
return ATLAS_IsAvailable();
}
CScriptVal LoadMapSettings(void* cbdata, VfsPath pathname)
{
CGUIManager* guiManager = static_cast (cbdata);
CMapSummaryReader reader;
if (reader.LoadMap(pathname.ChangeExtension(L".xml")) != PSRETURN_OK)
return CScriptVal();
return reader.GetMapSettings(guiManager->GetScriptInterface()).get();
}
CScriptVal GetMapSettings(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_Game)
return CScriptVal();
return guiManager->GetScriptInterface().CloneValueFromOtherContext(
g_Game->GetSimulation2()->GetScriptInterface(),
g_Game->GetSimulation2()->GetMapSettings().get());
}
/**
* Start / stop camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollow(void* UNUSED(cbdata), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, false);
}
/**
* Start / stop first-person camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, true);
}
/// Move camera to a 2D location
void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
target.X = x.ToFloat();
target.Z = z.ToFloat();
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetFollowedEntity();
return INVALID_ENTITY;
}
bool HotkeyIsPressed_(void* UNUSED(cbdata), std::string hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(void* UNUSED(cbdata), std::wstring msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
CScriptVal GetProfilerState(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
return g_ProfileViewer.SaveToJS(guiManager->GetScriptInterface());
}
bool IsUserReportEnabled(void* UNUSED(cbdata))
{
return g_UserReporter.IsReportingEnabled();
}
void SetUserReportEnabled(void* UNUSED(cbdata), bool enabled)
{
g_UserReporter.SetReportingEnabled(enabled);
}
std::string GetUserReportStatus(void* UNUSED(cbdata))
{
return g_UserReporter.GetStatus();
}
void SubmitUserReport(void* UNUSED(cbdata), std::string type, int version, std::wstring data)
{
g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
}
void SetSimRate(void* UNUSED(cbdata), float rate)
{
g_Game->SetSimRate(rate);
}
void SetTurnLength(void* UNUSED(cbdata), int length)
{
if (g_NetServer)
g_NetServer->SetTurnLength(length);
else
LOGERROR(L"Only network host can change turn length");
}
// Focus the game camera on a given position.
void SetCameraTarget(void* UNUSED(cbdata), float x, float y, float z)
{
g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z));
}
// Deliberately cause the game to crash.
// Currently implemented via access violation (read of address 0).
// Useful for testing the crashlog/stack trace code.
int Crash(void* UNUSED(cbdata))
{
debug_printf(L"Crashing at user's request.\n");
return *(volatile int*)0;
}
void DebugWarn(void* UNUSED(cbdata))
{
debug_warn(L"Warning at user's request.");
}
// Force a JS garbage collection cycle to take place immediately.
// Writes an indication of how long this took to the console.
void ForceGC(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
double time = timer_Time();
JS_GC(guiManager->GetScriptInterface().GetContext());
time = timer_Time() - time;
g_Console->InsertMessage(L"Garbage collection completed in: %f", time);
}
void DumpSimState(void* UNUSED(cbdata))
{
OsPath path = psLogDir()/"sim_dump.txt";
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
g_Game->GetSimulation2()->DumpDebugState(file);
}
+void DumpTerrainMipmap(void* UNUSED(cbdata))
+{
+ VfsPath filename(L"screenshots/terrainmipmap.png");
+ g_Game->GetWorld()->GetTerrain()->GetHeightMipmap().DumpToDisk(filename);
+ OsPath realPath;
+ g_VFS->GetRealPath(filename, realPath);
+ LOGMESSAGERENDER(L"Terrain mipmap written to '%ls'", realPath.string().c_str());
+}
+
void EnableTimeWarpRecording(void* UNUSED(cbdata), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
}
void RewindTimeWarp(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->RewindTimeWarp();
}
void QuickSave(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->QuickSave();
}
void QuickLoad(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
// GUI manager functions:
scriptInterface.RegisterFunction("GetActiveGui");
scriptInterface.RegisterFunction("PushGuiPage");
scriptInterface.RegisterFunction("SwitchGuiPage");
scriptInterface.RegisterFunction("PopGuiPage");
// Simulation<->GUI interface functions:
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
scriptInterface.RegisterFunction("GetTerrainAtPoint");
// Network / game setup functions
scriptInterface.RegisterFunction("StartNetworkGame");
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
scriptInterface.RegisterFunction, &GetSavedGames>("GetSavedGames");
scriptInterface.RegisterFunction("DeleteSavedGame");
scriptInterface.RegisterFunction("SaveGame");
scriptInterface.RegisterFunction("QuickSave");
scriptInterface.RegisterFunction("QuickLoad");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("GetDefaultPlayerName");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("LoadMapSettings");
scriptInterface.RegisterFunction("GetMapSettings");
scriptInterface.RegisterFunction("CameraFollow");
scriptInterface.RegisterFunction("CameraFollowFPS");
scriptInterface.RegisterFunction("CameraMoveTo");
scriptInterface.RegisterFunction("GetFollowedEntity");
scriptInterface.RegisterFunction("HotkeyIsPressed");
scriptInterface.RegisterFunction("DisplayErrorDialog");
scriptInterface.RegisterFunction("GetProfilerState");
// User report functions
scriptInterface.RegisterFunction("IsUserReportEnabled");
scriptInterface.RegisterFunction("SetUserReportEnabled");
scriptInterface.RegisterFunction("GetUserReportStatus");
scriptInterface.RegisterFunction("SubmitUserReport");
// Development/debugging functions
scriptInterface.RegisterFunction("SetSimRate");
scriptInterface.RegisterFunction("SetTurnLength");
scriptInterface.RegisterFunction("SetCameraTarget");
scriptInterface.RegisterFunction("Crash");
scriptInterface.RegisterFunction("DebugWarn");
scriptInterface.RegisterFunction("ForceGC");
scriptInterface.RegisterFunction("DumpSimState");
+ scriptInterface.RegisterFunction("DumpTerrainMipmap");
scriptInterface.RegisterFunction("EnableTimeWarpRecording");
scriptInterface.RegisterFunction("RewindTimeWarp");
scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay");
}
Index: ps/trunk/source/graphics/Terrain.cpp
===================================================================
--- ps/trunk/source/graphics/Terrain.cpp (revision 11555)
+++ ps/trunk/source/graphics/Terrain.cpp (revision 11556)
@@ -1,633 +1,664 @@
/* 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 .
*/
/*
* Describes ground via heightmap and array of CPatch.
*/
#include "precompiled.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/sysdep/cpu.h"
#include "renderer/Renderer.h"
#include "TerrainProperties.h"
#include "TerrainTextureEntry.h"
#include "TerrainTextureManager.h"
#include
#include "Terrain.h"
#include "Patch.h"
#include "maths/FixedVector3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::CTerrain()
: m_Heightmap(0), m_Patches(0), m_MapSize(0), m_MapSizePatches(0),
m_BaseColour(255, 255, 255, 255)
{
}
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::~CTerrain()
{
ReleaseData();
}
///////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete any data allocated by this terrain
void CTerrain::ReleaseData()
{
+ m_HeightMipmap.ReleaseData();
+
delete[] m_Heightmap;
delete[] m_Patches;
}
///////////////////////////////////////////////////////////////////////////////
// Initialise: initialise this terrain to the given size
// using given heightmap to setup elevation data
-bool CTerrain::Initialize(ssize_t patchesPerSide,const u16* data)
+bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
{
// clean up any previous terrain
ReleaseData();
// store terrain size
- m_MapSize=patchesPerSide*PATCH_SIZE+1;
- m_MapSizePatches=patchesPerSide;
+ m_MapSize = patchesPerSide*PATCH_SIZE+1;
+ m_MapSizePatches = patchesPerSide;
// allocate data for new terrain
- m_Heightmap=new u16[m_MapSize*m_MapSize];
- m_Patches=new CPatch[m_MapSizePatches*m_MapSizePatches];
+ m_Heightmap = new u16[m_MapSize*m_MapSize];
+ m_Patches = new CPatch[m_MapSizePatches*m_MapSizePatches];
// given a heightmap?
- if (data) {
+ if (data)
+ {
// yes; keep a copy of it
- memcpy(m_Heightmap,data,m_MapSize*m_MapSize*sizeof(u16));
- } else {
+ memcpy(m_Heightmap, data, m_MapSize*m_MapSize*sizeof(u16));
+ }
+ else
+ {
// build a flat terrain
- memset(m_Heightmap,0,m_MapSize*m_MapSize*sizeof(u16));
+ memset(m_Heightmap, 0, m_MapSize*m_MapSize*sizeof(u16));
}
// setup patch parents, indices etc
InitialisePatches();
+ // initialise mipmap
+ m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
+
return true;
}
///////////////////////////////////////////////////////////////////////////////
CStr8 CTerrain::GetMovementClass(ssize_t i, ssize_t j) const
{
CMiniPatch* tile = GetTile(i, j);
if (tile && tile->GetTextureEntry())
return tile->GetTextureEntry()->GetProperties().GetMovementClass();
return "default";
}
///////////////////////////////////////////////////////////////////////////////
// CalcPosition: calculate the world space position of the vertex at (i,j)
// If i,j is off the map, it acts as if the edges of the terrain are extended
// outwards to infinity
void CTerrain::CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const
{
ssize_t hi = clamp(i, (ssize_t)0, m_MapSize-1);
ssize_t hj = clamp(j, (ssize_t)0, m_MapSize-1);
u16 height = m_Heightmap[hj*m_MapSize + hi];
pos.X = float(i*TERRAIN_TILE_SIZE);
pos.Y = float(height*HEIGHT_SCALE);
pos.Z = float(j*TERRAIN_TILE_SIZE);
}
///////////////////////////////////////////////////////////////////////////////
// CalcPositionFixed: calculate the world space position of the vertex at (i,j)
void CTerrain::CalcPositionFixed(ssize_t i, ssize_t j, CFixedVector3D& pos) const
{
ssize_t hi = clamp(i, (ssize_t)0, m_MapSize-1);
ssize_t hj = clamp(j, (ssize_t)0, m_MapSize-1);
u16 height = m_Heightmap[hj*m_MapSize + hi];
pos.X = fixed::FromInt(i) * (int)TERRAIN_TILE_SIZE;
pos.Y = fixed::FromInt(height) / (int)HEIGHT_UNITS_PER_METRE;
pos.Z = fixed::FromInt(j) * (int)TERRAIN_TILE_SIZE;
}
///////////////////////////////////////////////////////////////////////////////
// CalcNormal: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const
{
CVector3D left, right, up, down;
// Calculate normals of the four half-tile triangles surrounding this vertex:
// get position of vertex where normal is being evaluated
CVector3D basepos;
CalcPosition(i, j, basepos);
if (i > 0) {
CalcPosition(i-1, j, left);
left -= basepos;
left.Normalize();
}
if (i < m_MapSize-1) {
CalcPosition(i+1, j, right);
right -= basepos;
right.Normalize();
}
if (j > 0) {
CalcPosition(i, j-1, up);
up -= basepos;
up.Normalize();
}
if (j < m_MapSize-1) {
CalcPosition(i, j+1, down);
down -= basepos;
down.Normalize();
}
CVector3D n0 = up.Cross(left);
CVector3D n1 = left.Cross(down);
CVector3D n2 = down.Cross(right);
CVector3D n3 = right.Cross(up);
// Compute the mean of the normals
normal = n0 + n1 + n2 + n3;
float nlen=normal.Length();
if (nlen>0.00001f) normal*=1.0f/nlen;
}
///////////////////////////////////////////////////////////////////////////////
// CalcNormalFixed: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const
{
CFixedVector3D left, right, up, down;
// Calculate normals of the four half-tile triangles surrounding this vertex:
// get position of vertex where normal is being evaluated
CFixedVector3D basepos;
CalcPositionFixed(i, j, basepos);
if (i > 0) {
CalcPositionFixed(i-1, j, left);
left -= basepos;
left.Normalize();
}
if (i < m_MapSize-1) {
CalcPositionFixed(i+1, j, right);
right -= basepos;
right.Normalize();
}
if (j > 0) {
CalcPositionFixed(i, j-1, up);
up -= basepos;
up.Normalize();
}
if (j < m_MapSize-1) {
CalcPositionFixed(i, j+1, down);
down -= basepos;
down.Normalize();
}
CFixedVector3D n0 = up.Cross(left);
CFixedVector3D n1 = left.Cross(down);
CFixedVector3D n2 = down.Cross(right);
CFixedVector3D n3 = right.Cross(up);
// Compute the mean of the normals
normal = n0 + n1 + n2 + n3;
normal.Normalize();
}
CVector3D CTerrain::CalcExactNormal(float x, float z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = clamp((ssize_t)floor(x/TERRAIN_TILE_SIZE), (ssize_t)0, m_MapSize-2);
const ssize_t zi = clamp((ssize_t)floor(z/TERRAIN_TILE_SIZE), (ssize_t)0, m_MapSize-2);
const float xf = clamp(x/TERRAIN_TILE_SIZE-xi, 0.0f, 1.0f);
const float zf = clamp(z/TERRAIN_TILE_SIZE-zi, 0.0f, 1.0f);
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Determine which terrain triangle this point is on,
// then compute the normal of that triangle's plane
if (GetTriangulationDir(xi, zi))
{
if (xf + zf <= 1.f)
{
// Lower-left triangle (don't use h11)
return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
else
{
// Upper-right triangle (don't use h00)
return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
}
else
{
if (xf <= zf)
{
// Upper-left triangle (don't use h10)
return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
else
{
// Lower-right triangle (don't use h01)
return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
}
}
///////////////////////////////////////////////////////////////////////////////
// GetPatch: return the patch at (i,j) in patch space, or null if the patch is
// out of bounds
CPatch* CTerrain::GetPatch(ssize_t i, ssize_t j) const
{
// range check (invalid indices are passed in by the culling and
// patch blend code because they iterate from 0..#patches and examine
// neighbors without checking if they're already on the edge)
if( (size_t)i >= (size_t)m_MapSizePatches || (size_t)j >= (size_t)m_MapSizePatches )
return 0;
return &m_Patches[(j*m_MapSizePatches)+i];
}
///////////////////////////////////////////////////////////////////////////////
// GetTile: return the tile at (i,j) in tile space, or null if the tile is out
// of bounds
CMiniPatch* CTerrain::GetTile(ssize_t i, ssize_t j) const
{
// see comment above
if( (size_t)i >= (size_t)(m_MapSize-1) || (size_t)j >= (size_t)(m_MapSize-1) )
return 0;
CPatch* patch=GetPatch(i/PATCH_SIZE, j/PATCH_SIZE); // can't fail (due to above check)
return &patch->m_MiniPatches[j%PATCH_SIZE][i%PATCH_SIZE];
}
float CTerrain::GetVertexGroundLevel(ssize_t i, ssize_t j) const
{
i = clamp(i, (ssize_t)0, m_MapSize-1);
j = clamp(j, (ssize_t)0, m_MapSize-1);
return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
}
fixed CTerrain::GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const
{
i = clamp(i, (ssize_t)0, m_MapSize-1);
j = clamp(j, (ssize_t)0, m_MapSize-1);
// Convert to fixed metres (being careful to avoid intermediate overflows)
return fixed::FromInt(m_Heightmap[j*m_MapSize + i] / 2) / (int)(HEIGHT_UNITS_PER_METRE / 2);
}
fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
i = clamp(i, (ssize_t)0, m_MapSize-2);
j = clamp(j, (ssize_t)0, m_MapSize-2);
u16 h00 = m_Heightmap[j*m_MapSize + i];
u16 h01 = m_Heightmap[(j+1)*m_MapSize + i];
u16 h10 = m_Heightmap[j*m_MapSize + (i+1)];
u16 h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
// Difference of highest point from lowest point
u16 delta = std::max(std::max(h00, h01), std::max(h10, h11)) -
std::min(std::min(h00, h01), std::min(h10, h11));
// Compute fractional slope (being careful to avoid intermediate overflows)
return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}
+float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
+{
+ // convert to [0,1] interval
+ float nx = x / (TERRAIN_TILE_SIZE*m_MapSize);
+ float nz = z / (TERRAIN_TILE_SIZE*m_MapSize);
+ float nr = radius / (TERRAIN_TILE_SIZE*m_MapSize);
+
+ // get trilinear filtered mipmap height
+ return HEIGHT_SCALE * m_HeightMipmap.GetTrilinearGroundLevel(nx, nz, nr);
+}
+
float CTerrain::GetExactGroundLevel(float x, float z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = clamp((ssize_t)floor(x/TERRAIN_TILE_SIZE), (ssize_t)0, m_MapSize-2);
const ssize_t zi = clamp((ssize_t)floor(z/TERRAIN_TILE_SIZE), (ssize_t)0, m_MapSize-2);
const float xf = clamp(x/TERRAIN_TILE_SIZE-xi, 0.0f, 1.0f);
const float zf = clamp(z/TERRAIN_TILE_SIZE-zi, 0.0f, 1.0f);
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Determine which terrain triangle this point is on,
// then compute the linearly-interpolated height on that triangle's plane
if (GetTriangulationDir(xi, zi))
{
if (xf + zf <= 1.f)
{
// Lower-left triangle (don't use h11)
return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h01-h00)*zf);
}
else
{
// Upper-right triangle (don't use h00)
return HEIGHT_SCALE * (h11 + (h01-h11)*(1-xf) + (h10-h11)*(1-zf));
}
}
else
{
if (xf <= zf)
{
// Upper-left triangle (don't use h10)
return HEIGHT_SCALE * (h00 + (h11-h01)*xf + (h01-h00)*zf);
}
else
{
// Lower-right triangle (don't use h01)
return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h11-h10)*zf);
}
}
}
fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = clamp((ssize_t)(x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2);
const ssize_t zi = clamp((ssize_t)(z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2);
const fixed one = fixed::FromInt(1);
const fixed xf = clamp((x / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(xi), fixed::Zero(), one);
const fixed zf = clamp((z / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(zi), fixed::Zero(), one);
u16 h00 = m_Heightmap[zi*m_MapSize + xi];
u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Intermediate scaling of xf, so we don't overflow in the multiplications below
// (h00 <= 65535, xf <= 1, max fixed is < 32768; divide by 2 here so xf1*h00 <= 32767.5)
const fixed xf0 = xf / 2;
const fixed xf1 = (one - xf) / 2;
// Linearly interpolate
return ((one - zf).Multiply(xf1 * h00 + xf0 * h10)
+ zf.Multiply(xf1 * h01 + xf0 * h11)) / (int)(HEIGHT_UNITS_PER_METRE / 2);
// TODO: This should probably be more like GetExactGroundLevel()
// in handling triangulation properly
}
bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
i = clamp(i, (ssize_t)0, m_MapSize-2);
j = clamp(j, (ssize_t)0, m_MapSize-2);
int h00 = m_Heightmap[j*m_MapSize + i];
int h01 = m_Heightmap[(j+1)*m_MapSize + i];
int h10 = m_Heightmap[j*m_MapSize + (i+1)];
int h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
// Prefer triangulating in whichever direction means the midpoint of the diagonal
// will be the highest. (In particular this means a diagonal edge will be straight
// along the top, and jagged along the bottom, which makes sense for terrain.)
int mid1 = h00+h11;
int mid2 = h01+h10;
return (mid1 < mid2);
}
///////////////////////////////////////////////////////////////////////////////
// Resize: resize this terrain to the given size (in patches per side)
void CTerrain::Resize(ssize_t size)
{
if (size==m_MapSizePatches) {
// inexplicable request to resize terrain to the same size .. ignore it
return;
}
if (!m_Heightmap) {
// not yet created a terrain; build a default terrain of the given size now
Initialize(size,0);
return;
}
// allocate data for new terrain
ssize_t newMapSize=size*PATCH_SIZE+1;
u16* newHeightmap=new u16[newMapSize*newMapSize];
CPatch* newPatches=new CPatch[size*size];
if (size>m_MapSizePatches) {
// new map is bigger than old one - zero the heightmap so we don't get uninitialised
// height data along the expanded edges
memset(newHeightmap,0,newMapSize*newMapSize*sizeof(u16));
}
// now copy over rows of data
u16* src=m_Heightmap;
u16* dst=newHeightmap;
ssize_t copysize=std::min(newMapSize, m_MapSize);
for (ssize_t j=0;jm_MapSize) {
// extend the last height to the end of the row
for (size_t i=0;im_MapSize) {
// copy over heights of the last row to any remaining rows
src=newHeightmap+((m_MapSize-1)*newMapSize);
dst=src+newMapSize;
for (ssize_t i=0;im_MapSizePatches) {
// copy over the last tile from each column
for (ssize_t n=0;nm_MapSizePatches) {
// copy over the last tile from each column
CPatch* srcpatch=&newPatches[(m_MapSizePatches-1)*size];
CPatch* dstpatch=srcpatch+size;
for (ssize_t p=0;p<(ssize_t)size-m_MapSizePatches;p++) {
for (ssize_t n=0;n<(ssize_t)size;n++) {
for (ssize_t m=0;mm_MiniPatches[15][k];
CMiniPatch& dst=dstpatch->m_MiniPatches[m][k];
dst = src;
}
}
srcpatch++;
dstpatch++;
}
}
}
// release all the original data
ReleaseData();
// store new data
m_Heightmap=newHeightmap;
m_Patches=newPatches;
m_MapSize=(ssize_t)newMapSize;
m_MapSizePatches=(ssize_t)size;
// initialise all the new patches
InitialisePatches();
+
+ // initialise mipmap
+ m_HeightMipmap.Initialize(m_MapSize,m_Heightmap);
}
///////////////////////////////////////////////////////////////////////////////
// InitialisePatches: initialise patch data
void CTerrain::InitialisePatches()
{
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
patch->Initialize(this, i, j);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// SetHeightMap: set up a new heightmap from 16-bit source data;
// assumes heightmap matches current terrain size
void CTerrain::SetHeightMap(u16* heightmap)
{
// keep a copy of the given heightmap
memcpy(m_Heightmap, heightmap, m_MapSize*m_MapSize*sizeof(u16));
// recalculate patch bounds, invalidate vertices
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
patch->InvalidateBounds();
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
+
+ // update mipmap
+ m_HeightMipmap.Update(m_Heightmap);
}
///////////////////////////////////////////////////////////////////////////////
void CTerrain::MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags)
{
// Finds the inclusive limits of the patches that include the specified range of tiles
ssize_t pi0 = clamp( i0 /PATCH_SIZE, (ssize_t)0, m_MapSizePatches-1);
ssize_t pi1 = clamp((i1-1)/PATCH_SIZE, (ssize_t)0, m_MapSizePatches-1);
ssize_t pj0 = clamp( j0 /PATCH_SIZE, (ssize_t)0, m_MapSizePatches-1);
ssize_t pj1 = clamp((j1-1)/PATCH_SIZE, (ssize_t)0, m_MapSizePatches-1);
for (ssize_t j = pj0; j <= pj1; j++)
{
for (ssize_t i = pi0; i <= pi1; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail (i,j were clamped)
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
+
+ if (m_Heightmap)
+ m_HeightMipmap.Update(m_Heightmap, i0, j0, i1, j1);
}
void CTerrain::MakeDirty(int dirtyFlags)
{
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
+
+ if (m_Heightmap)
+ m_HeightMipmap.Update(m_Heightmap);
}
CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
i0 = clamp(i0, (ssize_t)0, m_MapSize-1);
j0 = clamp(j0, (ssize_t)0, m_MapSize-1);
i1 = clamp(i1, (ssize_t)0, m_MapSize-1);
j1 = clamp(j1, (ssize_t)0, m_MapSize-1);
u16 minH = 65535;
u16 maxH = 0;
for (ssize_t j = j0; j <= j1; ++j)
{
for (ssize_t i = i0; i <= i1; ++i)
{
minH = std::min(minH, m_Heightmap[j*m_MapSize + i]);
maxH = std::max(maxH, m_Heightmap[j*m_MapSize + i]);
}
}
CBoundingBoxAligned bound;
bound[0].X = (float)(i0*TERRAIN_TILE_SIZE);
bound[0].Y = (float)(minH*HEIGHT_SCALE);
bound[0].Z = (float)(j0*TERRAIN_TILE_SIZE);
bound[1].X = (float)(i1*TERRAIN_TILE_SIZE);
bound[1].Y = (float)(maxH*HEIGHT_SCALE);
bound[1].Z = (float)(j1*TERRAIN_TILE_SIZE);
return bound;
}
Index: ps/trunk/source/graphics/HeightMipmap.cpp
===================================================================
--- ps/trunk/source/graphics/HeightMipmap.cpp (nonexistent)
+++ ps/trunk/source/graphics/HeightMipmap.cpp (revision 11556)
@@ -0,0 +1,261 @@
+/* Copyright (C) 2012 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 "HeightMipmap.h"
+
+#include "lib/bits.h"
+#include "lib/timer.h"
+#include "lib/allocators/shared_ptr.h"
+#include "lib/tex/tex.h"
+#include "maths/MathUtil.h"
+#include "ps/Filesystem.h"
+
+#include
+
+CHeightMipmap::CHeightMipmap()
+{
+}
+
+CHeightMipmap::~CHeightMipmap()
+{
+ ReleaseData();
+}
+
+void CHeightMipmap::ReleaseData()
+{
+ for (size_t i = 0; i < m_Mipmap.size(); ++i)
+ {
+ delete[] m_Mipmap[i].m_Heightmap;
+ m_Mipmap[i].m_MapSize = 0;
+ }
+ m_Mipmap.clear();
+}
+
+void CHeightMipmap::Update(const u16* ptr)
+{
+ ENSURE(ptr != 0);
+
+ Update(ptr, 0, 0, m_MapSize, m_MapSize);
+}
+
+void CHeightMipmap::Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
+{
+ ENSURE(ptr != 0);
+
+ size_t mapSize = m_MapSize;
+
+ for (size_t i = 0; i < m_Mipmap.size(); ++i)
+ {
+ // update window
+ left = clamp((size_t)floorf((float)left / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
+ bottom = clamp((size_t)floorf((float)bottom / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
+
+ right = clamp((size_t)ceilf((float)right / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
+ top = clamp((size_t)ceilf((float)top / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
+
+ // TODO: should verify that the bounds calculations are actually correct
+
+ // update mipmap
+ BilinearUpdate(m_Mipmap[i], mapSize, ptr, left, bottom, right, top);
+
+ mapSize = m_Mipmap[i].m_MapSize;
+ ptr = m_Mipmap[i].m_Heightmap;
+ }
+}
+
+void CHeightMipmap::Initialize(size_t mapSize, const u16* ptr)
+{
+ ENSURE(ptr != 0);
+ ENSURE(mapSize > 0);
+
+ ReleaseData();
+
+ m_MapSize = mapSize;
+ size_t mipmapSize = round_down_to_pow2(mapSize);
+
+ while (mipmapSize > 1)
+ {
+ m_Mipmap.push_back(SMipmap(mipmapSize, new u16[mipmapSize*mipmapSize]));
+ mipmapSize >>= 1;
+ };
+
+ Update(ptr);
+}
+
+float CHeightMipmap::GetTrilinearGroundLevel(float x, float z, float radius) const
+{
+ float y;
+ if (radius <= 0.0f) // avoid logf of non-positive value
+ y = 0.0f;
+ else
+ y = clamp(logf(radius * m_Mipmap[0].m_MapSize) / logf(2), 0, m_Mipmap.size());
+
+ const size_t iy = (size_t)clamp((ssize_t)floorf(y), 0, m_Mipmap.size() - 2);
+
+ const float fy = y - iy;
+
+ const float h0 = BilinearFilter(m_Mipmap[iy], x, z);
+ const float h1 = BilinearFilter(m_Mipmap[iy + 1], x, z);
+
+ return (1 - fy) * h0 + fy * h1;
+}
+
+float CHeightMipmap::BilinearFilter(const SMipmap &mipmap, float x, float z) const
+{
+ x *= mipmap.m_MapSize;
+ z *= mipmap.m_MapSize;
+
+ const size_t xi = (size_t)clamp((ssize_t)floor(x), 0, mipmap.m_MapSize - 2);
+ const size_t zi = (size_t)clamp((ssize_t)floor(z), 0, mipmap.m_MapSize - 2);
+
+ const float xf = clamp(x-xi, 0.0f, 1.0f);
+ const float zf = clamp(z-zi, 0.0f, 1.0f);
+
+ const float h00 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + xi];
+ const float h01 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + xi];
+ const float h10 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + (xi+1)];
+ const float h11 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + (xi+1)];
+
+ return
+ (1.f - xf) * (1.f - zf) * h00 +
+ xf * (1.f - zf) * h10 +
+ (1.f - xf) * zf * h01 +
+ xf * zf * h11;
+}
+
+void CHeightMipmap::HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
+{
+ // specialized, faster version of BilinearUpdate for powers of 2
+
+ ENSURE(out_mipmap.m_MapSize != 0);
+
+ if (out_mipmap.m_MapSize * 2 != mapSize)
+ debug_warn(L"wrong size");
+
+ // valid update window
+ ENSURE(left < out_mipmap.m_MapSize);
+ ENSURE(bottom < out_mipmap.m_MapSize);
+ ENSURE(right > left && right <= out_mipmap.m_MapSize);
+ ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
+
+ for (size_t dstZ = bottom; dstZ < top; ++dstZ)
+ {
+ for (size_t dstX = left; dstX < right; ++dstX)
+ {
+ size_t srcX = dstX << 1;
+ size_t srcZ = dstZ << 1;
+
+ u16 h00 = ptr[srcX + 0 + srcZ * mapSize];
+ u16 h10 = ptr[srcX + 1 + srcZ * mapSize];
+ u16 h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
+ u16 h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
+
+ out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (h00 + h10 + h01 + h11) / 4;
+ }
+ }
+}
+
+void CHeightMipmap::BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
+{
+ ENSURE(out_mipmap.m_MapSize != 0);
+
+ // filter should have full coverage
+ ENSURE(out_mipmap.m_MapSize <= mapSize && out_mipmap.m_MapSize * 2 >= mapSize);
+
+ // valid update window
+ ENSURE(left < out_mipmap.m_MapSize);
+ ENSURE(bottom < out_mipmap.m_MapSize);
+ ENSURE(right > left && right <= out_mipmap.m_MapSize);
+ ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
+
+ if (out_mipmap.m_MapSize * 2 == mapSize)
+ {
+ // optimized for powers of 2
+ HalfResizeUpdate(out_mipmap, mapSize, ptr, left, bottom, right, top);
+ }
+ else
+ {
+ for (size_t dstZ = bottom; dstZ < top; ++dstZ)
+ {
+ for (size_t dstX = left; dstX < right; ++dstX)
+ {
+ const float x = ((float)dstX / (float)out_mipmap.m_MapSize) * mapSize;
+ const float z = ((float)dstZ / (float)out_mipmap.m_MapSize) * mapSize;
+
+ const size_t srcX = clamp((size_t)x, 0, mapSize - 2);
+ const size_t srcZ = clamp((size_t)z, 0, mapSize - 2);
+
+ const float fx = clamp(x - srcX, 0.0f, 1.0f);
+ const float fz = clamp(z - srcZ, 0.0f, 1.0f);
+
+ const float h00 = ptr[srcX + 0 + srcZ * mapSize];
+ const float h10 = ptr[srcX + 1 + srcZ * mapSize];
+ const float h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
+ const float h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
+
+ out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (u16)
+ ((1.f - fx) * (1.f - fz) * h00 +
+ fx * (1.f - fz) * h10 +
+ (1.f - fx) * fz * h01 +
+ fx * fz * h11);
+ }
+ }
+ }
+}
+
+void CHeightMipmap::DumpToDisk(const VfsPath& filename) const
+{
+ const size_t w = m_MapSize;
+ const size_t h = m_MapSize * 2;
+ const size_t bpp = 8;
+ int flags = TEX_GREY|TEX_TOP_DOWN;
+
+ 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);
+ void* img = buf.get() + hdr_size;
+ Tex t;
+ WARN_IF_ERR(tex_wrap(w, h, bpp, flags, buf, hdr_size, &t));
+
+ memset(img, 0x00, img_size);
+ size_t yoff = 0;
+ for (size_t i = 0; i < m_Mipmap.size(); ++i)
+ {
+ size_t size = m_Mipmap[i].m_MapSize;
+ u16* heightmap = m_Mipmap[i].m_Heightmap;
+ ENSURE(size+yoff <= h);
+ for (size_t y = 0; y < size; ++y)
+ {
+ for (size_t x = 0; x < size; ++x)
+ {
+ u16 val = heightmap[x + y*size];
+ ((u8*)img)[x + (y+yoff)*w] = val >> 8;
+ }
+ }
+ yoff += size;
+ }
+
+ DynArray da;
+ WARN_IF_ERR(tex_encode(&t, filename.Extension(), &da));
+ g_VFS->CreateFile(filename, DummySharedPtr(da.base), da.pos);
+ (void)da_free(&da);
+
+ tex_free(&t);
+}
Index: ps/trunk/source/graphics/GameView.cpp
===================================================================
--- ps/trunk/source/graphics/GameView.cpp (revision 11555)
+++ ps/trunk/source/graphics/GameView.cpp (revision 11556)
@@ -1,1078 +1,1120 @@
/* Copyright (C) 2012 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/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.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/TouchInput.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;
// 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*TERRAIN_TILE_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(g_VFS), 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),
ViewFOV(DEGTORAD(45.f)),
ViewNear(2.f),
ViewFar(4096.f),
JoystickPanX(-1),
JoystickPanY(-1),
JoystickRotateX(-1),
JoystickRotateY(-1),
JoystickZoomIn(-1),
JoystickZoomOut(-1),
+ HeightSmoothness(0.5f),
+ HeightMin(16.f),
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;
+ float HeightSmoothness;
+ float HeightMin;
////////////////////////////////////////
// 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(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(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.height.smoothness", Float, m->HeightSmoothness);
+ CFG_GET_SYS_VAL("view.height.min", Float, m->HeightMin);
+
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(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
CBoundingBoxAligned bounds = patch->GetWorldBounds();
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)
+static void FocusHeight(CGameViewImpl* m, bool smooth)
{
+ /*
+ The camera pivot height is moved towards ground level.
+ To prevent excessive zoom when looking over a cliff,
+ the target ground level is the maximum of the ground level at the camera's near and pivot points.
+ The ground levels are filtered to achieve smooth camera movement.
+ The filter radius is proportional to the zoom level.
+ The camera height is clamped to prevent map penetration.
+ */
+
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;
+ const CVector3D position = targetCam.m_Orientation.GetTranslation();
+ const CVector3D forwards = targetCam.m_Orientation.GetIn();
- if (!diff)
+ // horizontal view radius
+ const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue();
+ const float near_radius = radius * m->HeightSmoothness;
+ const float pivot_radius = radius * m->HeightSmoothness;
+
+ const CVector3D nearPoint = position + forwards * m->ViewNear;
+ const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue();
+
+ const float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
+
+ // filter ground levels for smooth camera movement
+ const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
+ const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
+
+ // filtered maximum visible ground level in view
+ const float filtered_ground = std::max(filtered_near_ground, filtered_pivot_ground);
+
+ // target camera height above pivot point
+ const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear);
+ // minimum camera height above filtered ground level
+ const float min_height = (m->HeightMin + ground - filtered_ground);
+
+ const float target_height = std::max(pivot_height, min_height);
+ const float height = (nearPoint.Y - filtered_ground);
+ const float diff = target_height - height;
+ if (fabsf(diff) < 0.0001f)
return;
if (smooth)
{
- m->PosX.AddSmoothly(forwards.X * -diff);
- m->PosY.AddSmoothly(forwards.Y * -diff);
- m->PosZ.AddSmoothly(forwards.Z * -diff);
+ m->PosY.AddSmoothly(diff);
}
else
{
- m->PosX.Add(forwards.X * -diff);
- m->PosY.Add(forwards.Y * -diff);
- m->PosZ.Add(forwards.Z * -diff);
+ m->PosY.Add(diff);
}
}
+CVector3D CGameView::GetSmoothPivot(CCamera& camera) const
+{
+ return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue();
+}
+
void CGameView::Update(float DeltaTime)
{
// If camera movement is being handled by the touch-input system,
// then we should stop to avoid conflicting with it
if (g_TouchInput.IsEnabled())
return;
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 && 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 pivot = GetSmoothPivot(targetCam);
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);
+ if (HotkeyIsPressed("camera.zoom.out"))
+ m->Zoom.AddSmoothly(m->ViewZoomSpeed * DeltaTime);
+
+ if (m->ConstrainCamera)
+ m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax);
- float zoomDelta = m->Zoom.Update(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);
+ FocusHeight(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 pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
CVector3D desiredPivot = pivot;
CmpPtr cmpRangeManager(*m->Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpRangeManager && 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 pivot = GetSmoothPivot(targetCam);
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 pivot = GetSmoothPivot(targetCam);
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)
{
// 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 pivot = GetSmoothPivot(targetCam);
CVector3D delta = target - pivot;
-
+
m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
- ClampDistance(m, false);
+ FocusHeight(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));
+ m->Zoom.SetValue(m->ViewZoomDefault);
- ClampDistance(m, false);
+ FocusHeight(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();
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation();
float dist = delta.Dot(forwards);
- m->Zoom.AddSmoothly(dist - m->ViewZoomDefault);
+ m->Zoom.AddSmoothly(m->ViewZoomDefault - dist);
// 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);
+ m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.zoom.wheel.out")
{
- m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
+ 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/Terrain.h
===================================================================
--- ps/trunk/source/graphics/Terrain.h (revision 11555)
+++ ps/trunk/source/graphics/Terrain.h (revision 11556)
@@ -1,172 +1,178 @@
/* 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 .
*/
/*
* Describes ground via heightmap and array of CPatch.
*/
#ifndef INCLUDED_TERRAIN
#define INCLUDED_TERRAIN
#include "maths/Vector3D.h"
#include "maths/Fixed.h"
#include "graphics/SColor.h"
+#include "graphics/HeightMipmap.h"
class CPatch;
class CMiniPatch;
class CFixedVector3D;
class CStr8;
class CBoundingBoxAligned;
///////////////////////////////////////////////////////////////////////////////
// Terrain Constants:
/// metres [world space units] per tile in x and z
const ssize_t TERRAIN_TILE_SIZE = 4;
/// number of u16 height units per metre
const ssize_t HEIGHT_UNITS_PER_METRE = 732; // == approx int(256.0f/0.35f)
/// metres per u16 height unit
const float HEIGHT_SCALE = 1.f / HEIGHT_UNITS_PER_METRE;
///////////////////////////////////////////////////////////////////////////////
// CTerrain: main terrain class; contains the heightmap describing elevation
// data, and the smaller subpatches that form the terrain
class CTerrain
{
public:
CTerrain();
~CTerrain();
// Coordinate naming convention: world-space coordinates are float x,z;
// tile-space coordinates are ssize_t i,j. rationale: signed types can
// more efficiently be converted to/from floating point. use ssize_t
// instead of int/long because these are sizes.
bool Initialize(ssize_t patchesPerSide, const u16* ptr);
// return number of vertices along edge of the terrain
ssize_t GetVerticesPerSide() const { return m_MapSize; }
// return number of tiles along edge of the terrain
ssize_t GetTilesPerSide() const { return GetVerticesPerSide()-1; }
// return number of patches along edge of the terrain
ssize_t GetPatchesPerSide() const { return m_MapSizePatches; }
float GetMinX() const { return 0.0f; }
float GetMinZ() const { return 0.0f; }
float GetMaxX() const { return (float)((m_MapSize-1) * TERRAIN_TILE_SIZE); }
float GetMaxZ() const { return (float)((m_MapSize-1) * TERRAIN_TILE_SIZE); }
bool IsOnMap(float x, float z) const
{
return ((x >= GetMinX()) && (x < GetMaxX())
&& (z >= GetMinZ()) && (z < GetMaxZ()));
}
CStr8 GetMovementClass(ssize_t i, ssize_t j) const;
float GetVertexGroundLevel(ssize_t i, ssize_t j) const;
fixed GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const;
float GetExactGroundLevel(float x, float z) const;
fixed GetExactGroundLevelFixed(fixed x, fixed z) const;
+ float GetFilteredGroundLevel(float x, float z, float radius) const;
// get the approximate slope (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc)
fixed GetSlopeFixed(ssize_t i, ssize_t j) const;
// Returns true if the triangulation diagonal for tile (i, j)
// should be in the direction (1,-1); false if it should be (1,1)
bool GetTriangulationDir(ssize_t i, ssize_t j) const;
// resize this terrain such that each side has given number of patches
void Resize(ssize_t size);
// set up a new heightmap from 16 bit data; assumes heightmap matches current terrain size
void SetHeightMap(u16* heightmap);
// return a pointer to the heightmap
u16* GetHeightMap() const { return m_Heightmap; }
// get patch at given coordinates, expressed in patch-space; return 0 if
// coordinates represent patch off the edge of the map
CPatch* GetPatch(ssize_t i, ssize_t j) const;
// get tile at given coordinates, expressed in tile-space; return 0 if
// coordinates represent tile off the edge of the map
CMiniPatch* GetTile(ssize_t i, ssize_t j) const;
// calculate the position of a given vertex
void CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const;
void CalcPositionFixed(ssize_t i, ssize_t j, CFixedVector3D& pos) const;
// calculate the vertex under a given position (rounding down coordinates)
static void CalcFromPosition(const CVector3D& pos, ssize_t& i, ssize_t& j)
{
i = (ssize_t)(pos.X/TERRAIN_TILE_SIZE);
j = (ssize_t)(pos.Z/TERRAIN_TILE_SIZE);
}
// calculate the vertex under a given position (rounding down coordinates)
static void CalcFromPosition(float x, float z, ssize_t& i, ssize_t& j)
{
i = (ssize_t)(x/TERRAIN_TILE_SIZE);
j = (ssize_t)(z/TERRAIN_TILE_SIZE);
}
// calculate the normal at a given vertex
void CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const;
void CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const;
CVector3D CalcExactNormal(float x, float z) const;
// Mark a specific square of tiles (inclusive lower bound, exclusive upper bound)
// as dirty - use this after modifying the heightmap.
// If you modify a vertex (i,j), you should dirty tiles
// from (i-1, j-1) [inclusive] to (i+1, j+1) [exclusive]
// since their geometry depends on that vertex.
// If you modify a tile (i,j), you should dirty tiles
// from (i-1, j-1) [inclusive] to (i+2, j+2) [exclusive]
// since their texture blends depend on that tile.
void MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags);
// mark the entire map as dirty
void MakeDirty(int dirtyFlags);
/**
* Returns a 3D bounding box encompassing the given vertex range (inclusive)
*/
CBoundingBoxAligned GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1);
// get the base colour for the terrain (typically pure white - other colours
// will interact badly with LOS - but used by the Actor Viewer tool)
SColor4ub GetBaseColour() const { return m_BaseColour; }
// set the base colour for the terrain
void SetBaseColour(SColor4ub colour) { m_BaseColour = colour; }
+ const CHeightMipmap& GetHeightMipmap() const { return m_HeightMipmap; }
+
private:
// delete any data allocated by this terrain
void ReleaseData();
// setup patch pointers etc
void InitialisePatches();
// size of this map in each direction, in vertices; ie. total tiles = sqr(m_MapSize-1)
ssize_t m_MapSize;
// size of this map in each direction, in patches; total patches = sqr(m_MapSizePatches)
ssize_t m_MapSizePatches;
// the patches comprising this terrain
CPatch* m_Patches;
// 16-bit heightmap data
u16* m_Heightmap;
// base colour (usually white)
SColor4ub m_BaseColour;
+ // heightmap mipmap
+ CHeightMipmap m_HeightMipmap;
};
#endif
Index: ps/trunk/source/graphics/HeightMipmap.h
===================================================================
--- ps/trunk/source/graphics/HeightMipmap.h (nonexistent)
+++ ps/trunk/source/graphics/HeightMipmap.h (revision 11556)
@@ -0,0 +1,79 @@
+/* Copyright (C) 2012 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 .
+ */
+
+
+/*
+ * Describes ground using heightmap mipmaps
+ * Used for camera movement
+ */
+
+#ifndef INCLUDED_HEIGHTMIPMAP
+#define INCLUDED_HEIGHTMIPMAP
+
+#include "lib/file/vfs/vfs_path.h"
+
+struct SMipmap
+{
+ SMipmap() : m_MapSize(0), m_Heightmap(0) { }
+ SMipmap(size_t MapSize, u16* Heightmap) : m_MapSize(MapSize), m_Heightmap(Heightmap) { }
+
+ size_t m_MapSize;
+ u16* m_Heightmap;
+};
+
+class CHeightMipmap
+{
+ NONCOPYABLE(CHeightMipmap);
+public:
+
+ CHeightMipmap();
+ ~CHeightMipmap();
+
+ void Initialize(size_t mapSize, const u16* ptr);
+ void ReleaseData();
+
+ // update the heightmap mipmaps
+ void Update(const u16* ptr);
+
+ // update a section of the heightmap mipmaps
+ // (coordinates are heightmap cells, inclusive of lower bounds,
+ // exclusive of upper bounds)
+ void Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
+
+ float GetTrilinearGroundLevel(float x, float z, float radius) const;
+
+ void DumpToDisk(const VfsPath& path) const;
+
+private:
+
+ // get bilinear filtered height from mipmap
+ float BilinearFilter(const SMipmap &mipmap, float x, float z) const;
+
+ // update rectangle of the output mipmap by bilinear interpolating an input mipmap of exactly twice its size
+ void HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
+
+ // update rectangle of the output mipmap by bilinear interpolating the input mipmap
+ void BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
+
+ // size of this map in each direction
+ size_t m_MapSize;
+
+ // mipmap list
+ std::vector m_Mipmap;
+};
+
+#endif
Index: ps/trunk/source/graphics/GameView.h
===================================================================
--- ps/trunk/source/graphics/GameView.h (revision 11555)
+++ ps/trunk/source/graphics/GameView.h (revision 11556)
@@ -1,104 +1,106 @@
/* 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);
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);
void ResetCameraTarget(const CVector3D& target);
void ResetCameraAngleZoom();
void CameraFollow(entity_id_t entity, bool firstPerson);
entity_id_t GetFollowedEntity();
+ CVector3D GetSmoothPivot(CCamera &camera) const;
+
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/lib/bits.h
===================================================================
--- ps/trunk/source/lib/bits.h (revision 11555)
+++ ps/trunk/source/lib/bits.h (revision 11556)
@@ -1,292 +1,301 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* bit-twiddling.
*/
#ifndef INCLUDED_BITS
#define INCLUDED_BITS
/**
* value of bit number \.
*
* @param n bit index.
*
* requirements:
* - T should be an unsigned type
* - n must be in [0, CHAR_BIT*sizeof(T)), else the result is undefined!
**/
template
inline T Bit(size_t n)
{
const T one = T(1);
return (T)(one << n);
}
/**
* pretty much the same as Bit\.
* this is intended for the initialization of enum values, where a
* compile-time constant is required.
**/
#define BIT(n) (1u << (n))
template
inline bool IsBitSet(T value, size_t index)
{
const T bit = Bit(index);
return (value & bit) != 0;
}
// these are declared in the header and inlined to aid compiler optimizations
// (they can easily end up being time-critical).
// note: GCC can't inline extern functions, while VC's "Whole Program
// Optimization" can.
/**
* a mask that includes the lowest N bits
*
* @param numBits Number of bits in mask.
**/
template
inline T bit_mask(size_t numBits)
{
const T bitsInT = sizeof(T)*CHAR_BIT;
const T allBits = (T)~T(0);
// (shifts of at least bitsInT are undefined)
if(numBits >= bitsInT)
return allBits;
// (note: the previous allBits >> (bitsInT-numBits) is not safe
// because right-shifts of negative numbers are undefined.)
const T mask = (T)((T(1) << numBits)-1);
return mask;
}
/**
* extract the value of bits hi_idx:lo_idx within num
*
* example: bits(0x69, 2, 5) == 0x0A
*
* @param num number whose bits are to be extracted
* @param lo_idx bit index of lowest bit to include
* @param hi_idx bit index of highest bit to include
* @return value of extracted bits.
**/
template
inline T bits(T num, size_t lo_idx, size_t hi_idx)
{
const size_t numBits = (hi_idx - lo_idx)+1; // # bits to return
T result = T(num >> lo_idx);
result = T(result & bit_mask(numBits));
return result;
}
/**
* set the value of bits hi_idx:lo_idx
*
* @param lo_idx bit index of lowest bit to include
* @param hi_idx bit index of highest bit to include
* @param value new value to be assigned to these bits
**/
template
inline T SetBitsTo(T num, size_t lo_idx, size_t hi_idx, size_t value)
{
const size_t numBits = (hi_idx - lo_idx)+1;
ASSERT(value < (T(1) << numBits));
const T mask = bit_mask(numBits) << lo_idx;
T result = num & ~mask;
result = T(result | (value << lo_idx));
return result;
}
/**
* @return number of 1-bits in mask.
* execution time is proportional to number of 1-bits in mask.
**/
template
inline size_t SparsePopulationCount(T mask)
{
size_t num1Bits = 0;
while(mask)
{
mask &= mask-1; // clear least significant 1-bit
num1Bits++;
}
return num1Bits;
}
/**
* @return number of 1-bits in mask.
* execution time is logarithmic in the total number of bits.
* supports up to 128-bit integers (if their arithmetic operators are defined).
* [http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel]
**/
template
static inline size_t PopulationCount(T x)
{
const T mask = T(~T(0));
x -= (x >> 1) & (mask/3); // count 2 bits
x = (x & (mask/15*3)) + ((x >> 2) & (mask/15*3)); // count 4 bits
x = (x + (x >> 4)) & (mask/255*15); // count 8 bits
return (x * (mask/255)) >> ((sizeof(T)-1)*CHAR_BIT);
}
/**
* @return whether the given number is a power of two.
**/
template
inline bool is_pow2(T n)
{
// 0 would pass the test below but isn't a POT.
if(n == 0)
return false;
return (n & (n-1)) == 0;
}
// as above; intended for use in static_assert
#define IS_POW2(n) (((n) != 0) && ((n) & ((n)-1)) == 0)
template
inline T LeastSignificantBit(T x)
{
const T negX = T(~x + 1); // 2's complement (avoids 'negating unsigned type' warning)
return x & negX;
}
template
inline T ClearLeastSignificantBit(T x)
{
return x & (x-1);
}
/**
* ceil(log2(x))
*
* @param x (unsigned integer)
* @return ceiling of the base-2 logarithm (i.e. rounded up) or
* zero if the input is zero.
**/
template
inline size_t ceil_log2(T x)
{
T bit = 1;
size_t log = 0;
while(bit < x && bit != 0) // must detect overflow
{
log++;
bit *= 2;
}
return log;
}
// compile-time variant of the above
template
struct CeilLog2
{
enum { value = 1 + CeilLog2<(N+1)/2>::value };
};
template<>
struct CeilLog2<1>
{
enum { value = 0 };
};
template<>
struct CeilLog2<0>
{
enum { value = 0 };
};
/**
* floor(log2(f))
* fast, uses the FPU normalization hardware.
*
* @param x (float) input; MUST be > 0, else results are undefined.
* @return floor of the base-2 logarithm (i.e. rounded down).
**/
extern int floor_log2(const float x);
/**
* round up to next larger power of two.
**/
template
inline T round_up_to_pow2(T x)
{
return T(1) << ceil_log2(x);
}
/**
+ * round down to next larger power of two.
+ **/
+template
+inline T round_down_to_pow2(T x)
+{
+ return T(1) << floor_log2(x);
+}
+
+/**
* round number up/down to the next given multiple.
*
* @param n Number to round.
* @param multiple Must be a power of two.
**/
template
inline T round_up(T n, T multiple)
{
ASSERT(is_pow2(multiple));
const T result = (n + multiple-1) & ~(multiple-1);
ASSERT(n <= result && result < n+multiple);
return result;
}
template
inline T round_down(T n, T multiple)
{
ASSERT(is_pow2(multiple));
const T result = n & ~(multiple-1);
ASSERT(result <= n && n < result+multiple);
return result;
}
// evaluates to an expression suitable as an initializer
// for constant static data members.
#define ROUND_UP(n, multiple) (((n) + (multiple)-1) & ~((multiple)-1))
template
inline T MaxPowerOfTwoDivisor(T value)
{
ASSERT(value != T(0));
for(size_t log2 = 0; log2 < sizeof(T)*CHAR_BIT; log2++)
{
if(IsBitSet(value, log2))
return T(1) << log2;
}
DEBUG_WARN_ERR(ERR::LOGIC); // unreachable (!= 0 => there is a set bit)
return 0;
}
#endif // #ifndef INCLUDED_BITS
Index: ps/trunk/source/lib/tests/test_bits.h
===================================================================
--- ps/trunk/source/lib/tests/test_bits.h (revision 11555)
+++ ps/trunk/source/lib/tests/test_bits.h (revision 11556)
@@ -1,160 +1,169 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "lib/self_test.h"
#include "lib/bits.h"
-#define EQUALS(actual, expected) ENSURE((actual) == (expected))
+//#define EQUALS(actual, expected) ENSURE((actual) == (expected))
+#define EQUALS TS_ASSERT_EQUALS
class TestBits : public CxxTest::TestSuite
{
public:
void test_Bit()
{
- EQUALS(Bit(0), 1);
- EQUALS(Bit(8), 0x100);
+ EQUALS(Bit(0), 1u);
+ EQUALS(Bit(8), 0x100u);
EQUALS(Bit(31), u32(0x80000000ul));
EQUALS(Bit(1), u64(2));
EQUALS(Bit(32), u64(0x100000000ull));
EQUALS(Bit(63), u64(0x8000000000000000ull));
}
void test_IsBitSet()
{
EQUALS(IsBitSet(0u, 1), false);
EQUALS(IsBitSet(1u, 1), false);
EQUALS(IsBitSet(2u, 1), true);
EQUALS(IsBitSet(0xFFFFFFFFul, 0), true);
EQUALS(IsBitSet(0xFFFFFFFFul, 31), true);
EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 0), true);
EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 31), true);
EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 32), true);
EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 63), true);
}
void test_bit_mask()
{
EQUALS(bit_mask(0), 0);
EQUALS(bit_mask(2), 0x3);
EQUALS(bit_mask(16), 0xFFFF);
- EQUALS(bit_mask(0), 0);
- EQUALS(bit_mask(2), 0x3);
+ EQUALS(bit_mask(0), 0u);
+ EQUALS(bit_mask(2), 0x3u);
EQUALS(bit_mask(32), 0xFFFFFFFFul);
- EQUALS(bit_mask(0), 0);
- EQUALS(bit_mask(2), 0x3);
+ EQUALS(bit_mask(0), 0u);
+ EQUALS(bit_mask(2), 0x3u);
EQUALS(bit_mask(32), 0xFFFFFFFFull);
EQUALS(bit_mask(64), 0xFFFFFFFFFFFFFFFFull);
}
void test_bits()
{
EQUALS(bits(0xFFFF, 0, 15), 0xFFFF);
EQUALS(bits(0xFFFF, 0, 7), 0xFF);
EQUALS(bits(0xFFFF, 8, 15), 0xFF);
EQUALS(bits(0xFFFF, 14, 15), 0x3);
EQUALS(bits(0xAA55, 4, 11), 0xA5);
EQUALS(bits(0xAA55, 14, 15), 0x2);
EQUALS(bits(0ul, 0, 31), 0ul);
EQUALS(bits(0xFFFFFFFFul, 0, 31), 0xFFFFFFFFul);
EQUALS(bits(0ull, 0, 63), 0ull);
EQUALS(bits(0xFFFFFFFFull, 0, 31), 0xFFFFFFFFull);
EQUALS(bits(0x0000FFFFFFFF0000ull, 16, 47), 0xFFFFFFFFull);
EQUALS(bits(0xFFFFFFFFFFFFFFFFull, 0, 63), 0xFFFFFFFFFFFFFFFFull);
EQUALS(bits(0xA5A5A5A5A5A5A5A5ull, 32, 63), 0xA5A5A5A5ull);
}
void test_PopulationCount()
{
- EQUALS(PopulationCount(0), 0);
- EQUALS(PopulationCount(4), 1);
- EQUALS(PopulationCount(0x28), 2);
- EQUALS(PopulationCount(0xFF), 8);
- EQUALS(PopulationCount(0x0ul), 0);
- EQUALS(PopulationCount(0x8ul), 1);
- EQUALS(PopulationCount(0xFFFFul), 16);
- EQUALS(PopulationCount(0xFFFFFFFFul), 32);
- EQUALS(PopulationCount(0x0ull), 0);
- EQUALS(PopulationCount(0x10ull), 1);
- EQUALS(PopulationCount(0xFFFFull), 16);
- EQUALS(PopulationCount(0xFFFFFFFFull), 32);
- EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFEull), 63);
- EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFFull), 64);
+ EQUALS(PopulationCount(0), 0u);
+ EQUALS(PopulationCount(4), 1u);
+ EQUALS(PopulationCount(0x28), 2u);
+ EQUALS(PopulationCount(0xFF), 8u);
+ EQUALS(PopulationCount(0x0ul), 0u);
+ EQUALS(PopulationCount(0x8ul), 1u);
+ EQUALS(PopulationCount(0xFFFFul), 16u);
+ EQUALS(PopulationCount(0xFFFFFFFFul), 32u);
+ EQUALS(PopulationCount(0x0ull), 0u);
+ EQUALS(PopulationCount(0x10ull), 1u);
+ EQUALS(PopulationCount(0xFFFFull), 16u);
+ EQUALS(PopulationCount(0xFFFFFFFFull), 32u);
+ EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFEull), 63u);
+ EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFFull), 64u);
}
void test_is_pow2()
{
EQUALS(is_pow2(0u), false);
EQUALS(is_pow2(~0u), false);
EQUALS(is_pow2(0x80000001), false);
EQUALS(is_pow2(1), true);
EQUALS(is_pow2(1u << 31), true);
}
void test_ceil_log2()
{
EQUALS(ceil_log2(3u), 2u);
EQUALS(ceil_log2(0xffffffffu), 32u);
EQUALS(ceil_log2(1u), 0u);
EQUALS(ceil_log2(256u), 8u);
EQUALS(ceil_log2(0x80000000u), 31u);
}
void test_floor_log2()
{
EQUALS(floor_log2(1.f), 0);
EQUALS(floor_log2(3.f), 1);
EQUALS(floor_log2(256.f), 8);
}
void test_round_up_to_pow2()
{
EQUALS(round_up_to_pow2(0u), 1u);
EQUALS(round_up_to_pow2(1u), 1u);
EQUALS(round_up_to_pow2(127u), 128u);
EQUALS(round_up_to_pow2(128u), 128u);
EQUALS(round_up_to_pow2(129u), 256u);
}
+ void test_round_down_to_pow2()
+ {
+ EQUALS(round_down_to_pow2(1u), 1u);
+ EQUALS(round_down_to_pow2(127u), 64u);
+ EQUALS(round_down_to_pow2(128u), 128u);
+ EQUALS(round_down_to_pow2(129u), 128u);
+ }
+
void test_round_up()
{
EQUALS(round_up( 0u, 16u), 0u);
EQUALS(round_up( 4u, 16u), 16u);
EQUALS(round_up(15u, 16u), 16u);
EQUALS(round_up(20u, 32u), 32u);
EQUALS(round_up(29u, 32u), 32u);
EQUALS(round_up(0x1000u, 0x1000u), 0x1000u);
EQUALS(round_up(0x1001u, 0x1000u), 0x2000u);
EQUALS(round_up(0x1900u, 0x1000u), 0x2000u);
}
void test_round_down()
{
EQUALS(round_down( 0u, 16u), 0u);
EQUALS(round_down( 4u, 16u), 0u);
EQUALS(round_down(15u, 16u), 0u);
EQUALS(round_down(20u, 16u), 16u);
EQUALS(round_down(29u, 16u), 16u);
EQUALS(round_down(0x1900u, 0x1000u), 0x1000u);
EQUALS(round_down(0x2001u, 0x2000u), 0x2000u);
}
};
Index: ps/trunk/source/maths/tests/test_Brush.h
===================================================================
--- ps/trunk/source/maths/tests/test_Brush.h (revision 11555)
+++ ps/trunk/source/maths/tests/test_Brush.h (revision 11556)
@@ -1,183 +1,183 @@
/* Copyright (C) 2012 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 "lib/self_test.h"
#include "maths/Brush.h"
#include "maths/BoundingBoxAligned.h"
#include "graphics/Frustum.h"
class TestBrush : public CxxTest::TestSuite
{
public:
void setUp()
{
CxxTest::setAbortTestOnFail(true);
}
void tearDown()
{
}
void test_slice_empty_brush()
{
// verifies that the result of slicing an empty bound with a plane yields an empty bound
CBrush brush;
CPlane plane(CVector4D(0, 0, -1, 0.5f)); // can be anything, really
CBrush result;
brush.Slice(plane, result);
TS_ASSERT(brush.IsEmpty());
}
void test_slice_plane_simple()
{
// slice a 1x1x1 cube vertically down the middle at z = 0.5, with the plane normal pointing towards the negative
// end of the Z axis (i.e., anything with Z lower than 0.5 is considered 'in front of' the plane and is kept)
CPlane plane(CVector4D(0, 0, -1, 0.5f));
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
CBrush result;
brush.Slice(plane, result);
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
- TS_ASSERT_EQUALS(8, result.GetVertices().size());
+ TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 0.5f)); // right-bottom-back
size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 0.5f)); // etc.
size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 0.5f));
size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 0.5f));
// verify that the brush contains the six expected planes (one of which is the slicing plane)
VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
}
void test_slice_plane_behind_brush()
{
// slice the (0,0,0) to (1,1,1) cube by the plane z = 1.5, with the plane normal pointing towards the negative
// end of the Z axis (i.e. the entire cube is 'in front of' the plane and should be kept)
CPlane plane(CVector4D(0, 0, -1, 1.5f));
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
CBrush result;
brush.Slice(plane, result);
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
- TS_ASSERT_EQUALS(8, result.GetVertices().size());
+ TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 1)); // right-bottom-back
size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 1)); // etc.
size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 1));
size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 1));
// verify that the brush contains the six expected planes (one of which is the slicing plane)
VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
}
void test_slice_plane_in_front_of_brush()
{
// slices the (0,0,0) to (1,1,1) cube by the plane z = -0.5, with the plane normal pointing towards the negative
// end of the Z axis (i.e. the entire cube is 'behind' the plane and should be cut away)
CPlane plane(CVector4D(0, 0, -1, -0.5f));
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
CBrush result;
brush.Slice(plane, result);
- TS_ASSERT_EQUALS(0, result.GetVertices().size());
+ TS_ASSERT_EQUALS((size_t)0, result.GetVertices().size());
std::vector > faces;
result.GetFaces(faces);
- TS_ASSERT_EQUALS(0, faces.size());
+ TS_ASSERT_EQUALS((size_t)0, faces.size());
}
private:
size_t GetUniqueVertexIndex(const CBrush& brush, const CVector3D& vertex, float eps = 1e-6f)
{
std::vector vertices = brush.GetVertices();
for (size_t i = 0; i < vertices.size(); ++i)
{
const CVector3D& v = vertices[i];
if (fabs(v.X - vertex.X) < eps
&& fabs(v.Y - vertex.Y) < eps
&& fabs(v.Z - vertex.Z) < eps)
return i;
}
TS_FAIL("Vertex not found in brush");
return ~0u;
}
void VerifyFacePresent(const CBrush& brush, int count, ...)
{
std::vector face;
va_list args;
va_start(args, count);
for (int x = 0; x < count; ++x)
face.push_back(va_arg(args, size_t));
va_end(args);
if (face.size() == 0)
return;
std::vector > faces;
brush.GetFaces(faces);
// the brush is free to use any starting vertex along the face, and to use any winding order, so have 'face'
// cycle through various starting values and see if any of them (or their reverse) matches one found in the brush.
for (size_t c = 0; c < face.size() - 1; ++c)
{
std::vector >::iterator it1 = std::find(faces.begin(), faces.end(), face);
if (it1 != faces.end())
return;
// no match, try the reverse
std::vector faceReverse = face;
std::reverse(faceReverse.begin(), faceReverse.end());
std::vector >::iterator it2 = std::find(faces.begin(), faces.end(), faceReverse);
if (it2 != faces.end())
return;
// no match, cycle it
face.erase(face.begin());
face.push_back(face[0]);
}
TS_FAIL("Face not found in brush");
}
};
Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 11555)
+++ ps/trunk/binaries/data/config/default.cfg (revision 11556)
@@ -1,236 +1,238 @@
; 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
; Pause the game on window focus loss (Only applicable to single player mode)
pauseonfocusloss = true
; 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
; Disable hardware cursors
nohwcursor = 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
; Prefer GLSL shaders over ARB shaders (not recommended)
preferglsl = false
; Replace alpha-blending with alpha-testing, for performance experiments
forcealphatest = false
; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk
gpuskinning = false
; Opt-in online user reporting system
userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/"
; 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 = 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 = 50.0
view.zoom.max = 200.0
view.zoom.default = 120.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 = 2.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
+view.height.smoothness = 0.5
+view.height.min = 16
; 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.milonly = Alt ; Add only military 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.massbarter = Shift ; Modifier to barter bunch of resources
hotkey.session.unloadtype = Shift ; Modifier to unload all units of type
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 = "Alt+F" ; Toggle frame counter
hotkey.session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel
hotkey.session.gui.toggle = "Alt+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
hotkey.attackmove = Super
; > 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