1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "StarburstProjectile.h"
5 #include "Game/Camera.h"
6 #include "Game/GameHelper.h"
7 #include "Map/Ground.h"
8 #include "Rendering/GlobalRendering.h"
9 #include "Rendering/ProjectileDrawer.h"
10 #include "Rendering/GL/VertexArray.h"
11 #include "Rendering/Textures/TextureAtlas.h"
12 #include "Sim/Projectiles/ProjectileHandler.h"
13 #include "Sim/Projectiles/Unsynced/SmokeTrailProjectile.h"
14 #include "Sim/Units/Unit.h"
15 #include "Sim/Weapons/WeaponDef.h"
16 #include "System/Sync/SyncTracer.h"
17 #include "System/Color.h"
18 #include "System/Matrix44f.h"
19 #include "System/myMath.h"
20 #include "System/TimeProfiler.h"
21 
22 // the smokes life-time in frames
23 static const float SMOKE_TIME = 70.0f;
24 
25 static const float TRACER_PARTS_STEP = 2.0f;
26 static const unsigned int MAX_NUM_AGEMODS = 32;
27 
28 CR_BIND(CStarburstProjectile::TracerPart, )
29 CR_REG_METADATA_SUB(CStarburstProjectile, TracerPart, (
30 	CR_MEMBER(pos),
31 	CR_MEMBER(dir),
32 	CR_MEMBER(speedf),
33 	CR_MEMBER(ageMods),
34 	CR_MEMBER(numAgeMods)
35 ))
36 
37 
38 CR_BIND_DERIVED(CStarburstProjectile, CWeaponProjectile, (ProjectileParams()))
39 CR_REG_METADATA(CStarburstProjectile, (
40 	CR_SETFLAG(CF_Synced),
41 	CR_MEMBER(tracking),
42 	CR_MEMBER(maxGoodDif),
43 	CR_MEMBER(maxSpeed),
44 	CR_MEMBER(acceleration),
45 	CR_MEMBER(uptime),
46 	CR_MEMBER(areaOfEffect),
47 	CR_MEMBER(age),
48 	CR_MEMBER(oldSmoke),
49 	CR_MEMBER(oldSmokeDir),
50 	CR_MEMBER(drawTrail),
51 	CR_MEMBER(numParts),
52 	CR_MEMBER(doturn),
53 	CR_MEMBER(curCallback),
54 	CR_MEMBER(missileAge),
55 	CR_MEMBER(distanceToTravel),
56 	CR_MEMBER(aimError),
57 	CR_MEMBER(curTracerPart),
58 	CR_MEMBER(tracerParts)
59 ))
60 
61 
CStarburstProjectile(const ProjectileParams & params)62 CStarburstProjectile::CStarburstProjectile(const ProjectileParams& params): CWeaponProjectile(params)
63 	, tracking(params.tracking)
64 	, maxGoodDif(0.0f)
65 	, maxSpeed(0.0f)
66 	, acceleration(0.f)
67 	, areaOfEffect(0.0f)
68 	, distanceToTravel(params.maxRange)
69 
70 	, uptime(0)
71 	, age(0)
72 
73 	, oldSmoke(pos)
74 	, aimError(params.error)
75 
76 	, drawTrail(true)
77 	, doturn(true)
78 	, curCallback(NULL)
79 
80 	, numParts(0)
81 	, missileAge(0)
82 	, curTracerPart(0)
83 {
84 	projectileType = WEAPON_STARBURST_PROJECTILE;
85 
86 
87 	if (weaponDef != NULL) {
88 		maxSpeed = weaponDef->projectilespeed;
89 		areaOfEffect = weaponDef->damageAreaOfEffect;
90 		uptime = weaponDef->uptime * GAME_SPEED;
91 
92 		if (weaponDef->flighttime == 0) {
93 			ttl = std::min(3000.0f, uptime + weaponDef->range / maxSpeed + 100);
94 		} else {
95 			ttl = weaponDef->flighttime;
96 		}
97 	}
98 
99 	maxGoodDif = math::cos(tracking * 0.6f);
100 	oldSmokeDir = dir;
101 
102 	const float3 camDir = (pos - camera->GetPos()).ANormalize();
103 	const float camDist = (camera->GetPos().distance(pos) * 0.2f) + ((1.0f - math::fabs(camDir.dot(dir))) * 3000);
104 
105 	drawTrail = (camDist >= 200.0f);
106 	drawRadius = maxSpeed * 8.0f;
107 
108 	for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
109 		tracerParts[a].dir = dir;
110 		tracerParts[a].pos = pos;
111 		tracerParts[a].speedf = speed.w;
112 
113 		tracerParts[a].ageMods.resize(MAX_NUM_AGEMODS, 1.0f);
114 		tracerParts[a].numAgeMods = std::min(MAX_NUM_AGEMODS, static_cast<unsigned int>((speed.w + 0.6f) / TRACER_PARTS_STEP));
115 	}
116 	castShadow = true;
117 
118 #ifdef TRACE_SYNC
119 	tracefile << "[" << __FUNCTION__ << "] ";
120 	tracefile << pos.x << " " << pos.y << " " << pos.z << " " << speed.x << " " << speed.y << " " << speed.z << "\n";
121 #endif
122 }
123 
Detach()124 void CStarburstProjectile::Detach()
125 {
126 	// SYNCED
127 	if (curCallback) {
128 		// this is unsynced, but it prevents some callback crash on exit
129 		curCallback->drawCallbacker = NULL;
130 	}
131 
132 	CProjectile::Detach();
133 }
134 
~CStarburstProjectile()135 CStarburstProjectile::~CStarburstProjectile()
136 {
137 	// UNSYNCED
138 	for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
139 		tracerParts[a].ageMods.clear();
140 	}
141 }
142 
143 
Collision()144 void CStarburstProjectile::Collision()
145 {
146 	if (weaponDef->visuals.smokeTrail) {
147 		new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
148 	}
149 
150 	oldSmokeDir = dir;
151 	CWeaponProjectile::Collision();
152 	oldSmoke = pos;
153 }
154 
Collision(CUnit * unit)155 void CStarburstProjectile::Collision(CUnit* unit)
156 {
157 	if (weaponDef->visuals.smokeTrail) {
158 		new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
159 	}
160 
161 	oldSmokeDir = dir;
162 	CWeaponProjectile::Collision(unit);
163 	oldSmoke = pos;
164 }
165 
Collision(CFeature * feature)166 void CStarburstProjectile::Collision(CFeature* feature)
167 {
168 	if (weaponDef->visuals.smokeTrail) {
169 		new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
170 	}
171 
172 	oldSmokeDir = dir;
173 	CWeaponProjectile::Collision(feature);
174 	oldSmoke = pos;
175 }
176 
177 
Update()178 void CStarburstProjectile::Update()
179 {
180 	ttl--;
181 	uptime--;
182 	missileAge++;
183 
184 	if (target != NULL && owner() != NULL && weaponDef->tracks) {
185 		targetPos = target->pos;
186 		CUnit* u = dynamic_cast<CUnit*>(target);
187 
188 		if (u != NULL) {
189 			targetPos = u->GetErrorPos(owner()->allyteam, true);
190 		}
191 	}
192 
193 	if (uptime > 0) {
194 		if (!luaMoveCtrl) {
195 			if (speed.w < maxSpeed)
196 				speed.w += weaponDef->weaponacceleration;
197 
198 			// do not need to update dir or speed.w here
199 			CWorldObject::SetVelocity(dir * speed.w);
200 		}
201 	} else if (doturn && ttl > 0 && distanceToTravel > 0.0f) {
202 		if (!luaMoveCtrl) {
203 			float3 targetErrorVec = ((targetPos - pos).Normalize() + aimError).Normalize();
204 
205 			if (targetErrorVec.dot(dir) > 0.99f) {
206 				dir = targetErrorVec;
207 				doturn = false;
208 			} else {
209 				targetErrorVec = targetErrorVec - dir;
210 				targetErrorVec -= dir * (targetErrorVec.dot(dir));
211 				targetErrorVec.Normalize();
212 
213 				if (weaponDef->turnrate != 0) {
214 					dir = (dir + (targetErrorVec * weaponDef->turnrate)).Normalize();
215 				} else {
216 					dir = (dir + (targetErrorVec * 0.06f)).Normalize();
217 				}
218 			}
219 
220 			// do not need to update dir or speed.w here
221 			CWorldObject::SetVelocity(dir * speed.w);
222 
223 			if (distanceToTravel != MAX_PROJECTILE_RANGE) {
224 				distanceToTravel -= speed.Length2D();
225 			}
226 		}
227 	} else if (ttl > 0 && distanceToTravel > 0.0f) {
228 		if (!luaMoveCtrl) {
229 			if (speed.w < maxSpeed)
230 				speed.w += weaponDef->weaponacceleration;
231 
232 			float3 targetErrorVec = (targetPos - pos).Normalize();
233 
234 			if (targetErrorVec.dot(dir) > maxGoodDif) {
235 				dir = targetErrorVec;
236 			} else {
237 				targetErrorVec = targetErrorVec - dir;
238 				targetErrorVec = (targetErrorVec - (dir * (targetErrorVec.dot(dir)))).SafeNormalize();
239 
240 				dir = (dir + (targetErrorVec * tracking)).SafeNormalize();
241 			}
242 
243 			// do not need to update dir or speed.w here
244 			CWorldObject::SetVelocity(dir * speed.w);
245 
246 			if (distanceToTravel != MAX_PROJECTILE_RANGE) {
247 				distanceToTravel -= speed.Length2D();
248 			}
249 		}
250 	} else {
251 		if (!luaMoveCtrl) {
252 			// changes dir and speed.w, must keep speed-vector in sync
253 			SetDirectionAndSpeed((dir + (UpVector * mygravity)).Normalize(), speed.w - mygravity);
254 		}
255 	}
256 
257 	if (!luaMoveCtrl) {
258 		SetPosition(pos + speed);
259 	}
260 
261 	if (ttl > 0) {
262 		explGenHandler->GenExplosion(cegID, pos, dir, ttl, areaOfEffect, 0.0f, NULL, NULL);
263 	}
264 
265 
266 	{
267 		const unsigned int newTracerPart = (curTracerPart + 1) % NUM_TRACER_PARTS;
268 
269 		curTracerPart = newTracerPart;
270 		TracerPart* tracerPart = &tracerParts[curTracerPart];
271 		tracerPart->pos = pos;
272 		tracerPart->dir = dir;
273 		tracerPart->speedf = speed.w;
274 
275 		unsigned int newsize = 0;
276 
277 		for (float aa = 0; aa < speed.w + 0.6f && newsize < MAX_NUM_AGEMODS; aa += TRACER_PARTS_STEP, ++newsize) {
278 			const float ageMod = (missileAge < 20) ? 1.0f : (0.6f + (rand() * 0.8f) / RAND_MAX);
279 			tracerPart->ageMods[newsize] = ageMod;
280 		}
281 
282 		if (tracerPart->numAgeMods != newsize) {
283 			tracerPart->numAgeMods = newsize;
284 		}
285 	}
286 
287 	age++;
288 	numParts++;
289 
290 	if (weaponDef->visuals.smokeTrail && !(age & 7)) {
291 		if (curCallback != NULL) {
292 			curCallback->drawCallbacker = NULL;
293 		}
294 
295 		curCallback = new CSmokeTrailProjectile(
296 			owner(),
297 			pos,
298 			oldSmoke,
299 			dir,
300 			oldSmokeDir,
301 			age == 8,
302 			false,
303 			7,
304 			SMOKE_TIME,
305 			0.7f,
306 			drawTrail,
307 			this,
308 			weaponDef->visuals.texture2
309 		);
310 
311 		oldSmoke = pos;
312 		oldSmokeDir = dir;
313 		numParts = 0;
314 		useAirLos = curCallback->useAirLos;
315 
316 		if (!drawTrail) {
317 			const float3 camDir = (pos - camera->GetPos()).ANormalize();
318 			const float camDist = (camera->GetPos().distance(pos) * 0.2f + (1 - math::fabs(camDir.dot(dir))) * 3000);
319 
320 			drawTrail = (camDist > 300.0f);
321 		}
322 	}
323 
324 	UpdateInterception();
325 }
326 
Draw()327 void CStarburstProjectile::Draw()
328 {
329 	inArray = true;
330 
331 	if (weaponDef->visuals.smokeTrail) {
332 		const int curNumParts = numParts;
333 
334 		va->EnlargeArrays(4 + (4 * curNumParts), 0, VA_SIZE_TC);
335 
336 		const float age2 = (age & 7) + globalRendering->timeOffset;
337 		const float color = 0.7f;
338 
339 		if (drawTrail) {
340 			// draw the trail as a single quad
341 			const float3 dif1 = (drawPos - camera->GetPos()).ANormalize();
342 			const float3 dir1 = (dif1.cross(dir)).ANormalize();
343 			const float3 dif2 = (oldSmoke - camera->GetPos()).ANormalize();
344 			const float3 dir2 = (dif2.cross(oldSmokeDir)).ANormalize();
345 
346 			const float a1 =
347 				((1.0f - (0.0f / SMOKE_TIME))) *
348 				(0.7f + math::fabs(dif1.dot(dir)));
349 			const float a2 =
350 				(age < 8)? 0.0f:
351 				((1.0f - (age2 / SMOKE_TIME))) *
352 				(0.7f + math::fabs(dif2.dot(oldSmokeDir)));
353 			const float alpha1 = Clamp(a1, 0.0f, 1.0f);
354 			const float alpha2 = Clamp(a2, 0.0f, 1.0f);
355 
356 			const float size1 = 1.0f;
357 			const float size2 = (1.0f + age2 * (1.0f / SMOKE_TIME) * 7.0f);
358 
359 			const float txs =
360 				weaponDef->visuals.texture2->xend -
361 				(weaponDef->visuals.texture2->xend - weaponDef->visuals.texture2->xstart) *
362 				(age2 / 8.0f);
363 
364 			SColor col (color * alpha1, color * alpha1, color * alpha1, alpha1);
365 			SColor col2(color * alpha2, color * alpha2, color * alpha2, alpha2);
366 
367 			va->AddVertexQTC(drawPos  - dir1 * size1, txs,                               weaponDef->visuals.texture2->ystart, col);
368 			va->AddVertexQTC(drawPos  + dir1 * size1, txs,                               weaponDef->visuals.texture2->yend,   col);
369 			va->AddVertexQTC(oldSmoke + dir2 * size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->yend,   col2);
370 			va->AddVertexQTC(oldSmoke - dir2 * size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->ystart, col2);
371 		} else {
372 			// draw the trail as particles
373 			const float dist = pos.distance(oldSmoke);
374 			const float3 dirpos1 = pos - dir * dist * 0.33f;
375 			const float3 dirpos2 = oldSmoke + oldSmokeDir * dist * 0.33f;
376 			const SColor col(color, color, color);
377 
378 			for (int a = 0; a < curNumParts; ++a) {
379 				// CAUTION: loop count must match EnlargeArrays above
380 				const float size = 1 + (a * (1.0f / SMOKE_TIME) * 7.0f);
381 				const float3 pos1 = CalcBeizer((float)a / curNumParts, pos, dirpos1, dirpos2, oldSmoke);
382 
383 				#define st projectileDrawer->smoketex[0]
384 				va->AddVertexQTC(pos1 + ( camera->up + camera->right) * size, st->xstart, st->ystart, col);
385 				va->AddVertexQTC(pos1 + ( camera->up - camera->right) * size, st->xend,   st->ystart, col);
386 				va->AddVertexQTC(pos1 + (-camera->up - camera->right) * size, st->xend,   st->ystart, col);
387 				va->AddVertexQTC(pos1 + (-camera->up + camera->right) * size, st->xstart, st->ystart, col);
388 				#undef st
389 			}
390 		}
391 	}
392 
393 	DrawCallback();
394 }
395 
DrawCallback()396 void CStarburstProjectile::DrawCallback()
397 {
398 	inArray = true;
399 
400 	unsigned char col[4];
401 
402 	unsigned int part = curTracerPart;
403 
404 	for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
405 		const TracerPart* tracerPart = &tracerParts[part];
406 		const float3& opos = tracerPart->pos;
407 		const float3& odir = tracerPart->dir;
408 		const float ospeed = tracerPart->speedf;
409 		float aa = 0;
410 
411 		unsigned int numAgeMods = tracerPart->numAgeMods;
412 
413 		for (int num = 0; num < numAgeMods; aa += TRACER_PARTS_STEP, ++num) {
414 			const float ageMod = tracerPart->ageMods[num];
415 			const float age2 = (a + (aa / (ospeed + 0.01f))) * 0.2f;
416 			const float3 interPos = opos - (odir * ((a * 0.5f) + aa));
417 
418 			col[3] = 1;
419 
420 			if (missileAge < 20) {
421 				const float alpha = std::max(0.0f, (1.0f - age2) * (1.0f - age2));
422 				col[0] = (255 * alpha);
423 				col[1] = (200 * alpha);
424 				col[2] = (150 * alpha);
425 			} else {
426 				const float alpha = std::max(0.0f, ((1.0f - age2) * std::max(0.0f, 1.0f - age2)));
427 				col[0] = (255 * alpha);
428 				col[1] = (200 * alpha);
429 				col[2] = (150 * alpha);
430 			}
431 
432 			const float drawsize = 1.0f + age2 * 0.8f * ageMod * 7;
433 
434 			#define wt3 weaponDef->visuals.texture3
435 			va->AddVertexTC(interPos - camera->right * drawsize - camera->up * drawsize, wt3->xstart, wt3->ystart, col);
436 			va->AddVertexTC(interPos + camera->right * drawsize - camera->up * drawsize, wt3->xend,   wt3->ystart, col);
437 			va->AddVertexTC(interPos + camera->right * drawsize + camera->up * drawsize, wt3->xend,   wt3->yend,   col);
438 			va->AddVertexTC(interPos - camera->right * drawsize + camera->up * drawsize, wt3->xstart, wt3->yend,   col);
439 			#undef wt3
440 		}
441 
442 		// unsigned, so LHS will wrap around to UINT_MAX
443 		part = std::min(part - 1, NUM_TRACER_PARTS - 1);
444 	}
445 
446 	// draw the engine flare
447 	col[0] = 255;
448 	col[1] = 180;
449 	col[2] = 180;
450 	col[3] =   1;
451 
452 	const float fsize = 25.0f;
453 
454 	#define wt1 weaponDef->visuals.texture1
455 	va->AddVertexTC(drawPos - camera->right * fsize - camera->up * fsize, wt1->xstart, wt1->ystart, col);
456 	va->AddVertexTC(drawPos + camera->right * fsize - camera->up * fsize, wt1->xend,   wt1->ystart, col);
457 	va->AddVertexTC(drawPos + camera->right * fsize + camera->up * fsize, wt1->xend,   wt1->yend,   col);
458 	va->AddVertexTC(drawPos - camera->right * fsize + camera->up * fsize, wt1->xstart, wt1->yend,   col);
459 	#undef wt1
460 }
461 
ShieldRepulse(CPlasmaRepulser * shield,float3 shieldPos,float shieldForce,float shieldMaxSpeed)462 int CStarburstProjectile::ShieldRepulse(CPlasmaRepulser* shield, float3 shieldPos, float shieldForce, float shieldMaxSpeed)
463 {
464 	const float3 rdir = (pos - shieldPos).Normalize();
465 
466 	if (ttl > 0) {
467 		float3 dif2 = rdir - dir;
468 
469 		// steer away twice as fast as we can steer toward target
470 		const float tracking = std::max(shieldForce * 0.05f, weaponDef->turnrate * 2.0f);
471 
472 		if (dif2.Length() < tracking) {
473 			dir = rdir;
474 		} else {
475 			dif2 = (dif2 - (dir * (dif2.dot(dir)))).Normalize();
476 			dir = (dir + (dif2 * tracking)).Normalize();
477 		}
478 
479 		return 2;
480 	}
481 
482 	return 0;
483 }
484