1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "Game/Camera.h"
5 #include "Game/GameHelper.h"
6 #include "Game/GlobalUnsynced.h"
7 #include "Map/Ground.h"
8 #include "Rendering/GlobalRendering.h"
9 #include "Rendering/GL/VertexArray.h"
10 #include "Rendering/Textures/TextureAtlas.h"
11 #include "Rendering/Colors.h"
12 #include "Rendering/ProjectileDrawer.h"
13 #include "Rendering/UnitDrawer.h"
14 #include "Rendering/Models/IModelParser.h"
15 #include "Rendering/Models/3DOParser.h"
16 #include "Rendering/Models/S3OParser.h"
17 #include "Sim/Misc/GlobalSynced.h"
18 #include "Sim/Projectiles/PieceProjectile.h"
19 #include "Sim/Projectiles/ProjectileHandler.h"
20 #include "Sim/Projectiles/Unsynced/SmokeTrailProjectile.h"
21 #include "Sim/Units/Unit.h"
22 #include "Sim/Units/UnitDef.h"
23 #include "System/Matrix44f.h"
24 #include "System/myMath.h"
25 #include "System/Sync/SyncTracer.h"
26 #include "System/Util.h"
27 
28 #define SMOKE_TIME 40
29 #define NUM_TRAIL_PARTS (sizeof(fireTrailPoints) / sizeof(fireTrailPoints[0]))
30 
31 CR_BIND_DERIVED(CPieceProjectile, CProjectile, (NULL, NULL, ZeroVector, ZeroVector, 0, 0))
32 
33 CR_REG_METADATA(CPieceProjectile,(
34 	CR_SETFLAG(CF_Synced),
35 
36 	CR_MEMBER(age),
37 	CR_MEMBER(explFlags),
38 	CR_MEMBER(dispList),
39 	// NOTE: what about this?
40 	// CR_MEMBER(omp),
41 	CR_MEMBER(curCallback),
42 	CR_MEMBER(spinVec),
43 	CR_MEMBER(spinSpeed),
44 	CR_MEMBER(spinAngle),
45 	CR_MEMBER(oldSmokePos),
46 	CR_MEMBER(oldSmokeDir),
47 	CR_MEMBER(alphaThreshold),
48 	// CR_MEMBER(target),
49 	CR_MEMBER(drawTrail),
50 
51 	CR_RESERVED(36)
52 ))
53 
CPieceProjectile(CUnit * owner,LocalModelPiece * lmp,const float3 & pos,const float3 & speed,int flags,float radius)54 CPieceProjectile::CPieceProjectile(
55 	CUnit* owner,
56 	LocalModelPiece* lmp,
57 	const float3& pos,
58 	const float3& speed,
59 	int flags,
60 	float radius
61 ):
62 	CProjectile(pos, speed, owner, true, false, true),
63 
64 	age(0),
65 	explFlags(flags),
66 	dispList((lmp != NULL)? lmp->dispListID: 0),
67 
68 	omp(NULL),
69 	curCallback(NULL),
70 
71 	spinSpeed(0.0f),
72 	spinAngle(0.0f),
73 	alphaThreshold(0.1f),
74 
75 	oldSmokePos(pos),
76 	oldSmokeDir(FwdVector),
77 
78 	drawTrail(true)
79 {
80 	checkCol = false;
81 
82 	if (owner != NULL) {
83 		if ((explFlags & PF_NoCEGTrail) == 0) {
84 			explGenHandler->GenExplosion((cegID = owner->unitDef->GetPieceExplosionGeneratorID(gs->randInt())), pos, speed, 100, 0.0f, 0.0f, NULL, NULL);
85 		}
86 
87 		model = owner->model;
88 		alphaThreshold = owner->alphaThreshold;
89 		explFlags |= (PF_NoCEGTrail * (cegID == -1u));
90 	}
91 
92 	if (lmp) {
93 		omp = lmp->original;
94 	} else {
95 		omp = NULL;
96 	}
97 
98 	castShadow = true;
99 
100 	if (pos.y - CGround::GetApproximateHeight(pos.x, pos.z) > 10) {
101 		useAirLos = true;
102 	}
103 
104 	oldSmokeDir = speed;
105 	oldSmokeDir.Normalize();
106 
107 
108 	const float3 camVec = pos - camera->GetPos();
109 	const float  camDst = camVec.Length() + 0.01f;
110 	const float3 camDir = camVec / camDst;
111 
112 	drawTrail = ((camDst + (1.0f - math::fabs(camDir.dot(oldSmokeDir))) * 3000.0f) >= 200.0f);
113 
114 	// neither spinVec nor spinSpeed technically
115 	// needs to be synced, but since instances of
116 	// this class are themselves synced and have
117 	// LuaSynced{Ctrl, Read} exposure we treat
118 	// them that way for consistency
119 	spinVec = gs->randVector();
120 	spinVec.Normalize();
121 	spinSpeed = gs->randFloat() * 20;
122 
123 	for (unsigned int a = 0; a < NUM_TRAIL_PARTS; ++a) {
124 		fireTrailPoints[a] = new FireTrailPoint();
125 		fireTrailPoints[a]->pos = pos;
126 		fireTrailPoints[a]->size = gu->RandFloat() * 2 + 2;
127 	}
128 
129 	SetRadiusAndHeight(radius, 0.0f);
130 	drawRadius = 32.0f;
131 
132 #ifdef TRACE_SYNC
133 	tracefile << "New CPieceProjectile: ";
134 	tracefile << pos.x << " " << pos.y << " " << pos.z << " " << speed.x << " " << speed.y << " " << speed.z << "\n";
135 #endif
136 
137 	projectileHandler->AddProjectile(this);
138 }
139 
Detach()140 void CPieceProjectile::Detach()
141 {
142 	// SYNCED
143 	if (curCallback) {
144 		// this is unsynced, but it prevents some callback crash on exit
145 		curCallback->drawCallbacker = NULL;
146 	}
147 
148 	CProjectile::Detach();
149 }
150 
~CPieceProjectile()151 CPieceProjectile::~CPieceProjectile()
152 {
153 	// UNSYNCED
154 	for (unsigned int a = 0; a < NUM_TRAIL_PARTS; ++a) {
155 		delete fireTrailPoints[a];
156 	}
157 }
158 
159 
Collision()160 void CPieceProjectile::Collision()
161 {
162 	const float3& norm = CGround::GetNormal(pos.x, pos.z);
163 	const float ns = speed.dot(norm);
164 
165 	SetVelocityAndSpeed(speed - (norm * ns * 1.6f));
166 	SetPosition(pos + (norm * 0.1f));
167 
168 	if (explFlags & PF_Explode) {
169 		const DamageArray damageArray(50.0f);
170 		const CGameHelper::ExplosionParams params = {
171 			pos,
172 			ZeroVector,
173 			damageArray,
174 			NULL,              // weaponDef
175 			owner(),
176 			NULL,              // hitUnit
177 			NULL,              // hitFeature
178 			5.0f,              // craterAreaOfEffect
179 			5.0f,              // damageAreaOfEffect
180 			0.0f,              // edgeEffectiveness
181 			10.0f,             // explosionSpeed
182 			1.0f,              // gfxMod
183 			false,             // impactOnly
184 			false,             // ignoreOwner
185 			true,              // damageGround
186 			static_cast<unsigned int>(id)
187 		};
188 
189 		helper->Explosion(params);
190 	}
191 	if (explFlags & PF_Smoke) {
192 		if (explFlags & PF_NoCEGTrail) {
193 			CSmokeTrailProjectile* tp = new CSmokeTrailProjectile(
194 				owner(),
195 				pos, oldSmokePos,
196 				dir, oldSmokeDir,
197 				false,
198 				true,
199 				(NUM_TRAIL_PARTS - 1),
200 				SMOKE_TIME,
201 				0.5f,
202 				drawTrail,
203 				NULL,
204 				projectileDrawer->smoketrailtex);
205 
206 			tp->creationTime += (NUM_TRAIL_PARTS - (age & (NUM_TRAIL_PARTS - 1)));
207 		}
208 	}
209 
210 	// keep flying with some small probability
211 	if (gs->randFloat() < 0.666f) {
212 		CProjectile::Collision();
213 	}
214 
215 	oldSmokePos = pos;
216 }
217 
Collision(CUnit * unit)218 void CPieceProjectile::Collision(CUnit* unit)
219 {
220 	if (unit == owner())
221 		return;
222 
223 	if (explFlags & PF_Explode) {
224 		const DamageArray damageArray(50.0f);
225 		const CGameHelper::ExplosionParams params = {
226 			pos,
227 			ZeroVector,
228 			damageArray,
229 			NULL,              // weaponDef
230 			owner(),
231 			unit,              // hitUnit
232 			NULL,              // hitFeature
233 			5.0f,              // craterAreaOfEffect
234 			5.0f,              // damageAreaOfEffect
235 			0.0f,              // edgeEffectiveness
236 			10.0f,             // explosionSpeed
237 			1.0f,              // gfxMod
238 			false,             // impactOnly
239 			false,             // ignoreOwner
240 			true,              // damageGround
241 			static_cast<unsigned int>(id)
242 		};
243 
244 		helper->Explosion(params);
245 	}
246 	if (explFlags & PF_Smoke) {
247 		if (explFlags & PF_NoCEGTrail) {
248 			CSmokeTrailProjectile* tp = new CSmokeTrailProjectile(
249 				owner(),
250 				pos, oldSmokePos,
251 				dir, oldSmokeDir,
252 				false,
253 				true,
254 				NUM_TRAIL_PARTS - 1,
255 				SMOKE_TIME,
256 				0.5f,
257 				drawTrail,
258 				NULL,
259 				projectileDrawer->smoketrailtex
260 			);
261 
262 			tp->creationTime += (NUM_TRAIL_PARTS - (age & (NUM_TRAIL_PARTS - 1)));
263 		}
264 	}
265 
266 	CProjectile::Collision(unit);
267 	oldSmokePos = pos;
268 }
269 
270 
HasVertices() const271 bool CPieceProjectile::HasVertices() const
272 {
273 	if (omp == NULL)
274 		return false;
275 
276 	return (omp->GetVertexCount() > 0);
277 }
278 
RandomVertexPos()279 float3 CPieceProjectile::RandomVertexPos()
280 {
281 	if (!HasVertices())
282 		return ZeroVector;
283 
284 	const int vertexNum = (int) (gu->RandFloat() * 0.99f * omp->GetVertexCount());
285 	const float3& pos = omp->GetVertexPos(vertexNum);
286 
287 	return pos;
288 }
289 
290 
Update()291 void CPieceProjectile::Update()
292 {
293 	if (!luaMoveCtrl) {
294 		if (explFlags & PF_Fall)
295 			speed.y += mygravity;
296 
297 		SetVelocityAndSpeed(speed * 0.997f);
298 		SetPosition(pos + speed);
299 	}
300 
301 	spinAngle += spinSpeed;
302 	age += 1;
303 	checkCol |= (age > 10);
304 
305 	if ((explFlags & PF_Fire) != 0 && HasVertices()) {
306 		FireTrailPoint* tempTrailPoint = fireTrailPoints[NUM_TRAIL_PARTS - 1];
307 
308 		for (int a = NUM_TRAIL_PARTS - 2; a >= 0; --a) {
309 			fireTrailPoints[a + 1] = fireTrailPoints[a];
310 		}
311 
312 		CMatrix44f m(pos);
313 		m.Rotate(spinAngle * PI / 180.0f, spinVec);
314 		m.Translate(RandomVertexPos());
315 
316 		fireTrailPoints[0] = tempTrailPoint;
317 		fireTrailPoints[0]->pos = m.GetPos();
318 		fireTrailPoints[0]->size = gu->RandFloat() * 1 + 1;
319 	}
320 
321 	if ((explFlags & PF_NoCEGTrail) == 0) {
322 		// TODO: pass a more sensible ttl to the CEG (age-related?)
323 		explGenHandler->GenExplosion(cegID, pos, speed, 100, 0.0f, 0.0f, NULL, NULL);
324 		return;
325 	}
326 
327 	if ((explFlags & PF_Smoke) != 0) {
328 		if ((age & (NUM_TRAIL_PARTS - 1)) == 0) {
329 			if (curCallback != NULL) {
330 				curCallback->drawCallbacker = NULL;
331 			}
332 
333 			curCallback = new CSmokeTrailProjectile(
334 				owner(),
335 				pos, oldSmokePos,
336 				dir, oldSmokeDir,
337 				age == (NUM_TRAIL_PARTS - 1),
338 				false,
339 				14,
340 				SMOKE_TIME,
341 				0.5f,
342 				drawTrail,
343 				this,
344 				projectileDrawer->smoketrailtex
345 			);
346 
347 			useAirLos = curCallback->useAirLos;
348 
349 			oldSmokePos = pos;
350 			oldSmokeDir = dir;
351 
352 			if (!drawTrail) {
353 				float3 camDir = pos - camera->GetPos();
354 
355 				const float camPieceDist = camDir.LengthNormalize();
356 				const float camAngleScale = (1.0f - math::fabs(camDir.dot(dir))) * 3000.0f;
357 
358 				drawTrail = ((camPieceDist + camAngleScale) >= 300.0f);
359 			}
360 		}
361 	}
362 }
363 
364 
365 
Draw()366 void CPieceProjectile::Draw()
367 {
368 	if ((explFlags & PF_NoCEGTrail) == 0)
369 		return;
370 
371 	if ((explFlags & PF_Smoke) != 0) {
372 		// this piece leaves a default (non-CEG) smoketrail
373 		inArray = true;
374 
375 		const int numParts = age & (NUM_TRAIL_PARTS - 1);
376 		float age2 = (age & (NUM_TRAIL_PARTS - 1)) + globalRendering->timeOffset;
377 		float color = 0.5f;
378 		unsigned char col[4];
379 
380 		va->EnlargeArrays(4+4*numParts,0,VA_SIZE_TC);
381 
382 		if (drawTrail) {
383 			// draw the trail as a single quad if camera close enough
384 			const float3 dif  = (drawPos - camera->GetPos()).Normalize();
385 			const float3 dir1 = (dif.cross(dir)).Normalize();
386 			const float3 dif2 = (oldSmokePos - camera->GetPos()).Normalize();
387 			const float3 dir2 = (dif2.cross(oldSmokeDir)).Normalize();
388 
389 			float a1 = ((1 - 0.0f / SMOKE_TIME) * 255) * (0.7f + math::fabs(dif.dot(dir)));
390 			float alpha = std::min(255.0f, std::max(0.0f, a1));
391 			col[0] = (unsigned char) (color * alpha);
392 			col[1] = (unsigned char) (color * alpha);
393 			col[2] = (unsigned char) (color * alpha);
394 			col[3] = (unsigned char) (alpha);
395 
396 			unsigned char col2[4];
397 			float a2 = ((1 - float(age2) / SMOKE_TIME) * 255) * (0.7f + math::fabs(dif2.dot(oldSmokeDir)));
398 
399 			if (age < NUM_TRAIL_PARTS)
400 				a2 = 0.0f;
401 
402 			alpha = std::min(255.0f, std::max(0.0f, a2));
403 			col2[0] = (unsigned char) (color * alpha);
404 			col2[1] = (unsigned char) (color * alpha);
405 			col2[2] = (unsigned char) (color * alpha);
406 			col2[3] = (unsigned char) (alpha);
407 
408 			const float size = 1.0f;
409 			const float size2 = 1 + (age2 * (1.0f / SMOKE_TIME)) * 14;
410 			const float txs =
411 				projectileDrawer->smoketrailtex->xstart -
412 				(projectileDrawer->smoketrailtex->xend - projectileDrawer->smoketrailtex->xstart) *
413 				(age2 / NUM_TRAIL_PARTS);
414 
415 			va->AddVertexQTC(drawPos - dir1 * size, txs, projectileDrawer->smoketrailtex->ystart, col);
416 			va->AddVertexQTC(drawPos + dir1 * size, txs, projectileDrawer->smoketrailtex->yend,   col);
417 			va->AddVertexQTC(oldSmokePos + dir2 * size2, projectileDrawer->smoketrailtex->xend, projectileDrawer->smoketrailtex->yend,   col2);
418 			va->AddVertexQTC(oldSmokePos - dir2 * size2, projectileDrawer->smoketrailtex->xend, projectileDrawer->smoketrailtex->ystart, col2);
419 		} else {
420 			// draw the trail as particles
421 			const float dist = pos.distance(oldSmokePos);
422 			const float3 dirpos1 = pos - dir * dist * 0.33f;
423 			const float3 dirpos2 = oldSmokePos + oldSmokeDir * dist * 0.33f;
424 
425 			// CAUTION: loop count must match EnlargeArrays above
426 			for (int a = 0; a < numParts; ++a) {
427 				float alpha = 255.0f;
428 				col[0] = (unsigned char) (color * alpha);
429 				col[1] = (unsigned char) (color * alpha);
430 				col[2] = (unsigned char) (color * alpha);
431 				col[3] = (unsigned char) (alpha);
432 
433 				const float size = 1.0f + ((a) * (1.0f / SMOKE_TIME)) * 14.0f;
434 				const float3 pos1 = CalcBeizer(float(a) / (numParts), pos, dirpos1, dirpos2, oldSmokePos);
435 
436 				#define st projectileDrawer->smoketex[0]
437 				va->AddVertexQTC(pos1 + ( camera->up+camera->right) * size, st->xstart, st->ystart, col);
438 				va->AddVertexQTC(pos1 + ( camera->up-camera->right) * size, st->xend,   st->ystart, col);
439 				va->AddVertexQTC(pos1 + (-camera->up-camera->right) * size, st->xend,   st->ystart, col);
440 				va->AddVertexQTC(pos1 + (-camera->up+camera->right) * size, st->xstart, st->ystart, col);
441 				#undef st
442 			}
443 		}
444 	}
445 
446 	DrawCallback();
447 }
448 
DrawOnMinimap(CVertexArray & lines,CVertexArray & points)449 void CPieceProjectile::DrawOnMinimap(CVertexArray& lines, CVertexArray& points)
450 {
451 	points.AddVertexQC(pos, color4::red);
452 }
453 
DrawCallback()454 void CPieceProjectile::DrawCallback()
455 {
456 	inArray = true;
457 
458 	// fireTrailPoints contains nothing useful if piece is empty
459 	if ((explFlags & PF_Fire) != 0 && HasVertices()) {
460 		va->EnlargeArrays(NUM_TRAIL_PARTS * 4, 0, VA_SIZE_TC);
461 
462 		for (unsigned int age = 0; age < NUM_TRAIL_PARTS; ++age) {
463 			const float3 interPos = fireTrailPoints[age]->pos;
464 			const float size = fireTrailPoints[age]->size;
465 			const float modage = age * 1.0f;
466 
467 			const float alpha = (7.5f - modage) * (1.0f / NUM_TRAIL_PARTS);
468 			const float drawsize = (0.5f + modage) * size;
469 
470 			unsigned char col[4];
471 
472 			col[0] = (unsigned char) (255 * alpha);
473 			col[1] = (unsigned char) (200 * alpha);
474 			col[2] = (unsigned char) (150 * alpha);
475 			col[3] = (unsigned char) (alpha * 50);
476 
477 			#define eft projectileDrawer->explofadetex
478 			va->AddVertexQTC(interPos - camera->right * drawsize-camera->up * drawsize, eft->xstart, eft->ystart, col);
479 			va->AddVertexQTC(interPos + camera->right * drawsize-camera->up * drawsize, eft->xend,   eft->ystart, col);
480 			va->AddVertexQTC(interPos + camera->right * drawsize+camera->up * drawsize, eft->xend,   eft->yend,   col);
481 			va->AddVertexQTC(interPos - camera->right * drawsize+camera->up * drawsize, eft->xstart, eft->yend,   col);
482 			#undef eft
483 		}
484 	}
485 }
486