1 /*
2  Copyright (c) 2013 yvt
3  based on code of pysnip (c) Mathias Kaerlev 2011-2012.
4 
5  This file is part of OpenSpades.
6 
7  OpenSpades is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  OpenSpades is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with OpenSpades.  If not, see <http://www.gnu.org/licenses/>.
19 
20  */
21 
22 #include "Client.h"
23 
24 #include <Core/ConcurrentDispatch.h>
25 #include <Core/Settings.h>
26 #include <Core/Strings.h>
27 
28 #include "CTFGameMode.h"
29 #include "Corpse.h"
30 #include "GameMap.h"
31 #include "Grenade.h"
32 #include "IGameMode.h"
33 #include "Player.h"
34 #include "TCGameMode.h"
35 #include "Weapon.h"
36 #include "World.h"
37 
38 #include "ClientPlayer.h"
39 #include "ILocalEntity.h"
40 
41 #include "NetClient.h"
42 
43 DEFINE_SPADES_SETTING(cg_fov, "68");
44 DEFINE_SPADES_SETTING(cg_thirdperson, "0");
45 DEFINE_SPADES_SETTING(cg_manualFocus, "0");
46 DEFINE_SPADES_SETTING(cg_depthOfFieldAmount, "1");
47 DEFINE_SPADES_SETTING(cg_shake, "1");
48 
49 namespace spades {
50 	namespace client {
51 
52 #pragma mark - Drawing
53 
GetCameraMode()54 		ClientCameraMode Client::GetCameraMode() {
55 			if (!world) {
56 				return ClientCameraMode::None;
57 			}
58 
59 			Player *p = world->GetLocalPlayer();
60 			if (!p) {
61 				return ClientCameraMode::NotJoined;
62 			}
63 
64 			if (p->IsAlive() && !p->IsSpectator()) {
65 				// There exists an alive (non-spectator) local player
66 				if ((int)cg_thirdperson != 0 && world->GetNumPlayers() <= 1) {
67 					return ClientCameraMode::ThirdPersonLocal;
68 				}
69 				return ClientCameraMode::FirstPersonLocal;
70 			} else {
71 				// The local player is dead or a spectator
72 				if (followCameraState.enabled) {
73 					bool isAlive = world->GetPlayer(followedPlayerId)->IsAlive();
74 					if (followCameraState.firstPerson && isAlive) {
75 						return ClientCameraMode::FirstPersonFollow;
76 					} else {
77 						return ClientCameraMode::ThirdPersonFollow;
78 					}
79 				} else {
80 					if (p->IsSpectator()) {
81 						return ClientCameraMode::Free;
82 					} else {
83 						// Look at your own cadaver!
84 						return ClientCameraMode::ThirdPersonLocal;
85 					}
86 				}
87 			}
88 		}
89 
GetCameraTargetPlayerId()90 		int Client::GetCameraTargetPlayerId() {
91 			switch (GetCameraMode()) {
92 				case ClientCameraMode::None:
93 				case ClientCameraMode::NotJoined:
94 				case ClientCameraMode::Free: SPUnreachable();
95 				case ClientCameraMode::FirstPersonLocal:
96 				case ClientCameraMode::ThirdPersonLocal:
97 					SPAssert(world);
98 					return world->GetLocalPlayerIndex();
99 				case ClientCameraMode::FirstPersonFollow:
100 				case ClientCameraMode::ThirdPersonFollow: return followedPlayerId;
101 			}
102 			SPUnreachable();
103 		}
104 
GetCameraTargetPlayer()105 		Player &Client::GetCameraTargetPlayer() {
106 			Player *p = world->GetPlayer(GetCameraTargetPlayerId());
107 			SPAssert(p);
108 			return *p;
109 		}
110 
GetLocalFireVibration()111 		float Client::GetLocalFireVibration() {
112 			float localFireVibration = 0.f;
113 			localFireVibration = time - localFireVibrationTime;
114 			localFireVibration = 1.f - localFireVibration / 0.1f;
115 			if (localFireVibration < 0.f)
116 				localFireVibration = 0.f;
117 			return localFireVibration;
118 		}
119 
GetAimDownZoomScale()120 		float Client::GetAimDownZoomScale() {
121 			Player &player = GetCameraTargetPlayer();
122 			if (!player.IsToolWeapon() || !player.IsAlive()) {
123 				return 1.f;
124 			}
125 
126 			ClientPlayer* clientPlayer = clientPlayers[player.GetId()];
127 			SPAssert(clientPlayer);
128 
129 			float delta = .8f;
130 			switch (player.GetWeapon()->GetWeaponType()) {
131 				case SMG_WEAPON: delta = .8f; break;
132 				case RIFLE_WEAPON: delta = 1.4f; break;
133 				case SHOTGUN_WEAPON: delta = .4f; break;
134 			}
135 
136 			float aimDownState = clientPlayer->GetAimDownState();
137 
138 			return 1.f + (3.f - 2.f * powf(aimDownState, 1.5f)) * powf(aimDownState, 3.f) * delta;
139 		}
140 
CreateSceneDefinition()141 		SceneDefinition Client::CreateSceneDefinition() {
142 			SPADES_MARK_FUNCTION();
143 
144 			int shakeLevel = cg_shake;
145 
146 			SceneDefinition def;
147 			def.time = (unsigned int)(time * 1000.f);
148 			def.denyCameraBlur = true;
149 			def.zFar = 200.f;
150 
151 			// Limit the range of cg_fov
152 			// (note: comparsion with a NaN always results in false)
153 			if (!((float)cg_fov < 90.0f)) {
154 				cg_fov = 90.0f;
155 			}
156 			if (!((float)cg_fov > 45.0f)) {
157 				cg_fov = 45.0f;
158 			}
159 
160 			if (world) {
161 				IntVector3 fogColor = world->GetFogColor();
162 				renderer->SetFogColor(
163 				  MakeVector3(fogColor.x / 255.f, fogColor.y / 255.f, fogColor.z / 255.f));
164 
165 				def.blurVignette = 0.0f;
166 
167 				float roll = 0.f;
168 				float scale = 1.f;
169 				float vibPitch = 0.f;
170 				float vibYaw = 0.f;
171 
172 				switch (GetCameraMode()) {
173 					case ClientCameraMode::None: SPUnreachable();
174 
175 					case ClientCameraMode::NotJoined:
176 						def.viewOrigin = MakeVector3(256, 256, 4);
177 						def.viewAxis[0] = MakeVector3(-1, 0, 0);
178 						def.viewAxis[1] = MakeVector3(0, 1, 0);
179 						def.viewAxis[2] = MakeVector3(0, 0, 1);
180 
181 						def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
182 						def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
183 										 renderer->ScreenHeight()) * 2.f;
184 
185 						def.zNear = 0.05f;
186 
187 						def.skipWorld = false;
188 						break;
189 
190 					case ClientCameraMode::FirstPersonLocal:
191 					case ClientCameraMode::FirstPersonFollow: {
192 						Player &player = GetCameraTargetPlayer();
193 
194 						Matrix4 eyeMatrix = clientPlayers[player.GetId()]->GetEyeMatrix();
195 
196 						def.viewOrigin = eyeMatrix.GetOrigin();
197 						def.viewAxis[0] = -eyeMatrix.GetAxis(0);
198 						def.viewAxis[1] = -eyeMatrix.GetAxis(2);
199 						def.viewAxis[2] = eyeMatrix.GetAxis(1);
200 
201 						if (shakeLevel >= 1) {
202 							float localFireVibration = GetLocalFireVibration();
203 							localFireVibration *= localFireVibration;
204 
205 							if (player.GetTool() == Player::ToolSpade) {
206 								localFireVibration *= 0.4f;
207 							}
208 
209 							roll += (SampleRandomFloat() - SampleRandomFloat()) * 0.03f * localFireVibration;
210 							scale += SampleRandomFloat() * 0.04f * localFireVibration;
211 
212 							vibPitch += localFireVibration * (1.f - localFireVibration) * 0.01f;
213 							vibYaw += sinf(localFireVibration * (float)M_PI * 2.f) * 0.001f;
214 
215 							def.radialBlur += localFireVibration * 0.05f;
216 
217 							// sprint bob
218 							{
219 								float sp = SmoothStep(GetSprintState());
220 								vibYaw += sinf(player.GetWalkAnimationProgress() *
221 											   static_cast<float>(M_PI) * 2.f) *
222 								0.01f * sp;
223 								roll -= sinf(player.GetWalkAnimationProgress() *
224 											 static_cast<float>(M_PI) * 2.f) *
225 								0.005f * (sp);
226 								float p = cosf(player.GetWalkAnimationProgress() *
227 											   static_cast<float>(M_PI) * 2.f);
228 								p = p * p;
229 								p *= p;
230 								p *= p;
231 								vibPitch += p * 0.01f * sp;
232 
233 								if (shakeLevel >= 2) {
234 									vibYaw += coherentNoiseSamplers[0].Sample(
235 																			  player.GetWalkAnimationProgress() * 2.5f) *
236 									0.005f * sp;
237 									vibPitch += coherentNoiseSamplers[1].Sample(
238 																				player.GetWalkAnimationProgress() * 2.5f) *
239 									0.01f * sp;
240 									roll += coherentNoiseSamplers[2].Sample(
241 																			player.GetWalkAnimationProgress() * 2.5f) *
242 									0.008f * sp;
243 
244 									scale += sp * 0.1f;
245 								}
246 							}
247 						}
248 
249 						def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
250 						def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
251 										 renderer->ScreenHeight()) * 2.f;
252 
253 						// for 1st view, camera blur can be used
254 						def.denyCameraBlur = false;
255 
256 						// DoF when doing ADS
257 						float aimDownState = GetAimDownState();
258 						float per = aimDownState;
259 						per *= per * per;
260 						def.depthOfFieldFocalLength = per * 13.f + .054f;
261 
262 						// Hurt effect
263 						{
264 							float wTime = world->GetTime();
265 							if (wTime < lastHurtTime + .15f && wTime >= lastHurtTime) {
266 								float per = 1.f - (wTime - lastHurtTime) / .15f;
267 								per *= .5f - player.GetHealth() / 100.f * .3f;
268 								def.blurVignette += per * 6.f;
269 							}
270 							if (wTime < lastHurtTime + .2f && wTime >= lastHurtTime) {
271 								float per = 1.f - (wTime - lastHurtTime) / .2f;
272 								per *= .5f - player.GetHealth() / 100.f * .3f;
273 								def.saturation *= std::max(0.f, 1.f - per * 4.f);
274 							}
275 						}
276 
277 						// Apply ADS zoom
278 						scale /= GetAimDownZoomScale();
279 
280 						// Update initial floating camera pos
281 						freeCameraState.position = def.viewOrigin;
282 						freeCameraState.velocity = MakeVector3(0, 0, 0);
283 						break;
284 					}
285 
286 					case ClientCameraMode::ThirdPersonLocal:
287 					case ClientCameraMode::ThirdPersonFollow: {
288 						Player &player = GetCameraTargetPlayer();
289 						Vector3 center = player.GetEye();
290 
291 						if (!player.IsAlive() && lastMyCorpse &&
292 							&player == world->GetLocalPlayer()) {
293 							center = lastMyCorpse->GetCenter();
294 						}
295 						if (map->IsSolidWrapped((int)floorf(center.x), (int)floorf(center.y),
296 												(int)floorf(center.z))) {
297 							float z = center.z;
298 							while (z > center.z - 5.f) {
299 								if (!map->IsSolidWrapped((int)floorf(center.x),
300 														 (int)floorf(center.y), (int)floorf(z))) {
301 									center.z = z;
302 									break;
303 								} else {
304 									z -= 1.f;
305 								}
306 							}
307 						}
308 
309 						float distance = 5.f;
310 						if (&player == world->GetLocalPlayer() &&
311 							world->GetLocalPlayer()->GetTeamId() < 2 &&
312 							!world->GetLocalPlayer()->IsAlive()) {
313 							// deathcam.
314 							float elapsedTime = time - lastAliveTime;
315 							distance -= 3.f * expf(-elapsedTime * 1.f);
316 						}
317 
318 						auto &state = followAndFreeCameraState;
319 						Vector3 eye = center;
320 						eye.x += cosf(state.yaw) * cosf(state.pitch) * distance;
321 						eye.y += sinf(state.yaw) * cosf(state.pitch) * distance;
322 						eye.z -= sinf(state.pitch) * distance;
323 
324 						// Prevent the camera from being behind a wall
325 						GameMap::RayCastResult result;
326 						result = map->CastRay2(center, (eye - center).Normalize(), 256);
327 						if (result.hit) {
328 							float dist = (result.hitPos - center).GetLength();
329 							float curDist = (eye - center).GetLength();
330 							dist -= 0.3f; // near clip plane
331 							if (curDist > dist) {
332 								float diff = curDist - dist;
333 								eye += (center - eye).Normalize() * diff;
334 							}
335 						}
336 
337 						Vector3 front = center - eye;
338 						front = front.Normalize();
339 
340 						Vector3 up = MakeVector3(0, 0, -1);
341 
342 						def.viewOrigin = eye;
343 						def.viewAxis[0] = -Vector3::Cross(up, front).Normalize();
344 						def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize();
345 						def.viewAxis[2] = front;
346 
347 						def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
348 						def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
349 										 renderer->ScreenHeight()) * 2.f;
350 
351 						// Update initial floating camera pos
352 						freeCameraState.position = def.viewOrigin;
353 						freeCameraState.velocity = MakeVector3(0, 0, 0);
354 						break;
355 					}
356 					case ClientCameraMode::Free: {
357 						// spectator view (noclip view)
358 						Vector3 center = freeCameraState.position;
359 						Vector3 front;
360 						Vector3 up = {0, 0, -1};
361 
362 						auto &state = followAndFreeCameraState;
363 						front.x = -cosf(state.yaw) * cosf(state.pitch);
364 						front.y = -sinf(state.yaw) * cosf(state.pitch);
365 						front.z = sinf(state.pitch);
366 
367 						def.viewOrigin = center;
368 						def.viewAxis[0] = -Vector3::Cross(up, front).Normalize();
369 						def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize();
370 						def.viewAxis[2] = front;
371 
372 						def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
373 						def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
374 										 renderer->ScreenHeight()) * 2.f;
375 
376 						// for 1st view, camera blur can be used
377 						def.denyCameraBlur = false;
378 						break;
379 					}
380 				}
381 
382 				// Add vibration effects
383 				{
384 					float grenVib = grenadeVibration;
385 					if (grenVib > 0.f) {
386 						if (shakeLevel >= 1) {
387 							grenVib *= 10.f;
388 							if (grenVib > 1.f)
389 								grenVib = 1.f;
390 							roll += (SampleRandomFloat() - SampleRandomFloat()) * 0.2f * grenVib;
391 							vibPitch += (SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
392 							vibYaw += (SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
393 							scale -= (SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
394 
395 							def.radialBlur += grenVib * 0.1f;
396 						}
397 					}
398 				}
399 				{
400 					float grenVib = grenadeVibrationSlow;
401 					if (grenVib > 0.f) {
402 						if (shakeLevel >= 2) {
403 							grenVib *= 4.f;
404 							if (grenVib > 1.f)
405 								grenVib = 1.f;
406 							grenVib *= grenVib;
407 
408 							roll += coherentNoiseSamplers[0].Sample(time * 8.f) * 0.2f * grenVib;
409 							vibPitch +=
410 							  coherentNoiseSamplers[1].Sample(time * 12.f) * 0.1f * grenVib;
411 							vibYaw += coherentNoiseSamplers[2].Sample(time * 11.f) * 0.1f * grenVib;
412 						}
413 					}
414 				}
415 
416 				// Add roll / scale
417 				{
418 					Vector3 right = def.viewAxis[0];
419 					Vector3 up = def.viewAxis[1];
420 
421 					def.viewAxis[0] = right * cosf(roll) - up * sinf(roll);
422 					def.viewAxis[1] = up * cosf(roll) + right * sinf(roll);
423 
424 					def.fovX = atanf(tanf(def.fovX * .5f) * scale) * 2.f;
425 					def.fovY = atanf(tanf(def.fovY * .5f) * scale) * 2.f;
426 				}
427 				{
428 					Vector3 u = def.viewAxis[1];
429 					Vector3 v = def.viewAxis[2];
430 
431 					def.viewAxis[1] = u * cosf(vibPitch) - v * sinf(vibPitch);
432 					def.viewAxis[2] = v * cosf(vibPitch) + u * sinf(vibPitch);
433 				}
434 				{
435 					Vector3 u = def.viewAxis[0];
436 					Vector3 v = def.viewAxis[2];
437 
438 					def.viewAxis[0] = u * cosf(vibYaw) - v * sinf(vibYaw);
439 					def.viewAxis[2] = v * cosf(vibYaw) + u * sinf(vibYaw);
440 				}
441 
442 				if (def.viewOrigin.z < 0.f) {
443 					// Need to move the far plane because there's no vertical fog
444 					def.zFar -= def.viewOrigin.z;
445 				}
446 
447 				if ((int)cg_manualFocus) {
448 					// Depth of field is manually controlled
449 					def.depthOfFieldNearBlurStrength = def.depthOfFieldFarBlurStrength =
450 					0.5f * (float)cg_depthOfFieldAmount;
451 					def.depthOfFieldFocalLength = focalLength;
452 				} else {
453 					def.depthOfFieldNearBlurStrength = cg_depthOfFieldAmount;
454 					def.depthOfFieldFarBlurStrength = 0.f;
455 				}
456 
457 				def.zNear = 0.05f;
458 
459 				def.skipWorld = false;
460 			} else {
461 				SPAssert(GetCameraMode() == ClientCameraMode::None);
462 
463 				// Let there be darkness
464 				def.viewOrigin = MakeVector3(0, 0, 0);
465 				def.viewAxis[0] = MakeVector3(1, 0, 0);
466 				def.viewAxis[1] = MakeVector3(0, 0, -1);
467 				def.viewAxis[2] = MakeVector3(0, 0, 1);
468 
469 				def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
470 				def.fovX =
471 				  atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) *
472 				  2.f;
473 
474 				def.zNear = 0.05f;
475 
476 				def.skipWorld = true;
477 
478 				renderer->SetFogColor(MakeVector3(0, 0, 0));
479 			}
480 
481 			SPAssert(!std::isnan(def.viewOrigin.x));
482 			SPAssert(!std::isnan(def.viewOrigin.y));
483 			SPAssert(!std::isnan(def.viewOrigin.z));
484 
485 			def.radialBlur = std::min(def.radialBlur, 1.f);
486 
487 			return def;
488 		}
489 
AddGrenadeToScene(spades::client::Grenade * g)490 		void Client::AddGrenadeToScene(spades::client::Grenade *g) {
491 			SPADES_MARK_FUNCTION();
492 
493 			IModel *model;
494 			model = renderer->RegisterModel("Models/Weapons/Grenade/Grenade.kv6");
495 
496 			if (g->GetPosition().z > 63.f) {
497 				// work-around for water refraction problem
498 				return;
499 			}
500 
501 			// Move the grenade slightly so that it doesn't look like sinking in
502 			// the ground
503 			Vector3 position = g->GetPosition();
504 			position.z -= 0.03f * 3.0f;
505 
506 			ModelRenderParam param;
507 			Matrix4 mat = g->GetOrientation().ToRotationMatrix() * Matrix4::Scale(0.03f);
508 			mat = Matrix4::Translate(position) * mat;
509 			param.matrix = mat;
510 
511 			renderer->RenderModel(model, param);
512 		}
513 
AddDebugObjectToScene(const spades::OBB3 & obb,const Vector4 & color)514 		void Client::AddDebugObjectToScene(const spades::OBB3 &obb, const Vector4 &color) {
515 			const Matrix4 &mat = obb.m;
516 			Vector3 v[2][2][2];
517 			v[0][0][0] = (mat * MakeVector3(0, 0, 0)).GetXYZ();
518 			v[0][0][1] = (mat * MakeVector3(0, 0, 1)).GetXYZ();
519 			v[0][1][0] = (mat * MakeVector3(0, 1, 0)).GetXYZ();
520 			v[0][1][1] = (mat * MakeVector3(0, 1, 1)).GetXYZ();
521 			v[1][0][0] = (mat * MakeVector3(1, 0, 0)).GetXYZ();
522 			v[1][0][1] = (mat * MakeVector3(1, 0, 1)).GetXYZ();
523 			v[1][1][0] = (mat * MakeVector3(1, 1, 0)).GetXYZ();
524 			v[1][1][1] = (mat * MakeVector3(1, 1, 1)).GetXYZ();
525 
526 			renderer->AddDebugLine(v[0][0][0], v[1][0][0], color);
527 			renderer->AddDebugLine(v[0][0][1], v[1][0][1], color);
528 			renderer->AddDebugLine(v[0][1][0], v[1][1][0], color);
529 			renderer->AddDebugLine(v[0][1][1], v[1][1][1], color);
530 
531 			renderer->AddDebugLine(v[0][0][0], v[0][1][0], color);
532 			renderer->AddDebugLine(v[0][0][1], v[0][1][1], color);
533 			renderer->AddDebugLine(v[1][0][0], v[1][1][0], color);
534 			renderer->AddDebugLine(v[1][0][1], v[1][1][1], color);
535 
536 			renderer->AddDebugLine(v[0][0][0], v[0][0][1], color);
537 			renderer->AddDebugLine(v[0][1][0], v[0][1][1], color);
538 			renderer->AddDebugLine(v[1][0][0], v[1][0][1], color);
539 			renderer->AddDebugLine(v[1][1][0], v[1][1][1], color);
540 		}
541 
DrawCTFObjects()542 		void Client::DrawCTFObjects() {
543 			SPADES_MARK_FUNCTION();
544 			CTFGameMode *mode = static_cast<CTFGameMode *>(world->GetMode());
545 			int tId;
546 			IModel *base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6");
547 			IModel *intel = renderer->RegisterModel("Models/MapObjects/Intel.kv6");
548 			for (tId = 0; tId < 2; tId++) {
549 				CTFGameMode::Team &team = mode->GetTeam(tId);
550 				IntVector3 col = world->GetTeam(tId).color;
551 				Vector3 color = {col.x / 255.f, col.y / 255.f, col.z / 255.f};
552 
553 				ModelRenderParam param;
554 				param.customColor = color;
555 
556 				// draw base
557 				param.matrix = Matrix4::Translate(team.basePos);
558 				param.matrix = param.matrix * Matrix4::Scale(.3f);
559 				renderer->RenderModel(base, param);
560 
561 				// draw flag
562 				if (!mode->GetTeam(1 - tId).hasIntel) {
563 					param.matrix = Matrix4::Translate(team.flagPos);
564 					param.matrix = param.matrix * Matrix4::Scale(.1f);
565 					renderer->RenderModel(intel, param);
566 				}
567 			}
568 		}
569 
DrawTCObjects()570 		void Client::DrawTCObjects() {
571 			SPADES_MARK_FUNCTION();
572 			TCGameMode *mode = static_cast<TCGameMode *>(world->GetMode());
573 			int tId;
574 			IModel *base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6");
575 			int cnt = mode->GetNumTerritories();
576 			for (tId = 0; tId < cnt; tId++) {
577 				TCGameMode::Territory *t = mode->GetTerritory(tId);
578 				IntVector3 col;
579 				if (t->ownerTeamId == 2) {
580 					col = IntVector3::Make(255, 255, 255);
581 				} else {
582 					col = world->GetTeam(t->ownerTeamId).color;
583 				}
584 				Vector3 color = {col.x / 255.f, col.y / 255.f, col.z / 255.f};
585 
586 				ModelRenderParam param;
587 				param.customColor = color;
588 
589 				// draw base
590 				param.matrix = Matrix4::Translate(t->pos);
591 				param.matrix = param.matrix * Matrix4::Scale(.3f);
592 				renderer->RenderModel(base, param);
593 			}
594 		}
595 
DrawScene()596 		void Client::DrawScene() {
597 			SPADES_MARK_FUNCTION();
598 
599 			renderer->StartScene(lastSceneDef);
600 
601 			if (world) {
602 				Player *p = world->GetLocalPlayer();
603 
604 				for (size_t i = 0; i < world->GetNumPlayerSlots(); i++)
605 					if (world->GetPlayer(static_cast<unsigned int>(i))) {
606 						SPAssert(clientPlayers[i]);
607 						clientPlayers[i]->AddToScene();
608 					}
609 				std::vector<Grenade *> nades = world->GetAllGrenades();
610 				for (size_t i = 0; i < nades.size(); i++) {
611 					AddGrenadeToScene(nades[i]);
612 				}
613 
614 				{
615 					for (auto &c : corpses) {
616 						Vector3 center = c->GetCenter();
617 						if ((center - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f)
618 							continue;
619 						c->AddToScene();
620 					}
621 				}
622 
623 				if (IGameMode::m_CTF == world->GetMode()->ModeType()) {
624 					DrawCTFObjects();
625 				} else if (IGameMode::m_TC == world->GetMode()->ModeType()) {
626 					DrawTCObjects();
627 				}
628 
629 				{
630 					for (auto &ent : localEntities) {
631 						ent->Render3D();
632 					}
633 				}
634 
635 				// Draw block cursor
636 				// FIXME: don't use debug line
637 				if (p) {
638 					if (p->IsReadyToUseTool() && p->GetTool() == Player::ToolBlock &&
639 					    p->IsAlive()) {
640 						std::vector<IntVector3> blocks;
641 						if (p->IsBlockCursorDragging()) {
642 							blocks = world->CubeLine(p->GetBlockCursorDragPos(),
643 													 p->GetBlockCursorPos(), 256);
644 						} else {
645 							blocks.push_back(p->GetBlockCursorPos());
646 						}
647 
648 						bool active = p->IsBlockCursorActive() && CanLocalPlayerUseToolNow();
649 
650 						Vector4 color = {1, 1, 1, 1};
651 						if (!active)
652 							color = Vector4(1, 1, 0, 1);
653 						if ((int)blocks.size() > p->GetNumBlocks())
654 							color = MakeVector4(1, 0, 0, 1);
655 						if (!active)
656 							color.w *= 0.5f;
657 
658 						for (size_t i = 0; i < blocks.size(); i++) {
659 							IntVector3 &v = blocks[i];
660 
661 							if (active) {
662 
663 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z),
664 								                       MakeVector3(v.x + 1, v.y, v.z), color);
665 								renderer->AddDebugLine(MakeVector3(v.x, v.y + 1, v.z),
666 								                       MakeVector3(v.x + 1, v.y + 1, v.z), color);
667 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z + 1),
668 								                       MakeVector3(v.x + 1, v.y, v.z + 1), color);
669 								renderer->AddDebugLine(MakeVector3(v.x, v.y + 1, v.z + 1),
670 								                       MakeVector3(v.x + 1, v.y + 1, v.z + 1),
671 								                       color);
672 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z),
673 								                       MakeVector3(v.x + 1, v.y, v.z), color);
674 
675 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z),
676 								                       MakeVector3(v.x, v.y + 1, v.z), color);
677 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z + 1),
678 								                       MakeVector3(v.x, v.y + 1, v.z + 1), color);
679 								renderer->AddDebugLine(MakeVector3(v.x + 1, v.y, v.z),
680 								                       MakeVector3(v.x + 1, v.y + 1, v.z), color);
681 								renderer->AddDebugLine(MakeVector3(v.x + 1, v.y, v.z + 1),
682 								                       MakeVector3(v.x + 1, v.y + 1, v.z + 1),
683 								                       color);
684 
685 								renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z),
686 								                       MakeVector3(v.x, v.y, v.z + 1), color);
687 								renderer->AddDebugLine(MakeVector3(v.x, v.y + 1, v.z),
688 								                       MakeVector3(v.x, v.y + 1, v.z + 1), color);
689 								renderer->AddDebugLine(MakeVector3(v.x + 1, v.y, v.z),
690 								                       MakeVector3(v.x + 1, v.y, v.z + 1), color);
691 								renderer->AddDebugLine(MakeVector3(v.x + 1, v.y + 1, v.z),
692 								                       MakeVector3(v.x + 1, v.y + 1, v.z + 1),
693 								                       color);
694 							} else {
695 								// not active
696 
697 								const float ln = 0.1f;
698 								{
699 									float xx = v.x, yy = v.y, zz = v.z;
700 									renderer->AddDebugLine(Vector3(xx, yy, zz),
701 									                       Vector3(xx + ln, yy, zz), color);
702 									renderer->AddDebugLine(Vector3(xx, yy, zz),
703 									                       Vector3(xx, yy + ln, zz), color);
704 									renderer->AddDebugLine(Vector3(xx, yy, zz),
705 									                       Vector3(xx, yy, zz + ln), color);
706 								}
707 								{
708 									float xx = v.x + 1, yy = v.y, zz = v.z;
709 									renderer->AddDebugLine(Vector3(xx, yy, zz),
710 									                       Vector3(xx - ln, yy, zz), color);
711 									renderer->AddDebugLine(Vector3(xx, yy, zz),
712 									                       Vector3(xx, yy + ln, zz), color);
713 									renderer->AddDebugLine(Vector3(xx, yy, zz),
714 									                       Vector3(xx, yy, zz + ln), color);
715 								}
716 								{
717 									float xx = v.x, yy = v.y + 1, zz = v.z;
718 									renderer->AddDebugLine(Vector3(xx, yy, zz),
719 									                       Vector3(xx + ln, yy, zz), color);
720 									renderer->AddDebugLine(Vector3(xx, yy, zz),
721 									                       Vector3(xx, yy - ln, zz), color);
722 									renderer->AddDebugLine(Vector3(xx, yy, zz),
723 									                       Vector3(xx, yy, zz + ln), color);
724 								}
725 								{
726 									float xx = v.x + 1, yy = v.y + 1, zz = v.z;
727 									renderer->AddDebugLine(Vector3(xx, yy, zz),
728 									                       Vector3(xx - ln, yy, zz), color);
729 									renderer->AddDebugLine(Vector3(xx, yy, zz),
730 									                       Vector3(xx, yy - ln, zz), color);
731 									renderer->AddDebugLine(Vector3(xx, yy, zz),
732 									                       Vector3(xx, yy, zz + ln), color);
733 								}
734 								{
735 									float xx = v.x, yy = v.y, zz = v.z + 1;
736 									renderer->AddDebugLine(Vector3(xx, yy, zz),
737 									                       Vector3(xx + ln, yy, zz), color);
738 									renderer->AddDebugLine(Vector3(xx, yy, zz),
739 									                       Vector3(xx, yy + ln, zz), color);
740 									renderer->AddDebugLine(Vector3(xx, yy, zz),
741 									                       Vector3(xx, yy, zz - ln), color);
742 								}
743 								{
744 									float xx = v.x + 1, yy = v.y, zz = v.z + 1;
745 									renderer->AddDebugLine(Vector3(xx, yy, zz),
746 									                       Vector3(xx - ln, yy, zz), color);
747 									renderer->AddDebugLine(Vector3(xx, yy, zz),
748 									                       Vector3(xx, yy + ln, zz), color);
749 									renderer->AddDebugLine(Vector3(xx, yy, zz),
750 									                       Vector3(xx, yy, zz - ln), color);
751 								}
752 								{
753 									float xx = v.x, yy = v.y + 1, zz = v.z + 1;
754 									renderer->AddDebugLine(Vector3(xx, yy, zz),
755 									                       Vector3(xx + ln, yy, zz), color);
756 									renderer->AddDebugLine(Vector3(xx, yy, zz),
757 									                       Vector3(xx, yy - ln, zz), color);
758 									renderer->AddDebugLine(Vector3(xx, yy, zz),
759 									                       Vector3(xx, yy, zz - ln), color);
760 								}
761 								{
762 									float xx = v.x + 1, yy = v.y + 1, zz = v.z + 1;
763 									renderer->AddDebugLine(Vector3(xx, yy, zz),
764 									                       Vector3(xx - ln, yy, zz), color);
765 									renderer->AddDebugLine(Vector3(xx, yy, zz),
766 									                       Vector3(xx, yy - ln, zz), color);
767 									renderer->AddDebugLine(Vector3(xx, yy, zz),
768 									                       Vector3(xx, yy, zz - ln), color);
769 								}
770 							}
771 							// --- one block drawn
772 						} // end for
773 
774 					} // p->IsReadyToUseTool
775 				}
776 			}
777 
778 			for (size_t i = 0; i < flashDlights.size(); i++)
779 				renderer->AddLight(flashDlights[i]);
780 			flashDlightsOld.clear();
781 			flashDlightsOld.swap(flashDlights);
782 
783 			// draw player hottrack
784 			// FIXME: don't use debug line
785 			{
786 				hitTag_t tag = hit_None;
787 				Player *hottracked = HotTrackedPlayer(&tag);
788 				if (hottracked) {
789 					IntVector3 col = world->GetTeam(hottracked->GetTeamId()).color;
790 					Vector4 color = Vector4::Make(col.x / 255.f, col.y / 255.f, col.z / 255.f, 1.f);
791 					Vector4 color2 = Vector4::Make(1, 1, 1, 1);
792 
793 					Player::HitBoxes hb = hottracked->GetHitBoxes();
794 					AddDebugObjectToScene(hb.head, (tag & hit_Head) ? color2 : color);
795 					AddDebugObjectToScene(hb.torso, (tag & hit_Torso) ? color2 : color);
796 					AddDebugObjectToScene(hb.limbs[0], (tag & hit_Legs) ? color2 : color);
797 					AddDebugObjectToScene(hb.limbs[1], (tag & hit_Legs) ? color2 : color);
798 					AddDebugObjectToScene(hb.limbs[2], (tag & hit_Arms) ? color2 : color);
799 				}
800 			}
801 
802 			renderer->EndScene();
803 		}
804 
Project(spades::Vector3 v)805 		Vector3 Client::Project(spades::Vector3 v) {
806 			v -= lastSceneDef.viewOrigin;
807 
808 			// transform to NDC
809 			Vector3 v2;
810 			v2.x = Vector3::Dot(v, lastSceneDef.viewAxis[0]);
811 			v2.y = Vector3::Dot(v, lastSceneDef.viewAxis[1]);
812 			v2.z = Vector3::Dot(v, lastSceneDef.viewAxis[2]);
813 
814 			float tanX = tanf(lastSceneDef.fovX * .5f);
815 			float tanY = tanf(lastSceneDef.fovY * .5f);
816 
817 			v2.x /= tanX * v2.z;
818 			v2.y /= tanY * v2.z;
819 
820 			// transform to IRenderer 2D coord
821 			v2.x = (v2.x + 1.f) / 2.f * renderer->ScreenWidth();
822 			v2.y = (-v2.y + 1.f) / 2.f * renderer->ScreenHeight();
823 
824 			return v2;
825 		}
826 	}
827 }
828