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 "simulation2/system/Component.h"
21 : #include "ICmpUnitRenderer.h"
22 :
23 : #include "simulation2/MessageTypes.h"
24 :
25 : #include "ICmpPosition.h"
26 : #include "ICmpRangeManager.h"
27 : #include "ICmpSelectable.h"
28 : #include "ICmpVisibility.h"
29 : #include "ICmpVisual.h"
30 :
31 : #include "graphics/ModelAbstract.h"
32 : #include "graphics/ObjectEntry.h"
33 : #include "graphics/Overlay.h"
34 : #include "graphics/Unit.h"
35 : #include "maths/BoundingSphere.h"
36 : #include "maths/Frustum.h"
37 : #include "maths/Matrix3D.h"
38 : #include "ps/GameSetup/Config.h"
39 : #include "ps/Profile.h"
40 : #include "renderer/RenderingOptions.h"
41 : #include "renderer/Scene.h"
42 :
43 : #include "tools/atlas/GameInterface/GameLoop.h"
44 :
45 : /**
46 : * Efficiently(ish) renders all the units in the world.
47 : *
48 : * The class maintains a list of all units that currently exist, and the data
49 : * needed for frustum-culling them. To minimise the amount of work done per
50 : * frame (despite a unit's interpolated position changing every frame), the
51 : * culling data is only updated once per turn: we store the position at the
52 : * start of the turn, and the position at the end of the turn, and assume the
53 : * unit might be anywhere between those two points (linearly).
54 : *
55 : * (Note this is a slightly invalid assumption: units don't always move linearly,
56 : * since their interpolated position depends on terrain and water. But over a
57 : * single turn it's probably going to be a good enough approximation, and will
58 : * only break for units that both start and end the turn off-screen.)
59 : *
60 : * We want to ignore rotation entirely, since it's a complex function of
61 : * interpolated position and terrain. So we store a bounding sphere, which
62 : * is rotation-independent, instead of a bounding box.
63 : */
64 9 : class CCmpUnitRenderer final : public ICmpUnitRenderer
65 : {
66 : public:
67 0 : struct SUnit
68 : {
69 : CEntityHandle entity;
70 :
71 : CUnit* actor;
72 :
73 : int flags;
74 :
75 : /**
76 : * m_FrameNumber from when the model's transform was last updated.
77 : * This is used to avoid recomputing it multiple times per frame
78 : * if a model is visible in multiple cull groups.
79 : */
80 : int lastTransformFrame;
81 :
82 : /**
83 : * Worst-case bounding shape, relative to position. Needs to account
84 : * for all possible animations, orientations, etc.
85 : */
86 : CBoundingSphere boundsApprox;
87 :
88 : /**
89 : * Cached LOS visibility status.
90 : */
91 : LosVisibility visibility;
92 : bool visibilityDirty;
93 :
94 : /**
95 : * Whether the unit has a valid position. If false, pos0 and pos1
96 : * are meaningless.
97 : */
98 : bool inWorld;
99 :
100 : /**
101 : * World-space positions to interpolate between.
102 : */
103 : CVector3D pos0;
104 : CVector3D pos1;
105 :
106 : /**
107 : * Bounds encompassing the unit's bounds when it is anywhere between
108 : * pos0 and pos1.
109 : */
110 : CBoundingSphere sweptBounds;
111 :
112 : /**
113 : * For debug overlay.
114 : */
115 : bool culled;
116 : };
117 :
118 : std::vector<SUnit> m_Units;
119 : std::vector<tag_t> m_UnitTagsFree;
120 :
121 : int m_FrameNumber;
122 : float m_FrameOffset;
123 :
124 : bool m_EnableDebugOverlays;
125 : std::vector<SOverlaySphere> m_DebugSpheres;
126 :
127 116 : static void ClassInit(CComponentManager& componentManager)
128 : {
129 116 : componentManager.SubscribeToMessageType(MT_TurnStart);
130 116 : componentManager.SubscribeToMessageType(MT_Interpolate);
131 116 : componentManager.SubscribeToMessageType(MT_RenderSubmit);
132 116 : }
133 :
134 6 : DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer)
135 :
136 116 : static std::string GetSchema()
137 : {
138 116 : return "<a:component type='system'/><empty/>";
139 : }
140 :
141 3 : void Init(const CParamNode& UNUSED(paramNode)) override
142 : {
143 3 : m_FrameNumber = 0;
144 3 : m_FrameOffset = 0.0f;
145 3 : m_EnableDebugOverlays = false;
146 3 : }
147 :
148 3 : void Deinit() override
149 : {
150 3 : }
151 :
152 0 : void Serialize(ISerializer& UNUSED(serialize)) override
153 : {
154 0 : }
155 :
156 0 : void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) override
157 : {
158 0 : Init(paramNode);
159 0 : }
160 :
161 1 : void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
162 : {
163 1 : switch (msg.GetType())
164 : {
165 1 : case MT_TurnStart:
166 : {
167 1 : TurnStart();
168 1 : break;
169 : }
170 0 : case MT_Interpolate:
171 : {
172 0 : const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
173 0 : Interpolate(msgData.deltaSimTime, msgData.offset);
174 0 : break;
175 : }
176 0 : case MT_RenderSubmit:
177 : {
178 0 : const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
179 0 : RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
180 0 : break;
181 : }
182 : }
183 1 : }
184 :
185 0 : SUnit* LookupUnit(tag_t tag)
186 : {
187 0 : if (tag.n < 1 || tag.n - 1 >= m_Units.size())
188 0 : return NULL;
189 0 : return &m_Units[tag.n - 1];
190 : }
191 :
192 0 : tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags) override
193 : {
194 0 : ENSURE(actor != NULL);
195 :
196 0 : tag_t tag;
197 0 : if (!m_UnitTagsFree.empty())
198 : {
199 0 : tag = m_UnitTagsFree.back();
200 0 : m_UnitTagsFree.pop_back();
201 : }
202 : else
203 : {
204 0 : m_Units.push_back(SUnit());
205 0 : tag.n = m_Units.size();
206 : }
207 :
208 0 : SUnit* unit = LookupUnit(tag);
209 0 : unit->entity = entity;
210 0 : unit->actor = actor;
211 0 : unit->lastTransformFrame = -1;
212 0 : unit->flags = flags;
213 0 : unit->boundsApprox = boundsApprox;
214 0 : unit->inWorld = false;
215 0 : unit->visibilityDirty = true;
216 0 : unit->pos0 = unit->pos1 = CVector3D();
217 :
218 0 : return tag;
219 : }
220 :
221 0 : void RemoveUnit(tag_t tag) override
222 : {
223 0 : SUnit* unit = LookupUnit(tag);
224 0 : unit->actor = NULL;
225 0 : unit->inWorld = false;
226 0 : m_UnitTagsFree.push_back(tag);
227 0 : }
228 :
229 0 : void RecomputeSweptBounds(SUnit* unit)
230 : {
231 : // Compute the bounding sphere of the capsule formed by
232 : // sweeping boundsApprox from pos0 to pos1
233 0 : CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter();
234 0 : float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius();
235 0 : unit->sweptBounds = CBoundingSphere(mid, radius);
236 0 : }
237 :
238 0 : void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox) override
239 : {
240 0 : SUnit* unit = LookupUnit(tag);
241 0 : unit->actor = actor;
242 0 : unit->boundsApprox = boundsApprox;
243 0 : RecomputeSweptBounds(unit);
244 0 : }
245 :
246 0 : void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) override
247 : {
248 0 : SUnit* unit = LookupUnit(tag);
249 0 : unit->inWorld = inWorld;
250 0 : unit->pos0 = pos0;
251 0 : unit->pos1 = pos1;
252 0 : unit->visibilityDirty = true;
253 0 : RecomputeSweptBounds(unit);
254 0 : }
255 :
256 : void TurnStart();
257 : void Interpolate(float frameTime, float frameOffset);
258 : void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
259 :
260 : void UpdateVisibility(SUnit& unit) const;
261 :
262 0 : float GetFrameOffset() const override
263 : {
264 0 : return m_FrameOffset;
265 : }
266 :
267 0 : void SetDebugOverlay(bool enabled) override
268 : {
269 0 : m_EnableDebugOverlays = enabled;
270 0 : }
271 :
272 0 : void PickAllEntitiesAtPoint(std::vector<std::pair<CEntityHandle, CVector3D> >& outEntities, const CVector3D& origin, const CVector3D& dir, bool allowEditorSelectables) const override
273 : {
274 : // First, make a rough test with the worst-case bounding boxes to pick all
275 : // entities/models that could possibly be hit by the ray.
276 0 : std::vector<const SUnit*> candidates;
277 0 : for (const SUnit& unit : m_Units)
278 : {
279 0 : if (!unit.actor || !unit.inWorld)
280 0 : continue;
281 0 : if (unit.sweptBounds.RayIntersect(origin, dir))
282 0 : candidates.push_back(&unit);
283 : }
284 :
285 : // Now make a more precise test to get rid of the remaining false positives
286 : float tmin, tmax;
287 0 : CVector3D center;
288 0 : for (size_t i = 0; i< candidates.size(); ++i)
289 : {
290 0 : const SUnit& unit = *candidates[i];
291 :
292 0 : CmpPtr<ICmpVisual> cmpVisual(unit.entity);
293 0 : if (!cmpVisual)
294 0 : continue;
295 :
296 0 : CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox();
297 0 : if (selectionBox.IsEmpty())
298 : {
299 0 : if (!allowEditorSelectables)
300 0 : continue;
301 :
302 : // Fall back to using old AABB selection method for decals
303 : // see: http://trac.wildfiregames.com/ticket/1032
304 : // Decals are flat objects without a selectionShape defined,
305 : // but they should still be selectable in the editor to move them
306 : // around or delete them after they are placed.
307 : // Check campaigns/labels/ in the Actors tab of atlas for examples.
308 0 : CBoundingBoxAligned aABBox = cmpVisual->GetBounds();
309 0 : if (aABBox.IsEmpty())
310 0 : continue;
311 :
312 0 : if (!aABBox.RayIntersect(origin, dir, tmin, tmax))
313 0 : continue;
314 :
315 0 : aABBox.GetCenter(center);
316 : }
317 : else
318 : {
319 0 : if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
320 0 : continue;
321 :
322 0 : center = selectionBox.m_Center;
323 : }
324 0 : outEntities.emplace_back(unit.entity, center);
325 : }
326 0 : }
327 : };
328 :
329 1 : void CCmpUnitRenderer::TurnStart()
330 : {
331 2 : PROFILE3("UnitRenderer::TurnStart");
332 :
333 : // Assume units have stopped moving after the previous turn. If that assumption is not
334 : // correct, we will get a UpdateUnitPos to tell us about its movement in the new turn.
335 1 : for (size_t i = 0; i < m_Units.size(); i++)
336 : {
337 0 : SUnit& unit = m_Units[i];
338 0 : unit.pos0 = unit.pos1;
339 0 : unit.sweptBounds = CBoundingSphere(unit.pos1, unit.boundsApprox.GetRadius());
340 :
341 : // Visibility must be recomputed on the first frame during this turn
342 0 : unit.visibilityDirty = true;
343 : }
344 1 : }
345 :
346 0 : void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset)
347 : {
348 0 : PROFILE3("UnitRenderer::Interpolate");
349 :
350 0 : ++m_FrameNumber;
351 0 : m_FrameOffset = frameOffset;
352 :
353 : // TODO: we shouldn't update all the animations etc for units that are off-screen
354 : // (but need to be careful about e.g. sounds triggered by animations of off-screen
355 : // units)
356 0 : for (size_t i = 0; i < m_Units.size(); i++)
357 : {
358 0 : SUnit& unit = m_Units[i];
359 0 : if (unit.actor)
360 0 : unit.actor->UpdateModel(frameTime);
361 : }
362 :
363 0 : m_DebugSpheres.clear();
364 0 : if (m_EnableDebugOverlays)
365 : {
366 0 : for (const SUnit& unit : m_Units)
367 : {
368 0 : if (!(unit.actor && unit.inWorld))
369 0 : continue;
370 :
371 0 : SOverlaySphere sphere;
372 0 : sphere.m_Center = unit.sweptBounds.GetCenter();
373 0 : sphere.m_Radius = unit.sweptBounds.GetRadius();
374 0 : if (unit.culled)
375 0 : sphere.m_Color = CColor(1.0f, 0.5f, 0.5f, 0.5f);
376 : else
377 0 : sphere.m_Color = CColor(0.5f, 0.5f, 1.0f, 0.5f);
378 :
379 0 : m_DebugSpheres.push_back(sphere);
380 : }
381 : }
382 0 : }
383 :
384 0 : void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
385 : {
386 : // TODO: need a coarse culling pass based on some kind of spatial data
387 : // structure - that's the main point of this design. Once we've got a
388 : // rough list of possibly-visible units, then we can do the more precise
389 : // culling. (And once it's cheap enough, we can do multiple culling passes
390 : // per frame - one for shadow generation, one for water reflections, etc.)
391 :
392 0 : PROFILE3("UnitRenderer::RenderSubmit");
393 :
394 0 : for (size_t i = 0; i < m_Units.size(); ++i)
395 : {
396 0 : SUnit& unit = m_Units[i];
397 :
398 0 : unit.culled = true;
399 :
400 0 : if (!unit.actor)
401 0 : continue;
402 :
403 0 : if (unit.visibilityDirty)
404 0 : UpdateVisibility(unit);
405 :
406 0 : if (unit.visibility == LosVisibility::HIDDEN)
407 0 : continue;
408 :
409 0 : if (!g_AtlasGameLoop->running && !g_RenderingOptions.GetRenderActors() && (unit.flags & ACTOR_ONLY))
410 0 : continue;
411 :
412 0 : if (!g_AtlasGameLoop->running && (unit.flags & VISIBLE_IN_ATLAS_ONLY))
413 0 : continue;
414 :
415 0 : if (culling && !frustum.IsSphereVisible(unit.sweptBounds.GetCenter(), unit.sweptBounds.GetRadius()))
416 0 : continue;
417 :
418 0 : unit.culled = false;
419 :
420 0 : CModelAbstract& unitModel = unit.actor->GetModel();
421 :
422 0 : if (unit.lastTransformFrame != m_FrameNumber)
423 : {
424 0 : CmpPtr<ICmpPosition> cmpPosition(unit.entity);
425 0 : if (!cmpPosition)
426 0 : continue;
427 :
428 0 : CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
429 :
430 0 : unitModel.SetTransform(transform);
431 :
432 0 : unit.lastTransformFrame = m_FrameNumber;
433 : }
434 :
435 0 : if (culling && !frustum.IsBoxVisible(unitModel.GetWorldBoundsRec()))
436 0 : continue;
437 :
438 0 : collector.SubmitRecursive(&unitModel);
439 : }
440 :
441 0 : for (size_t i = 0; i < m_DebugSpheres.size(); ++i)
442 0 : collector.Submit(&m_DebugSpheres[i]);
443 0 : }
444 :
445 0 : void CCmpUnitRenderer::UpdateVisibility(SUnit& unit) const
446 : {
447 0 : if (unit.inWorld)
448 : {
449 : // The 'always visible' flag means we should always render the unit
450 : // (regardless of whether the LOS system thinks it's visible)
451 0 : CmpPtr<ICmpVisibility> cmpVisibility(unit.entity);
452 0 : if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
453 0 : unit.visibility = LosVisibility::VISIBLE;
454 : else
455 : {
456 0 : CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
457 0 : unit.visibility = cmpRangeManager->GetLosVisibility(unit.entity,
458 0 : GetSimContext().GetCurrentDisplayedPlayer());
459 : }
460 : }
461 : else
462 0 : unit.visibility = LosVisibility::HIDDEN;
463 :
464 : // Change the visibility of the visual actor's selectable if it has one.
465 0 : CmpPtr<ICmpSelectable> cmpSelectable(unit.entity);
466 0 : if (cmpSelectable)
467 0 : cmpSelectable->SetVisibility(unit.visibility != LosVisibility::HIDDEN);
468 :
469 0 : unit.visibilityDirty = false;
470 0 : }
471 :
472 119 : REGISTER_COMPONENT_TYPE(UnitRenderer)
|