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 : }
|