LCOV - code coverage report
Current view: top level - source/simulation2/helpers - Selection.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 123 0.8 %
Date: 2023-01-19 00:18:29 Functions: 2 9 22.2 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "Selection.h"
      21             : 
      22             : #include "graphics/Camera.h"
      23             : #include "ps/CLogger.h"
      24             : #include "ps/Profiler2.h"
      25             : #include "simulation2/components/ICmpIdentity.h"
      26             : #include "simulation2/components/ICmpOwnership.h"
      27             : #include "simulation2/components/ICmpRangeManager.h"
      28             : #include "simulation2/components/ICmpTemplateManager.h"
      29             : #include "simulation2/components/ICmpSelectable.h"
      30             : #include "simulation2/components/ICmpVisual.h"
      31             : #include "simulation2/components/ICmpUnitRenderer.h"
      32             : #include "simulation2/system/ComponentManager.h"
      33             : 
      34             : #include <string_view>
      35             : 
      36           0 : entity_id_t EntitySelection::PickEntityAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables)
      37             : {
      38           0 :     PROFILE2("PickEntityAtPoint");
      39           0 :     CVector3D origin, dir;
      40           0 :     camera.BuildCameraRay(screenX, screenY, origin, dir);
      41             : 
      42           0 :     CmpPtr<ICmpUnitRenderer> cmpUnitRenderer(simulation.GetSimContext().GetSystemEntity());
      43           0 :     ENSURE(cmpUnitRenderer);
      44             : 
      45           0 :     std::vector<std::pair<CEntityHandle, CVector3D> > entities;
      46           0 :     cmpUnitRenderer->PickAllEntitiesAtPoint(entities, origin, dir, allowEditorSelectables);
      47           0 :     if (entities.empty())
      48           0 :         return INVALID_ENTITY;
      49             : 
      50             :     // Filter for relevent entities in the list of candidates (all entities below the mouse)
      51           0 :     std::vector<std::pair<float, CEntityHandle> > hits; // (dist^2, entity) pairs
      52           0 :     for (size_t i = 0; i < entities.size(); ++i)
      53             :     {
      54             :         // Find the perpendicular distance from the object's centre to the picker ray
      55             :         float dist2;
      56           0 :         const CVector3D center = entities[i].second;
      57           0 :         CVector3D closest = origin + dir * (center - origin).Dot(dir);
      58           0 :         dist2 = (closest - center).LengthSquared();
      59           0 :         hits.emplace_back(dist2, entities[i].first);
      60             :     }
      61             : 
      62             :     // Sort hits by distance
      63           0 :     std::sort(hits.begin(), hits.end(),
      64           0 :         [](const std::pair<float, CEntityHandle>& a, const std::pair<float, CEntityHandle>& b) {
      65           0 :             return a.first < b.first;
      66           0 :         });
      67             : 
      68           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
      69           0 :     ENSURE(cmpRangeManager);
      70             : 
      71           0 :     for (size_t i = 0; i < hits.size(); ++i)
      72             :     {
      73           0 :         const CEntityHandle& handle = hits[i].second;
      74             : 
      75           0 :         CmpPtr<ICmpSelectable> cmpSelectable(handle);
      76           0 :         if (!cmpSelectable)
      77           0 :             continue;
      78             : 
      79             :         // Check if this entity is only selectable in Atlas
      80           0 :         if (!allowEditorSelectables && cmpSelectable->IsEditorOnly())
      81           0 :             continue;
      82             : 
      83             :         // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
      84           0 :         if (cmpRangeManager->GetLosVisibility(handle, player) == LosVisibility::HIDDEN)
      85           0 :             continue;
      86             : 
      87           0 :         return handle.GetId();
      88             :     }
      89           0 :     return INVALID_ENTITY;
      90             : }
      91             : 
      92             : /**
      93             :  * Returns true if the given entity is visible in the given screen area.
      94             :  * If the entity is a decorative, the function will only return true if allowEditorSelectables.
      95             :  */
      96           0 : bool CheckEntityInRect(CEntityHandle handle, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables)
      97             : {
      98             :     // Check if this entity is only selectable in Atlas
      99           0 :     CmpPtr<ICmpSelectable> cmpSelectable(handle);
     100           0 :     if (!cmpSelectable || (!allowEditorSelectables && cmpSelectable->IsEditorOnly()))
     101           0 :         return false;
     102             : 
     103             :     // Find the current interpolated model position.
     104             :     // (We just use the centre position and not the whole bounding box, because maybe
     105             :     // that's better for users trying to select objects in busy areas)
     106             : 
     107           0 :     CmpPtr<ICmpVisual> cmpVisual(handle);
     108           0 :     if (!cmpVisual)
     109           0 :         return false;
     110             : 
     111           0 :     CVector3D position = cmpVisual->GetPosition();
     112             : 
     113             :     // Reject if it's not on-screen (e.g. it's behind the camera)
     114           0 :     if (!camera.GetFrustum().IsPointVisible(position))
     115           0 :         return false;
     116             : 
     117             :     // Compare screen-space coordinates
     118             :     float x, y;
     119           0 :     camera.GetScreenCoordinates(position, x, y);
     120           0 :     int ix = (int)x;
     121           0 :     int iy = (int)y;
     122           0 :     return sx0 <= ix && ix <= sx1 && sy0 <= iy && iy <= sy1;
     123             : }
     124             : 
     125             : /**
     126             :  * Returns true if the given entity is visible to the given player and visible in the given screen area.
     127             :  */
     128           0 : static bool CheckEntityVisibleAndInRect(CEntityHandle handle, CmpPtr<ICmpRangeManager> cmpRangeManager, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t player, bool allowEditorSelectables)
     129             : {
     130             :     // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
     131           0 :     if (cmpRangeManager->GetLosVisibility(handle, player) == LosVisibility::HIDDEN)
     132           0 :         return false;
     133             : 
     134           0 :     return CheckEntityInRect(handle, camera, sx0, sy0, sx1, sy1, allowEditorSelectables);
     135             : }
     136             : 
     137           0 : std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t owner, bool allowEditorSelectables)
     138             : {
     139           0 :     PROFILE2("PickEntitiesInRect");
     140             :     // Make sure sx0 <= sx1, and sy0 <= sy1
     141           0 :     if (sx0 > sx1)
     142           0 :         std::swap(sx0, sx1);
     143           0 :     if (sy0 > sy1)
     144           0 :         std::swap(sy0, sy1);
     145             : 
     146           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
     147           0 :     ENSURE(cmpRangeManager);
     148             : 
     149           0 :     std::vector<entity_id_t> hitEnts;
     150             : 
     151           0 :     if (owner != INVALID_PLAYER)
     152             :     {
     153           0 :         CComponentManager& componentManager = simulation.GetSimContext().GetComponentManager();
     154           0 :         std::vector<entity_id_t> ents = cmpRangeManager->GetEntitiesByPlayer(owner);
     155           0 :         for (std::vector<entity_id_t>::iterator it = ents.begin(); it != ents.end(); ++it)
     156             :         {
     157           0 :             if (CheckEntityVisibleAndInRect(componentManager.LookupEntityHandle(*it), cmpRangeManager, camera, sx0, sy0, sx1, sy1, owner, allowEditorSelectables))
     158           0 :                 hitEnts.push_back(*it);
     159             :         }
     160             :     }
     161             :     else // owner == INVALID_PLAYER; Used when selecting units in Atlas or other mods that allow all kinds of selectables to be selected.
     162             :     {
     163           0 :         const CSimulation2::InterfaceListUnordered& selectableEnts = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable);
     164           0 :         for (CSimulation2::InterfaceListUnordered::const_iterator it = selectableEnts.begin(); it != selectableEnts.end(); ++it)
     165             :         {
     166           0 :             if (CheckEntityVisibleAndInRect(it->second->GetEntityHandle(), cmpRangeManager, camera, sx0, sy0, sx1, sy1, owner, allowEditorSelectables))
     167           0 :                 hitEnts.push_back(it->first);
     168             :         }
     169             :     }
     170             : 
     171           0 :     return hitEnts;
     172             : }
     173             : 
     174           0 : std::vector<entity_id_t> EntitySelection::PickNonGaiaEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables)
     175             : {
     176           0 :     PROFILE2("PickNonGaiaEntitiesInRect");
     177             : 
     178             :     // Make sure sx0 <= sx1, and sy0 <= sy1
     179           0 :     if (sx0 > sx1)
     180           0 :         std::swap(sx0, sx1);
     181           0 :     if (sy0 > sy1)
     182           0 :         std::swap(sy0, sy1);
     183             : 
     184           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
     185           0 :     ENSURE(cmpRangeManager);
     186             : 
     187           0 :     std::vector<entity_id_t> hitEnts;
     188             : 
     189           0 :     CComponentManager& componentManager = simulation.GetSimContext().GetComponentManager();
     190           0 :     for (entity_id_t ent : cmpRangeManager->GetNonGaiaEntities())
     191           0 :         if (CheckEntityInRect(componentManager.LookupEntityHandle(ent), camera, sx0, sy0, sx1, sy1, allowEditorSelectables))
     192           0 :             hitEnts.push_back(ent);
     193             : 
     194           0 :     return hitEnts;
     195             : }
     196             : 
     197           0 : std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera,
     198             :     const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank,
     199             :     bool allowEditorSelectables, bool allowFoundations)
     200             : {
     201           0 :     PROFILE2("PickSimilarEntities");
     202           0 :     CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
     203           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
     204             : 
     205           0 :     std::vector<entity_id_t> hitEnts;
     206             : 
     207           0 :     const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable);
     208           0 :     for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it)
     209             :     {
     210           0 :         entity_id_t ent = it->first;
     211           0 :         CEntityHandle handle = it->second->GetEntityHandle();
     212             : 
     213             :         // Check if this entity is only selectable in Atlas
     214           0 :         if (static_cast<ICmpSelectable*>(it->second)->IsEditorOnly() && !allowEditorSelectables)
     215           0 :             continue;
     216             : 
     217           0 :         if (matchRank)
     218             :         {
     219             :             // Exact template name matching, optionally also allowing foundations
     220           0 :             const std::string curTemplateName = cmpTemplateManager->GetCurrentTemplateName(ent);
     221           0 :             bool matches = (curTemplateName == templateName ||
     222           0 :                 (allowFoundations &&
     223           0 :                 std::string_view{curTemplateName}.substr(0, 11) == "foundation|" &&
     224           0 :                 std::string_view{curTemplateName}.substr(11) == templateName));
     225           0 :             if (!matches)
     226           0 :                 continue;
     227             :         }
     228             : 
     229             :         // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
     230             :         // In this case, the checking is done to avoid selecting garrisoned units
     231           0 :         if (cmpRangeManager->GetLosVisibility(handle, owner) == LosVisibility::HIDDEN)
     232           0 :             continue;
     233             : 
     234             :         // Ignore entities not owned by 'owner'
     235           0 :         CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), ent);
     236           0 :         if (owner != INVALID_PLAYER && (!cmpOwnership || cmpOwnership->GetOwner() != owner))
     237           0 :             continue;
     238             : 
     239             :         // Ignore off screen entities
     240           0 :         if (!includeOffScreen)
     241             :         {
     242             :             // Find the current interpolated model position.
     243           0 :             CmpPtr<ICmpVisual> cmpVisual(simulation.GetSimContext(), ent);
     244           0 :             if (!cmpVisual)
     245           0 :                 continue;
     246           0 :             CVector3D position = cmpVisual->GetPosition();
     247             : 
     248             :             // Reject if it's not on-screen (e.g. it's behind the camera)
     249           0 :             if (!camera.GetFrustum().IsPointVisible(position))
     250           0 :                 continue;
     251             :         }
     252             : 
     253           0 :         if (!matchRank)
     254             :         {
     255             :             // Match by selection group name
     256             :             // (This is relatively expensive since it involves script calls, so do it after all other tests)
     257           0 :             CmpPtr<ICmpIdentity> cmpIdentity(simulation.GetSimContext(), ent);
     258           0 :             if (!cmpIdentity || cmpIdentity->GetSelectionGroupName() != templateName)
     259           0 :                 continue;
     260             :         }
     261             : 
     262           0 :         hitEnts.push_back(ent);
     263             :     }
     264             : 
     265           0 :     return hitEnts;
     266           3 : }

Generated by: LCOV version 1.13