1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "SolidObject.h"
5 #include "SolidObjectDef.h"
6 #include "Map/ReadMap.h"
7 #include "Map/Ground.h"
8 #include "Sim/Misc/CollisionVolume.h"
9 #include "Sim/Misc/DamageArray.h"
10 #include "Sim/Misc/GroundBlockingObjectMap.h"
11 #include "Sim/MoveTypes/MoveDefHandler.h"
12 #include "System/myMath.h"
13 
14 int CSolidObject::deletingRefID = -1;
15 
16 const float CSolidObject::DEFAULT_MASS = 1e5f;
17 const float CSolidObject::MINIMUM_MASS = 1e0f; // 1.0f
18 const float CSolidObject::MAXIMUM_MASS = 1e6f;
19 
20 CR_BIND_DERIVED(CSolidObject, CWorldObject, )
21 CR_REG_METADATA(CSolidObject,
22 (
23 	CR_MEMBER(health),
24 	CR_MEMBER(mass),
25 	CR_MEMBER(crushResistance),
26 
27 	CR_MEMBER(crushable),
28 	CR_MEMBER(immobile),
29 	CR_MEMBER(blockEnemyPushing),
30 	CR_MEMBER(blockHeightChanges),
31 
32 	CR_MEMBER(luaDraw),
33 	CR_MEMBER(noSelect),
34 
35 	CR_MEMBER(xsize),
36 	CR_MEMBER(zsize),
37  	CR_MEMBER(footprint),
38 	CR_MEMBER(heading),
39 
40 	CR_ENUM_MEMBER(physicalState),
41 	CR_ENUM_MEMBER(collidableState),
42 
43 	CR_MEMBER(team),
44 	CR_MEMBER(allyteam),
45 
46 	CR_MEMBER(tempNum),
47 
48 	CR_MEMBER(objectDef),
49 	CR_MEMBER(moveDef),
50 	CR_MEMBER(collisionVolume),
51 	CR_IGNORED(groundDecal),
52 
53 	CR_MEMBER(frontdir),
54 	CR_MEMBER(rightdir),
55 	CR_MEMBER(updir),
56 
57 	CR_MEMBER(relMidPos),
58  	CR_MEMBER(relAimPos),
59 	CR_MEMBER(midPos),
60 	CR_MEMBER(aimPos),
61 	CR_MEMBER(mapPos),
62 	CR_MEMBER(groundBlockPos),
63 
64 	CR_MEMBER(dragScales),
65 
66 	CR_MEMBER(drawPos),
67 	CR_MEMBER(drawMidPos),
68 	// CR_MEMBER(blockMap), //FIXME add bitwiseenum to creg
69 
70 	CR_MEMBER(buildFacing)
71 ))
72 
73 
CSolidObject()74 CSolidObject::CSolidObject():
75 	health(0.0f),
76 	mass(DEFAULT_MASS),
77 	crushResistance(0.0f),
78 
79 	crushable(false),
80 	immobile(false),
81 	blockEnemyPushing(true),
82 	blockHeightChanges(false),
83 
84 	luaDraw(false),
85 	noSelect(false),
86 
87 	xsize(1),
88 	zsize(1),
89 	footprint(1, 1),
90 
91 	heading(0),
92 
93 	// objects start out non-blocking but fully collidable
94 	// SolidObjectDef::collidable controls only the SO-bit
95 	physicalState(PhysicalState(PSTATE_BIT_ONGROUND)),
96 	collidableState(CollidableState(CSTATE_BIT_SOLIDOBJECTS | CSTATE_BIT_PROJECTILES | CSTATE_BIT_QUADMAPRAYS)),
97 
98 	team(0),
99 	allyteam(0),
100 
101 	tempNum(0),
102 
103 	objectDef(NULL),
104 	moveDef(NULL),
105 	collisionVolume(NULL),
106 	groundDecal(NULL),
107 
108 	frontdir( FwdVector),
109 	rightdir(-RgtVector),
110 	updir(UpVector),
111 
112 	midPos(pos),
113 	mapPos(GetMapPos()),
114 
115 	dragScales(OnesVector),
116 
117 	blockMap(NULL),
118 	buildFacing(0)
119 {
120 }
121 
~CSolidObject()122 CSolidObject::~CSolidObject() {
123 	ClearCollidableStateBit(CSTATE_BIT_SOLIDOBJECTS | CSTATE_BIT_PROJECTILES | CSTATE_BIT_QUADMAPRAYS);
124 
125 	delete collisionVolume;
126 	collisionVolume = NULL;
127 }
128 
UpdatePhysicalState(float eps)129 void CSolidObject::UpdatePhysicalState(float eps) {
130 	const float gh = CGround::GetHeightReal(pos.x, pos.z);
131 	const float wh = std::max(gh, 0.0f);
132 
133 	unsigned int ps = physicalState;
134 
135 	// clear all non-void non-special bits
136 	ps &= (~PSTATE_BIT_ONGROUND   );
137 	ps &= (~PSTATE_BIT_INWATER    );
138 	ps &= (~PSTATE_BIT_UNDERWATER );
139 	ps &= (~PSTATE_BIT_UNDERGROUND);
140 	ps &= (~PSTATE_BIT_INAIR      );
141 
142 	// NOTE:
143 	//   height is not in general equivalent to radius * 2.0
144 	//   the height property is used for much fewer purposes
145 	//   than radius, so less reliable for determining state
146 	#define MASK_NOAIR (PSTATE_BIT_ONGROUND | PSTATE_BIT_INWATER | PSTATE_BIT_UNDERWATER | PSTATE_BIT_UNDERGROUND)
147 	ps |= (PSTATE_BIT_ONGROUND    * ((   pos.y -         gh) <=  eps));
148 	ps |= (PSTATE_BIT_INWATER     * ((   pos.y             ) <= 0.0f));
149 //	ps |= (PSTATE_BIT_UNDERWATER  * ((   pos.y +     height) <  0.0f));
150 //	ps |= (PSTATE_BIT_UNDERGROUND * ((   pos.y +     height) <    gh));
151 	ps |= (PSTATE_BIT_UNDERWATER  * ((midPos.y +     radius) <  0.0f));
152 	ps |= (PSTATE_BIT_UNDERGROUND * ((midPos.y +     radius) <    gh));
153 	ps |= (PSTATE_BIT_INAIR       * ((   pos.y -         wh) >   eps));
154 	ps |= (PSTATE_BIT_INAIR       * ((    ps   & MASK_NOAIR) ==    0));
155 	#undef MASK_NOAIR
156 
157 	physicalState = static_cast<PhysicalState>(ps);
158 
159 	// verify mutex relations (A != B); if one
160 	// fails then A and B *must* both be false
161 	//
162 	// problem case: pos.y < eps (but > 0) &&
163 	// gh < -eps causes ONGROUND and INAIR to
164 	// both be false but INWATER will fail too
165 	#if 0
166 	assert((IsInAir() != IsOnGround()) || IsInWater());
167 	assert((IsInAir() != IsInWater()) || IsOnGround());
168 	assert((IsInAir() != IsUnderWater()) || (IsOnGround() || IsInWater()));
169 	#endif
170 }
171 
172 
SetVoidState()173 bool CSolidObject::SetVoidState() {
174 	if (IsInVoid())
175 		return false;
176 
177 	// make us transparent to raycasts, quadfield queries, etc.
178 	// need to push and pop state bits in case Lua changes them
179 	// (otherwise gadgets must listen to all Unit*Loaded events)
180 	PushCollidableStateBit(CSTATE_BIT_SOLIDOBJECTS);
181 	PushCollidableStateBit(CSTATE_BIT_PROJECTILES);
182 	PushCollidableStateBit(CSTATE_BIT_QUADMAPRAYS);
183 	ClearCollidableStateBit(CSTATE_BIT_SOLIDOBJECTS | CSTATE_BIT_PROJECTILES | CSTATE_BIT_QUADMAPRAYS);
184 	SetPhysicalStateBit(PSTATE_BIT_INVOID);
185 
186 	UnBlock();
187 	collisionVolume->SetIgnoreHits(true);
188 	return true;
189 }
190 
ClearVoidState()191 bool CSolidObject::ClearVoidState() {
192 	if (!IsInVoid())
193 		return false;
194 
195 	PopCollidableStateBit(CSTATE_BIT_SOLIDOBJECTS);
196 	PopCollidableStateBit(CSTATE_BIT_PROJECTILES);
197 	PopCollidableStateBit(CSTATE_BIT_QUADMAPRAYS);
198 	ClearPhysicalStateBit(PSTATE_BIT_INVOID);
199 
200 	Block();
201 	collisionVolume->SetIgnoreHits(false);
202 	return true;
203 }
204 
UpdateVoidState(bool set)205 void CSolidObject::UpdateVoidState(bool set) {
206 	if (set) {
207 		SetVoidState();
208 	} else {
209 		ClearVoidState();
210 	}
211 
212 	noSelect = (set || !objectDef->selectable);
213 }
214 
215 
216 
UnBlock()217 void CSolidObject::UnBlock() {
218 	if (!IsBlocking())
219 		return;
220 
221 	groundBlockingObjectMap->RemoveGroundBlockingObject(this);
222 	assert(!IsBlocking());
223 }
224 
Block()225 void CSolidObject::Block() {
226 	// no point calling this if object is not
227 	// collidable in principle, but simplifies
228 	// external code to allow it
229 	if (!HasCollidableStateBit(CSTATE_BIT_SOLIDOBJECTS))
230 		return;
231 
232 	if (IsBlocking() && !BlockMapPosChanged())
233 		return;
234 
235 	UnBlock();
236 
237 	// only block when `touching` the ground
238 	if ((pos.y - radius) <= CGround::GetHeightAboveWater(pos.x, pos.z)) {
239 		groundBlockingObjectMap->AddGroundBlockingObject(this);
240 		assert(IsBlocking());
241 	}
242 }
243 
244 
GetGroundBlockingMaskAtPos(float3 gpos) const245 YardMapStatus CSolidObject::GetGroundBlockingMaskAtPos(float3 gpos) const
246 {
247 	if (!blockMap)
248 		return YARDMAP_OPEN;
249 
250 	const int hxsize = footprint.x >> 1;
251 	const int hzsize = footprint.y >> 1;
252 
253 	float3 frontv;
254 	float3 rightv;
255 
256 	#if 1
257 		// use continuous floating-point space
258 		gpos   -= pos;
259 		gpos.x += SQUARE_SIZE / 2; //??? needed to move to SQUARE-center? (possibly current input is wrong)
260 		gpos.z += SQUARE_SIZE / 2;
261 
262 		frontv =  frontdir;
263 		rightv = -rightdir; //??? spring's unit-rightdir is in real the LEFT vector :x
264 	#else
265 		// use old fixed space (4 facing dirs & ints for unit positions)
266 
267 		// form the rotated axis vectors
268 		static float3 fronts[] = {FwdVector,  RgtVector, -FwdVector, -RgtVector};
269 		static float3 rights[] = {RgtVector, -FwdVector, -RgtVector,  FwdVector};
270 
271 		// get used axis vectors
272 		frontv = fronts[buildFacing];
273 		rightv = rights[buildFacing];
274 
275 		gpos -= float3(mapPos.x * SQUARE_SIZE, 0.0f, mapPos.y * SQUARE_SIZE);
276 
277 		// need to revert some of the transformations of CSolidObject::GetMapPos()
278 		gpos.x += SQUARE_SIZE / 2 - (this->xsize >> 1) * SQUARE_SIZE;
279 		gpos.z += SQUARE_SIZE / 2 - (this->zsize >> 1) * SQUARE_SIZE;
280 	#endif
281 
282 	// transform worldspace pos to unit rotation dependent `centered blockmap space` [-hxsize .. +hxsize] x [-hzsize .. +hzsize]
283 	float by = frontv.dot(gpos) / SQUARE_SIZE;
284 	float bx = rightv.dot(gpos) / SQUARE_SIZE;
285 
286 	// outside of `blockmap space`?
287 	if ((math::fabsf(bx) >= hxsize) || (math::fabsf(by) >= hzsize))
288 		return YARDMAP_OPEN;
289 
290 	// transform: [(-hxsize + eps) .. (+hxsize - eps)] x [(-hzsize + eps) .. (+hzsize - eps)] -> [0 .. (xsize - 1)] x [0 .. (zsize - 1)]
291 	bx += hxsize;
292 	by += hzsize;
293 
294 	assert(int(bx) >= 0 && int(bx) < footprint.x);
295 	assert(int(by) >= 0 && int(by) < footprint.y);
296 
297 	// read from blockmap
298 	return blockMap[int(bx) + int(by) * footprint.x];
299 }
300 
301 
302 // FIXME move somewhere else?
GetMapPos(const float3 & position) const303 int2 CSolidObject::GetMapPos(const float3& position) const
304 {
305 	int2 mp;
306 
307 	mp.x = (int(position.x + SQUARE_SIZE / 2) / SQUARE_SIZE) - (xsize / 2);
308 	mp.y = (int(position.z + SQUARE_SIZE / 2) / SQUARE_SIZE) - (zsize / 2);
309 	mp.x = Clamp(mp.x, 0, gs->mapx - xsize);
310 	mp.y = Clamp(mp.y, 0, gs->mapy - zsize);
311 
312 	return mp;
313 }
314 
GetDragAccelerationVec(const float4 & params) const315 float3 CSolidObject::GetDragAccelerationVec(const float4& params) const {
316 	// KISS: use the cross-sectional area of a sphere, object shapes are complex
317 	// this is a massive over-estimation so pretend the radius is in centimeters
318 	// other units as normal: mass in kg, speed in elmos/frame, density in kg/m^3
319 	//
320 	// params.xyzw map: {{atmosphere, water}Density, {drag, friction}Coefficient}
321 	//
322 	const float3 speedSignVec = float3(Sign(speed.x), Sign(speed.y), Sign(speed.z));
323 	const float3 dragScaleVec = float3(
324 		IsInAir()    * dragScales.x * (0.5f * params.x * params.z * (M_PI * sqRadius * 0.01f * 0.01f)), // air
325 		IsInWater()  * dragScales.y * (0.5f * params.y * params.z * (M_PI * sqRadius * 0.01f * 0.01f)), // water
326 		IsOnGround() * dragScales.z * (                  params.w * (                           mass))  // ground
327 	);
328 
329 	float3 dragAccelVec;
330 
331 	dragAccelVec.x += (speed.x * speed.x * dragScaleVec.x * -speedSignVec.x);
332 	dragAccelVec.y += (speed.y * speed.y * dragScaleVec.x * -speedSignVec.y);
333 	dragAccelVec.z += (speed.z * speed.z * dragScaleVec.x * -speedSignVec.z);
334 
335 	dragAccelVec.x += (speed.x * speed.x * dragScaleVec.y * -speedSignVec.x);
336 	dragAccelVec.y += (speed.y * speed.y * dragScaleVec.y * -speedSignVec.y);
337 	dragAccelVec.z += (speed.z * speed.z * dragScaleVec.y * -speedSignVec.z);
338 
339 	// FIXME?
340 	//   magnitude of dynamic friction may or may not depend on speed
341 	//   coefficient must be multiplied by mass or it will be useless
342 	//   (due to division by mass since the coefficient is normalized)
343 	dragAccelVec.x += (math::fabs(speed.x) * dragScaleVec.z * -speedSignVec.x);
344 	dragAccelVec.y += (math::fabs(speed.y) * dragScaleVec.z * -speedSignVec.y);
345 	dragAccelVec.z += (math::fabs(speed.z) * dragScaleVec.z * -speedSignVec.z);
346 
347 	// convert from force
348 	dragAccelVec /= mass;
349 
350 	// limit the acceleration
351 	dragAccelVec.x = Clamp(dragAccelVec.x, -math::fabs(speed.x), math::fabs(speed.x));
352 	dragAccelVec.y = Clamp(dragAccelVec.y, -math::fabs(speed.y), math::fabs(speed.y));
353 	dragAccelVec.z = Clamp(dragAccelVec.z, -math::fabs(speed.z), math::fabs(speed.z));
354 
355 	return dragAccelVec;
356 }
357 
GetWantedUpDir(bool useGroundNormal) const358 float3 CSolidObject::GetWantedUpDir(bool useGroundNormal) const {
359 	// NOTE:
360 	//   for aircraft IsOnGround is already factored into useGroundNormal
361 	//   for ground-units the situation is more complicated because 1) it
362 	//   depends on the 'upright' tag and 2) ships and hovercraft are not
363 	//   "on the ground" all the time ('ground' is the ocean floor, *not*
364 	//   the water surface) and neither are tanks / bots due to impulses,
365 	//   gravity, ...
366 	//
367 	const float3 gn = CGround::GetSmoothNormal(pos.x, pos.z) * (    useGroundNormal);
368 	const float3 wn =                             UpVector  * (1 - useGroundNormal);
369 
370 	if (moveDef == NULL) {
371 		// aircraft cannot use updir reliably or their
372 		// coordinate-system would degenerate too much
373 		// over time without periodic re-ortho'ing
374 		return (gn + UpVector * (1 - useGroundNormal));
375 	}
376 
377 	// not an aircraft if we get here, prevent pitch changes
378 	// if(f) the object is neither on the ground nor in water
379 	// for whatever reason (GMT also prevents heading changes)
380 	if (!IsInAir()) {
381 		switch (moveDef->speedModClass) {
382 			case MoveDef::Tank:  { return ((gn + wn) * IsOnGround() + updir * (1 - IsOnGround())); } break;
383 			case MoveDef::KBot:  { return ((gn + wn) * IsOnGround() + updir * (1 - IsOnGround())); } break;
384 
385 			case MoveDef::Hover: { return ((UpVector * IsInWater()) + (gn + wn) * (1 - IsInWater())); } break;
386 			case MoveDef::Ship:  { return ((UpVector * IsInWater()) + (gn + wn) * (1 - IsInWater())); } break;
387 		}
388 	}
389 
390 	// prefer to keep local up-vector as long as possible
391 	return updir;
392 }
393 
394 
395 
SetHeadingFromDirection()396 void CSolidObject::SetHeadingFromDirection() {
397 	heading = GetHeadingFromVector(frontdir.x, frontdir.z);
398 }
399 
UpdateDirVectors(bool useGroundNormal)400 void CSolidObject::UpdateDirVectors(bool useGroundNormal)
401 {
402 	updir    = GetWantedUpDir(useGroundNormal);
403 	frontdir = GetVectorFromHeading(heading);
404 	rightdir = (frontdir.cross(updir)).Normalize();
405 	frontdir = updir.cross(rightdir);
406 }
407 
408 
409 
ForcedSpin(const float3 & newDir)410 void CSolidObject::ForcedSpin(const float3& newDir) {
411 	// new front-direction should be normalized
412 	assert(math::fabsf(newDir.SqLength() - 1.0f) <= float3::NORMALIZE_EPS);
413 
414 	// if zdir is parallel to world-y, use heading-vector
415 	// (or its inverse) as auxiliary to avoid degeneracies
416 	const float3 zdir = newDir;
417 	const float3 udir = mix(UpVector, (frontdir * Sign(-zdir.y)), (math::fabs(zdir.dot(UpVector)) >= 0.99f));
418 	const float3 xdir = (zdir.cross(udir)).Normalize();
419 	const float3 ydir = (xdir.cross(zdir)).Normalize();
420 
421 	frontdir = zdir;
422 	rightdir = xdir;
423 	   updir = ydir;
424 
425 	SetHeadingFromDirection();
426 	UpdateMidAndAimPos();
427 }
428 
429 
430 
Kill(CUnit * killer,const float3 & impulse,bool crushed)431 void CSolidObject::Kill(CUnit* killer, const float3& impulse, bool crushed) {
432 	UpdateVoidState(false);
433 
434 	if (crushed) {
435 		DoDamage(DamageArray(health + 1.0f), impulse, killer, -DAMAGE_EXTSOURCE_CRUSHED, -1);
436 	} else {
437 		DoDamage(DamageArray(health + 1.0f), impulse, killer, -DAMAGE_EXTSOURCE_KILLED, -1);
438 	}
439 }
440 
441