0ad/source/graphics/GameView.cpp

408 lines
10 KiB
C++

/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "GameView.h"
#include "graphics/CameraController.h"
#include "graphics/CinemaManager.h"
#include "graphics/ColladaManager.h"
#include "graphics/HFTracer.h"
#include "graphics/LOSTexture.h"
#include "graphics/LightEnv.h"
#include "graphics/MiniMapTexture.h"
#include "graphics/Model.h"
#include "graphics/ObjectManager.h"
#include "graphics/Patch.h"
#include "graphics/SkeletonAnimManager.h"
#include "graphics/SmoothedValue.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 "lobby/IXmppClient.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/Loader.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/TouchInput.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
#include <memory>
class CGameViewImpl
{
NONCOPYABLE(CGameViewImpl);
public:
CGameViewImpl(Renderer::Backend::IDevice* device, CGame* game)
: Game(game),
ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),
ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()),
LOSTexture(*game->GetSimulation2()),
TerritoryTexture(*game->GetSimulation2()),
MiniMapTexture(device, *game->GetSimulation2()),
ViewCamera(),
CullCamera(),
LockCullCamera(false),
Culling(true),
CameraController(new CCameraController(ViewCamera))
{
}
CGame* Game;
CColladaManager ColladaManager;
CMeshManager MeshManager;
CSkeletonAnimManager SkeletonAnimManager;
CObjectManager ObjectManager;
CLOSTexture LOSTexture;
CTerritoryTexture TerritoryTexture;
CMiniMapTexture MiniMapTexture;
/**
* 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;
CCinemaManager CinemaManager;
/**
* Controller of the view's camera. We use a std::unique_ptr for an easy
* on the fly replacement. It's guaranteed that the pointer is never nulllptr.
*/
std::unique_ptr<ICameraController> CameraController;
};
#define IMPLEMENT_BOOLEAN_SETTING(NAME) \
bool CGameView::Get##NAME##Enabled() const \
{ \
return m->NAME; \
} \
\
void CGameView::Set##NAME##Enabled(bool Enabled) \
{ \
m->NAME = Enabled; \
}
IMPLEMENT_BOOLEAN_SETTING(Culling);
IMPLEMENT_BOOLEAN_SETTING(LockCullCamera);
bool CGameView::GetConstrainCameraEnabled() const
{
return m->CameraController->GetConstrainCamera();
}
void CGameView::SetConstrainCameraEnabled(bool enabled)
{
m->CameraController->SetConstrainCamera(enabled);
}
#undef IMPLEMENT_BOOLEAN_SETTING
CGameView::CGameView(Renderer::Backend::IDevice* device, CGame *pGame):
m(new CGameViewImpl(device, pGame))
{
m->CullCamera = m->ViewCamera;
g_Renderer.GetSceneRenderer().SetSceneCamera(m->ViewCamera, m->CullCamera);
}
CGameView::~CGameView()
{
UnloadResources();
delete m;
}
void CGameView::SetViewport(const SViewPort& vp)
{
m->CameraController->SetViewport(vp);
}
CObjectManager& CGameView::GetObjectManager()
{
return m->ObjectManager;
}
CCamera* CGameView::GetCamera()
{
return &m->ViewCamera;
}
CCinemaManager* CGameView::GetCinema()
{
return &m->CinemaManager;
}
CLOSTexture& CGameView::GetLOSTexture()
{
return m->LOSTexture;
}
CTerritoryTexture& CGameView::GetTerritoryTexture()
{
return m->TerritoryTexture;
}
CMiniMapTexture& CGameView::GetMiniMapTexture()
{
return m->MiniMapTexture;
}
void CGameView::RegisterInit()
{
// CGameView init
LDR_Register([this](const double)
{
m->CameraController->LoadConfig();
return 0;
}, L"CGameView init", 1);
LDR_Register([](const double)
{
return g_TexMan.LoadTerrainTextures();
}, L"LoadTerrainTextures", 60);
}
void CGameView::BeginFrame()
{
if (m->LockCullCamera == false)
{
// Set up cull camera
m->CullCamera = m->ViewCamera;
}
g_Renderer.GetSceneRenderer().SetSceneCamera(m->ViewCamera, m->CullCamera);
m->Game->CachePlayerColors();
}
void CGameView::Prepare(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
g_Renderer.GetSceneRenderer().PrepareScene(deviceCommandContext, *this);
}
void CGameView::Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
g_Renderer.GetSceneRenderer().RenderScene(deviceCommandContext);
}
void CGameView::RenderOverlays(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
g_Renderer.GetSceneRenderer().RenderSceneOverlays(deviceCommandContext);
}
///////////////////////////////////////////////////////////
// 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");
const CTerrain& terrain = m->Game->GetWorld()->GetTerrain();
float waterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight + 0.001f;
const ssize_t patchesPerSide = terrain.GetPatchesPerSide();
// find out which patches will be drawn
for (ssize_t j=0; j<patchesPerSide; ++j)
{
for (ssize_t i=0; i<patchesPerSide; ++i)
{
CPatch* const patch = terrain.GetPatch(i,j); // can't fail
// If the patch is underwater, calculate a bounding box that also contains the water plane
CBoundingBoxAligned bounds = patch->GetWorldBounds();
if(bounds[1].Y < waterHeight)
bounds[1].Y = waterHeight;
if (!m->Culling || frustum.IsBoxVisible(bounds))
c->Submit(patch);
}
}
}
m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling);
}
void CGameView::UnloadResources()
{
g_TexMan.UnloadTerrainTextures();
g_Renderer.GetSceneRenderer().GetWaterManager().UnloadWaterTextures();
}
void CGameView::Update(const float deltaRealTime)
{
m->MiniMapTexture.Update(deltaRealTime);
// 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;
m->CinemaManager.Update(deltaRealTime);
if (m->CinemaManager.IsEnabled())
return;
m->CameraController->Update(deltaRealTime);
}
CVector3D CGameView::GetCameraPivot() const
{
return m->CameraController->GetCameraPivot();
}
CVector3D CGameView::GetCameraPosition() const
{
return m->CameraController->GetCameraPosition();
}
CVector3D CGameView::GetCameraRotation() const
{
return m->CameraController->GetCameraRotation();
}
float CGameView::GetCameraZoom() const
{
return m->CameraController->GetCameraZoom();
}
void CGameView::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
{
m->CameraController->SetCamera(pos, rotX, rotY, zoom);
}
void CGameView::MoveCameraTarget(const CVector3D& target)
{
m->CameraController->MoveCameraTarget(target);
}
void CGameView::ResetCameraTarget(const CVector3D& target)
{
m->CameraController->ResetCameraTarget(target);
}
void CGameView::FollowEntity(entity_id_t entity, bool firstPerson)
{
m->CameraController->FollowEntity(entity, firstPerson);
}
entity_id_t CGameView::GetFollowedEntity()
{
return m->CameraController->GetFollowedEntity();
}
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() || g_Game->GetView()->GetCinema()->IsEnabled())
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_HOTKEYPRESS:
{
std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
if (hotkey == "wireframe")
{
if (g_XmppClient && g_rankedGame == true)
break;
else if (sceneRenderer.GetModelRenderMode() == SOLID)
{
sceneRenderer.SetTerrainRenderMode(EDGED_FACES);
sceneRenderer.SetWaterRenderMode(EDGED_FACES);
sceneRenderer.SetModelRenderMode(EDGED_FACES);
sceneRenderer.SetOverlayRenderMode(EDGED_FACES);
}
else if (sceneRenderer.GetModelRenderMode() == EDGED_FACES)
{
sceneRenderer.SetTerrainRenderMode(WIREFRAME);
sceneRenderer.SetWaterRenderMode(WIREFRAME);
sceneRenderer.SetModelRenderMode(WIREFRAME);
sceneRenderer.SetOverlayRenderMode(WIREFRAME);
}
else
{
sceneRenderer.SetTerrainRenderMode(SOLID);
sceneRenderer.SetWaterRenderMode(SOLID);
sceneRenderer.SetModelRenderMode(SOLID);
sceneRenderer.SetOverlayRenderMode(SOLID);
}
return IN_HANDLED;
}
}
}
return m->CameraController->HandleEvent(ev);
}