File: CCmpProjectileManager.cpp

package info (click to toggle)
0ad 0~r11863-2
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 30,560 kB
  • sloc: cpp: 201,230; ansic: 28,387; sh: 10,593; perl: 4,847; python: 2,240; makefile: 658; java: 412; xml: 243; sql: 40
file content (357 lines) | stat: -rw-r--r-- 10,750 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/* Copyright (C) 2012 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "simulation2/system/Component.h"
#include "ICmpProjectileManager.h"

#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpTerrain.h"
#include "ICmpVisual.h"
#include "simulation2/MessageTypes.h"

#include "graphics/Frustum.h"
#include "graphics/Model.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "renderer/Scene.h"

// Time (in seconds) before projectiles that stuck in the ground are destroyed
const static float PROJECTILE_DECAY_TIME = 30.f;

class CCmpProjectileManager : public ICmpProjectileManager
{
public:
	static void ClassInit(CComponentManager& componentManager)
	{
		componentManager.SubscribeToMessageType(MT_Interpolate);
		componentManager.SubscribeToMessageType(MT_RenderSubmit);
	}

	DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)

	static std::string GetSchema()
	{
		return "<a:component type='system'/><empty/>";
	}

	virtual void Init(const CParamNode& UNUSED(paramNode))
	{
		m_ActorSeed = 0;
	}

	virtual void Deinit()
	{
		for (size_t i = 0; i < m_Projectiles.size(); ++i)
			GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles[i].unit);
		m_Projectiles.clear();
	}

	virtual void Serialize(ISerializer& UNUSED(serialize))
	{
		// Because this is just graphical effects, and because it's all non-deterministic floating point,
		// we don't do any serialization here.
		// (That means projectiles will vanish if you save/load - is that okay?)
	}

	virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
	{
		Init(paramNode);
	}

	virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
	{
		switch (msg.GetType())
		{
		case MT_Interpolate:
		{
			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
			Interpolate(msgData.frameTime, msgData.offset);
			break;
		}
		case MT_RenderSubmit:
		{
			const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
			RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
			break;
		}
		}
	}

	virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity)
	{
		LaunchProjectile(source, CFixedVector3D(), target, speed, gravity);
	}

	virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity)
	{
		LaunchProjectile(source, target, INVALID_ENTITY, speed, gravity);
	}

private:
	struct Projectile
	{
		CUnit* unit;
		CVector3D pos;
		CVector3D target;
		entity_id_t targetEnt; // INVALID_ENTITY if the target is just a point
		float timeLeft;
		float speedFactor;
		float gravity;
		bool stopped;
	};

	std::vector<Projectile> m_Projectiles;

	uint32_t m_ActorSeed;

	void LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity);

	void AdvanceProjectile(Projectile& projectile, float dt, float frameOffset);

	void Interpolate(float frameTime, float frameOffset);

	void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
};

REGISTER_COMPONENT_TYPE(ProjectileManager)

void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity)
{
	if (!GetSimContext().HasUnitManager())
		return; // do nothing if graphics are disabled

	CmpPtr<ICmpVisual> cmpSourceVisual(GetSimContext(), source);
	if (!cmpSourceVisual)
		return;

	std::wstring name = cmpSourceVisual->GetProjectileActor();
	if (name.empty())
	{
		// If the actor was actually loaded, complain that it doesn't have a projectile
		if (!cmpSourceVisual->GetActorShortName().empty())
			LOGERROR(L"Unit with actor '%ls' launched a projectile but has no actor on 'projectile' attachpoint", cmpSourceVisual->GetActorShortName().c_str());
		return;
	}

	CVector3D sourceVec(cmpSourceVisual->GetProjectileLaunchPoint());
	if (!sourceVec)
	{
		// If there's no explicit launch point, take a guess based on the entity position

		CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
		if (!sourcePos)
			return;

		sourceVec = sourcePos->GetPosition();
		sourceVec.Y += 3.f;
	}

	CVector3D targetVec;

	if (targetEnt == INVALID_ENTITY)
	{
		targetVec = CVector3D(targetPoint);
	}
	else
	{
		CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), targetEnt);
		if (!cmpTargetPosition)
			return;

		targetVec = CVector3D(cmpTargetPosition->GetPosition());
	}

	Projectile projectile;
	std::set<CStr> selections;
	projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections);
	if (!projectile.unit)
	{
		// The error will have already been logged
		return;
	}

	projectile.pos = sourceVec;
	projectile.target = targetVec;
	projectile.targetEnt = targetEnt;

	CVector3D offset = projectile.target - projectile.pos;
	float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);

	projectile.speedFactor = 1.f;
	projectile.timeLeft = horizDistance / speed.ToFloat();
	projectile.stopped = false;

	projectile.gravity = gravity.ToFloat();

	m_Projectiles.push_back(projectile);
}

void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt, float frameOffset)
{
	// Do special processing if we've already reached the target
	if (projectile.timeLeft <= 0)
	{
		if (projectile.stopped)
		{
			projectile.timeLeft -= dt;
			return;
		}
		// else continue moving the projectile

		// To prevent arrows going crazily far after missing the target,
		// apply a bit of drag to them
		projectile.speedFactor *= powf(1.0f - 0.4f*projectile.speedFactor, dt);
	}
	else
	{
		// Projectile hasn't reached the target yet:
		// Track the target entity (if there is one, and it's still alive)
		if (projectile.targetEnt != INVALID_ENTITY)
		{
			CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), projectile.targetEnt);
			if (cmpTargetPosition && cmpTargetPosition->IsInWorld())
			{
				CMatrix3D t = cmpTargetPosition->GetInterpolatedTransform(frameOffset, false);
				projectile.target = t.GetTranslation();
				projectile.target.Y += 2.f; // TODO: ought to aim towards a random point in the solid body of the target

				// TODO: if the unit is moving, we should probably aim a bit in front of it
				// so we don't have to curve so much just before reaching it
			}
		}
	}

	CVector3D offset = (projectile.target - projectile.pos) * projectile.speedFactor;

	// Compute the vertical velocity that's needed so we travel in a ballistic curve and
	// reach the target after timeLeft.
	// (This is just a linear approximation to the curve, but it'll converge to hit the target)
	float vh = (projectile.gravity / 2.f) * projectile.timeLeft + offset.Y / projectile.timeLeft;

	// Move an appropriate fraction towards the target
	CVector3D delta (offset.X * dt/projectile.timeLeft, vh * dt, offset.Z * dt/projectile.timeLeft);

	projectile.pos += delta;
	projectile.timeLeft -= dt;

	// If we've passed the target position and haven't stopped yet,
	// carry on until we reach solid land
	if (projectile.timeLeft <= 0)
	{
		CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
		if (cmpTerrain)
		{
			float h = cmpTerrain->GetExactGroundLevel(projectile.pos.X, projectile.pos.Z);
			if (projectile.pos.Y < h)
			{
				projectile.pos.Y = h; // stick precisely to the terrain
				projectile.stopped = true;
			}
		}
	}

	// Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'

	CVector3D up(0, 1, 0);

	delta.Normalize();
	CVector3D axis = up.Cross(delta);
	if (axis.LengthSquared() < 0.0001f)
		axis = CVector3D(1, 0, 0); // if up & delta are almost collinear, rotate around some other arbitrary axis
	else
		axis.Normalize();

	float angle = acosf(up.Dot(delta));

	CMatrix3D transform;
	CQuaternion quat;
	quat.FromAxisAngle(axis, angle);
	quat.ToMatrix(transform);

	// Then apply the translation
	transform.Translate(projectile.pos);

	// Move the model
	projectile.unit->GetModel().SetTransform(transform);
}

void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset)
{
	for (size_t i = 0; i < m_Projectiles.size(); ++i)
	{
		AdvanceProjectile(m_Projectiles[i], frameTime, frameOffset);
	}

	// Remove the ones that have reached their target
	for (size_t i = 0; i < m_Projectiles.size(); )
	{
		// Projectiles hitting targets get removed immediately.
		// Those hitting the ground stay for a while, because it looks pretty.
		if (m_Projectiles[i].timeLeft <= 0.f)
		{
			if (m_Projectiles[i].targetEnt == INVALID_ENTITY && m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME)
			{
				// Keep the projectile until it exceeds the decay time
			}
			else
			{
				// Delete in-place by swapping with the last in the list
				std::swap(m_Projectiles[i], m_Projectiles.back());
				GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
				m_Projectiles.pop_back();
				continue; // don't increment i
			}
		}

		++i;
	}
}

void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
	CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
	int player = GetSimContext().GetCurrentDisplayedPlayer();
	ICmpRangeManager::CLosQuerier los (cmpRangeManager->GetLosQuerier(player));
	bool losRevealAll = cmpRangeManager->GetLosRevealAll(player);

	for (size_t i = 0; i < m_Projectiles.size(); ++i)
	{
		// Don't display projectiles outside the visible area
		ssize_t posi = (ssize_t)(0.5f + m_Projectiles[i].pos.X / TERRAIN_TILE_SIZE);
		ssize_t posj = (ssize_t)(0.5f + m_Projectiles[i].pos.Z / TERRAIN_TILE_SIZE);
		if (!losRevealAll && !los.IsVisible(posi, posj))
			continue;

		CModelAbstract& model = m_Projectiles[i].unit->GetModel();

		model.ValidatePosition();

		if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
			continue;

		// TODO: do something about LOS (copy from CCmpVisualActor)

		collector.SubmitRecursive(&model);
	}
}