1 /************************************************************************************
2
3 AstroMenace
4 Hardcore 3D space scroll-shooter with spaceship upgrade possibilities.
5 Copyright (c) 2006-2019 Mikhail Kurinnoi, Viewizard
6
7
8 AstroMenace is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 AstroMenace is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with AstroMenace. If not, see <https://www.gnu.org/licenses/>.
20
21
22 Website: https://viewizard.com/
23 Project: https://github.com/viewizard/astromenace
24 E-mail: viewizard@viewizard.com
25
26 *************************************************************************************/
27
28 #include "../object3d.h"
29 #include "../space_ship/space_ship.h"
30 #include "../ground_object/ground_object.h"
31 #include "../projectile/projectile.h"
32 #include "../space_object/space_object.h"
33
34 // NOTE switch to nested namespace definition (namespace A::B::C { ... }) (since C++17)
35 namespace viewizard {
36 namespace astromenace {
37
38 namespace {
39
40 constexpr float RadToDeg = 180.0f / 3.14159f; // convert radian to degree
41
42 } // unnamed namespace
43
44
45 /*
46 * Find missile target and target intercept course for missile.
47 */
48 std::weak_ptr<cObject3D>
FindTargetAndInterceptCourse(eObjectStatus MissileObjectStatus,const sVECTOR3D & MissileLocation,const sVECTOR3D & MissileRotation,const float (& MissileRotationMatrix)[9],sVECTOR3D & NeedAngle,const float MaxMissileFlyDistance)49 FindTargetAndInterceptCourse(eObjectStatus MissileObjectStatus, const sVECTOR3D &MissileLocation,
50 const sVECTOR3D &MissileRotation, const float (&MissileRotationMatrix)[9],
51 sVECTOR3D &NeedAngle, const float MaxMissileFlyDistance)
52 {
53 NeedAngle = MissileRotation;
54 float tmpDistanceToLockedTarget2{1000.0f * 1000.0f};
55 std::weak_ptr<cObject3D> LockedTarget{};
56
57 sVECTOR3D Orientation{0.0f, 0.0f, 1.0f};
58 vw_Matrix33CalcPoint(Orientation, MissileRotationMatrix);
59 sVECTOR3D PointUp{0.0f, 1.0f, 0.0f};
60 vw_Matrix33CalcPoint(PointUp, MissileRotationMatrix);
61 sVECTOR3D PointRight{1.0f, 0.0f, 0.0f};
62 vw_Matrix33CalcPoint(PointRight, MissileRotationMatrix);
63
64 // vertical plane (ahead/behind), note, OpenGL use right-handed coordinate system
65 float A, B, C, D;
66 vw_GetPlaneABCD(A, B, C, D, MissileLocation, MissileLocation + PointRight, MissileLocation + PointUp);
67
68 // horizontal plane (up/down), note, OpenGL use right-handed coordinate system
69 float A2, B2, C2, D2;
70 vw_GetPlaneABCD(A2, B2, C2, D2, MissileLocation, MissileLocation + PointRight, MissileLocation + Orientation);
71 float A2B2C2D2NormalLength = vw_sqrtf(A2 * A2 + B2 * B2 + C2 * C2);
72
73 // vertical plane (left/right), note, OpenGL use right-handed coordinate system
74 float A3, B3, C3, D3;
75 vw_GetPlaneABCD(A3, B3, C3, D3, MissileLocation, MissileLocation + PointUp, MissileLocation + Orientation);
76 float A3B3C3D3NormalLength = vw_sqrtf(A3 * A3 + B3 * B3 + C3 * C3);
77
78 // the idea of tmpDistanceFactorByObjectType is provide targeting priority, we increase factor
79 // for different types of objects, so, we need next type objects position closer than previous
80 // in order to change target, but, reset factor to 1.0f in case we lock and check same type
81 // objects (or don't have any target yet)
82 float tmpDistanceFactorByObjectType{1.0f};
83
84 // check object location with current missile course, if object in missile
85 // "targeting" zone - find target intercept course for missile
86 auto CheckObjectLocation = [&] (const sVECTOR3D &TargetLocation) {
87 if ((A * TargetLocation.x +
88 B * TargetLocation.y +
89 C * TargetLocation.z + D) <= 0.0f)
90 return false;
91
92 sVECTOR3D tmpDistance = TargetLocation - MissileLocation;
93 float tmpLength2 = tmpDistance.x * tmpDistance.x +
94 tmpDistance.y * tmpDistance.y +
95 tmpDistance.z * tmpDistance.z;
96
97 if ((tmpDistanceToLockedTarget2 / tmpDistanceFactorByObjectType) <= tmpLength2)
98 return false;
99
100 float tmpLength = vw_sqrtf(tmpLength2);
101 if (tmpLength > MaxMissileFlyDistance)
102 return false;
103
104 if ((tmpLength > 0.0f) && (A2B2C2D2NormalLength > 0.0f)) {
105 // see "Angle between line and plane" (geometry) for more info about what we are doing here
106 float tmpSineOfAngle = (A2 * tmpDistance.x + B2 * tmpDistance.y + C2 * tmpDistance.z) /
107 (tmpLength * A2B2C2D2NormalLength);
108 // with asinf(), arc sine could be computed in the interval [-1, +1] only
109 vw_Clamp(tmpSineOfAngle, -1.0f, 1.0f);
110 NeedAngle.x = MissileRotation.x + asinf(tmpSineOfAngle) * RadToDeg;
111 }
112
113 if ((tmpLength > 0.0f) && (A3B3C3D3NormalLength > 0.0f)) {
114 // see "Angle between line and plane" (geometry) for more info about what we are doing here
115 float tmpSineOfAngle = (A3 * tmpDistance.x + B3 * tmpDistance.y + C3 * tmpDistance.z) /
116 (tmpLength * A3B3C3D3NormalLength);
117 // with asinf(), arc sine could be computed in the interval [-1, +1] only
118 vw_Clamp(tmpSineOfAngle, -1.0f, 1.0f);
119 NeedAngle.y = MissileRotation.y + asinf(tmpSineOfAngle) * RadToDeg;
120 }
121
122 tmpDistanceToLockedTarget2 = tmpLength2;
123 tmpDistanceFactorByObjectType = 1.0f;
124 return true;
125 };
126
127 ForEachProjectile([&] (const cProjectile &tmpProjectile) {
128 if ((tmpProjectile.ProjectileType == 3) && // flares
129 NeedCheckCollision(tmpProjectile) &&
130 ObjectsStatusFoe(MissileObjectStatus, tmpProjectile.ObjectStatus) &&
131 CheckObjectLocation(tmpProjectile.Location))
132 LockedTarget = GetProjectilePtr(tmpProjectile);
133 });
134
135 if (!LockedTarget.expired())
136 tmpDistanceFactorByObjectType = 3.0f;
137 ForEachGroundObject([&] (const cGroundObject &tmpGround) {
138 if (!NeedCheckCollision(tmpGround) ||
139 !ObjectsStatusFoe(MissileObjectStatus, tmpGround.ObjectStatus))
140 return;
141
142 sVECTOR3D TargetLocation = tmpGround.GeometryCenter;
143 vw_Matrix33CalcPoint(TargetLocation, tmpGround.CurrentRotationMat);
144 TargetLocation += tmpGround.Location;
145
146 if (CheckObjectLocation(TargetLocation))
147 LockedTarget = GetGroundObjectPtr(tmpGround);
148 });
149
150 if (!LockedTarget.expired())
151 tmpDistanceFactorByObjectType = 6.0f;
152 ForEachSpaceShip([&] (const cSpaceShip &tmpShip) {
153 if (NeedCheckCollision(tmpShip) &&
154 ObjectsStatusFoe(MissileObjectStatus, tmpShip.ObjectStatus) &&
155 CheckObjectLocation(tmpShip.Location))
156 LockedTarget = GetSpaceShipPtr(tmpShip);
157 });
158
159 if (!LockedTarget.expired())
160 tmpDistanceFactorByObjectType = 10.0f;
161 ForEachSpaceObject([&] (const cSpaceObject &tmpSpace) {
162 if (NeedCheckCollision(tmpSpace) &&
163 ObjectsStatusFoe(MissileObjectStatus, tmpSpace.ObjectStatus) &&
164 (tmpSpace.ObjectType != eObjectType::SpaceDebris) &&
165 CheckObjectLocation(tmpSpace.Location))
166 LockedTarget = GetSpaceObjectPtr(tmpSpace);
167 });
168
169 return LockedTarget;
170 }
171
172 /*
173 * Correct target intercept course for missile.
174 */
CorrectTargetInterceptCourse(const sVECTOR3D & MissileLocation,const sVECTOR3D & MissileRotation,const float (& MissileRotationMatrix)[9],std::weak_ptr<cObject3D> & Target,sVECTOR3D & NeedAngle)175 bool CorrectTargetInterceptCourse(const sVECTOR3D &MissileLocation, const sVECTOR3D &MissileRotation,
176 const float (&MissileRotationMatrix)[9],
177 std::weak_ptr<cObject3D> &Target, sVECTOR3D &NeedAngle)
178 {
179 auto sharedTarget = Target.lock();
180 if (!sharedTarget)
181 return false;
182
183 sVECTOR3D Orientation{0.0f, 0.0f, 1.0f};
184 vw_Matrix33CalcPoint(Orientation, MissileRotationMatrix);
185 sVECTOR3D PointUp{0.0f, 1.0f, 0.0f};
186 vw_Matrix33CalcPoint(PointUp, MissileRotationMatrix);
187 sVECTOR3D PointRight{1.0f, 0.0f, 0.0f};
188 vw_Matrix33CalcPoint(PointRight, MissileRotationMatrix);
189
190 // vertical plane (ahead/behind), note, OpenGL use right-handed coordinate system
191 float A, B, C, D;
192 vw_GetPlaneABCD(A, B, C, D, MissileLocation, MissileLocation + PointRight, MissileLocation + PointUp);
193 if ((A * sharedTarget->Location.x +
194 B * sharedTarget->Location.y +
195 C * sharedTarget->Location.z + D) <= 0.0f)
196 return false;
197
198 NeedAngle = MissileRotation;
199
200 sVECTOR3D tmpTargetGeometryCenter = sharedTarget->GeometryCenter;
201 vw_Matrix33CalcPoint(tmpTargetGeometryCenter, sharedTarget->CurrentRotationMat);
202 sVECTOR3D tmpDistance = sharedTarget->Location + tmpTargetGeometryCenter - MissileLocation;
203 float tmpLength = tmpDistance.Length();
204
205 // horizontal plane (up/down), note, OpenGL use right-handed coordinate system
206 vw_GetPlaneABCD(A, B, C, D, MissileLocation, MissileLocation + PointRight, MissileLocation + Orientation);
207 float tmpNormalLength = vw_sqrtf(A * A + B * B + C * C);
208 if ((tmpLength > 0.0f) && (tmpNormalLength > 0.0f)) {
209 // see "Angle between line and plane" (geometry) for more info about what we are doing here
210 float tmpSineOfAngle = (A * tmpDistance.x + B * tmpDistance.y + C * tmpDistance.z) /
211 (tmpLength * tmpNormalLength);
212 // with asinf(), arc sine could be computed in the interval [-1, +1] only
213 vw_Clamp(tmpSineOfAngle, -1.0f, 1.0f);
214 NeedAngle.x = MissileRotation.x + asinf(tmpSineOfAngle) * RadToDeg;
215 }
216
217 // vertical plane (left/right), note, OpenGL use right-handed coordinate system
218 vw_GetPlaneABCD(A, B, C, D, MissileLocation, MissileLocation + PointUp, MissileLocation + Orientation);
219 tmpNormalLength = vw_sqrtf(A * A + B * B + C * C);
220 if ((tmpLength > 0.0f) && (tmpNormalLength > 0.0f)) {
221 // see "Angle between line and plane" (geometry) for more info about what we are doing here
222 float tmpSineOfAngle = (A * tmpDistance.x + B * tmpDistance.y + C * tmpDistance.z) /
223 (tmpLength * tmpNormalLength);
224 // with asinf(), arc sine could be computed in the interval [-1, +1] only
225 vw_Clamp(tmpSineOfAngle, -1.0f, 1.0f);
226 NeedAngle.y = MissileRotation.y + asinf(tmpSineOfAngle) * RadToDeg;
227 }
228
229 return true;
230 }
231
232 /*
233 * Check, that missile target stay ahead.
234 */
MissileTargetStayAhead(const cObject3D & Target,const sVECTOR3D & MissileLocation,const float (& MissileRotationMatrix)[9])235 static bool MissileTargetStayAhead(const cObject3D &Target,
236 const sVECTOR3D &MissileLocation,
237 const float (&MissileRotationMatrix)[9])
238 {
239 sVECTOR3D PointUp(0.0f, 1.0f, 0.0f);
240 vw_Matrix33CalcPoint(PointUp, MissileRotationMatrix);
241 sVECTOR3D PointRight(1.0f, 0.0f, 0.0f);
242 vw_Matrix33CalcPoint(PointRight, MissileRotationMatrix);
243
244 // vertical plane (ahead/behind), note, OpenGL use right-handed coordinate system
245 float A, B, C, D;
246 vw_GetPlaneABCD(A, B, C, D, MissileLocation, MissileLocation + PointRight, MissileLocation + PointUp);
247 if ((A * Target.Location.x +
248 B * Target.Location.y +
249 C * Target.Location.z + D) <= 0.0f)
250 return false;
251
252 return true;
253 }
254
255 /*
256 * Check locked by missle target.
257 */
CheckMissileTarget(std::weak_ptr<cObject3D> & Target,const sVECTOR3D & MissileLocation,const float (& MissileRotationMatrix)[9])258 bool CheckMissileTarget(std::weak_ptr<cObject3D> &Target, const sVECTOR3D &MissileLocation,
259 const float (&MissileRotationMatrix)[9])
260 {
261 auto sharedTarget = Target.lock();
262 if (!sharedTarget)
263 return false;
264
265 // TODO probably, we could use ObjectType here in order to speed up
266
267 bool ObjectFound{false};
268 ForEachProjectile([&sharedTarget, &ObjectFound] (const cProjectile &tmpProjectile, eProjectileCycle &Command) {
269 if (&tmpProjectile == sharedTarget.get()) {
270 ObjectFound = true;
271 Command = eProjectileCycle::Break;
272 }
273 });
274 if (ObjectFound)
275 return MissileTargetStayAhead(*sharedTarget, MissileLocation, MissileRotationMatrix);
276
277 ForEachGroundObject([&sharedTarget, &ObjectFound] (const cGroundObject &tmpGround, eGroundCycle &Command) {
278 if (&tmpGround == sharedTarget.get()) {
279 ObjectFound = true;
280 Command = eGroundCycle::Break;
281 }
282 });
283 if (ObjectFound)
284 return MissileTargetStayAhead(*sharedTarget, MissileLocation, MissileRotationMatrix);
285
286 ForEachSpaceShip([&sharedTarget, &ObjectFound] (const cSpaceShip &tmpShip, eShipCycle &Command) {
287 if (&tmpShip == sharedTarget.get()) {
288 ObjectFound = true;
289 Command = eShipCycle::Break;
290 }
291 });
292 if (ObjectFound)
293 return MissileTargetStayAhead(*sharedTarget, MissileLocation, MissileRotationMatrix);
294
295 ForEachSpaceObject([&sharedTarget, &ObjectFound] (const cSpaceObject &tmpSpace, eSpaceCycle &Command) {
296 if (&tmpSpace == sharedTarget.get()) {
297 ObjectFound = true;
298 Command = eSpaceCycle::Break;
299 }
300 });
301 if (ObjectFound)
302 return MissileTargetStayAhead(*sharedTarget, MissileLocation, MissileRotationMatrix);
303
304 return false;
305 }
306
307 /*
308 * Get closest target to mine.
309 */
GetClosestTargetToMine(eObjectStatus MineStatus,const sVECTOR3D & MineLocation)310 std::weak_ptr<cObject3D> GetClosestTargetToMine(eObjectStatus MineStatus, const sVECTOR3D &MineLocation)
311 {
312 std::weak_ptr<cObject3D> ClosestTarget{};
313 float MinDistance2{-1.0f};
314
315 ForEachSpaceShip([&] (const cSpaceShip &tmpShip) {
316 if (!NeedCheckCollision(tmpShip) ||
317 !ObjectsStatusFoe(MineStatus, tmpShip.ObjectStatus))
318 return;
319
320 float tmpDistance2 = (tmpShip.Location.x - MineLocation.x) * (tmpShip.Location.x - MineLocation.x) +
321 (tmpShip.Location.y - MineLocation.y) * (tmpShip.Location.y - MineLocation.y) +
322 (tmpShip.Location.z - MineLocation.z) * (tmpShip.Location.z - MineLocation.z);
323
324 if ((MinDistance2 < 0.0f) ||
325 (tmpDistance2 < MinDistance2)) {
326 MinDistance2 = tmpDistance2;
327 ClosestTarget = GetSpaceShipPtr(tmpShip);
328 }
329 });
330
331 return ClosestTarget;
332 }
333
334 } // astromenace namespace
335 } // viewizard namespace
336