Index: ps/trunk/source/graphics/Decal.h =================================================================== --- ps/trunk/source/graphics/Decal.h (revision 26248) +++ ps/trunk/source/graphics/Decal.h (revision 26249) @@ -1,92 +1,87 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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_DECAL #define INCLUDED_DECAL #include "graphics/Material.h" #include "graphics/ModelAbstract.h" class CTerrain; /** * Terrain decal definition. * Decals are rectangular textures that are projected vertically downwards * onto the terrain. */ struct SDecal { SDecal(const CMaterial& material, float sizeX, float sizeZ, float angle, float offsetX, float offsetZ, bool floating) : m_Material(material), m_SizeX(sizeX), m_SizeZ(sizeZ), m_Angle(angle), m_OffsetX(offsetX), m_OffsetZ(offsetZ), m_Floating(floating) { } CMaterial m_Material; float m_SizeX; float m_SizeZ; float m_Angle; float m_OffsetX; float m_OffsetZ; bool m_Floating; }; class CModelDecal : public CModelAbstract { public: CModelDecal(CTerrain* terrain, const SDecal& decal) : m_Terrain(terrain), m_Decal(decal) { ENSURE(terrain != NULL); } /// Dynamic cast virtual CModelDecal* ToCModelDecal() { return this; } virtual CModelAbstract* Clone() const; - virtual void SetDirtyRec(int dirtyflags) - { - SetDirty(dirtyflags); - } - virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1); virtual void CalcBounds(); virtual void ValidatePosition(); virtual void InvalidatePosition(); virtual void SetTransform(const CMatrix3D& transform); // remove shadow receiving void RemoveShadows(); /** * Compute the terrain vertex indexes that bound the decal's * projection onto the terrain. * The returned indexes are clamped to the terrain size. */ void CalcVertexExtents(ssize_t& i0, ssize_t& j0, ssize_t& i1, ssize_t& j1); CTerrain* m_Terrain; SDecal m_Decal; }; #endif // INCLUDED_DECAL Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 26248) +++ ps/trunk/source/graphics/GameView.cpp (revision 26249) @@ -1,423 +1,400 @@ /* Copyright (C) 2022 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/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/LoaderThunks.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 class CGameViewImpl { 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()), MiniMapTexture(*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; /** * 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 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 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(CGame *pGame): m(new CGameViewImpl(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; } int CGameView::Initialize() { m->CameraController->LoadConfig(); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::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); - CheckLightEnv(); - m->Game->CachePlayerColors(); } void CGameView::Render() { g_Renderer.GetSceneRenderer().RenderScene(g_Renderer.GetDeviceCommandContext(), *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(); float waterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight + 0.001f; 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(); 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::CheckLightEnv() -{ - if (m->CachedLightEnv == g_LightEnv) - return; - - 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.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(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); } Index: ps/trunk/source/graphics/GameView.h =================================================================== --- ps/trunk/source/graphics/GameView.h (revision 26248) +++ ps/trunk/source/graphics/GameView.h (revision 26249) @@ -1,103 +1,100 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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 CCamera; class CCinemaManager; class CGame; class CObjectManager; class CVector3D; struct SViewPort; class CGameViewImpl; class CGameView : private Scene { NONCOPYABLE(CGameView); public: CGameView(CGame *pGame); ~CGameView(); void SetViewport(const SViewPort& vp); void RegisterInit(); int Initialize(); /** * Updates all the view information (i.e. rotate camera, scroll, whatever). This will *not* change any * World information - only the *presentation*. * * @param deltaRealTime Elapsed real time since the last frame. */ void Update(const float deltaRealTime); void BeginFrame(); void Render(); InReaction HandleEvent(const SDL_Event_* ev); CVector3D GetCameraPivot() const; CVector3D GetCameraPosition() const; CVector3D GetCameraRotation() const; float GetCameraZoom() const; void SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom); void MoveCameraTarget(const CVector3D& target); void ResetCameraTarget(const CVector3D& target); void FollowEntity(entity_id_t entity, bool firstPerson); entity_id_t GetFollowedEntity(); #define DECLARE_BOOLEAN_SETTING(NAME) \ bool Get##NAME##Enabled() const; \ void Set##NAME##Enabled(bool Enabled); DECLARE_BOOLEAN_SETTING(Culling); DECLARE_BOOLEAN_SETTING(LockCullCamera); DECLARE_BOOLEAN_SETTING(ConstrainCamera); #undef DECLARE_BOOLEAN_SETTING CCamera* GetCamera(); CCinemaManager* GetCinema(); CObjectManager& GetObjectManager(); // Implementations of Scene void EnumerateObjects(const CFrustum& frustum, SceneCollector* c) override; CLOSTexture& GetLOSTexture() override; CTerritoryTexture& GetTerritoryTexture() override; CMiniMapTexture& GetMiniMapTexture() override; private: // Unloads all graphics resources loaded by RegisterInit. void UnloadResources(); - // Checks whether lighting environment has changed and update vertex data if necessary. - void CheckLightEnv(); - CGameViewImpl* m; }; extern InReaction game_view_handler(const SDL_Event_* ev); #endif // INCLUDED_GAMEVIEW Index: ps/trunk/source/graphics/Model.h =================================================================== --- ps/trunk/source/graphics/Model.h (revision 26248) +++ ps/trunk/source/graphics/Model.h (revision 26249) @@ -1,306 +1,298 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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 . */ /* * Mesh object with texture and skinning information */ #ifndef INCLUDED_MODEL #define INCLUDED_MODEL #include "graphics/Material.h" #include "graphics/MeshManager.h" #include "graphics/ModelAbstract.h" #include struct SPropPoint; class CObjectEntry; class CSkeletonAnim; class CSkeletonAnimDef; class CSkeletonAnimManager; class CSimulation2; #define MODELFLAG_CASTSHADOWS (1<<0) #define MODELFLAG_NOLOOPANIMATION (1<<1) #define MODELFLAG_SILHOUETTE_DISPLAY (1<<2) #define MODELFLAG_SILHOUETTE_OCCLUDER (1<<3) #define MODELFLAG_IGNORE_LOS (1<<4) #define MODELFLAG_FLOATONWATER (1<<5) /////////////////////////////////////////////////////////////////////////////// // CModel: basically, a mesh object - holds the texturing and skinning // information for a model in game class CModel : public CModelAbstract { NONCOPYABLE(CModel); public: struct Prop { Prop() : m_MinHeight(0.f), m_MaxHeight(0.f), m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false), m_Selectable(true) {} float m_MinHeight; float m_MaxHeight; /** * Location of the prop point within its parent model, relative to either a bone in the parent model or to the * parent model's origin. See the documentation for @ref SPropPoint for more details. * @see SPropPoint */ const SPropPoint* m_Point; /** * Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world * space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic). * @see CModel::ValidatePosition */ CModelAbstract* m_Model; CObjectEntry* m_ObjectEntry; bool m_Hidden; ///< Should this prop be temporarily removed from rendering? bool m_Selectable; /// < should this prop count in the selection size? }; public: // constructor CModel(CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation); // destructor ~CModel(); /// Dynamic cast virtual CModel* ToCModel() { return this; } // setup model from given geometry bool InitModel(const CModelDefPtr& modeldef); // update this model's state; 'time' is the absolute time since the start of the animation, in MS void UpdateTo(float time); // get the model's geometry data const CModelDefPtr& GetModelDef() { return m_pModelDef; } // set the model's material void SetMaterial(const CMaterial &material); // set the model's player ID, recursively through props void SetPlayerID(player_id_t id); // set the models mod color virtual void SetShadingColor(const CColor& color); // get the model's material CMaterial& GetMaterial() { return m_Material; } // set the given animation as the current animation on this model bool SetAnimation(CSkeletonAnim* anim, bool once = false); // get the currently playing animation, if any CSkeletonAnim* GetAnimation() const { return m_Anim; } // set the animation state to be the same as from another; both models should // be compatible types (same type of skeleton) void CopyAnimationFrom(CModel* source); // set object flags void SetFlags(int flags) { m_Flags=flags; } // get object flags int GetFlags() const { return m_Flags; } // add object flags, recursively through props void AddFlagsRec(int flags); // remove shadow casting and receiving, recursively through props // TODO: replace with more generic shader define + flags setting void RemoveShadowsRec(); - // recurse down tree setting dirty bits - virtual void SetDirtyRec(int dirtyflags) { - SetDirty(dirtyflags); - for (size_t i=0;iSetDirtyRec(dirtyflags); - } - } - virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) { for (size_t i = 0; i < m_Props.size(); ++i) m_Props[i].m_Model->SetTerrainDirty(i0, j0, i1, j1); } virtual void SetEntityVariable(const std::string& name, float value) { for (size_t i = 0; i < m_Props.size(); ++i) m_Props[i].m_Model->SetEntityVariable(name, value); } // --- WORLD/OBJECT SPACE BOUNDS ----------------------------------------------------------------- /// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in /// m_Bounds and m_ObjectBounds, respectively. virtual void CalcBounds(); /// Returns the object-space bounds for this model, excluding its children. const CBoundingBoxAligned& GetObjectBounds() { RecalculateBoundsIfNecessary(); // recalculates both object-space and world-space bounds if necessary return m_ObjectBounds; } virtual const CBoundingBoxAligned GetWorldBoundsRec(); // reimplemented here /// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined /// that the object-space bounds are static. void CalcStaticObjectBounds(); /// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the /// object-space bounds need to take animations into account. void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBoundingBoxAligned& result); // --- SELECTION BOX/BOUNDS ---------------------------------------------------------------------- /// Reimplemented here since proper models should participate in selection boxes. virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec(); /** * Set transform of this object. * * @note In order to ensure that all child props are updated properly, * you must call ValidatePosition(). */ virtual void SetTransform(const CMatrix3D& transform); /** * Return whether this is a skinned/skeletal model. If it is, Get*BoneMatrices() * will return valid non-NULL arrays. */ bool IsSkinned() { return (m_BoneMatrices != NULL); } // return the models bone matrices; 16-byte aligned for SSE reads const CMatrix3D* GetAnimatedBoneMatrices() { ENSURE(m_PositionValid); return m_BoneMatrices; } /** * Load raw animation frame animation from given file, and build an * animation specific to this model. * @param pathname animation file to load * @param name animation name (e.g. "idle") * @param ID specific ID of the animation, to sync with props * @param frequency influences the random choices * @param speed animation speed as a factor of the default animation speed * @param actionpos offset of 'action' event, in range [0, 1] * @param actionpos2 offset of 'action2' event, in range [0, 1] * @param sound offset of 'sound' event, in range [0, 1] * @return new animation, or NULL on error */ CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos); /** * Add a prop to the model on the given point. */ void AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight = 0.f, float maxHeight = 0.f, bool selectable = true); /** * Add a prop to the model on the given point, and treat it as the ammo prop. * The prop will be hidden by default. */ void AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry); /** * Show the ammo prop (if any), and hide any other props on that prop point. */ void ShowAmmoProp(); /** * Hide the ammo prop (if any), and show any other props on that prop point. */ void HideAmmoProp(); /** * Find the first prop used for ammo, by this model or its own props. */ CModelAbstract* FindFirstAmmoProp(); // return prop list std::vector& GetProps() { return m_Props; } const std::vector& GetProps() const { return m_Props; } // return a clone of this model virtual CModelAbstract* Clone() const; /** * Ensure that both the transformation and the bone * matrices are correct for this model and all its props. */ virtual void ValidatePosition(); /** * Mark this model's position and bone matrices, * and all props' positions as invalid. */ virtual void InvalidatePosition(); private: // delete anything allocated by the model void ReleaseData(); // Needed for terrain aligned props CSimulation2& m_Simulation; // object flags int m_Flags; // model's material CMaterial m_Material; // pointer to the model's raw 3d data CModelDefPtr m_pModelDef; // object space bounds of model - accounts for bounds of all possible animations // that can play on a model. Not always up-to-date - currently CalcBounds() // updates it when necessary. CBoundingBoxAligned m_ObjectBounds; // animation currently playing on this model, if any CSkeletonAnim* m_Anim; // time (in MS) into the current animation float m_AnimTime; /** * Current state of all bones on this model; null if associated modeldef isn't skeletal. * Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their * transformation matrix held is relative to the bone transformation (see @ref SPropPoint and * @ref CModel::ValidatePosition). * * @see SPropPoint */ CMatrix3D* m_BoneMatrices; // list of current props on model std::vector m_Props; /** * The prop point to which the ammo prop is attached, or NULL if none */ const SPropPoint* m_AmmoPropPoint; /** * If m_AmmoPropPoint is not NULL, then the index in m_Props of the ammo prop */ size_t m_AmmoLoadedProp; // manager object which can load animations for us CSkeletonAnimManager& m_SkeletonAnimManager; }; #endif Index: ps/trunk/source/graphics/ModelAbstract.h =================================================================== --- ps/trunk/source/graphics/ModelAbstract.h (revision 26248) +++ ps/trunk/source/graphics/ModelAbstract.h (revision 26249) @@ -1,195 +1,192 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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_MODELABSTRACT #define INCLUDED_MODELABSTRACT #include "graphics/Color.h" #include "graphics/RenderableObject.h" #include "maths/BoundingBoxOriented.h" #include "simulation2/helpers/Player.h" class CModelDummy; class CModel; class CModelDecal; class CModelParticleEmitter; /** * Abstract base class for graphical objects that are used by units, * or as props attached to other CModelAbstract objects. * This includes meshes, terrain decals, and sprites. * These objects exist in a tree hierarchy. */ class CModelAbstract : public CRenderableObject { NONCOPYABLE(CModelAbstract); public: /** * Describes a custom selection shape to be used for a model's selection box instead of the default * recursive bounding boxes. */ struct CustomSelectionShape { enum EType { /// The selection shape is determined by an oriented box of custom, user-specified size. BOX, /// The selection shape is determined by a cylinder of custom, user-specified size. CYLINDER }; EType m_Type; ///< Type of shape. float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER }; public: CModelAbstract() : m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER), m_SelectionBoxValid(false), m_CustomSelectionShape(NULL) { } ~CModelAbstract() { delete m_CustomSelectionShape; // allocated and set externally by CCmpVisualActor, but our responsibility to clean up } virtual CModelAbstract* Clone() const = 0; /// Dynamic cast virtual CModelDummy* ToCModelDummy() { return nullptr; } /// Dynamic cast virtual CModel* ToCModel() { return nullptr; } /// Dynamic cast virtual CModelDecal* ToCModelDecal() { return nullptr; } /// Dynamic cast virtual CModelParticleEmitter* ToCModelParticleEmitter() { return nullptr; } // (This dynamic casting is a bit ugly, but we won't have many subclasses // and this seems the easiest way to integrate with other code that wants // type-specific processing) - /// Calls SetDirty on this model and all child objects. - virtual void SetDirtyRec(int dirtyflags) = 0; - /// Returns world space bounds of this object and all child objects. virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } // default implementation /** * Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The * returned selection box may be empty to indicate that it does not wish to participate in the selection process. */ virtual const CBoundingBoxOriented& GetSelectionBox(); virtual void InvalidateBounds() { m_BoundsValid = false; // a call to this method usually means that the model's transform has changed, i.e. it has moved or rotated, so we'll also // want to update the selection box accordingly regardless of the shape it is built from. m_SelectionBoxValid = false; } /// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL /// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes. void SetCustomSelectionShape(CustomSelectionShape* descriptor) { if (m_CustomSelectionShape != descriptor) { m_CustomSelectionShape = descriptor; m_SelectionBoxValid = false; // update the selection box when it is next requested } } /** * Returns the (object-space) bounds that should be used to construct a selection box for this model and its children. * May return an empty bound to indicate that this model and its children should not be selectable themselves, or should * not be included in its parent model's selection box. This method is used for constructing the default selection boxes, * as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape. * * If you wish your model type to be included in selection boxes, override this method and have it return the object-space * bounds of itself, augmented recursively (via this method) with the object-space selection bounds of its children. */ virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec() { return CBoundingBoxAligned::EMPTY; } /** * Called when terrain has changed in the given inclusive bounds. * Might call SetDirty if the change affects this model. */ virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0; /** * Called when the entity tries to set some variable to affect the display of this model * and/or its child objects. */ virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { } /** * Ensure that both the transformation and the bone matrices are correct for this model and all its props. */ virtual void ValidatePosition() = 0; /** * Mark this model's position and bone matrices, and all props' positions as invalid. */ virtual void InvalidatePosition() = 0; virtual void SetPlayerID(player_id_t id) { m_PlayerID = id; } // get the model's player ID; initial default is INVALID_PLAYER virtual player_id_t GetPlayerID() const { return m_PlayerID; } virtual void SetShadingColor(const CColor& color) { m_ShadingColor = color; } virtual CColor GetShadingColor() const { return m_ShadingColor; } protected: void CalcSelectionBox(); public: /// If non-null, points to the model that we are attached to. CModelAbstract* m_Parent; /// True if both transform and and bone matrices are valid. bool m_PositionValid; player_id_t m_PlayerID; /// Modulating color CColor m_ShadingColor; protected: /// Selection box for this model. CBoundingBoxOriented m_SelectionBox; /// Is the current selection box valid? bool m_SelectionBoxValid; /// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL /// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this /// field will be used. /// @see SetCustomSelectionShape CustomSelectionShape* m_CustomSelectionShape; }; #endif // INCLUDED_MODELABSTRACT Index: ps/trunk/source/graphics/ModelDummy.h =================================================================== --- ps/trunk/source/graphics/ModelDummy.h (revision 26248) +++ ps/trunk/source/graphics/ModelDummy.h (revision 26249) @@ -1,45 +1,44 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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_MODELDUMMY #define INCLUDED_MODELDUMMY #include "graphics/ModelAbstract.h" /** * Empty placeholder ModelAbstract implementation, to render nothing. */ class CModelDummy final : public CModelAbstract { NONCOPYABLE(CModelDummy); public: CModelDummy() = default; virtual ~CModelDummy() = default; virtual CModelAbstract* Clone() const { return new CModelDummy(); } virtual CModelDummy* ToCModelDummy() { return this; } virtual void CalcBounds() {}; - virtual void SetDirtyRec(int) {}; virtual void SetTerrainDirty(ssize_t, ssize_t, ssize_t, ssize_t) {} virtual void ValidatePosition() {}; virtual void InvalidatePosition() {}; }; #endif // INCLUDED_MODELDUMMY Index: ps/trunk/source/graphics/ParticleEmitter.h =================================================================== --- ps/trunk/source/graphics/ParticleEmitter.h (revision 26248) +++ ps/trunk/source/graphics/ParticleEmitter.h (revision 26249) @@ -1,209 +1,204 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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_PARTICLEEMITTER #define INCLUDED_PARTICLEEMITTER #include "graphics/ModelAbstract.h" #include "graphics/ParticleEmitterType.h" #include "maths/Quaternion.h" #include "renderer/VertexArray.h" #include /** * Simulation state for a single particle. */ struct SParticle { CVector3D pos; CVector3D velocity; float angle; float angleSpeed; float size; float sizeGrowthRate; SColor4ub color; float age; float maxAge; }; typedef std::shared_ptr CParticleEmitterPtr; /** * Particle emitter. * * Emitters store particle data in two forms: * * m_Particles contains the raw data used for the CPU particle simulation. * * m_VertexArray contains the data required for rendering. * Particles are rendered as billboard quads, so the vertex array contains four vertices * per particle with different UV coordinates. The billboard position computation is * performed by a vertex shader. * * The number of particles is a constant for the entire life of the emitter, * to simplify the updating and rendering. * m_Particles acts like a ring buffer, so we don't have to worry about dynamically * allocating particles. If particles have variable lifetimes, they'll exist in the * array with alpha=0 until they're overwritten by a new particle after the maximum * lifetime. * * (It's quite likely this could be made more efficient, if the overhead of any added * complexity is not high.) */ class CParticleEmitter { public: CParticleEmitter(const CParticleEmitterTypePtr& type); /** * Set the position to be used for emission of new particles. */ void SetPosition(const CVector3D& pos) { m_Pos = pos; } CVector3D GetPosition() const { return m_Pos; } /** * Set the rotation to be used for emission of new particles (note: depends on particles). */ void SetRotation(const CQuaternion& rot) { m_Rot = rot; } const CQuaternion& GetRotation() const { return m_Rot; } /** * Get the bounding box of the center points of particles at their current positions. */ const CBoundingBoxAligned& GetParticleBounds() const { return m_ParticleBounds; } /** * Push a new particle onto the ring buffer. (May overwrite an old particle.) */ void AddParticle(const SParticle& particle); /** * Update particle and vertex array data. Must be called before RenderArray. * * If frameNumber is the same as the previous call to UpdateArrayData, * then the function will do no work and return immediately. */ void UpdateArrayData(int frameNumber); /** * Make the vertex data available for subsequent binding and rendering. */ void PrepareForRendering(); /** * Bind rendering state (textures and blend modes). */ void Bind(const CShaderProgramPtr& shader); /** * Draw the vertex array. */ void RenderArray(const CShaderProgramPtr& shader); /** * Stop this emitter emitting new particles, and pass responsibility for rendering * to the CParticleManager. This should be called before dropping the last std::shared_ptr * to this object so that it will carry on rendering (until all particles have dissipated) * even when it's no longer attached to a model. * @param self the std::shared_ptr you're about to drop */ void Unattach(const CParticleEmitterPtr& self); void SetEntityVariable(const std::string& name, float value); CParticleEmitterTypePtr m_Type; /// Whether this emitter is still emitting new particles bool m_Active; CVector3D m_Pos; CQuaternion m_Rot; std::map m_EntityVariables; std::vector m_Particles; size_t m_NextParticleIdx; float m_LastUpdateTime; float m_EmissionRoundingError; private: /// Bounding box of the current particle center points CBoundingBoxAligned m_ParticleBounds; VertexIndexArray m_IndexArray; VertexArray m_VertexArray; VertexArray::Attribute m_AttributePos; VertexArray::Attribute m_AttributeAxis; VertexArray::Attribute m_AttributeUV; VertexArray::Attribute m_AttributeColor; int m_LastFrameNumber; }; /** * Particle emitter model, for attaching emitters as props on other models. */ class CModelParticleEmitter : public CModelAbstract { public: CModelParticleEmitter(const CParticleEmitterTypePtr& type); ~CModelParticleEmitter(); /// Dynamic cast virtual CModelParticleEmitter* ToCModelParticleEmitter() { return this; } virtual CModelAbstract* Clone() const; - virtual void SetDirtyRec(int dirtyflags) - { - SetDirty(dirtyflags); - } - virtual void SetTerrainDirty(ssize_t UNUSED(i0), ssize_t UNUSED(j0), ssize_t UNUSED(i1), ssize_t UNUSED(j1)) { } virtual void SetEntityVariable(const std::string& name, float value); virtual void CalcBounds(); virtual void ValidatePosition(); virtual void InvalidatePosition(); virtual void SetTransform(const CMatrix3D& transform); CParticleEmitterTypePtr m_Type; CParticleEmitterPtr m_Emitter; }; #endif // INCLUDED_PARTICLEEMITTER Index: ps/trunk/source/graphics/RenderableObject.h =================================================================== --- ps/trunk/source/graphics/RenderableObject.h (revision 26248) +++ ps/trunk/source/graphics/RenderableObject.h (revision 26249) @@ -1,163 +1,162 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2022 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 . */ /* * Base class for renderable objects */ #ifndef INCLUDED_RENDERABLEOBJECT #define INCLUDED_RENDERABLEOBJECT #include "maths/BoundingBoxAligned.h" #include "maths/Matrix3D.h" // dirty flags - used as notification to the renderer that some bit of data // need updating #define RENDERDATA_UPDATE_VERTICES (1<<1) #define RENDERDATA_UPDATE_INDICES (1<<2) -#define RENDERDATA_UPDATE_COLOR (1<<4) /////////////////////////////////////////////////////////////////////////////// // CRenderData: base class of all the renderer's renderdata classes - the // derived class stores necessary information for rendering an object of a // particular type class CRenderData { public: CRenderData() : m_UpdateFlags(0) {} virtual ~CRenderData() {} int m_UpdateFlags; }; /////////////////////////////////////////////////////////////////////////////// // CRenderableObject: base class of all renderable objects - patches, models, // sprites, etc; stores position and bound information, and a pointer to // some renderdata necessary for the renderer to actually render it class CRenderableObject { NONCOPYABLE(CRenderableObject); public: // constructor CRenderableObject() : m_RenderData(0), m_BoundsValid(false) { m_Transform.SetIdentity(); } // destructor virtual ~CRenderableObject() { delete m_RenderData; } // set object transform virtual void SetTransform(const CMatrix3D& transform) { if (m_Transform == transform) return; // store transform, calculate inverse m_Transform=transform; m_Transform.GetInverse(m_InvTransform); // normal recalculation likely required on transform change; flag it SetDirty(RENDERDATA_UPDATE_VERTICES); // need to rebuild world space bounds InvalidateBounds(); } // get object to world space transform const CMatrix3D& GetTransform() const { return m_Transform; } // get world to object space transform const CMatrix3D& GetInvTransform() const { return m_InvTransform; } // mark some part of the renderdata as dirty, and requiring // an update on next render void SetDirty(u32 dirtyflags) { if (m_RenderData) m_RenderData->m_UpdateFlags |= dirtyflags; } /** * (Re)calculates and stores any bounds or bound-dependent data for this object. At this abstraction level, this is only the world-space * bounds stored in @ref m_WorldBounds; subclasses may use this method to (re)compute additional bounds if necessary, or any data that * depends on the bounds. Whenever bound-dependent data is requested through a public interface, @ref RecalculateBoundsIfNecessary should * be called first to ensure bound correctness, which will in turn call this method if it turns out that they're outdated. * * @see m_BoundsValid * @see RecalculateBoundsIfNecessary */ virtual void CalcBounds() = 0; /// Returns the world-space axis-aligned bounds of this object. const CBoundingBoxAligned& GetWorldBounds() { RecalculateBoundsIfNecessary(); return m_WorldBounds; } /** * Marks the bounds as invalid. This will trigger @ref RecalculateBoundsIfNecessary to recompute any bound-related data the next time * any bound-related data is requested through a public interface -- at least, if you've made sure to call it before returning the * stored data. */ virtual void InvalidateBounds() { m_BoundsValid = false; } // Set the object renderdata and free previous renderdata, if any. void SetRenderData(CRenderData* renderdata) { delete m_RenderData; m_RenderData = renderdata; } /// Return object renderdata - can be null if renderer hasn't yet created the renderdata CRenderData* GetRenderData() { return m_RenderData; } protected: /// Factored out so subclasses don't need to repeat this if they want to add additional getters for bounds-related methods /// (since they'll have to make sure to recalc the bounds if necessary before they return it). void RecalculateBoundsIfNecessary() { if (!m_BoundsValid) { CalcBounds(); m_BoundsValid = true; } } protected: /// World-space bounds of this object CBoundingBoxAligned m_WorldBounds; // local->world space transform CMatrix3D m_Transform; // world->local space transform CMatrix3D m_InvTransform; // object renderdata CRenderData* m_RenderData; /** * Remembers whether any bounds need to be recalculated. Subclasses that add any data that depends on the bounds should * take care to consider the validity of the bounds and recalculate their data when necessary -- overriding @ref CalcBounds * to do so would be a good idea, since it's already set up to be called by @ref RecalculateBoundsIfNecessary whenever the * bounds are marked as invalid. The latter should then be called before returning any bounds or bounds-derived data through * a public interface (see the implementation of @ref GetWorldBounds for an example). * * @see CalcBounds * @see InvalidateBounds * @see RecalculateBoundsIfNecessary */ bool m_BoundsValid; }; #endif Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 26248) +++ ps/trunk/source/renderer/Renderer.cpp (revision 26249) @@ -1,838 +1,834 @@ /* Copyright (C) 2022 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 "Renderer.h" #include "graphics/Canvas2D.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/ModelDef.h" #include "graphics/TerrainTextureManager.h" #include "i18n/L10n.h" #include "lib/allocators/shared_ptr.h" #include "lib/ogl.h" #include "lib/tex/tex.h" #include "gui/GUIManager.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "ps/Globals.h" #include "ps/Loader.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/FontManager.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "renderer/backend/gl/Device.h" #include "renderer/DebugRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderingOptions.h" #include "renderer/RenderModifiers.h" #include "renderer/SceneRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "tools/atlas/GameInterface/GameLoop.h" #include "tools/atlas/GameInterface/View.h" #include namespace { size_t g_NextScreenShotNumber = 0; /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } } // anonymous namespace /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ class CRenderer::Internals { NONCOPYABLE(Internals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Texture manager CTextureManager textureManager; /// Time manager CTimeManager timeManager; /// Postprocessing effect manager CPostprocManager postprocManager; CSceneRenderer sceneRenderer; CDebugRenderer debugRenderer; CFontManager fontManager; std::unique_ptr deviceCommandContext; Internals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } }; CRenderer::CRenderer() { TIMER(L"InitRenderer"); m = std::make_unique(); g_ProfileViewer.AddRootTable(&m->profileTable); m_Width = 0; m_Height = 0; m_Stats.Reset(); // Create terrain related stuff. new CTerrainTextureManager; Open(g_xres, g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. GetSceneRenderer().SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X = 0; vp.m_Y = 0; vp.m_Width = g_xres; vp.m_Height = g_yres; SetViewport(vp); ModelDefActivateFastImpl(); ColorActivateFastImpl(); ModelRenderer::Init(); } CRenderer::~CRenderer() { // We no longer UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). m.reset(); } void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; m_Caps.m_PrettyWater = false; // now start querying extensions if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } // GLSL shaders are in core since GL2.0. if (ogl_HaveVersion(2, 0)) m_Caps.m_VertexShader = m_Caps.m_FragmentShader = true; #if CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif #if CONFIG2_GLES m_Caps.m_PrettyWater = true; #else if (m_Caps.m_VertexShader && m_Caps.m_FragmentShader) m_Caps.m_PrettyWater = true; #endif } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->sceneRenderer.ReloadShaders(); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // Dimensions m_Width = width; m_Height = height; // Validate the currently selected render path SetRenderPath(g_RenderingOptions.GetRenderPath()); m->deviceCommandContext = Renderer::Backend::GL::CDeviceCommandContext::Create(); if (g_RenderingOptions.GetPostProc()) m->postprocManager.Initialize(); m->sceneRenderer.Initialize(); return true; } void CRenderer::Resize(int width, int height) { m_Width = width; m_Height = height; m->postprocManager.Resize(); m->sceneRenderer.Resize(width, height); } void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. return; } // Renderer has been opened, so validate the selected renderpath if (rp == RenderPath::DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB)) rp = RenderPath::SHADER; else rp = RenderPath::FIXED; } if (rp == RenderPath::SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB))) { LOGWARNING("Falling back to fixed function\n"); rp = RenderPath::FIXED; } } // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere. g_RenderingOptions.m_RenderPath = rp; MakeShadersDirty(); - - // We might need to regenerate some render data after changing path - if (g_Game) - g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } bool CRenderer::ShouldRender() const { return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen()); } void CRenderer::RenderFrame(const bool needsPresent) { // Do not render if not focused while in fullscreen or minimised, // as that triggers a difficult-to-reproduce crash on some graphic cards. if (!ShouldRender()) return; if (m_ShouldPreloadResourcesBeforeNextFrame) { m_ShouldPreloadResourcesBeforeNextFrame = false; // We don't meed to render logger for the preload. RenderFrameImpl(true, false); } if (m_ScreenShotType == ScreenShotType::BIG) { RenderBigScreenShot(needsPresent); } else { if (m_ScreenShotType == ScreenShotType::DEFAULT) RenderScreenShot(); else RenderFrameImpl(true, true); m->deviceCommandContext->Flush(); if (needsPresent) g_VideoMode.GetBackendDevice()->Present(); } } void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger) { PROFILE3("render"); g_Profiler2.RecordGPUFrameStart(); ogl_WarnIfError(); g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get()); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); if (g_Game) m->sceneRenderer.SetSimulation(g_Game->GetSimulation2()); // start new frame BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); ogl_WarnIfError(); } m->sceneRenderer.RenderTextOverlays(); // If we're in Atlas game view, render special tools if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawCinemaPathTool(); ogl_WarnIfError(); } if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->GetCinema()->Render(); ogl_WarnIfError(); } glDisable(GL_DEPTH_TEST); if (renderGUI) { OGL_SCOPED_DEBUG_GROUP("Draw GUI"); // All GUI elements are drawn in Z order to render semi-transparent // objects correctly. g_GUI->Draw(); ogl_WarnIfError(); } // If we're in Atlas game view, render special overlays (e.g. editor bandbox). if (g_AtlasGameLoop && g_AtlasGameLoop->view) { CCanvas2D canvas; g_AtlasGameLoop->view->DrawOverlays(canvas); ogl_WarnIfError(); } g_Console->Render(); ogl_WarnIfError(); if (renderLogger) { g_Logger->Render(); ogl_WarnIfError(); } // Profile information g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); glEnable(GL_DEPTH_TEST); EndFrame(); const Stats& stats = GetStats(); PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls); PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris); PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris); PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris); PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris); PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats); PROFILE2_ATTR("particles: %zu", stats.m_Particles); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameEnd(); ogl_WarnIfError(); } void CRenderer::RenderScreenShot() { m_ScreenShotType = ScreenShotType::NONE; // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath filenameFormat(L"screenshots/screenshot%04d.png"); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename); const size_t w = (size_t)g_xres, h = (size_t)g_yres; const size_t bpp = 24; GLenum fmt = GL_RGB; int flags = TEX_BOTTOM_UP; // Hide log messages and re-render RenderFrameImpl(true, false); const size_t img_size = w * h * bpp / 8; const size_t hdr_size = tex_hdr_size(filename); std::shared_ptr buf; AllocateAligned(buf, hdr_size + img_size, maxSectorSize); GLvoid* img = buf.get() + hdr_size; Tex t; if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0) return; glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img); if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); debug_printf( CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), realPath.string8().c_str()); } else LOGERROR("Error writing screenshot to '%s'", filename.string8()); } void CRenderer::RenderBigScreenShot(const bool needsPresent) { m_ScreenShotType = ScreenShotType::NONE; // If the game hasn't started yet then use WriteScreenshot to generate the image. if (!g_Game) return RenderScreenShot(); int tiles = 4, tileWidth = 256, tileHeight = 256; CFG_GET_VAL("screenshot.tiles", tiles); CFG_GET_VAL("screenshot.tilewidth", tileWidth); CFG_GET_VAL("screenshot.tileheight", tileHeight); if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0) { LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight); return; } // get next available numbered filename // note: %04d -> always 4 digits, so sorting by filename works correctly. const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp"); VfsPath filename; vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename); // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and // hope the screen is actually large enough for that. ENSURE(g_xres >= tileWidth && g_yres >= tileHeight); const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles; const int bpp = 24; // we want writing BMP to be as fast as possible, // so read data from OpenGL in BMP format to obviate conversion. #if CONFIG2_GLES // GLES doesn't support BGR const GLenum fmt = GL_RGB; const int flags = TEX_BOTTOM_UP; #else const GLenum fmt = GL_BGR; const int flags = TEX_BOTTOM_UP | TEX_BGR; #endif const size_t imageSize = imageWidth * imageHeight * bpp / 8; const size_t tileSize = tileWidth * tileHeight * bpp / 8; const size_t headerSize = tex_hdr_size(filename); void* tileData = malloc(tileSize); if (!tileData) { WARN_IF_ERR(ERR::NO_MEM); return; } std::shared_ptr imageBuffer; AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize); Tex t; GLvoid* img = imageBuffer.get() + headerSize; if (t.wrap(imageWidth, imageHeight, bpp, flags, imageBuffer, headerSize) < 0) { free(tileData); return; } ogl_WarnIfError(); CCamera oldCamera = *g_Game->GetView()->GetCamera(); // Resize various things so that the sizes and aspect ratios are correct { g_Renderer.Resize(tileWidth, tileHeight); SViewPort vp = { 0, 0, tileWidth, tileHeight }; g_Game->GetView()->SetViewport(vp); } // Render each tile CMatrix3D projection; projection.SetIdentity(); const float aspectRatio = 1.0f * tileWidth / tileHeight; for (int tileY = 0; tileY < tiles; ++tileY) { for (int tileX = 0; tileX < tiles; ++tileX) { // Adjust the camera to render the appropriate region if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY); } g_Game->GetView()->GetCamera()->SetProjection(projection); RenderFrameImpl(false, false); // Copy the tile pixels into the main image glReadPixels(0, 0, tileWidth, tileHeight, fmt, GL_UNSIGNED_BYTE, tileData); for (int y = 0; y < tileHeight; ++y) { void* dest = static_cast(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8; void* src = static_cast(tileData) + y * tileWidth * bpp / 8; memcpy(dest, src, tileWidth * bpp / 8); } m->deviceCommandContext->Flush(); if (needsPresent) g_VideoMode.GetBackendDevice()->Present(); } } // Restore the viewport settings { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->SetViewport(vp); g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera); } if (tex_write(&t, filename) == INFO::OK) { OsPath realPath; g_VFS->GetRealPath(filename, realPath); LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); debug_printf( CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), realPath.string8().c_str()); } else LOGERROR("Error writing screenshot to '%s'", filename.string8()); free(tileData); } void CRenderer::BeginFrame() { PROFILE("begin frame"); // Zero out all the per-frame stats. m_Stats.Reset(); if (m->ShadersDirty) ReloadShaders(); m->sceneRenderer.BeginFrame(); } void CRenderer::EndFrame() { PROFILE3("end frame"); m->sceneRenderer.EndFrame(); BindTexture(0, 0); } void CRenderer::SetViewport(const SViewPort &vp) { m_Viewport = vp; glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::BindTexture(int unit, GLuint tex) { glActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D, tex); } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; m->sceneRenderer.MakeShadersDirty(); } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CSceneRenderer& CRenderer::GetSceneRenderer() { return m->sceneRenderer; } CDebugRenderer& CRenderer::GetDebugRenderer() { return m->debugRenderer; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; } void CRenderer::PreloadResourcesBeforeNextFrame() { m_ShouldPreloadResourcesBeforeNextFrame = true; } void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType) { m_ScreenShotType = screenShotType; } Renderer::Backend::GL::CDeviceCommandContext* CRenderer::GetDeviceCommandContext() { return m->deviceCommandContext.get(); } Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26248) +++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26249) @@ -1,558 +1,557 @@ /* Copyright (C) 2022 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 "ActorViewer.h" #include "View.h" #include "graphics/ColladaManager.h" #include "graphics/LOSTexture.h" #include "graphics/MiniMapTexture.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ObjectManager.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/Overlay.h" #include "maths/MathUtil.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "ps/GameSetup/Config.h" #include "ps/ProfileViewer.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/Scene.h" #include "renderer/SceneRenderer.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptContext.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpAttack.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/helpers/Render.h" struct ActorViewerImpl : public Scene { NONCOPYABLE(ActorViewerImpl); public: ActorViewerImpl() : Entity(INVALID_ENTITY), Terrain(), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), UnitManager(), Simulation2(&UnitManager, g_ScriptContext, &Terrain), ObjectManager(MeshManager, SkeletonAnimManager, Simulation2), LOSTexture(Simulation2), TerritoryTexture(Simulation2), MiniMapTexture(Simulation2) { UnitManager.SetObjectManager(ObjectManager); } entity_id_t Entity; CStrW CurrentUnitID; CStr CurrentUnitAnim; float CurrentSpeed; bool WalkEnabled; bool GroundEnabled; bool WaterEnabled; bool ShadowsEnabled; // Whether shadows, sky and water are enabled outside of the actor viewer. bool OldShadows; bool OldSky; bool OldWater; bool SelectionBoxEnabled; bool AxesMarkerEnabled; int PropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes CTerrain Terrain; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CUnitManager UnitManager; CSimulation2 Simulation2; CObjectManager ObjectManager; // Keep this after Simulation2 - it needs it for initialisation. CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; CMiniMapTexture MiniMapTexture; SOverlayLine SelectionBoxOverlay; SOverlayLine AxesMarkerOverlays[3]; std::vector Props; std::vector PropPointOverlays; // Simplistic implementation of the Scene interface virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { if (GroundEnabled) { for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj) for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi) c->Submit(Terrain.GetPatch(pi, pj)); } CmpPtr cmpVisual(Simulation2, Entity); if (cmpVisual) { // add selection box outlines manually if (SelectionBoxEnabled) { SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue SelectionBoxOverlay.m_Thickness = 0.1f; SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay); c->Submit(&SelectionBoxOverlay); } // add origin axis thingy if (AxesMarkerEnabled) { CMatrix3D worldSpaceAxes; // offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation // sets the identity 3x3 transformation matrix, which are the world axes) worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0)); SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]); c->Submit(&AxesMarkerOverlays[0]); c->Submit(&AxesMarkerOverlays[1]); c->Submit(&AxesMarkerOverlays[2]); } // add prop point overlays if (PropPointsMode > 0 && Props.size() > 0) { PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited for (size_t i = 0; i < Props.size(); ++i) { CModel::Prop& prop = Props[i]; if (prop.m_Model) // should always be the case { // prop point positions are automatically updated during animations etc. by CModel::ValidatePosition const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform(); SOverlayLine pointGimbal; pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f); SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal); PropPointOverlays.push_back(pointGimbal); if (PropPointsMode > 1) { // scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers CMatrix3D displayCoordSystem = propCoordSystem; displayCoordSystem.Scale(0.5f, 0.5f, 0.5f); // revert translation scaling displayCoordSystem._14 = propCoordSystem._14; displayCoordSystem._24 = propCoordSystem._24; displayCoordSystem._34 = propCoordSystem._34; // construct an XYZ axes marker for the prop's coordinate system SOverlayLine xAxis, yAxis, zAxis; SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis); PropPointOverlays.push_back(xAxis); PropPointOverlays.push_back(yAxis); PropPointOverlays.push_back(zAxis); } } } for (size_t i = 0; i < PropPointOverlays.size(); ++i) { c->Submit(&PropPointOverlays[i]); } } } // send a RenderSubmit message so the components can submit their visuals to the renderer Simulation2.RenderSubmit(*c, frustum, false); } virtual CLOSTexture& GetLOSTexture() { return LOSTexture; } virtual CTerritoryTexture& GetTerritoryTexture() { return TerritoryTexture; } virtual CMiniMapTexture& GetMiniMapTexture() { return MiniMapTexture; } /** * Recursively fetches the props of the currently displayed entity model and its submodels, and stores them for rendering. */ void UpdatePropList(); void UpdatePropListRecursive(CModelAbstract* model); }; void ActorViewerImpl::UpdatePropList() { Props.clear(); CmpPtr cmpVisual(Simulation2, Entity); if (cmpVisual) { CUnit* unit = cmpVisual->GetUnit(); if (unit) { CModelAbstract& modelAbstract = unit->GetModel(); UpdatePropListRecursive(&modelAbstract); } } } void ActorViewerImpl::UpdatePropListRecursive(CModelAbstract* modelAbstract) { ENSURE(modelAbstract); CModel* model = modelAbstract->ToCModel(); if (model) { std::vector& modelProps = model->GetProps(); for (CModel::Prop& modelProp : modelProps) { Props.push_back(modelProp); if (modelProp.m_Model) UpdatePropListRecursive(modelProp.m_Model); } } } ActorViewer::ActorViewer() : m(*new ActorViewerImpl()) { m.WalkEnabled = false; m.GroundEnabled = true; m.WaterEnabled = false; m.ShadowsEnabled = g_RenderingOptions.GetShadows(); m.SelectionBoxEnabled = false; m.AxesMarkerEnabled = false; m.PropPointsMode = 0; // Create a tiny empty piece of terrain, just so we can put shadows // on it without having to think too hard m.Terrain.Initialize(2, NULL); CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness"); if (tex) { for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi) { for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj) { CPatch* patch = m.Terrain.GetPatch(pi, pj); for (ssize_t i = 0; i < PATCH_SIZE; ++i) { for (ssize_t j = 0; j < PATCH_SIZE; ++j) { CMiniPatch& mp = patch->m_MiniPatches[i][j]; mp.Tex = tex; mp.Priority = 0; } } } } } else { debug_warn(L"Failed to load whiteness texture"); } // Prepare the simulation m.Simulation2.LoadDefaultScripts(); m.Simulation2.ResetState(); // Set player data m.Simulation2.SetMapSettings(m.Simulation2.GetPlayerDefaults()); m.Simulation2.LoadPlayerSettings(true); // Tell the simulation we've already loaded the terrain CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(false); // Remove FOW since we're in Atlas CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(-1, true); m.Simulation2.InitGame(); } ActorViewer::~ActorViewer() { delete &m; } CSimulation2* ActorViewer::GetSimulation2() { return &m.Simulation2; } entity_id_t ActorViewer::GetEntity() { return m.Entity; } void ActorViewer::UnloadObjects() { m.ObjectManager.UnloadObjects(); } void ActorViewer::SetActor(const CStrW& name, const CStr& animation, player_id_t playerID) { bool needsAnimReload = false; CStrW id = name; // Recreate the entity, if we don't have one or if the new one is different if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID) { // Delete the old entity (if any) if (m.Entity != INVALID_ENTITY) { m.Simulation2.DestroyEntity(m.Entity); m.Simulation2.FlushDestroyedEntities(); m.Entity = INVALID_ENTITY; } // Clear particles associated with deleted entity g_Renderer.GetSceneRenderer().GetParticleManager().ClearUnattachedEmitters(); // If there's no actor to display, return with nothing loaded if (id.empty()) return; m.Entity = m.Simulation2.AddEntity(L"preview|" + id); if (m.Entity == INVALID_ENTITY) return; CmpPtr cmpPosition(m.Simulation2, m.Entity); if (cmpPosition) { ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; cmpPosition->JumpTo(entity_pos_t::FromInt(c), entity_pos_t::FromInt(c)); cmpPosition->SetYRotation(entity_angle_t::Pi()); } CmpPtr cmpOwnership(m.Simulation2, m.Entity); if (cmpOwnership) cmpOwnership->SetOwner(playerID); needsAnimReload = true; } if (animation != m.CurrentUnitAnim) needsAnimReload = true; if (needsAnimReload) { // Emulate the typical simulation animation behaviour. CStr anim = animation.LowerCase(); float speed = 1.0f; // Speed will be ignored if we have a repeat time. float repeatTime = 0.0f; m.CurrentSpeed = 0.0f; if (anim == "walk") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (cmpUnitMotion) speed = cmpUnitMotion->GetWalkSpeed().ToFloat(); else speed = 7.f; // Typical unit walk speed. m.CurrentSpeed = speed; } else if (anim == "run") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (cmpUnitMotion) speed = cmpUnitMotion->GetWalkSpeed().ToFloat() * cmpUnitMotion->GetRunMultiplier().ToFloat(); else speed = 12.f; // Typical unit run speed. m.CurrentSpeed = speed; } else if (anim.Find("attack_") == 0) { CmpPtr cmpAttack(m.Simulation2, m.Entity); if (cmpAttack) for (const CStr& type : cmpAttack->GetAttackTypes()) if (anim == "attack_" + type.LowerCase()) { repeatTime = GetRepeatTimeByAttackType(type); break; } } CmpPtr cmpVisual(m.Simulation2, m.Entity); if (cmpVisual) { // TODO: SetEntitySelection(anim) cmpVisual->SelectAnimation(anim, false, fixed::FromFloat(speed)); if (repeatTime > 0.0f) cmpVisual->SetAnimationSyncRepeat(fixed::FromFloat(repeatTime)); } // update prop list for new entity/animation (relies on needsAnimReload also getting called for entire entity changes) m.UpdatePropList(); } m.CurrentUnitID = id; m.CurrentUnitAnim = animation; } void ActorViewer::SetEnabled(bool enabled) { if (enabled) { // Set shadows, sky and water. m.OldShadows = g_RenderingOptions.GetShadows(); SetShadowsEnabled(m.ShadowsEnabled); m.OldSky = g_Renderer.GetSceneRenderer().GetSkyManager().GetRenderSky(); g_Renderer.GetSceneRenderer().GetSkyManager().SetRenderSky(false); m.OldWater = g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater; g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.WaterEnabled; } else { // Restore the old renderer state SetShadowsEnabled(m.OldShadows); g_Renderer.GetSceneRenderer().GetSkyManager().SetRenderSky(m.OldSky); g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.OldWater; } } void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; } void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; } void ActorViewer::SetWaterEnabled(bool enabled) { m.WaterEnabled = enabled; // Adjust water level entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f); CmpPtr cmpWaterManager(m.Simulation2, SYSTEM_ENTITY); if (cmpWaterManager) cmpWaterManager->SetWaterLevel(waterLevel); } void ActorViewer::SetShadowsEnabled(bool enabled) { g_RenderingOptions.SetShadows(enabled); m.ShadowsEnabled = enabled; } void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; } void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; } void ActorViewer::SetPropPointsMode(int mode) { m.PropPointsMode = mode; } void ActorViewer::SetStatsEnabled(bool enabled) { if (enabled) g_ProfileViewer.ShowTable("renderer"); else g_ProfileViewer.ShowTable(""); } float ActorViewer::GetRepeatTimeByAttackType(const std::string& type) const { CmpPtr cmpAttack(m.Simulation2, m.Entity); if (cmpAttack) return cmpAttack->GetRepeatTime(type); return 0.0f; } void ActorViewer::Render() { // TODO: ActorViewer should reuse CRenderer code and not duplicate it. - m.Terrain.MakeDirty(RENDERDATA_UPDATE_COLOR); // Set simulation context for rendering purposes g_Renderer.GetSceneRenderer().SetSimulation(&m.Simulation2); // Find the centre of the interesting region, in the middle of the patch // and half way up the model (assuming there is one) CVector3D centre; CmpPtr cmpVisual(m.Simulation2, m.Entity); if (cmpVisual) cmpVisual->GetBounds().GetCenter(centre); else centre.Y = 0.f; centre.X = centre.Z = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; CCamera camera = AtlasView::GetView_Actor()->GetCamera(); camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z); camera.UpdateFrustum(); g_Renderer.GetSceneRenderer().SetSceneCamera(camera, camera); g_Renderer.BeginFrame(); g_Renderer.GetSceneRenderer().RenderScene(g_Renderer.GetDeviceCommandContext(), m); glDisable(GL_DEPTH_TEST); g_Logger->Render(); g_ProfileViewer.RenderProfile(); glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); ogl_WarnIfError(); } void ActorViewer::Update(float simFrameLength, float realFrameLength) { m.Simulation2.Update((int)(simFrameLength*1000)); m.Simulation2.Interpolate(simFrameLength, 0, realFrameLength); if (m.WalkEnabled && m.CurrentSpeed) { CmpPtr cmpPosition(m.Simulation2, m.Entity); if (cmpPosition) { // Move the model by speed*simFrameLength forwards float z = cmpPosition->GetPosition().Z.ToFloat(); z -= m.CurrentSpeed*simFrameLength; // Wrap at the edges, so it doesn't run off into the horizon ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; if (z < c - TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f) z = c + TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f; cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z)); } } }