space-game001/src/Game.cpp
Vladislav Khorev 8f3528f9a1 merge
2026-01-18 13:29:48 +03:00

1170 lines
39 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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>
#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;
}
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/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;
});
// Set UI button callbacks
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;
fireProjectiles();
}
});
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10);
if (newVel != Environment::shipState.selectedVelocity) {
newShipVelocity = newVel;
}
});
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);
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();
drawBoxes();
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();
// Итерируемся по актуальным данным из 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::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();
}
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