1 /* Copyright (C) 2017 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 "ICmpProjectileManager.h"
22
23 #include "ICmpObstruction.h"
24 #include "ICmpObstructionManager.h"
25 #include "ICmpPosition.h"
26 #include "ICmpRangeManager.h"
27 #include "ICmpTerrain.h"
28 #include "simulation2/MessageTypes.h"
29
30 #include "graphics/Frustum.h"
31 #include "graphics/Model.h"
32 #include "graphics/Unit.h"
33 #include "graphics/UnitManager.h"
34 #include "maths/Matrix3D.h"
35 #include "maths/Quaternion.h"
36 #include "maths/Vector3D.h"
37 #include "ps/CLogger.h"
38 #include "renderer/Scene.h"
39
40 // Time (in seconds) before projectiles that stuck in the ground are destroyed
41 const static float PROJECTILE_DECAY_TIME = 30.f;
42
43 class CCmpProjectileManager : public ICmpProjectileManager
44 {
45 public:
ClassInit(CComponentManager & componentManager)46 static void ClassInit(CComponentManager& componentManager)
47 {
48 componentManager.SubscribeToMessageType(MT_Interpolate);
49 componentManager.SubscribeToMessageType(MT_RenderSubmit);
50 }
51
DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)52 DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)
53
54 static std::string GetSchema()
55 {
56 return "<a:component type='system'/><empty/>";
57 }
58
Init(const CParamNode & UNUSED (paramNode))59 virtual void Init(const CParamNode& UNUSED(paramNode))
60 {
61 m_ActorSeed = 0;
62 m_NextId = 1;
63 }
64
Deinit()65 virtual void Deinit()
66 {
67 for (size_t i = 0; i < m_Projectiles.size(); ++i)
68 GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles[i].unit);
69 m_Projectiles.clear();
70 }
71
Serialize(ISerializer & serialize)72 virtual void Serialize(ISerializer& serialize)
73 {
74 // Because this is just graphical effects, and because it's all non-deterministic floating point,
75 // we don't do much serialization here.
76 // (That means projectiles will vanish if you save/load - is that okay?)
77
78 // The attack code stores the id so that the projectile gets deleted when it hits the target
79 serialize.NumberU32_Unbounded("next id", m_NextId);
80 }
81
Deserialize(const CParamNode & paramNode,IDeserializer & deserialize)82 virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
83 {
84 Init(paramNode);
85
86 // The attack code stores the id so that the projectile gets deleted when it hits the target
87 deserialize.NumberU32_Unbounded("next id", m_NextId);
88 }
89
HandleMessage(const CMessage & msg,bool UNUSED (global))90 virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
91 {
92 switch (msg.GetType())
93 {
94 case MT_Interpolate:
95 {
96 const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
97 Interpolate(msgData.deltaSimTime);
98 break;
99 }
100 case MT_RenderSubmit:
101 {
102 const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
103 RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
104 break;
105 }
106 }
107 }
108
LaunchProjectileAtPoint(const CFixedVector3D & launchPoint,const CFixedVector3D & target,fixed speed,fixed gravity,const std::wstring & actorName,const std::wstring & impactActorName,fixed impactAnimationLifetime)109 virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
110 {
111 return LaunchProjectile(launchPoint, target, speed, gravity, actorName, impactActorName, impactAnimationLifetime);
112 }
113
114 virtual void RemoveProjectile(uint32_t);
115
116 void RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, const CFrustum& frustum, bool culling,
117 ICmpRangeManager::CLosQuerier los, bool losRevealAll) const;
118
119 private:
120 struct Projectile
121 {
122 CUnit* unit;
123 CVector3D origin;
124 CVector3D pos;
125 CVector3D v;
126 float time;
127 float timeHit;
128 float gravity;
129 float impactAnimationLifetime;
130 uint32_t id;
131 std::wstring impactActorName;
132 bool isImpactAnimationCreated;
133 bool stopped;
134
positionCCmpProjectileManager::Projectile135 CVector3D position(float t)
136 {
137 float t2 = t;
138 if (t2 > timeHit)
139 t2 = timeHit + logf(1.f + t2 - timeHit);
140
141 CVector3D ret(origin);
142 ret.X += v.X * t2;
143 ret.Z += v.Z * t2;
144 ret.Y += v.Y * t2 - 0.5f * gravity * t * t;
145 return ret;
146 }
147 };
148
149 struct ProjectileImpactAnimation
150 {
151 CUnit* unit;
152 CVector3D pos;
153 float time;
154 };
155
156 std::vector<Projectile> m_Projectiles;
157
158 std::vector<ProjectileImpactAnimation> m_ProjectileImpactAnimations;
159
160 uint32_t m_ActorSeed;
161
162 uint32_t m_NextId;
163
164 uint32_t LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity,
165 const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime);
166
167 void AdvanceProjectile(Projectile& projectile, float dt) const;
168
169 void Interpolate(float frameTime);
170
171 void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const;
172 };
173
REGISTER_COMPONENT_TYPE(ProjectileManager)174 REGISTER_COMPONENT_TYPE(ProjectileManager)
175
176 uint32_t CCmpProjectileManager::LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
177 {
178 // This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations
179 uint32_t currentId = m_NextId++;
180
181 if (!GetSimContext().HasUnitManager() || actorName.empty())
182 return currentId; // do nothing if graphics are disabled
183
184 Projectile projectile;
185 projectile.id = currentId;
186 projectile.time = 0.f;
187 projectile.stopped = false;
188 projectile.gravity = gravity.ToFloat();
189 projectile.isImpactAnimationCreated = false;
190
191 if (!impactActorName.empty())
192 {
193 projectile.impactActorName = impactActorName;
194 projectile.impactAnimationLifetime = impactAnimationLifetime.ToFloat();
195 }
196 else
197 {
198 projectile.impactActorName = L"";
199 projectile.impactAnimationLifetime = 0.0f;
200 }
201
202 projectile.origin = launchPoint;
203
204 std::set<CStr> selections;
205 projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++, selections);
206 if (!projectile.unit) // The error will have already been logged
207 return currentId;
208
209 projectile.pos = projectile.origin;
210 CVector3D offset(targetPoint);
211 offset -= projectile.pos;
212 float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);
213 projectile.timeHit = horizDistance / speed.ToFloat();
214 projectile.v = offset * (1.f / projectile.timeHit);
215
216 projectile.v.Y = offset.Y / projectile.timeHit + 0.5f * projectile.gravity * projectile.timeHit;
217
218 m_Projectiles.push_back(projectile);
219
220 return projectile.id;
221 }
222
AdvanceProjectile(Projectile & projectile,float dt) const223 void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt) const
224 {
225 projectile.time += dt;
226 if (projectile.stopped)
227 return;
228
229 CVector3D delta;
230 if (dt < 0.1f)
231 delta = projectile.pos;
232 else // For big dt delta is unprecise
233 delta = projectile.position(projectile.time - 0.1f);
234
235 projectile.pos = projectile.position(projectile.time);
236
237 delta = projectile.pos - delta;
238
239 // If we've passed the target position and haven't stopped yet,
240 // carry on until we reach solid land
241 if (projectile.time >= projectile.timeHit)
242 {
243 CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
244 if (cmpTerrain)
245 {
246 float h = cmpTerrain->GetExactGroundLevel(projectile.pos.X, projectile.pos.Z);
247 if (projectile.pos.Y < h)
248 {
249 projectile.pos.Y = h; // stick precisely to the terrain
250 projectile.stopped = true;
251 }
252 }
253 }
254
255 // Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'
256
257 CVector3D up(0, 1, 0);
258
259 delta.Normalize();
260 CVector3D axis = up.Cross(delta);
261 if (axis.LengthSquared() < 0.0001f)
262 axis = CVector3D(1, 0, 0); // if up & delta are almost collinear, rotate around some other arbitrary axis
263 else
264 axis.Normalize();
265
266 float angle = acosf(up.Dot(delta));
267
268 CMatrix3D transform;
269 CQuaternion quat;
270 quat.FromAxisAngle(axis, angle);
271 quat.ToMatrix(transform);
272
273 // Then apply the translation
274 transform.Translate(projectile.pos);
275
276 // Move the model
277 projectile.unit->GetModel().SetTransform(transform);
278 }
279
Interpolate(float frameTime)280 void CCmpProjectileManager::Interpolate(float frameTime)
281 {
282 for (size_t i = 0; i < m_Projectiles.size(); ++i)
283 {
284 AdvanceProjectile(m_Projectiles[i], frameTime);
285 }
286
287 // Remove the ones that have reached their target
288 for (size_t i = 0; i < m_Projectiles.size(); )
289 {
290 if (!m_Projectiles[i].stopped)
291 {
292 ++i;
293 continue;
294 }
295
296 if (!m_Projectiles[i].impactActorName.empty() && !m_Projectiles[i].isImpactAnimationCreated)
297 {
298 m_Projectiles[i].isImpactAnimationCreated = true;
299 CMatrix3D transform;
300 CQuaternion quat;
301 quat.ToMatrix(transform);
302 transform.Translate(m_Projectiles[i].pos);
303
304 std::set<CStr> selections;
305 CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++, selections);
306 unit->GetModel().SetTransform(transform);
307
308 ProjectileImpactAnimation projectileImpactAnimation;
309 projectileImpactAnimation.unit = unit;
310 projectileImpactAnimation.time = m_Projectiles[i].impactAnimationLifetime;
311 projectileImpactAnimation.pos = m_Projectiles[i].pos;
312 m_ProjectileImpactAnimations.push_back(projectileImpactAnimation);
313 }
314
315 // Projectiles hitting targets get removed immediately.
316 // Those hitting the ground stay for a while, because it looks pretty.
317 if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME)
318 {
319 // Delete in-place by swapping with the last in the list
320 std::swap(m_Projectiles[i], m_Projectiles.back());
321 GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
322 m_Projectiles.pop_back();
323 continue;
324 }
325 ++i;
326 }
327
328 for (size_t i = 0; i < m_ProjectileImpactAnimations.size();)
329 {
330 if (m_ProjectileImpactAnimations[i].time > 0)
331 {
332 m_ProjectileImpactAnimations[i].time -= frameTime;
333 ++i;
334 }
335 else
336 {
337 std::swap(m_ProjectileImpactAnimations[i], m_ProjectileImpactAnimations.back());
338 GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileImpactAnimations.back().unit);
339 m_ProjectileImpactAnimations.pop_back();
340 }
341 }
342 }
343
RemoveProjectile(uint32_t id)344 void CCmpProjectileManager::RemoveProjectile(uint32_t id)
345 {
346 // Scan through the projectile list looking for one with the correct id to remove
347 for (size_t i = 0; i < m_Projectiles.size(); i++)
348 {
349 if (m_Projectiles[i].id == id)
350 {
351 // Delete in-place by swapping with the last in the list
352 std::swap(m_Projectiles[i], m_Projectiles.back());
353 GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
354 m_Projectiles.pop_back();
355 return;
356 }
357 }
358 }
359
RenderModel(CModelAbstract & model,const CVector3D & position,SceneCollector & collector,const CFrustum & frustum,bool culling,ICmpRangeManager::CLosQuerier los,bool losRevealAll) const360 void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector,
361 const CFrustum& frustum, bool culling, ICmpRangeManager::CLosQuerier los, bool losRevealAll) const
362 {
363 // Don't display objects outside the visible area
364 ssize_t posi = (ssize_t)(0.5f + position.X / TERRAIN_TILE_SIZE);
365 ssize_t posj = (ssize_t)(0.5f + position.Z / TERRAIN_TILE_SIZE);
366 if (!losRevealAll && !los.IsVisible(posi, posj))
367 return;
368
369 model.ValidatePosition();
370
371 if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec()))
372 return;
373
374 // TODO: do something about LOS (copy from CCmpVisualActor)
375
376 collector.SubmitRecursive(&model);
377 }
378
RenderSubmit(SceneCollector & collector,const CFrustum & frustum,bool culling) const379 void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const
380 {
381 CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
382 int player = GetSimContext().GetCurrentDisplayedPlayer();
383 ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(player));
384 bool losRevealAll = cmpRangeManager->GetLosRevealAll(player);
385
386 for (const Projectile& projectile : m_Projectiles)
387 {
388 RenderModel(projectile.unit->GetModel(), projectile.pos, collector, frustum, culling, los, losRevealAll);
389 }
390
391 for (const ProjectileImpactAnimation& projectileImpactAnimation : m_ProjectileImpactAnimations)
392 {
393 RenderModel(projectileImpactAnimation.unit->GetModel(), projectileImpactAnimation.pos,
394 collector, frustum, culling, los, losRevealAll);
395 }
396 }
397