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