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