openspades/Sources/Client/Client_Scene.cpp

733 lines
23 KiB
C++

/*
Copyright (c) 2013 yvt
based on code of pysnip (c) Mathias Kaerlev 2011-2012.
This file is part of OpenSpades.
OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Client.h"
#include <Core/ConcurrentDispatch.h>
#include <Core/Settings.h>
#include <Core/Strings.h>
#include "CTFGameMode.h"
#include "Corpse.h"
#include "GameMap.h"
#include "Grenade.h"
#include "IGameMode.h"
#include "Player.h"
#include "TCGameMode.h"
#include "Weapon.h"
#include "World.h"
#include "ClientPlayer.h"
#include "ILocalEntity.h"
#include "NetClient.h"
DEFINE_SPADES_SETTING(cg_fov, "68");
DEFINE_SPADES_SETTING(cg_thirdperson, "0");
DEFINE_SPADES_SETTING(cg_manualFocus, "0");
DEFINE_SPADES_SETTING(cg_depthOfFieldAmount, "1");
DEFINE_SPADES_SETTING(cg_shake, "1");
namespace spades {
namespace client {
#pragma mark - Drawing
ClientCameraMode Client::GetCameraMode() {
if (!world) {
return ClientCameraMode::None;
}
stmp::optional<Player &> p = world->GetLocalPlayer();
if (!p) {
return ClientCameraMode::NotJoined;
}
if (p->IsAlive() && !p->IsSpectator()) {
// There exists an alive (non-spectator) local player
if ((int)cg_thirdperson != 0 && world->GetNumPlayers() <= 1) {
return ClientCameraMode::ThirdPersonLocal;
}
return ClientCameraMode::FirstPersonLocal;
} else {
// The local player is dead or a spectator
if (followCameraState.enabled) {
bool isAlive = world->GetPlayer(followedPlayerId)->IsAlive();
if (followCameraState.firstPerson && isAlive) {
return ClientCameraMode::FirstPersonFollow;
} else {
return ClientCameraMode::ThirdPersonFollow;
}
} else {
if (p->IsSpectator()) {
return ClientCameraMode::Free;
} else {
// Look at your own cadaver!
return ClientCameraMode::ThirdPersonLocal;
}
}
}
}
int Client::GetCameraTargetPlayerId() {
switch (GetCameraMode()) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::Free: SPUnreachable();
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::ThirdPersonLocal:
SPAssert(world);
return world->GetLocalPlayerIndex().value();
case ClientCameraMode::FirstPersonFollow:
case ClientCameraMode::ThirdPersonFollow: return followedPlayerId;
}
SPUnreachable();
}
Player &Client::GetCameraTargetPlayer() {
return world->GetPlayer(GetCameraTargetPlayerId()).value();
}
float Client::GetLocalFireVibration() {
float localFireVibration = 0.f;
localFireVibration = time - localFireVibrationTime;
localFireVibration = 1.f - localFireVibration / 0.1f;
if (localFireVibration < 0.f)
localFireVibration = 0.f;
return localFireVibration;
}
float Client::GetAimDownZoomScale() {
Player &player = GetCameraTargetPlayer();
if (!player.IsToolWeapon() || !player.IsAlive()) {
return 1.f;
}
ClientPlayer &clientPlayer = *clientPlayers[player.GetId()];
float delta = .8f;
switch (player.GetWeapon().GetWeaponType()) {
case SMG_WEAPON: delta = .8f; break;
case RIFLE_WEAPON: delta = 1.4f; break;
case SHOTGUN_WEAPON: delta = .4f; break;
}
float aimDownState = clientPlayer.GetAimDownState();
return 1.f + (3.f - 2.f * powf(aimDownState, 1.5f)) * powf(aimDownState, 3.f) * delta;
}
SceneDefinition Client::CreateSceneDefinition() {
SPADES_MARK_FUNCTION();
int shakeLevel = cg_shake;
SceneDefinition def;
def.time = (unsigned int)(time * 1000.f);
def.denyCameraBlur = true;
def.zFar = 200.f;
// Limit the range of cg_fov
// (note: comparsion with a NaN always results in false)
if (!((float)cg_fov < 90.0f)) {
cg_fov = 90.0f;
}
if (!((float)cg_fov > 45.0f)) {
cg_fov = 45.0f;
}
if (world) {
IntVector3 fogColor = world->GetFogColor();
renderer->SetFogColor(
MakeVector3(fogColor.x / 255.f, fogColor.y / 255.f, fogColor.z / 255.f));
def.blurVignette = 0.0f;
float roll = 0.f;
float scale = 1.f;
float vibPitch = 0.f;
float vibYaw = 0.f;
switch (GetCameraMode()) {
case ClientCameraMode::None: SPUnreachable();
case ClientCameraMode::NotJoined:
def.viewOrigin = MakeVector3(256, 256, 4);
def.viewAxis[0] = MakeVector3(-1, 0, 0);
def.viewAxis[1] = MakeVector3(0, 1, 0);
def.viewAxis[2] = MakeVector3(0, 0, 1);
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
def.zNear = 0.05f;
def.skipWorld = false;
break;
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::FirstPersonFollow: {
Player &player = GetCameraTargetPlayer();
Matrix4 eyeMatrix = clientPlayers[player.GetId()]->GetEyeMatrix();
def.viewOrigin = eyeMatrix.GetOrigin();
def.viewAxis[0] = -eyeMatrix.GetAxis(0);
def.viewAxis[1] = -eyeMatrix.GetAxis(2);
def.viewAxis[2] = eyeMatrix.GetAxis(1);
if (shakeLevel >= 1) {
float localFireVibration = GetLocalFireVibration();
localFireVibration *= localFireVibration;
if (player.GetTool() == Player::ToolSpade) {
localFireVibration *= 0.4f;
}
roll += (SampleRandomFloat() - SampleRandomFloat()) * 0.03f *
localFireVibration;
scale += SampleRandomFloat() * 0.04f * localFireVibration;
vibPitch += localFireVibration * (1.f - localFireVibration) * 0.01f;
vibYaw += sinf(localFireVibration * (float)M_PI * 2.f) * 0.001f;
def.radialBlur += localFireVibration * 0.05f;
// sprint bob
{
float sp = SmoothStep(GetSprintState());
vibYaw += sinf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.01f * sp;
roll -= sinf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.005f * (sp);
float p = cosf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f);
p = p * p;
p *= p;
p *= p;
vibPitch += p * 0.01f * sp;
if (shakeLevel >= 2) {
vibYaw += coherentNoiseSamplers[0].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.005f * sp;
vibPitch += coherentNoiseSamplers[1].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.01f * sp;
roll += coherentNoiseSamplers[2].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.008f * sp;
scale += sp * 0.1f;
}
}
}
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
// for 1st view, camera blur can be used
def.denyCameraBlur = false;
// DoF when doing ADS
float aimDownState = GetAimDownState();
float per = aimDownState;
per *= per * per;
def.depthOfFieldFocalLength = per * 13.f + .054f;
// Hurt effect
{
float wTime = world->GetTime();
if (wTime < lastHurtTime + .15f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .15f;
per *= .5f - player.GetHealth() / 100.f * .3f;
def.blurVignette += per * 6.f;
}
if (wTime < lastHurtTime + .2f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .2f;
per *= .5f - player.GetHealth() / 100.f * .3f;
def.saturation *= std::max(0.f, 1.f - per * 4.f);
}
}
// Apply ADS zoom
scale /= GetAimDownZoomScale();
// Update initial floating camera pos
freeCameraState.position = def.viewOrigin;
freeCameraState.velocity = MakeVector3(0, 0, 0);
break;
}
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::ThirdPersonFollow: {
Player &player = GetCameraTargetPlayer();
Vector3 center = player.GetEye();
if (!player.IsAlive() && lastMyCorpse &&
&player == world->GetLocalPlayer()) {
center = lastMyCorpse->GetCenter();
}
if (map->IsSolidWrapped((int)floorf(center.x), (int)floorf(center.y),
(int)floorf(center.z))) {
float z = center.z;
while (z > center.z - 5.f) {
if (!map->IsSolidWrapped((int)floorf(center.x),
(int)floorf(center.y), (int)floorf(z))) {
center.z = z;
break;
} else {
z -= 1.f;
}
}
}
float distance = 5.f;
if (&player == world->GetLocalPlayer() &&
world->GetLocalPlayer()->GetTeamId() < 2 &&
!world->GetLocalPlayer()->IsAlive()) {
// deathcam.
float elapsedTime = time - lastAliveTime;
distance -= 3.f * expf(-elapsedTime * 1.f);
}
auto &state = followAndFreeCameraState;
Vector3 eye = center;
eye.x += cosf(state.yaw) * cosf(state.pitch) * distance;
eye.y += sinf(state.yaw) * cosf(state.pitch) * distance;
eye.z -= sinf(state.pitch) * distance;
// Prevent the camera from being behind a wall
GameMap::RayCastResult result;
result = map->CastRay2(center, (eye - center).Normalize(), 256);
if (result.hit) {
float dist = (result.hitPos - center).GetLength();
float curDist = (eye - center).GetLength();
dist -= 0.3f; // near clip plane
if (curDist > dist) {
float diff = curDist - dist;
eye += (center - eye).Normalize() * diff;
}
}
Vector3 front = center - eye;
front = front.Normalize();
Vector3 up = MakeVector3(0, 0, -1);
def.viewOrigin = eye;
def.viewAxis[0] = -Vector3::Cross(up, front).Normalize();
def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize();
def.viewAxis[2] = front;
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
// Update initial floating camera pos
freeCameraState.position = def.viewOrigin;
freeCameraState.velocity = MakeVector3(0, 0, 0);
break;
}
case ClientCameraMode::Free: {
// spectator view (noclip view)
Vector3 center = freeCameraState.position;
Vector3 front;
Vector3 up = {0, 0, -1};
auto &state = followAndFreeCameraState;
front.x = -cosf(state.yaw) * cosf(state.pitch);
front.y = -sinf(state.yaw) * cosf(state.pitch);
front.z = sinf(state.pitch);
def.viewOrigin = center;
def.viewAxis[0] = -Vector3::Cross(up, front).Normalize();
def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize();
def.viewAxis[2] = front;
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
// for 1st view, camera blur can be used
def.denyCameraBlur = false;
break;
}
}
// Add vibration effects
{
float grenVib = grenadeVibration;
if (grenVib > 0.f) {
if (shakeLevel >= 1) {
grenVib *= 10.f;
if (grenVib > 1.f)
grenVib = 1.f;
roll += (SampleRandomFloat() - SampleRandomFloat()) * 0.2f * grenVib;
vibPitch +=
(SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
vibYaw += (SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
scale -= (SampleRandomFloat() - SampleRandomFloat()) * 0.1f * grenVib;
def.radialBlur += grenVib * 0.1f;
}
}
}
{
float grenVib = grenadeVibrationSlow;
if (grenVib > 0.f) {
if (shakeLevel >= 2) {
grenVib *= 4.f;
if (grenVib > 1.f)
grenVib = 1.f;
grenVib *= grenVib;
roll += coherentNoiseSamplers[0].Sample(time * 8.f) * 0.2f * grenVib;
vibPitch +=
coherentNoiseSamplers[1].Sample(time * 12.f) * 0.1f * grenVib;
vibYaw += coherentNoiseSamplers[2].Sample(time * 11.f) * 0.1f * grenVib;
}
}
}
// Add roll / scale
{
Vector3 right = def.viewAxis[0];
Vector3 up = def.viewAxis[1];
def.viewAxis[0] = right * cosf(roll) - up * sinf(roll);
def.viewAxis[1] = up * cosf(roll) + right * sinf(roll);
def.fovX = atanf(tanf(def.fovX * .5f) * scale) * 2.f;
def.fovY = atanf(tanf(def.fovY * .5f) * scale) * 2.f;
}
{
Vector3 u = def.viewAxis[1];
Vector3 v = def.viewAxis[2];
def.viewAxis[1] = u * cosf(vibPitch) - v * sinf(vibPitch);
def.viewAxis[2] = v * cosf(vibPitch) + u * sinf(vibPitch);
}
{
Vector3 u = def.viewAxis[0];
Vector3 v = def.viewAxis[2];
def.viewAxis[0] = u * cosf(vibYaw) - v * sinf(vibYaw);
def.viewAxis[2] = v * cosf(vibYaw) + u * sinf(vibYaw);
}
if (def.viewOrigin.z < 0.f) {
// Need to move the far plane because there's no vertical fog
def.zFar -= def.viewOrigin.z;
}
if ((int)cg_manualFocus) {
// Depth of field is manually controlled
def.depthOfFieldNearBlurStrength = def.depthOfFieldFarBlurStrength =
0.5f * (float)cg_depthOfFieldAmount;
def.depthOfFieldFocalLength = focalLength;
} else {
def.depthOfFieldNearBlurStrength = cg_depthOfFieldAmount;
def.depthOfFieldFarBlurStrength = 0.f;
}
def.zNear = 0.05f;
def.skipWorld = false;
} else {
SPAssert(GetCameraMode() == ClientCameraMode::None);
// Let there be darkness
def.viewOrigin = MakeVector3(0, 0, 0);
def.viewAxis[0] = MakeVector3(1, 0, 0);
def.viewAxis[1] = MakeVector3(0, 0, -1);
def.viewAxis[2] = MakeVector3(0, 0, 1);
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX =
atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) *
2.f;
def.zNear = 0.05f;
def.skipWorld = true;
renderer->SetFogColor(MakeVector3(0, 0, 0));
}
SPAssert(!std::isnan(def.viewOrigin.x));
SPAssert(!std::isnan(def.viewOrigin.y));
SPAssert(!std::isnan(def.viewOrigin.z));
def.radialBlur = std::min(def.radialBlur, 1.f);
return def;
}
void Client::AddGrenadeToScene(Grenade &g) {
SPADES_MARK_FUNCTION();
Handle<IModel> model;
model = renderer->RegisterModel("Models/Weapons/Grenade/Grenade.kv6");
if (g.GetPosition().z > 63.f) {
// work-around for water refraction problem
return;
}
// Move the grenade slightly so that it doesn't look like sinking in
// the ground
Vector3 position = g.GetPosition();
position.z -= 0.03f * 3.0f;
ModelRenderParam param;
Matrix4 mat = g.GetOrientation().ToRotationMatrix() * Matrix4::Scale(0.03f);
mat = Matrix4::Translate(position) * mat;
param.matrix = mat;
renderer->RenderModel(*model, param);
}
void Client::AddDebugObjectToScene(const spades::OBB3 &obb, const Vector4 &color) {
const Matrix4 &mat = obb.m;
Vector3 v[2][2][2];
v[0][0][0] = (mat * MakeVector3(0, 0, 0)).GetXYZ();
v[0][0][1] = (mat * MakeVector3(0, 0, 1)).GetXYZ();
v[0][1][0] = (mat * MakeVector3(0, 1, 0)).GetXYZ();
v[0][1][1] = (mat * MakeVector3(0, 1, 1)).GetXYZ();
v[1][0][0] = (mat * MakeVector3(1, 0, 0)).GetXYZ();
v[1][0][1] = (mat * MakeVector3(1, 0, 1)).GetXYZ();
v[1][1][0] = (mat * MakeVector3(1, 1, 0)).GetXYZ();
v[1][1][1] = (mat * MakeVector3(1, 1, 1)).GetXYZ();
renderer->AddDebugLine(v[0][0][0], v[1][0][0], color);
renderer->AddDebugLine(v[0][0][1], v[1][0][1], color);
renderer->AddDebugLine(v[0][1][0], v[1][1][0], color);
renderer->AddDebugLine(v[0][1][1], v[1][1][1], color);
renderer->AddDebugLine(v[0][0][0], v[0][1][0], color);
renderer->AddDebugLine(v[0][0][1], v[0][1][1], color);
renderer->AddDebugLine(v[1][0][0], v[1][1][0], color);
renderer->AddDebugLine(v[1][0][1], v[1][1][1], color);
renderer->AddDebugLine(v[0][0][0], v[0][0][1], color);
renderer->AddDebugLine(v[0][1][0], v[0][1][1], color);
renderer->AddDebugLine(v[1][0][0], v[1][0][1], color);
renderer->AddDebugLine(v[1][1][0], v[1][1][1], color);
}
void Client::DrawCTFObjects() {
SPADES_MARK_FUNCTION();
CTFGameMode &mode = dynamic_cast<CTFGameMode &>(world->GetMode().value());
int tId;
Handle<IModel> base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6");
Handle<IModel> intel = renderer->RegisterModel("Models/MapObjects/Intel.kv6");
for (tId = 0; tId < 2; tId++) {
CTFGameMode::Team &team = mode.GetTeam(tId);
IntVector3 col = world->GetTeam(tId).color;
Vector3 color = {col.x / 255.f, col.y / 255.f, col.z / 255.f};
ModelRenderParam param;
param.customColor = color;
// draw base
param.matrix = Matrix4::Translate(team.basePos);
param.matrix = param.matrix * Matrix4::Scale(.3f);
renderer->RenderModel(*base, param);
// draw flag
if (!mode.GetTeam(1 - tId).hasIntel) {
param.matrix = Matrix4::Translate(team.flagPos);
param.matrix = param.matrix * Matrix4::Scale(.1f);
renderer->RenderModel(*intel, param);
}
}
}
void Client::DrawTCObjects() {
SPADES_MARK_FUNCTION();
TCGameMode &mode = dynamic_cast<TCGameMode &>(world->GetMode().value());
int tId;
Handle<IModel> base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6");
int cnt = mode.GetNumTerritories();
for (tId = 0; tId < cnt; tId++) {
TCGameMode::Territory &t = mode.GetTerritory(tId);
IntVector3 col;
if (t.ownerTeamId == 2) {
col = IntVector3::Make(255, 255, 255);
} else {
col = world->GetTeam(t.ownerTeamId).color;
}
Vector3 color = {col.x / 255.f, col.y / 255.f, col.z / 255.f};
ModelRenderParam param;
param.customColor = color;
// draw base
param.matrix = Matrix4::Translate(t.pos);
param.matrix = param.matrix * Matrix4::Scale(.3f);
renderer->RenderModel(*base, param);
}
}
void Client::DrawScene() {
SPADES_MARK_FUNCTION();
renderer->StartScene(lastSceneDef);
if (world) {
stmp::optional<Player &> p = world->GetLocalPlayer();
for (size_t i = 0; i < world->GetNumPlayerSlots(); i++)
if (world->GetPlayer(static_cast<unsigned int>(i))) {
SPAssert(clientPlayers[i]);
clientPlayers[i]->AddToScene();
}
auto &nades = world->GetAllGrenades();
for (auto &nade : nades) {
AddGrenadeToScene(*nade);
}
{
for (auto &c : corpses) {
Vector3 center = c->GetCenter();
if ((center - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f)
continue;
c->AddToScene();
}
}
if (IGameMode::m_CTF == world->GetMode()->ModeType()) {
DrawCTFObjects();
} else if (IGameMode::m_TC == world->GetMode()->ModeType()) {
DrawTCObjects();
}
{
for (auto &ent : localEntities) {
ent->Render3D();
}
}
// Draw block cursor
if (p) {
if (p->IsReadyToUseTool() && p->GetTool() == Player::ToolBlock &&
p->IsAlive()) {
std::vector<IntVector3> blocks;
if (p->IsBlockCursorDragging()) {
blocks = world->CubeLine(p->GetBlockCursorDragPos(),
p->GetBlockCursorPos(), 256);
} else {
blocks.push_back(p->GetBlockCursorPos());
}
bool active = p->IsBlockCursorActive() && CanLocalPlayerUseToolNow();
Vector3 color = {1.f, 1.f, 1.f};
if (!active)
color = MakeVector3(1.f, 1.f, 0.f);
if ((int)blocks.size() > p->GetNumBlocks())
color = MakeVector3(1.f, 0.f, 0.f);
Handle<IModel> curLine =
renderer->RegisterModel("Models/MapObjects/BlockCursorLine.kv6");
Handle<IModel> curSingle =
renderer->RegisterModel("Models/MapObjects/BlockCursorSingle.kv6");
for (size_t i = 0; i < blocks.size(); i++) {
IntVector3 &v = blocks[i];
bool solid = blocks.size() > 2 && map->IsSolid(v.x, v.y, v.z);
ModelRenderParam param;
param.ghost = true;
param.opacity = active && !solid ? .7f : .3f;
param.customColor = color;
param.matrix =
Matrix4::Translate(MakeVector3(v.x + .5f, v.y + .5f, v.z + .5f));
param.matrix =
param.matrix *
Matrix4::Scale(
1.f / 24.f +
(solid ? 0.0005f
: 0.f)); // make cursor larger if needed to stop z-fighting
renderer->RenderModel(blocks.size() > 1 ? *curLine : *curSingle, param);
}
}
}
}
for (size_t i = 0; i < flashDlights.size(); i++)
renderer->AddLight(flashDlights[i]);
flashDlightsOld.clear();
flashDlightsOld.swap(flashDlights);
// draw player hottrack
// FIXME: don't use debug line
auto hottracked = HotTrackedPlayer();
if (hottracked) {
Player &player = std::get<0>(*hottracked);
hitTag_t tag = std::get<1>(*hottracked);
IntVector3 col = world->GetTeam(player.GetTeamId()).color;
Vector4 color = Vector4::Make(col.x / 255.f, col.y / 255.f, col.z / 255.f, 1.f);
Vector4 color2 = Vector4::Make(1, 1, 1, 1);
Player::HitBoxes hb = player.GetHitBoxes();
AddDebugObjectToScene(hb.head, (tag & hit_Head) ? color2 : color);
AddDebugObjectToScene(hb.torso, (tag & hit_Torso) ? color2 : color);
AddDebugObjectToScene(hb.limbs[0], (tag & hit_Legs) ? color2 : color);
AddDebugObjectToScene(hb.limbs[1], (tag & hit_Legs) ? color2 : color);
AddDebugObjectToScene(hb.limbs[2], (tag & hit_Arms) ? color2 : color);
}
renderer->EndScene();
}
Vector3 Client::Project(spades::Vector3 v) {
v -= lastSceneDef.viewOrigin;
// transform to NDC
Vector3 v2;
v2.x = Vector3::Dot(v, lastSceneDef.viewAxis[0]);
v2.y = Vector3::Dot(v, lastSceneDef.viewAxis[1]);
v2.z = Vector3::Dot(v, lastSceneDef.viewAxis[2]);
float tanX = tanf(lastSceneDef.fovX * .5f);
float tanY = tanf(lastSceneDef.fovY * .5f);
v2.x /= tanX * v2.z;
v2.y /= tanY * v2.z;
// transform to IRenderer 2D coord
v2.x = (v2.x + 1.f) / 2.f * renderer->ScreenWidth();
v2.y = (-v2.y + 1.f) / 2.f * renderer->ScreenHeight();
return v2;
}
} // namespace client
} // namespace spades