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