1459 lines
49 KiB
C++
1459 lines
49 KiB
C++
#include "Game.h"
|
||
#include "AnimatedModel.h"
|
||
#include "BoneAnimatedModel.h"
|
||
#include "planet/PlanetData.h"
|
||
#include "utils/Utils.h"
|
||
#include "render/OpenGlExtensions.h"
|
||
#include <iostream>
|
||
#include "render/TextureManager.h"
|
||
#include "TextModel.h"
|
||
#include <random>
|
||
#include <cmath>
|
||
#include <algorithm>
|
||
#include <functional>
|
||
#ifdef __ANDROID__
|
||
#include <android/log.h>
|
||
#endif
|
||
|
||
#ifdef NETWORK
|
||
#include "network/WebSocketClient.h"
|
||
#else
|
||
#include "network/LocalClient.h"
|
||
#endif
|
||
|
||
namespace ZL
|
||
{
|
||
#ifdef EMSCRIPTEN
|
||
const char* CONST_ZIP_FILE = "resources.zip";
|
||
#else
|
||
const char* CONST_ZIP_FILE = "";
|
||
#endif
|
||
|
||
static bool g_exitBgAnimating = false;
|
||
|
||
float x = 0;
|
||
|
||
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
|
||
{
|
||
|
||
std::normal_distribution<> distrib(0.0, 1.0);
|
||
|
||
Eigen::Quaternionf randomQuat = {
|
||
(float)distrib(gen),
|
||
(float)distrib(gen),
|
||
(float)distrib(gen),
|
||
(float)distrib(gen)
|
||
};
|
||
|
||
return randomQuat.normalized();
|
||
}
|
||
|
||
std::vector<BoxCoords> generateRandomBoxCoords(int N)
|
||
{
|
||
const float MIN_DISTANCE = 3.0f;
|
||
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||
const float MIN_COORD = -100.0f;
|
||
const float MAX_COORD = 100.0f;
|
||
const int MAX_ATTEMPTS = 1000;
|
||
std::vector<BoxCoords> boxCoordsArr;
|
||
|
||
std::random_device rd;
|
||
std::mt19937 gen(rd());
|
||
|
||
std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);
|
||
|
||
int generatedCount = 0;
|
||
|
||
while (generatedCount < N)
|
||
{
|
||
bool accepted = false;
|
||
int attempts = 0;
|
||
|
||
while (!accepted && attempts < MAX_ATTEMPTS)
|
||
{
|
||
Vector3f newPos(
|
||
(float)distrib(gen),
|
||
(float)distrib(gen),
|
||
(float)distrib(gen)
|
||
);
|
||
|
||
accepted = true;
|
||
for (const auto& existingBox : boxCoordsArr)
|
||
{
|
||
|
||
Vector3f diff = newPos - existingBox.pos;
|
||
|
||
float distanceSquared = diff.squaredNorm();
|
||
|
||
if (distanceSquared < MIN_DISTANCE_SQUARED)
|
||
{
|
||
accepted = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (accepted)
|
||
{
|
||
Eigen::Quaternionf randomQuat = generateRandomQuaternion(gen);
|
||
|
||
Matrix3f randomMatrix = randomQuat.toRotationMatrix();
|
||
|
||
boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
|
||
generatedCount++;
|
||
}
|
||
attempts++;
|
||
}
|
||
|
||
if (!accepted) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return boxCoordsArr;
|
||
}
|
||
|
||
static Eigen::Matrix4f makeViewMatrix_FromYourCamera()
|
||
{
|
||
Eigen::Matrix4f Tz = Eigen::Matrix4f::Identity();
|
||
Tz(2, 3) = -1.0f * ZL::Environment::zoom;
|
||
|
||
Eigen::Matrix4f R = Eigen::Matrix4f::Identity();
|
||
R.block<3, 3>(0, 0) = ZL::Environment::inverseShipMatrix;
|
||
|
||
Eigen::Matrix4f Tship = Eigen::Matrix4f::Identity();
|
||
Tship(0, 3) = -ZL::Environment::shipState.position.x();
|
||
Tship(1, 3) = -ZL::Environment::shipState.position.y();
|
||
Tship(2, 3) = -ZL::Environment::shipState.position.z();
|
||
|
||
return Tz * R * Tship;
|
||
}
|
||
|
||
static Eigen::Matrix4f makePerspective(float fovyRadians, float aspect, float zNear, float zFar)
|
||
{
|
||
// Стандартная перспектива
|
||
float f = 1.0f / std::tan(fovyRadians * 0.5f);
|
||
|
||
Eigen::Matrix4f P = Eigen::Matrix4f::Zero();
|
||
P(0, 0) = f / aspect;
|
||
P(1, 1) = f;
|
||
P(2, 2) = (zFar + zNear) / (zNear - zFar);
|
||
P(2, 3) = (2.0f * zFar * zNear) / (zNear - zFar);
|
||
P(3, 2) = -1.0f;
|
||
return P;
|
||
}
|
||
|
||
bool Game::worldToScreen(const Vector3f& world, float& outX, float& outY, float& outDepth) const
|
||
{
|
||
// Матрицы должны совпасть с drawBoxes/drawShip по смыслу
|
||
float aspect = static_cast<float>(Environment::width) / static_cast<float>(Environment::height);
|
||
|
||
Eigen::Matrix4f V = makeViewMatrix_FromYourCamera();
|
||
Eigen::Matrix4f P = makePerspective(1.0f / 1.5f, aspect, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||
|
||
Eigen::Vector4f w(world.x(), world.y(), world.z(), 1.0f);
|
||
Eigen::Vector4f clip = P * V * w;
|
||
|
||
if (clip.w() <= 0.0001f) return false; // позади камеры
|
||
|
||
Eigen::Vector3f ndc = clip.head<3>() / clip.w(); // [-1..1]
|
||
outDepth = ndc.z();
|
||
|
||
// В пределах экрана?
|
||
// (можно оставить, можно клампить)
|
||
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width;
|
||
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height;
|
||
|
||
outX = sx;
|
||
outY = sy;
|
||
|
||
// Можно отсеять те, что вне:
|
||
if (sx < -200 || sx > Environment::width + 200) return false;
|
||
if (sy < -200 || sy > Environment::height + 200) return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
void Game::drawBoxesLabels()
|
||
{
|
||
if (!textRenderer) return;
|
||
|
||
// Текст рисуем как 2D поверх всего 3D, но ДО drawUI или после — как хочешь.
|
||
// Чтобы подписи были поверх — делай после drawBoxes и до drawUI (как мы и вставили).
|
||
|
||
glDisable(GL_DEPTH_TEST);
|
||
glEnable(GL_BLEND);
|
||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
||
for (size_t i = 0; i < boxCoordsArr.size(); ++i)
|
||
{
|
||
if (i >= boxAlive.size() || !boxAlive[i]) continue;
|
||
if (i >= boxLabels.size()) continue;
|
||
|
||
// ВАЖНО: твои боксы рисуются с Translate({0,0,45000}) + pos
|
||
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f };
|
||
|
||
// Чуть выше бокса по Y (или по Z — как нравится)
|
||
Vector3f labelWorld = boxWorld + Vector3f{ 0.0f, 2.2f, 0.0f };
|
||
|
||
float sx, sy, depth;
|
||
if (!worldToScreen(labelWorld, sx, sy, depth)) continue;
|
||
|
||
// В твоей UI-системе Y обычно перевёрнут (ты делаешь uiY = height - my).
|
||
// Наш worldToScreen отдаёт Y в системе "низ=0, верх=height" (NDC->screen).
|
||
// Чтобы совпало с твоей UI-логикой, перевернём:
|
||
float uiX = sx;
|
||
float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy)
|
||
|
||
// Можно делать масштаб по дальности: чем дальше — тем меньше.
|
||
// depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
|
||
float dist = (Environment::shipState.position - boxWorld).norm();
|
||
float scale = std::clamp(120.0f / (dist + 1.0f), 0.6f, 1.2f);
|
||
|
||
textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
|
||
}
|
||
|
||
glDisable(GL_BLEND);
|
||
glEnable(GL_DEPTH_TEST);
|
||
}
|
||
|
||
Game::Game()
|
||
: window(nullptr)
|
||
, glContext(nullptr)
|
||
, newTickCount(0)
|
||
, lastTickCount(0)
|
||
, planetObject(renderer, taskManager, mainThreadHandler)
|
||
{
|
||
projectiles.reserve(maxProjectiles);
|
||
for (int i = 0; i < maxProjectiles; ++i) {
|
||
projectiles.emplace_back(std::make_unique<Projectile>());
|
||
}
|
||
}
|
||
|
||
Game::~Game() {
|
||
if (glContext) {
|
||
SDL_GL_DeleteContext(glContext);
|
||
}
|
||
if (window) {
|
||
SDL_DestroyWindow(window);
|
||
}
|
||
SDL_Quit();
|
||
}
|
||
|
||
void Game::setup() {
|
||
glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
||
|
||
ZL::BindOpenGlFunctions();
|
||
ZL::CheckGlError();
|
||
|
||
|
||
//#ifndef SIMPLIFIED
|
||
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
|
||
|
||
/*#else
|
||
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||
#endif*/
|
||
|
||
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
|
||
bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
|
||
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
|
||
uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
|
||
std::function<void()> loadGameplayUI;
|
||
loadGameplayUI = [this]() {
|
||
uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
|
||
|
||
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
|
||
static bool isExitButtonAnimating = false;
|
||
uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() {
|
||
std::cerr << "Settings button animation finished -> переход в настройки" << std::endl;
|
||
if (uiManager.pushMenuFromFile("resources/config/settings.json", this->renderer, CONST_ZIP_FILE)) {
|
||
uiManager.setButtonCallback("Opt1", [this](const std::string& n) {
|
||
std::cerr << "Opt1 pressed: " << n << std::endl;
|
||
});
|
||
uiManager.setButtonCallback("Opt2", [this](const std::string& n) {
|
||
std::cerr << "Opt2 pressed: " << n << std::endl;
|
||
});
|
||
uiManager.setButtonCallback("backButton", [this](const std::string& n) {
|
||
uiManager.stopAllAnimations();
|
||
uiManager.popMenu();
|
||
});
|
||
}
|
||
else {
|
||
std::cerr << "Failed to open settings menu after animations" << std::endl;
|
||
}
|
||
});
|
||
|
||
uiManager.setAnimationCallback("exitButton", "bgScroll", []() {
|
||
std::cerr << "Exit button bgScroll animation finished" << std::endl;
|
||
g_exitBgAnimating = false;
|
||
});
|
||
|
||
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
|
||
std::cerr << "Play button pressed: " << name << std::endl;
|
||
});
|
||
|
||
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
|
||
std::cerr << "Settings button pressed: " << name << std::endl;
|
||
uiManager.startAnimationOnNode("playButton", "buttonsExit");
|
||
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
|
||
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
|
||
});
|
||
|
||
uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
|
||
std::cerr << "Exit button pressed: " << name << std::endl;
|
||
|
||
if (!g_exitBgAnimating) {
|
||
std::cerr << "start repeat anim bgScroll on exitButton" << std::endl;
|
||
g_exitBgAnimating = true;
|
||
uiManager.startAnimationOnNode("exitButton", "bgScroll");
|
||
}
|
||
else {
|
||
std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl;
|
||
g_exitBgAnimating = false;
|
||
uiManager.stopAnimationOnNode("exitButton", "bgScroll");
|
||
|
||
auto exitButton = uiManager.findButton("exitButton");
|
||
if (exitButton) {
|
||
exitButton->animOffsetX = 0.0f;
|
||
exitButton->animOffsetY = 0.0f;
|
||
exitButton->animScaleX = 1.0f;
|
||
exitButton->animScaleY = 1.0f;
|
||
exitButton->buildMesh();
|
||
}
|
||
}
|
||
});
|
||
|
||
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
|
||
uint64_t now = SDL_GetTicks64();
|
||
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
||
lastProjectileFireTime = now;
|
||
|
||
this->fireProjectiles();
|
||
|
||
Eigen::Vector3f localForward = { 0, 0, -1 };
|
||
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
|
||
|
||
Eigen::Vector3f centerPos = Environment::shipState.position +
|
||
Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };
|
||
|
||
std::string fireMsg = "FIRE:" +
|
||
std::to_string(now) + ":" +
|
||
std::to_string(centerPos.x()) + ":" +
|
||
std::to_string(centerPos.y()) + ":" +
|
||
std::to_string(centerPos.z()) + ":" +
|
||
std::to_string(worldForward.x()) + ":" +
|
||
std::to_string(worldForward.y()) + ":" +
|
||
std::to_string(worldForward.z()) + ":" +
|
||
"2";
|
||
|
||
networkClient->Send(fireMsg);
|
||
}
|
||
});
|
||
|
||
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
|
||
int newVel = roundf(value * 10);
|
||
if (newVel != Environment::shipState.selectedVelocity) {
|
||
newShipVelocity = newVel;
|
||
}
|
||
});
|
||
};
|
||
|
||
uiManager.setButtonCallback("singleButton", [loadGameplayUI](const std::string& name) {
|
||
std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n";
|
||
loadGameplayUI();
|
||
});
|
||
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
|
||
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n";
|
||
loadGameplayUI();
|
||
});
|
||
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
|
||
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
|
||
Environment::exitGameLoop = true;
|
||
});
|
||
|
||
cubemapTexture = std::make_shared<Texture>(
|
||
std::array<TextureDataStruct, 6>{
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
|
||
});
|
||
|
||
|
||
cubemap.data = ZL::CreateCubemap(500);
|
||
cubemap.RefreshVBO();
|
||
|
||
|
||
|
||
//Load texture
|
||
|
||
//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE));
|
||
//spaceshipBase = LoadFromTextFile02("resources/spaceship006.txt", CONST_ZIP_FILE);
|
||
//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
||
|
||
//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE));
|
||
//spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE);
|
||
//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
||
|
||
spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/MainCharacter_Base_color_sRGB.png", CONST_ZIP_FILE));
|
||
spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE);
|
||
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
||
|
||
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
|
||
|
||
spaceship.AssignFrom(spaceshipBase);
|
||
spaceship.RefreshVBO();
|
||
|
||
|
||
//Boxes
|
||
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
|
||
boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE);
|
||
|
||
boxCoordsArr = generateRandomBoxCoords(50);
|
||
boxRenderArr.resize(boxCoordsArr.size());
|
||
for (int i = 0; i < boxCoordsArr.size(); i++)
|
||
{
|
||
boxRenderArr[i].AssignFrom(boxBase);
|
||
//boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999);
|
||
boxRenderArr[i].RefreshVBO();
|
||
}
|
||
|
||
boxAlive.resize(boxCoordsArr.size(), true);
|
||
|
||
textRenderer = std::make_unique<ZL::TextRenderer>();
|
||
textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32);
|
||
|
||
boxLabels.clear();
|
||
boxLabels.reserve(boxCoordsArr.size());
|
||
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
|
||
boxLabels.push_back("Box " + std::to_string(i + 1));
|
||
}
|
||
|
||
if (!cfgLoaded)
|
||
{
|
||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||
}
|
||
|
||
renderer.InitOpenGL();
|
||
glEnable(GL_BLEND);
|
||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
||
planetObject.init();
|
||
|
||
#ifdef NETWORK
|
||
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
||
networkClient->Connect("127.0.0.1", 8080);
|
||
#else
|
||
networkClient = std::make_unique<LocalClient>();
|
||
networkClient->Connect("", 0);
|
||
#endif
|
||
}
|
||
|
||
void Game::drawCubemap(float skyPercent)
|
||
{
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string envShaderName = "env_sky";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
static const std::string skyPercentUniformName = "skyPercent";
|
||
|
||
renderer.shaderManager.PushShader(envShaderName);
|
||
renderer.RenderUniform1i(textureUniformName, 0);
|
||
renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
|
||
renderer.EnableVertexAttribArray(vPositionName);
|
||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||
renderer.PushMatrix();
|
||
renderer.LoadIdentity();
|
||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||
|
||
|
||
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
||
Matrix3f viewMatrix = Environment::inverseShipMatrix;
|
||
Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized();
|
||
|
||
|
||
// Передаем вектор НА источник света
|
||
Vector3f lightToSource = -viewLightDir;
|
||
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
|
||
|
||
// 2. Базовый цвет атмосферы (голубой)
|
||
Vector3f skyColor = { 0.0f, 0.5f, 1.0f };
|
||
renderer.RenderUniform3fv("uSkyColor", skyColor.data());
|
||
|
||
// 1. Вектор направления от центра планеты к игроку (в мировых координатах)
|
||
// Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter)
|
||
Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
||
|
||
// 2. Направление света в мировом пространстве
|
||
//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
||
|
||
// 3. Считаем глобальную освещенность для игрока (насколько он на свету)
|
||
// Это одно число для всего кадра
|
||
float playerLightFactor = playerDirWorld.dot(-worldLightDir);
|
||
// Ограничиваем и делаем переход мягче
|
||
playerLightFactor = std::clamp((playerLightFactor + 0.2f) / 1.2f, 0.0f, 1.0f);
|
||
|
||
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
|
||
renderer.RenderUniform1f("skyPercent", skyPercent);
|
||
|
||
CheckGlError();
|
||
|
||
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
|
||
renderer.DrawVertexRenderStruct(cubemap);
|
||
|
||
CheckGlError();
|
||
|
||
|
||
renderer.PopMatrix();
|
||
renderer.PopProjectionMatrix();
|
||
renderer.DisableVertexAttribArray(vPositionName);
|
||
|
||
renderer.shaderManager.PopShader();
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawShip()
|
||
{
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string envShaderName = "env";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
|
||
renderer.shaderManager.PushShader(defaultShaderName);
|
||
renderer.RenderUniform1i(textureUniformName, 0);
|
||
renderer.EnableVertexAttribArray(vPositionName);
|
||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||
renderer.PushMatrix();
|
||
|
||
renderer.LoadIdentity();
|
||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||
renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
|
||
|
||
if (shipAlive) {
|
||
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
||
renderer.DrawVertexRenderStruct(spaceship);
|
||
}
|
||
glEnable(GL_BLEND);
|
||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
||
for (const auto& p : projectiles) {
|
||
if (p && p->isActive()) {
|
||
p->draw(renderer);
|
||
}
|
||
}
|
||
|
||
if (shipAlive) {
|
||
renderer.PushMatrix();
|
||
renderer.TranslateMatrix({ 0, 0, 16 });
|
||
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||
renderer.PopMatrix();
|
||
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||
}
|
||
|
||
if (showExplosion) {
|
||
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||
}
|
||
|
||
glDisable(GL_BLEND);
|
||
renderer.PopMatrix();
|
||
renderer.PopProjectionMatrix();
|
||
renderer.DisableVertexAttribArray(vPositionName);
|
||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.shaderManager.PopShader();
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawBoxes()
|
||
{
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string envShaderName = "env";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
|
||
renderer.shaderManager.PushShader(defaultShaderName);
|
||
renderer.RenderUniform1i(textureUniformName, 0);
|
||
renderer.EnableVertexAttribArray(vPositionName);
|
||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||
|
||
for (int i = 0; i < boxCoordsArr.size(); i++)
|
||
{
|
||
if (!boxAlive[i]) continue;
|
||
renderer.PushMatrix();
|
||
|
||
renderer.LoadIdentity();
|
||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||
renderer.TranslateMatrix(-Environment::shipState.position);
|
||
renderer.TranslateMatrix({ 0.f, 0.f, 45000.f });
|
||
renderer.TranslateMatrix(boxCoordsArr[i].pos);
|
||
renderer.RotateMatrix(boxCoordsArr[i].m);
|
||
|
||
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
|
||
//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
|
||
renderer.DrawVertexRenderStruct(boxRenderArr[i]);
|
||
|
||
renderer.PopMatrix();
|
||
}
|
||
renderer.PopProjectionMatrix();
|
||
renderer.DisableVertexAttribArray(vPositionName);
|
||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.shaderManager.PopShader();
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawUI()
|
||
{
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string envShaderName = "env";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
|
||
glClear(GL_DEPTH_BUFFER_BIT);
|
||
|
||
renderer.shaderManager.PushShader(defaultShaderName);
|
||
renderer.RenderUniform1i(textureUniformName, 0);
|
||
renderer.EnableVertexAttribArray(vPositionName);
|
||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.DisableVertexAttribArray(vPositionName);
|
||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||
glEnable(GL_BLEND);
|
||
uiManager.draw(renderer);
|
||
glDisable(GL_BLEND);
|
||
renderer.shaderManager.PopShader();
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawScene() {
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string envShaderName = "env";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
|
||
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||
|
||
glViewport(0, 0, Environment::width, Environment::height);
|
||
|
||
CheckGlError();
|
||
|
||
float skyPercent = 0.0;
|
||
float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position);
|
||
if (distance > 1500.f)
|
||
{
|
||
skyPercent = 0.0f;
|
||
}
|
||
else if (distance < 800.f)
|
||
{
|
||
skyPercent = 1.0f;
|
||
}
|
||
else
|
||
{
|
||
skyPercent = (1500.f - distance) / (1500.f - 800.f);
|
||
}
|
||
|
||
|
||
drawCubemap(skyPercent);
|
||
planetObject.draw(renderer);
|
||
if (planetObject.distanceToPlanetSurface(Environment::shipState.position) > 100.f)
|
||
{
|
||
glClear(GL_DEPTH_BUFFER_BIT);
|
||
}
|
||
drawShip();
|
||
drawRemoteShips();
|
||
drawRemoteShipsLabels();
|
||
drawBoxes();
|
||
drawBoxesLabels();
|
||
|
||
drawUI();
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawRemoteShips() {
|
||
// Используем те же константы имен для шейдеров, что и в drawShip
|
||
static const std::string defaultShaderName = "default";
|
||
static const std::string vPositionName = "vPosition";
|
||
static const std::string vTexCoordName = "vTexCoord";
|
||
static const std::string textureUniformName = "Texture";
|
||
|
||
// Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый)
|
||
renderer.shaderManager.PushShader(defaultShaderName);
|
||
renderer.RenderUniform1i(textureUniformName, 0);
|
||
|
||
renderer.EnableVertexAttribArray(vPositionName);
|
||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||
|
||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||
|
||
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
|
||
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
||
|
||
auto now = std::chrono::system_clock::now();
|
||
|
||
//Apply server delay:
|
||
now -= std::chrono::milliseconds(CLIENT_DELAY);
|
||
|
||
latestRemotePlayers = networkClient->getRemotePlayers();
|
||
|
||
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
|
||
if (!serverBoxesApplied && networkClient) {
|
||
auto sboxes = networkClient->getServerBoxes();
|
||
if (!sboxes.empty()) {
|
||
boxCoordsArr.clear();
|
||
for (auto& b : sboxes) {
|
||
BoxCoords bc;
|
||
bc.pos = b.first;
|
||
bc.m = b.second;
|
||
boxCoordsArr.push_back(bc);
|
||
}
|
||
boxRenderArr.resize(boxCoordsArr.size());
|
||
for (int i = 0; i < (int)boxCoordsArr.size(); ++i) {
|
||
boxRenderArr[i].AssignFrom(boxBase);
|
||
boxRenderArr[i].RefreshVBO();
|
||
}
|
||
boxAlive.assign(boxCoordsArr.size(), true);
|
||
serverBoxesApplied = true;
|
||
}
|
||
}
|
||
|
||
// Итерируемся по актуальным данным из extrapolateRemotePlayers
|
||
for (auto const& [id, remotePlayer] : latestRemotePlayers) {
|
||
|
||
if (!remotePlayer.canFetchClientStateAtTime(now))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
ClientState playerState = remotePlayer.fetchClientStateAtTime(now);
|
||
|
||
|
||
renderer.PushMatrix();
|
||
renderer.LoadIdentity();
|
||
|
||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||
renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
|
||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
||
renderer.TranslateMatrix(-Environment::shipState.position);
|
||
|
||
|
||
Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition;
|
||
renderer.TranslateMatrix(relativePos);
|
||
|
||
// 3. Поворот врага
|
||
renderer.RotateMatrix(playerState.rotation);
|
||
|
||
renderer.DrawVertexRenderStruct(spaceship);
|
||
renderer.PopMatrix();
|
||
}
|
||
|
||
renderer.PopProjectionMatrix();
|
||
renderer.DisableVertexAttribArray(vPositionName);
|
||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||
renderer.shaderManager.PopShader();
|
||
|
||
CheckGlError();
|
||
}
|
||
|
||
void Game::drawRemoteShipsLabels()
|
||
{
|
||
if (!textRenderer) return;
|
||
|
||
#ifdef NETWORK
|
||
// 2D поверх 3D
|
||
glDisable(GL_DEPTH_TEST);
|
||
glEnable(GL_BLEND);
|
||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
||
// Берем удаленных игроков
|
||
latestRemotePlayers = networkClient->getRemotePlayers();
|
||
|
||
auto now = std::chrono::system_clock::now();
|
||
now -= std::chrono::milliseconds(CLIENT_DELAY);
|
||
|
||
for (auto const& [id, remotePlayer] : latestRemotePlayers)
|
||
{
|
||
if (!remotePlayer.canFetchClientStateAtTime(now))
|
||
continue;
|
||
|
||
ClientState st = remotePlayer.fetchClientStateAtTime(now);
|
||
|
||
// Позиция корабля в мире
|
||
Vector3f shipWorld = st.position;
|
||
|
||
float distSq = (Environment::shipState.position - shipWorld).squaredNorm();
|
||
/*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма
|
||
continue;*/
|
||
float dist = sqrt(distSq);
|
||
float alpha = 1.0f; // постоянная видимость
|
||
/*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма
|
||
if (alpha < 0.01f)
|
||
continue; */
|
||
Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты
|
||
float sx, sy, depth;
|
||
if (!worldToScreen(labelWorld, sx, sy, depth))
|
||
continue;
|
||
|
||
float uiX = sx, uiY = sy;
|
||
//float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);
|
||
float scale;
|
||
if (dist > CLOSE_DIST) {
|
||
scale = 0.4f;
|
||
}
|
||
else {
|
||
float t = 1.0f - (dist / CLOSE_DIST);
|
||
scale = 0.4f + (MAX_SCALE - 0.4f) * t;
|
||
}
|
||
scale = std::clamp(scale, MIN_SCALE, MAX_SCALE);
|
||
|
||
// Дефолтный лейбл
|
||
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m";
|
||
|
||
// TODO: nickname sync
|
||
|
||
textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, {0.f, 0.f, 0.f, alpha}); // color param
|
||
textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha });
|
||
}
|
||
|
||
glDisable(GL_BLEND);
|
||
glEnable(GL_DEPTH_TEST);
|
||
#endif
|
||
}
|
||
|
||
void Game::processTickCount() {
|
||
|
||
if (lastTickCount == 0) {
|
||
//lastTickCount = SDL_GetTicks64();
|
||
lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
std::chrono::system_clock::now().time_since_epoch()
|
||
).count();
|
||
return;
|
||
}
|
||
|
||
//newTickCount = SDL_GetTicks64();
|
||
newTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
std::chrono::system_clock::now().time_since_epoch()
|
||
).count();
|
||
|
||
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
||
|
||
size_t delta = newTickCount - lastTickCount;
|
||
if (delta > CONST_MAX_TIME_INTERVAL)
|
||
{
|
||
//throw std::runtime_error("Synchronization is lost");
|
||
}
|
||
|
||
auto now_ms = newTickCount;
|
||
|
||
sparkEmitter.update(static_cast<float>(delta));
|
||
planetObject.update(static_cast<float>(delta));
|
||
|
||
static float pingTimer = 0.0f;
|
||
pingTimer += delta;
|
||
if (pingTimer >= 1000.0f) {
|
||
std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
|
||
|
||
networkClient->Send(pingMsg);
|
||
std::cout << "Sending: " << pingMsg << std::endl;
|
||
pingTimer = 0.0f;
|
||
}
|
||
|
||
//Handle input:
|
||
|
||
if (newShipVelocity != Environment::shipState.selectedVelocity)
|
||
{
|
||
Environment::shipState.selectedVelocity = newShipVelocity;
|
||
|
||
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
|
||
networkClient->Send(msg);
|
||
}
|
||
|
||
float discreteMag;
|
||
int discreteAngle;
|
||
|
||
if (Environment::tapDownHold) {
|
||
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
|
||
float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);
|
||
|
||
float rawMag = sqrtf(diffx * diffx + diffy * diffy);
|
||
float maxRadius = 200.0f; // Максимальный вынос джойстика
|
||
|
||
if (rawMag > 10.0f) { // Мертвая зона
|
||
// 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1)
|
||
float normalizedMag = min(rawMag / maxRadius, 1.0f);
|
||
discreteMag = std::round(normalizedMag * 10.0f) / 10.0f;
|
||
|
||
// 2. Дискретизируем угол (0-359 градусов)
|
||
// atan2 возвращает радианы, переводим в градусы
|
||
float radians = atan2f(diffy, diffx);
|
||
discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
|
||
if (discreteAngle < 0) discreteAngle += 360;
|
||
|
||
}
|
||
else
|
||
{
|
||
discreteAngle = -1;
|
||
discreteMag = 0.0f;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
discreteAngle = -1;
|
||
discreteMag = 0.0f;
|
||
}
|
||
|
||
|
||
if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) {
|
||
Environment::shipState.discreteAngle = discreteAngle;
|
||
Environment::shipState.discreteMag = discreteMag;
|
||
|
||
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
|
||
networkClient->Send(msg);
|
||
std::cout << "Sending: " << msg << std::endl;
|
||
}
|
||
|
||
Environment::shipState.simulate_physics(delta);
|
||
Environment::inverseShipMatrix = Environment::shipState.rotation.inverse();
|
||
|
||
for (auto& p : projectiles) {
|
||
if (p && p->isActive()) {
|
||
p->update(static_cast<float>(delta), renderer);
|
||
}
|
||
}
|
||
|
||
std::vector<Vector3f> projCameraPoints;
|
||
for (const auto& p : projectiles) {
|
||
if (p && p->isActive()) {
|
||
Vector3f worldPos = p->getPosition();
|
||
Vector3f rel = worldPos - Environment::shipState.position;
|
||
Vector3f camPos = Environment::inverseShipMatrix * rel;
|
||
projCameraPoints.push_back(camPos);
|
||
}
|
||
}
|
||
if (!projCameraPoints.empty()) {
|
||
projectileEmitter.setEmissionPoints(projCameraPoints);
|
||
projectileEmitter.emit();
|
||
}
|
||
else {
|
||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
}
|
||
|
||
std::vector<Vector3f> shipCameraPoints;
|
||
for (const auto& lp : shipLocalEmissionPoints) {
|
||
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
|
||
shipCameraPoints.push_back(adjusted);
|
||
}
|
||
if (!shipCameraPoints.empty()) {
|
||
sparkEmitter.setEmissionPoints(shipCameraPoints);
|
||
}
|
||
|
||
sparkEmitter.update(static_cast<float>(delta));
|
||
projectileEmitter.update(static_cast<float>(delta));
|
||
|
||
explosionEmitter.update(static_cast<float>(delta));
|
||
if (showExplosion) {
|
||
uint64_t now = SDL_GetTicks64();
|
||
if (lastExplosionTime != 0 && now - lastExplosionTime >= explosionDurationMs) {
|
||
showExplosion = false;
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
explosionEmitter.setUseWorldSpace(false);
|
||
}
|
||
}
|
||
if (shipAlive) {
|
||
float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipState.position);
|
||
if (distToSurface <= 0.0f) {
|
||
|
||
Vector3f dir = (Environment::shipState.position - PlanetData::PLANET_CENTER_OFFSET).normalized();
|
||
Vector3f collisionPoint = PlanetData::PLANET_CENTER_OFFSET + dir * PlanetData::PLANET_RADIUS;
|
||
Environment::shipState.position = PlanetData::PLANET_CENTER_OFFSET + dir * (PlanetData::PLANET_RADIUS + shipCollisionRadius + 0.1f);
|
||
|
||
shipAlive = false;
|
||
gameOver = true;
|
||
Environment::shipState.velocity = 0.0f;
|
||
showExplosion = true;
|
||
|
||
explosionEmitter.setUseWorldSpace(true);
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ collisionPoint });
|
||
explosionEmitter.emit();
|
||
lastExplosionTime = SDL_GetTicks64();
|
||
|
||
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
|
||
|
||
if (!uiGameOverShown) {
|
||
if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) {
|
||
uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
|
||
this->shipAlive = true;
|
||
this->gameOver = false;
|
||
this->uiGameOverShown = false;
|
||
this->showExplosion = false;
|
||
this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
|
||
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
|
||
Environment::shipState.velocity = 0.0f;
|
||
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
|
||
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
|
||
Environment::zoom = DEFAULT_ZOOM;
|
||
Environment::tapDownHold = false;
|
||
|
||
uiManager.popMenu();
|
||
std::cerr << "Game restarted\n";
|
||
});
|
||
|
||
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
|
||
Environment::exitGameLoop = true;
|
||
});
|
||
|
||
uiGameOverShown = true;
|
||
}
|
||
else {
|
||
std::cerr << "Failed to load game_over.json\n";
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
bool stoneCollided = false;
|
||
int collidedTriIdx = -1;
|
||
Vector3f collidedStonePos = Vector3f{ 0.0f, 0.0f, 0.0f };
|
||
float collidedStoneRadius = 0.0f;
|
||
|
||
for (int triIdx : planetObject.triangleIndicesToDraw) {
|
||
if (triIdx < 0 || triIdx >= static_cast<int>(planetObject.planetStones.allInstances.size()))
|
||
continue;
|
||
|
||
if (planetObject.planetStones.statuses.size() <= static_cast<size_t>(triIdx))
|
||
continue;
|
||
|
||
if (planetObject.planetStones.statuses[triIdx] != ChunkStatus::Live)
|
||
continue;
|
||
|
||
const auto& instances = planetObject.planetStones.allInstances[triIdx];
|
||
for (const auto& inst : instances) {
|
||
|
||
Vector3f stoneWorld = inst.position;
|
||
Vector3f diff = Environment::shipState.position - stoneWorld;
|
||
|
||
float maxScale = (std::max)({ inst.scale(0), inst.scale(1), inst.scale(2) });
|
||
float stoneRadius = StoneParams::BASE_SCALE * maxScale * 0.9f;
|
||
float thresh = shipCollisionRadius + stoneRadius;
|
||
|
||
if (diff.squaredNorm() <= thresh * thresh) {
|
||
stoneCollided = true;
|
||
collidedTriIdx = triIdx;
|
||
collidedStonePos = stoneWorld;
|
||
collidedStoneRadius = stoneRadius;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (stoneCollided) break;
|
||
}
|
||
|
||
if (stoneCollided) {
|
||
Vector3f away = (Environment::shipState.position - collidedStonePos);
|
||
if (away.squaredNorm() <= 1e-6f) {
|
||
away = Vector3f{ 0.0f, 1.0f, 0.0f };
|
||
}
|
||
away.normalize();
|
||
|
||
Environment::shipState.position = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f);
|
||
|
||
shipAlive = false;
|
||
gameOver = true;
|
||
Environment::shipState.velocity = 0.0f;
|
||
showExplosion = true;
|
||
|
||
explosionEmitter.setUseWorldSpace(true);
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ collidedStonePos });
|
||
explosionEmitter.emit();
|
||
lastExplosionTime = SDL_GetTicks64();
|
||
|
||
std::cerr << "GAME OVER: collision with stone on triangle " << collidedTriIdx << std::endl;
|
||
|
||
if (collidedTriIdx >= 0 && collidedTriIdx < static_cast<int>(planetObject.stonesToRender.size())) {
|
||
planetObject.stonesToRender[collidedTriIdx].data.PositionData.clear();
|
||
planetObject.stonesToRender[collidedTriIdx].vao.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].positionVBO.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].normalVBO.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].tangentVBO.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].binormalVBO.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].colorVBO.reset();
|
||
planetObject.stonesToRender[collidedTriIdx].texCoordVBO.reset();
|
||
}
|
||
if (collidedTriIdx >= 0 && collidedTriIdx < static_cast<int>(planetObject.planetStones.statuses.size())) {
|
||
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
|
||
}
|
||
|
||
if (!uiGameOverShown) {
|
||
if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) {
|
||
uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
|
||
this->shipAlive = true;
|
||
this->gameOver = false;
|
||
this->uiGameOverShown = false;
|
||
this->showExplosion = false;
|
||
this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
|
||
Environment::shipState.velocity = 0.0f;
|
||
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
|
||
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
|
||
Environment::zoom = DEFAULT_ZOOM;
|
||
Environment::tapDownHold = false;
|
||
uiManager.popMenu();
|
||
std::cerr << "Game restarted\n";
|
||
});
|
||
|
||
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
|
||
Environment::exitGameLoop = true;
|
||
});
|
||
|
||
uiGameOverShown = true;
|
||
}
|
||
else {
|
||
std::cerr << "Failed to load game_over.json\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < boxCoordsArr.size(); ++i) {
|
||
if (!boxAlive[i]) continue;
|
||
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f };
|
||
Vector3f diff = Environment::shipState.position - boxWorld;
|
||
float thresh = shipCollisionRadius + boxCollisionRadius;
|
||
if (diff.squaredNorm() <= thresh * thresh) {
|
||
boxAlive[i] = false;
|
||
|
||
boxRenderArr[i].data.PositionData.clear();
|
||
boxRenderArr[i].vao.reset();
|
||
boxRenderArr[i].positionVBO.reset();
|
||
boxRenderArr[i].texCoordVBO.reset();
|
||
showExplosion = true;
|
||
|
||
Vector3f rel = boxWorld - Environment::shipState.position;
|
||
Vector3f camPos = Environment::inverseShipMatrix * rel;
|
||
explosionEmitter.setUseWorldSpace(true);
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ boxWorld });
|
||
explosionEmitter.emit();
|
||
lastExplosionTime = SDL_GetTicks64();
|
||
|
||
std::cerr << "Box destroyed at index " << i << std::endl;
|
||
}
|
||
}
|
||
|
||
const float projectileHitRadius = 1.5f;
|
||
for (auto& p : projectiles) {
|
||
if (!p || !p->isActive()) continue;
|
||
Vector3f ppos = p->getPosition();
|
||
Vector3f projInBoxSpace = Environment::inverseShipMatrix * (ppos - Environment::shipState.position);
|
||
for (int i = 0; i < boxCoordsArr.size(); ++i) {
|
||
if (!boxAlive[i]) continue;
|
||
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 6.0f, 45000.0f };
|
||
Vector3f dd = ppos - boxWorld;
|
||
float thresh = boxCollisionRadius + projectileHitRadius;
|
||
if (dd.squaredNorm() <= thresh * thresh) {
|
||
boxAlive[i] = false;
|
||
boxRenderArr[i].data.PositionData.clear();
|
||
boxRenderArr[i].vao.reset();
|
||
boxRenderArr[i].positionVBO.reset();
|
||
boxRenderArr[i].texCoordVBO.reset();
|
||
|
||
showExplosion = true;
|
||
explosionEmitter.setUseWorldSpace(true);
|
||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ boxWorld });
|
||
explosionEmitter.emit();
|
||
lastExplosionTime = SDL_GetTicks64();
|
||
|
||
p->deactivate();
|
||
std::cerr << "Box destroyed by projectile at index " << i << std::endl;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
uiManager.update(static_cast<float>(delta));
|
||
lastTickCount = newTickCount;
|
||
}
|
||
}
|
||
|
||
void Game::fireProjectiles() {
|
||
std::vector<Vector3f> localOffsets = {
|
||
Vector3f{ -1.5f, 0.9f, 5.0f },
|
||
Vector3f{ 1.5f, 0.9f, 5.0f }
|
||
};
|
||
|
||
const float projectileSpeed = 60.0f;
|
||
const float lifeMs = 5000.0f;
|
||
const float size = 0.5f;
|
||
|
||
Vector3f localForward = { 0,0,-1 };
|
||
Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
|
||
|
||
for (const auto& lo : localOffsets) {
|
||
Vector3f worldPos = Environment::shipState.position + Environment::shipState.rotation * lo;
|
||
Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipState.velocity);
|
||
|
||
for (auto& p : projectiles) {
|
||
if (!p->isActive()) {
|
||
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void Game::render() {
|
||
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
||
ZL::CheckGlError();
|
||
|
||
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
||
drawScene();
|
||
processTickCount();
|
||
|
||
SDL_GL_SwapWindow(ZL::Environment::window);
|
||
}
|
||
|
||
void Game::update() {
|
||
SDL_Event event;
|
||
while (SDL_PollEvent(&event)) {
|
||
if (event.type == SDL_QUIT) {
|
||
Environment::exitGameLoop = true;
|
||
}
|
||
#ifdef __ANDROID__
|
||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
|
||
Environment::exitGameLoop = true;
|
||
}
|
||
#endif
|
||
|
||
|
||
#ifdef __ANDROID__
|
||
if (event.type == SDL_FINGERDOWN) {
|
||
// Координаты Finger в SDL нормализованы от 0.0 до 1.0
|
||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||
handleDown(mx, my);
|
||
}
|
||
else if (event.type == SDL_FINGERUP) {
|
||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||
handleUp(mx, my);
|
||
}
|
||
else if (event.type == SDL_FINGERMOTION) {
|
||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||
handleMotion(mx, my);
|
||
}
|
||
#else
|
||
|
||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||
int mx = event.button.x;
|
||
int my = event.button.y;
|
||
handleDown(mx, my);
|
||
}
|
||
if (event.type == SDL_MOUSEBUTTONUP) {
|
||
|
||
int mx = event.button.x;
|
||
int my = event.button.y;
|
||
handleUp(mx, my);
|
||
}
|
||
if (event.type == SDL_MOUSEMOTION) {
|
||
|
||
int mx = event.motion.x;
|
||
int my = event.motion.y;
|
||
handleMotion(mx, my);
|
||
|
||
}
|
||
/*
|
||
if (event.type == SDL_MOUSEWHEEL) {
|
||
static const float zoomstep = 2.0f;
|
||
if (event.wheel.y > 0) {
|
||
Environment::zoom -= zoomstep;
|
||
}
|
||
else if (event.wheel.y < 0) {
|
||
Environment::zoom += zoomstep;
|
||
}
|
||
if (Environment::zoom < zoomstep) {
|
||
Environment::zoom = zoomstep;
|
||
}
|
||
}
|
||
if (event.type == SDL_KEYUP)
|
||
{
|
||
if (event.key.keysym.sym == SDLK_i)
|
||
{
|
||
|
||
}
|
||
}*/
|
||
#endif
|
||
}
|
||
render();
|
||
mainThreadHandler.processMainThreadTasks();
|
||
networkClient->Poll();
|
||
|
||
if (networkClient) {
|
||
auto pending = networkClient->getPendingProjectiles();
|
||
if (!pending.empty()) {
|
||
const float projectileSpeed = 60.0f;
|
||
const float lifeMs = 5000.0f;
|
||
const float size = 0.5f;
|
||
|
||
auto remotePlayersSnapshot = networkClient->getRemotePlayers();
|
||
for (const auto& pi : pending) {
|
||
Eigen::Vector3f dir = pi.direction;
|
||
float len = dir.norm();
|
||
if (len <= 1e-6f) continue;
|
||
dir /= len;
|
||
|
||
Eigen::Matrix3f shooterRot = Eigen::Matrix3f::Identity();
|
||
float shooterVel = 0.0f;
|
||
auto it = remotePlayersSnapshot.find(pi.shooterId);
|
||
if (it != remotePlayersSnapshot.end()) {
|
||
std::chrono::system_clock::time_point pktTime{ std::chrono::milliseconds(pi.clientTime) };
|
||
if (it->second.canFetchClientStateAtTime(pktTime)) {
|
||
ClientState shooterState = it->second.fetchClientStateAtTime(pktTime);
|
||
shooterRot = shooterState.rotation;
|
||
shooterVel = shooterState.velocity;
|
||
}
|
||
}
|
||
|
||
float speedWithOwner = projectileSpeed + shooterVel;
|
||
Eigen::Vector3f baseVel = dir * speedWithOwner;
|
||
|
||
int shotCount = 2;
|
||
|
||
std::vector<Eigen::Vector3f> localOffsets = {
|
||
{-1.5f, 0.9f, 5.0f},
|
||
{ 1.5f, 0.9f, 5.0f}
|
||
};
|
||
|
||
for (int i = 0; i < shotCount; ++i) {
|
||
Eigen::Vector3f rotatedOffset = shooterRot * localOffsets[i];
|
||
Eigen::Vector3f shotPos = pi.position + rotatedOffset;
|
||
|
||
for (auto& p : projectiles) {
|
||
if (!p->isActive()) {
|
||
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void Game::handleDown(int mx, int my)
|
||
{
|
||
int uiX = mx;
|
||
int uiY = Environment::height - my;
|
||
|
||
uiManager.onMouseDown(uiX, uiY);
|
||
|
||
bool uiHandled = false;
|
||
|
||
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
|
||
(void)button;
|
||
}
|
||
|
||
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
|
||
for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
|
||
(void)slider;
|
||
}
|
||
return nullptr;
|
||
}();
|
||
|
||
if (!uiManager.isUiInteraction()) {
|
||
Environment::tapDownHold = true;
|
||
|
||
Environment::tapDownStartPos(0) = mx;
|
||
Environment::tapDownStartPos(1) = my;
|
||
|
||
Environment::tapDownCurrentPos(0) = mx;
|
||
Environment::tapDownCurrentPos(1) = my;
|
||
}
|
||
}
|
||
|
||
void Game::handleUp(int mx, int my)
|
||
{
|
||
int uiX = mx;
|
||
int uiY = Environment::height - my;
|
||
|
||
uiManager.onMouseUp(uiX, uiY);
|
||
|
||
if (!uiManager.isUiInteraction()) {
|
||
Environment::tapDownHold = false;
|
||
}
|
||
}
|
||
|
||
void Game::handleMotion(int mx, int my)
|
||
{
|
||
int uiX = mx;
|
||
int uiY = Environment::height - my;
|
||
|
||
uiManager.onMouseMove(uiX, uiY);
|
||
|
||
if (Environment::tapDownHold && !uiManager.isUiInteraction()) {
|
||
Environment::tapDownCurrentPos(0) = mx;
|
||
Environment::tapDownCurrentPos(1) = my;
|
||
}
|
||
}
|
||
|
||
/*
|
||
std::string Game::formPingMessageContent()
|
||
{
|
||
Eigen::Quaternionf q(Environment::shipMatrix);
|
||
|
||
std::string pingMsg = std::to_string(Environment::shipPosition.x()) + ":"
|
||
+ std::to_string(Environment::shipPosition.y()) + ":"
|
||
+ std::to_string(Environment::shipPosition.z()) + ":"
|
||
+ std::to_string(q.w()) + ":"
|
||
+ std::to_string(q.x()) + ":"
|
||
+ std::to_string(q.y()) + ":"
|
||
+ std::to_string(q.z()) + ":"
|
||
+ std::to_string(Environment::currentAngularVelocity.x()) + ":"
|
||
+ std::to_string(Environment::currentAngularVelocity.y()) + ":"
|
||
+ std::to_string(Environment::currentAngularVelocity.z()) + ":"
|
||
+ std::to_string(Environment::shipVelocity) + ":"
|
||
+ std::to_string(Environment::shipSelectedVelocity) + ":"
|
||
+ std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT
|
||
+ std::to_string(Environment::lastSentAngle);
|
||
|
||
return pingMsg;
|
||
}*/
|
||
|
||
|
||
} // namespace ZL
|