Compare commits

...

12 Commits

Author SHA1 Message Date
d76342f3fa fixed rotation moving 2026-02-12 13:55:06 +06:00
fb4a860773 added class Camera and fixed control of ship 2026-02-09 21:16:54 +06:00
47da35a401 added new class camera 2026-02-05 15:01:16 +06:00
0a72040110 merge 2026-01-22 14:58:12 +06:00
1dcdf10d6a Merge origin/main into camera2 2026-01-16 18:48:33 +06:00
Vladislav Khorev
ed746891b7 Fixing according to Leyla feedback 2026-01-15 10:11:46 +03:00
3bd3202bf8 added joystick and attack button is moved to right 2026-01-14 22:39:38 +06:00
b7f5f43777 added to main new featherc 2026-01-12 20:37:21 +06:00
2ad6ebf38d added all 2026-01-12 14:41:24 +06:00
96deb8b393 corrected moving ship added moving camera 2025-12-29 18:59:06 +06:00
47fe0c4719 added ship control 2025-12-11 21:14:39 +06:00
da8946e9ef moving ship 2025-12-11 15:13:35 +06:00
15 changed files with 910 additions and 513 deletions

View File

@ -18,6 +18,8 @@ add_executable(space-game001
../src/Environment.h ../src/Environment.h
../src/render/Renderer.cpp ../src/render/Renderer.cpp
../src/render/Renderer.h ../src/render/Renderer.h
../src/render/Camera.cpp
../src/render/Camera.h
../src/render/ShaderManager.cpp ../src/render/ShaderManager.cpp
../src/render/ShaderManager.h ../src/render/ShaderManager.h
../src/render/TextureManager.cpp ../src/render/TextureManager.cpp

View File

@ -154,8 +154,8 @@
{ {
"type": "Button", "type": "Button",
"name": "shootButton", "name": "shootButton",
"x": 100, "x": 1115,
"y": 100, "y": 0,
"width": 100, "width": 100,
"height": 100, "height": 100,
"textures": { "textures": {

BIN
resources/joystick_base.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/joystick_knob.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,4 +1,5 @@
#include "Game.h" #include "GameConfig.h"
#include "Game.h"
#include "AnimatedModel.h" #include "AnimatedModel.h"
#include "BoneAnimatedModel.h" #include "BoneAnimatedModel.h"
#include "planet/PlanetData.h" #include "planet/PlanetData.h"
@ -116,8 +117,9 @@ namespace ZL
, glContext(nullptr) , glContext(nullptr)
, newTickCount(0) , newTickCount(0)
, lastTickCount(0) , lastTickCount(0)
, planetObject(renderer, taskManager, mainThreadHandler) , planetObject(renderer, taskManager, mainThreadHandler, camera)
{ {
Environment::shipState.selectedVelocity = 0;
projectiles.reserve(maxProjectiles); projectiles.reserve(maxProjectiles);
for (int i = 0; i < maxProjectiles; ++i) { for (int i = 0; i < maxProjectiles; ++i) {
projectiles.emplace_back(std::make_unique<Projectile>()); projectiles.emplace_back(std::make_unique<Projectile>());
@ -236,12 +238,22 @@ namespace ZL
} }
}); });
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { //uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10); // int newVel = roundf(value * 10);
if (newVel != Environment::shipState.selectedVelocity) { // if (newVel != Environment::shipState.selectedVelocity) {
newShipVelocity = newVel; // newShipVelocity = newVel;
} // }
}); // });
// Добавляем джойстик для управления кораблём
// centerX=150, centerY=150 (от левого нижнего угла в UI координатах)
// baseRadius=120, knobRadius=50
uiManager.addJoystick("shipJoystick", 150.0f, 150.0f, 120.0f, 50.0f,
renderer, CONST_ZIP_FILE,
"resources/joystick_base.png", "resources/joystick_knob.png");
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
@ -261,19 +273,18 @@ namespace ZL
//Load texture //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)); spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/MainCharacter_Base_color_sRGB.png", CONST_ZIP_FILE));
spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", 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.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
spaceshipBase.Move(Vector3f{ -0.52998, 0, -10 });
spaceship.AssignFrom(spaceshipBase); spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO(); spaceship.RefreshVBO();
@ -288,7 +299,7 @@ namespace ZL
for (int i = 0; i < boxCoordsArr.size(); i++) for (int i = 0; i < boxCoordsArr.size(); i++)
{ {
boxRenderArr[i].AssignFrom(boxBase); boxRenderArr[i].AssignFrom(boxBase);
//boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999);
boxRenderArr[i].RefreshVBO(); boxRenderArr[i].RefreshVBO();
} }
@ -331,17 +342,21 @@ namespace ZL
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.LoadIdentity();
// Для скайбокса берем только вращение от камеры
Matrix4f view = camera.getViewMatrix();
view.block<3, 1>(0, 3) = Vector3f::Zero();
renderer.PushSpecialMatrix(view);
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
Matrix3f viewMatrix = Environment::inverseShipMatrix;
Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized();
// Передаем вектор НА источник света Matrix3f viewRot = view.block<3, 3>(0, 0); // world->view rotation
Vector3f viewLightDir = (viewRot * worldLightDir).normalized();
Vector3f lightToSource = -viewLightDir; Vector3f lightToSource = -viewLightDir;
renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
// 2. Базовый цвет атмосферы (голубой) // 2. Базовый цвет атмосферы (голубой)
@ -372,7 +387,8 @@ namespace ZL
CheckGlError(); CheckGlError();
renderer.PopMatrix(); renderer.PopMatrix(); // Pop special matrix
renderer.PopMatrix(); // Pop original push
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
@ -399,13 +415,30 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset renderer.PushSpecialMatrix(camera.getViewMatrix());
// --- ship model matrix ---
renderer.PushMatrix();
renderer.TranslateMatrix(Environment::shipState.position);
renderer.RotateMatrix(Environment::shipState.rotation);
if (shipAlive) { if (shipAlive) {
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship); renderer.DrawVertexRenderStruct(spaceship);
} }
// Эмиттеры рисуем в той же матрице корабля
if (shipAlive) {
renderer.PushMatrix();
renderer.TranslateMatrix({ 0, 0, 16 });
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
renderer.PopMatrix();
}
renderer.PopMatrix(); // --- end ship model matrix ---
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -428,7 +461,8 @@ namespace ZL
} }
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.PopMatrix(); renderer.PopMatrix(); // Pop special matrix
renderer.PopMatrix(); // Pop original push
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
@ -454,15 +488,17 @@ namespace ZL
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.PushSpecialMatrix(camera.getViewMatrix());
for (int i = 0; i < boxCoordsArr.size(); i++) for (int i = 0; i < boxCoordsArr.size(); i++)
{ {
if (!boxAlive[i]) continue; if (!boxAlive[i]) continue;
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); // Коробки рисуются в мировых координатах
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); // Но у них есть offset { 0.f, 0.f, 45000.f }
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
renderer.TranslateMatrix({ 0.f, 0.f, 45000.f }); renderer.TranslateMatrix({ 0.f, 0.f, 45000.f });
renderer.TranslateMatrix(boxCoordsArr[i].pos); renderer.TranslateMatrix(boxCoordsArr[i].pos);
renderer.RotateMatrix(boxCoordsArr[i].m); renderer.RotateMatrix(boxCoordsArr[i].m);
@ -473,6 +509,8 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
} }
renderer.PopMatrix(); // Pop special matrix
renderer.PopMatrix(); // Pop original push
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
@ -519,6 +557,11 @@ namespace ZL
CheckGlError(); CheckGlError();
// Обновляем камеру
// camera.follow(Environment::shipState.position, shipWorldOrientation, Environment::zoom, 6.0f);
camera.followOrbit(Environment::shipState.position, camYaw, camPitch, Environment::zoom, 6.0f);
float skyPercent = 0.0; float skyPercent = 0.0;
float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position); float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position);
if (distance > 1500.f) if (distance > 1500.f)
@ -542,7 +585,9 @@ namespace ZL
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
} }
drawShip(); drawShip();
#if ENABLE_REMOTE_SHIPS
drawRemoteShips(); drawRemoteShips();
#endif
drawBoxes(); drawBoxes();
drawUI(); drawUI();
@ -550,13 +595,11 @@ namespace ZL
} }
void Game::drawRemoteShips() { void Game::drawRemoteShips() {
// Используем те же константы имен для шейдеров, что и в drawShip
static const std::string defaultShaderName = "default"; static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition"; static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord"; static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture"; static const std::string textureUniformName = "Texture";
// Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый)
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
@ -567,46 +610,32 @@ namespace ZL
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
//Apply server delay:
now -= std::chrono::milliseconds(CLIENT_DELAY); now -= std::chrono::milliseconds(CLIENT_DELAY);
latestRemotePlayers = networkClient->getRemotePlayers(); latestRemotePlayers = networkClient->getRemotePlayers();
// Итерируемся по актуальным данным из extrapolateRemotePlayers renderer.PushMatrix();
for (auto const& [id, remotePlayer] : latestRemotePlayers) { renderer.LoadIdentity();
renderer.PushSpecialMatrix(camera.getViewMatrix());
if (!remotePlayer.canFetchClientStateAtTime(now)) for (auto const& [id, remotePlayer] : latestRemotePlayers) {
{ if (!remotePlayer.canFetchClientStateAtTime(now)) continue;
continue;
}
ClientState playerState = remotePlayer.fetchClientStateAtTime(now); ClientState playerState = remotePlayer.fetchClientStateAtTime(now);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.TranslateMatrix(playerState.position);
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.RotateMatrix(playerState.rotation);
renderer.DrawVertexRenderStruct(spaceship); renderer.DrawVertexRenderStruct(spaceship);
renderer.PopMatrix(); renderer.PopMatrix();
} }
renderer.PopMatrix(); // pop special matrix stack (view)
renderer.PopMatrix(); // pop identity push
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
@ -615,6 +644,7 @@ namespace ZL
CheckGlError(); CheckGlError();
} }
void Game::processTickCount() { void Game::processTickCount() {
if (lastTickCount == 0) { if (lastTickCount == 0) {
@ -640,72 +670,127 @@ namespace ZL
auto now_ms = newTickCount; auto now_ms = newTickCount;
sparkEmitter.update(static_cast<float>(delta));
planetObject.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); // Input -> ControlState (joystick)
} // ------------------------
float discreteMag; float discreteMag = 0.0f;
int discreteAngle; int discreteAngle = -1;
if (Environment::tapDownHold) { auto joystick = uiManager.findJoystick("shipJoystick");
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); bool joystickActive = isUsingJoystick && joystick && joystick->isActive;
float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);
float rawMag = sqrtf(diffx * diffx + diffy * diffy); if (joystickActive) {
float maxRadius = 200.0f; // Максимальный вынос джойстика float joyX = joystick->getDirectionX(); // <-- оставляем инверсию X
float joyY = joystick->getDirectionY();
float magnitude = joystick->getMagnitude();
if (rawMag > 10.0f) { // Мертвая зона const float deadzone = 0.1f;
// 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 градусов) Vector3f newMoveDir = Vector3f::Zero();
// atan2 возвращает радианы, переводим в градусы float newMag = 0.0f;
float radians = atan2f(diffy, diffx);
discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
if (discreteAngle < 0) discreteAngle += 360;
if (magnitude > deadzone) {
// Берём ориентацию камеры из view matrix:
// view = World -> View, значит Rcw = (Rwv)^T = View -> World
Matrix4f viewM = camera.getViewMatrix();
Matrix3f Rwv = viewM.block<3, 3>(0, 0);
Matrix3f Rcw = Rwv.transpose();
Vector3f camRight = (Rcw * Vector3f(1, 0, 0));
Vector3f camForward = (Rcw * Vector3f(0, 0, -1)); // куда смотрит камера в мире
if (camRight.squaredNorm() > 1e-6f) camRight.normalize();
if (camForward.squaredNorm() > 1e-6f) camForward.normalize();
// joystick: X = вправо/влево, Y = вверх/вниз
// joyY обычно отрицательный при "вверх", поэтому "- camForward * joyY" даёт "вперёд"
Vector3f worldMove = camRight * joyX - camForward * joyY;
if (worldMove.squaredNorm() > 1e-6f) {
newMoveDir = worldMove.normalized();
newMag = (std::min)(magnitude, 1.0f);
newMag = std::round(newMag * 10.0f) / 10.0f;
}
} }
else
{ // Применяем в shipState
discreteAngle = -1; bool changed = false;
discreteMag = 0.0f;
if ((Environment::shipState.currentAngularVelocity - newMoveDir).norm() > 0.001f) {
Environment::shipState.currentAngularVelocity = newMoveDir; // <-- теперь это "moveDirWorld"
changed = true;
}
if (fabs(Environment::shipState.discreteMag - newMag) > 0.001f) {
Environment::shipState.discreteMag = newMag; // throttle
changed = true;
}
// discreteAngle больше не нужен для движения (можешь оставить -1)
if (Environment::shipState.discreteAngle != -1) {
Environment::shipState.discreteAngle = -1;
changed = true;
}
if (changed) {
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
networkClient->Send(msg);
}
}
// Network Update
//bool changed = false;
//if (discreteAngle != Environment::shipState.discreteAngle) {
// Environment::shipState.discreteAngle = discreteAngle;
// changed = true;
//}
//if (discreteMag != Environment::shipState.discreteMag) {
// Environment::shipState.discreteMag = discreteMag;
// changed = true;
//}
// slider value применяем здесь (ты раньше только newShipVelocity менял)
//int newVelInt = (int)newShipVelocity;
//if (newVelInt != Environment::shipState.selectedVelocity) {
// Environment::shipState.selectedVelocity = newVelInt;
// changed = true;
//}
//if (changed) {
// std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
// networkClient->Send(msg);
//}
// Network Update (только если НЕ используем джойстик)
if (!joystickActive) {
bool changed2 = false;
if (discreteAngle != Environment::shipState.discreteAngle) {
Environment::shipState.discreteAngle = discreteAngle;
changed2 = true;
}
if (discreteMag != Environment::shipState.discreteMag) {
Environment::shipState.discreteMag = discreteMag;
changed2 = true;
}
if (changed2) {
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
networkClient->Send(msg);
} }
} }
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::shipState.simulate_physics(delta);
Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); Environment::inverseShipMatrix = Environment::shipState.rotation.inverse();
@ -733,6 +818,7 @@ namespace ZL
projectileEmitter.setEmissionPoints(std::vector<Vector3f>()); projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
} }
std::vector<Vector3f> shipCameraPoints; std::vector<Vector3f> shipCameraPoints;
for (const auto& lp : shipLocalEmissionPoints) { for (const auto& lp : shipLocalEmissionPoints) {
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f }; Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
@ -1003,8 +1089,8 @@ namespace ZL
glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene();
processTickCount(); processTickCount();
drawScene();
SDL_GL_SwapWindow(ZL::Environment::window); SDL_GL_SwapWindow(ZL::Environment::window);
} }
@ -1093,27 +1179,20 @@ namespace ZL
uiManager.onMouseDown(uiX, uiY); uiManager.onMouseDown(uiX, uiY);
bool uiHandled = false; // Check if joystick became active
auto joystick = uiManager.findJoystick("shipJoystick");
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) { if (joystick && joystick->isActive) {
(void)button; isUsingJoystick = true;
isDraggingCamera = false;
Environment::tapDownHold = false; // Don't trigger camera drag
} }
else if (!uiManager.isUiInteraction()) {
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> { // No UI interaction (and no joystick), so it's camera drag
for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) { isUsingJoystick = false;
(void)slider; isDraggingCamera = true;
}
return nullptr;
}();
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = true; Environment::tapDownHold = true;
Environment::tapDownStartPos = Vector2f(static_cast<float>(mx), static_cast<float>(my));
Environment::tapDownStartPos(0) = mx; Environment::tapDownCurrentPos = Environment::tapDownStartPos;
Environment::tapDownStartPos(1) = my;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
} }
} }
@ -1124,9 +1203,12 @@ namespace ZL
uiManager.onMouseUp(uiX, uiY); uiManager.onMouseUp(uiX, uiY);
if (!uiManager.isUiInteraction()) { if (isUsingJoystick) {
Environment::tapDownHold = false; isUsingJoystick = false;
} }
isDraggingCamera = false;
Environment::tapDownHold = false;
} }
void Game::handleMotion(int mx, int my) void Game::handleMotion(int mx, int my)
@ -1136,10 +1218,26 @@ namespace ZL
uiManager.onMouseMove(uiX, uiY); uiManager.onMouseMove(uiX, uiY);
if (Environment::tapDownHold && !uiManager.isUiInteraction()) { if (isDraggingCamera && !uiManager.isUiInteraction()) {
Environment::tapDownCurrentPos(0) = mx; float curX = static_cast<float>(mx);
Environment::tapDownCurrentPos(1) = my; float curY = static_cast<float>(my);
float dx = curX - Environment::tapDownStartPos(0);
float dy = curY - Environment::tapDownStartPos(1);
const float sens = 0.005f; // rad per pixel
camYaw -= dx * sens;
camPitch -= dy * sens;
float pitchLimit = 80.0f * static_cast<float>(M_PI) / 180.0f;
if (camPitch > pitchLimit) camPitch = pitchLimit;
if (camPitch < -pitchLimit) camPitch = -pitchLimit;
// обновляем стартовую точку, чтобы drag был плавный
Environment::tapDownStartPos(0) = curX;
Environment::tapDownStartPos(1) = curY;
} }
} }
/* /*

View File

@ -1,8 +1,9 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "render/Camera.h"
#include "SparkEmitter.h" #include "SparkEmitter.h"
#include "planet/PlanetObject.h" #include "planet/PlanetObject.h"
#include "UiManager.h" #include "UiManager.h"
@ -33,6 +34,7 @@ namespace ZL {
bool shouldExit() const { return Environment::exitGameLoop; } bool shouldExit() const { return Environment::exitGameLoop; }
Renderer renderer; Renderer renderer;
Camera camera;
TaskManager taskManager; TaskManager taskManager;
MainThreadHandler mainThreadHandler; MainThreadHandler mainThreadHandler;
@ -64,7 +66,7 @@ namespace ZL {
std::unordered_map<int, ClientStateInterval> latestRemotePlayers; std::unordered_map<int, ClientStateInterval> latestRemotePlayers;
float newShipVelocity = 0; //float newShipVelocity = 0;
static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000; static const size_t CONST_MAX_TIME_INTERVAL = 1000;
@ -93,6 +95,23 @@ namespace ZL {
int maxProjectiles = 32; int maxProjectiles = 32;
std::vector<Vector3f> shipLocalEmissionPoints; std::vector<Vector3f> shipLocalEmissionPoints;
bool isDraggingShip = false;
bool isDraggingCamera = false;
bool isUsingJoystick = false;
float camYaw = 0.0f;
float camPitch = 0.0f;
// Movement direction locking
bool isMoving = false; // Is joystick movement active
float moveYawLocked = 0.0f; // Locked camera yaw at movement start
Vector3f moveForwardLocked = Vector3f::UnitZ(); // Locked forward direction (world space)
Vector3f moveRightLocked = Vector3f::UnitX(); // Locked right direction (world space)
Matrix3f rotateShipMat = Matrix3f::Identity(); // Локальный поворот от джойстика
Matrix3f shipWorldOrientation = Matrix3f::Identity(); // Ориентация корабля в мире (независимо от камеры)
bool shipMoveLockActive = false;
Matrix3f lockedCameraMat = Matrix3f::Identity();
bool shipAlive = true; bool shipAlive = true;
bool gameOver = false; bool gameOver = false;
@ -104,6 +123,10 @@ namespace ZL {
uint64_t lastExplosionTime = 0; uint64_t lastExplosionTime = 0;
const uint64_t explosionDurationMs = 500; const uint64_t explosionDurationMs = 500;
float camDistance = 0.0f; // будет равен Environment::zoom
float camHeight = 6.0f; // высота над кораблём
}; };

5
src/GameConfig.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
// Feature flags to temporarily disable unstable features
#define ENABLE_STONES 0
#define ENABLE_REMOTE_SHIPS 0

View File

@ -167,6 +167,91 @@ namespace ZL {
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
} }
void UiJoystick::buildBaseMesh() {
baseMesh.data.PositionData.clear();
baseMesh.data.TexCoordData.clear();
float x0 = centerX - baseRadius;
float y0 = centerY - baseRadius;
float x1 = centerX + baseRadius;
float y1 = centerY + baseRadius;
baseMesh.data.PositionData.push_back({ x0, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 0 });
baseMesh.data.PositionData.push_back({ x0, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 1 });
baseMesh.data.PositionData.push_back({ x1, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 1 });
baseMesh.data.PositionData.push_back({ x0, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 0, 0 });
baseMesh.data.PositionData.push_back({ x1, y1, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 1 });
baseMesh.data.PositionData.push_back({ x1, y0, 0 });
baseMesh.data.TexCoordData.push_back({ 1, 0 });
baseMesh.RefreshVBO();
}
void UiJoystick::buildKnobMesh() {
knobMesh.data.PositionData.clear();
knobMesh.data.TexCoordData.clear();
float cx = centerX + knobOffsetX;
float cy = centerY + knobOffsetY;
float x0 = cx - knobRadius;
float y0 = cy - knobRadius;
float x1 = cx + knobRadius;
float y1 = cy + knobRadius;
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x0, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 1 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x1, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 0 });
knobMesh.RefreshVBO();
}
void UiJoystick::draw(Renderer& renderer) const {
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
if (texBase) {
glBindTexture(GL_TEXTURE_2D, texBase->getTexID());
renderer.DrawVertexRenderStruct(baseMesh);
}
if (texKnob) {
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
renderer.DrawVertexRenderStruct(knobMesh);
}
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
}
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
std::string content; std::string content;
try { try {
@ -446,6 +531,57 @@ namespace ZL {
return true; return true;
} }
bool UiManager::addJoystick(const std::string& name, float centerX, float centerY, float baseRadius, float knobRadius,
Renderer& renderer, const std::string& zipFile,
const std::string& basePath, const std::string& knobPath) {
auto j = std::make_shared<UiJoystick>();
j->name = name;
j->centerX = centerX;
j->centerY = centerY;
j->baseRadius = baseRadius;
j->knobRadius = knobRadius;
j->knobOffsetX = 0;
j->knobOffsetY = 0;
j->isActive = false;
try {
if (!basePath.empty()) {
auto data = CreateTextureDataFromPng(basePath.c_str(), zipFile.c_str());
j->texBase = std::make_shared<Texture>(data);
}
if (!knobPath.empty()) {
auto data = CreateTextureDataFromPng(knobPath.c_str(), zipFile.c_str());
j->texKnob = std::make_shared<Texture>(data);
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: addJoystick failed to load textures: " << e.what() << std::endl;
return false;
}
j->buildBaseMesh();
j->buildKnobMesh();
joysticks.push_back(j);
return true;
}
std::shared_ptr<UiJoystick> UiManager::findJoystick(const std::string& name) {
for (auto& j : joysticks) if (j->name == name) return j;
return nullptr;
}
void UiManager::resetJoystick(const std::string& name) {
auto j = findJoystick(name);
if (j) {
j->knobOffsetX = 0;
j->knobOffsetY = 0;
j->isActive = false;
j->buildKnobMesh();
}
}
bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
MenuState prev; MenuState prev;
prev.root = root; prev.root = root;
@ -526,6 +662,9 @@ namespace ZL {
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
for (const auto& j : joysticks) {
j->draw(renderer);
}
for (const auto& b : buttons) { for (const auto& b : buttons) {
b->draw(renderer); b->draw(renderer);
} }
@ -724,6 +863,22 @@ namespace ZL {
s->buildKnobMesh(); s->buildKnobMesh();
if (s->onValueChanged) s->onValueChanged(s->name, s->value); if (s->onValueChanged) s->onValueChanged(s->name, s->value);
} }
if (pressedJoystick) {
auto j = pressedJoystick;
float dx = (float)x - j->centerX;
float dy = (float)y - j->centerY;
float dist = std::sqrt(dx * dx + dy * dy);
if (dist > j->baseRadius) {
dx = dx / dist * j->baseRadius;
dy = dy / dist * j->baseRadius;
}
j->knobOffsetX = dx;
j->knobOffsetY = dy;
j->buildKnobMesh();
}
} }
void UiManager::onMouseDown(int x, int y) { void UiManager::onMouseDown(int x, int y) {
@ -752,6 +907,27 @@ namespace ZL {
break; break;
} }
} }
for (auto& j : joysticks) {
if (j->contains((float)x, (float)y)) {
pressedJoystick = j;
j->isActive = true;
float dx = (float)x - j->centerX;
float dy = (float)y - j->centerY;
float dist = std::sqrt(dx * dx + dy * dy);
if (dist > j->baseRadius) {
dx = dx / dist * j->baseRadius;
dy = dy / dist * j->baseRadius;
}
j->knobOffsetX = dx;
j->knobOffsetY = dy;
j->buildKnobMesh();
break;
}
}
} }
void UiManager::onMouseUp(int x, int y) { void UiManager::onMouseUp(int x, int y) {
@ -771,6 +947,14 @@ namespace ZL {
if (pressedSlider) { if (pressedSlider) {
pressedSlider.reset(); pressedSlider.reset();
} }
if (pressedJoystick) {
pressedJoystick->knobOffsetX = 0;
pressedJoystick->knobOffsetY = 0;
pressedJoystick->isActive = false;
pressedJoystick->buildKnobMesh();
pressedJoystick.reset();
}
} }
std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) { std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) {

View File

@ -9,6 +9,8 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <map> #include <map>
#include <cmath>
#include <algorithm>
namespace ZL { namespace ZL {
@ -71,6 +73,47 @@ namespace ZL {
void draw(Renderer& renderer) const; void draw(Renderer& renderer) const;
}; };
struct UiJoystick {
std::string name;
float centerX = 0; // центр джойстика (UI координаты)
float centerY = 0;
float baseRadius = 100; // радиус основания
float knobRadius = 40; // радиус ручки
float knobOffsetX = 0; // смещение ручки от центра
float knobOffsetY = 0;
bool isActive = false; // нажат ли джойстик
std::shared_ptr<Texture> texBase; // текстура основания
std::shared_ptr<Texture> texKnob; // текстура ручки
VertexRenderStruct baseMesh;
VertexRenderStruct knobMesh;
// Возвращает направление -1..1 по каждой оси
float getDirectionX() const { return baseRadius > 0 ? knobOffsetX / baseRadius : 0; }
float getDirectionY() const { return baseRadius > 0 ? -knobOffsetY / baseRadius : 0; }
// Возвращает силу 0..1
float getMagnitude() const {
if (baseRadius <= 0) return 0;
float dist = std::sqrt(knobOffsetX * knobOffsetX + knobOffsetY * knobOffsetY);
float ratio = dist / baseRadius;
return ratio < 1.0f ? ratio : 1.0f;
}
bool contains(float px, float py) const {
float dx = px - centerX;
float dy = py - centerY;
return (dx * dx + dy * dy) <= (baseRadius * baseRadius);
}
void buildBaseMesh();
void buildKnobMesh();
void draw(Renderer& renderer) const;
};
struct UiNode { struct UiNode {
std::string type; std::string type;
UiRect rect; UiRect rect;
@ -108,7 +151,7 @@ namespace ZL {
void onMouseUp(int x, int y); void onMouseUp(int x, int y);
bool isUiInteraction() const { bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr; return pressedButton != nullptr || pressedSlider != nullptr || pressedJoystick != nullptr;
} }
void stopAllAnimations() { void stopAllAnimations() {
@ -135,6 +178,12 @@ namespace ZL {
bool setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb); bool setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb);
bool setSliderValue(const std::string& name, float value); // programmatic set (clamped 0..1) bool setSliderValue(const std::string& name, float value); // programmatic set (clamped 0..1)
bool addJoystick(const std::string& name, float centerX, float centerY, float baseRadius, float knobRadius,
Renderer& renderer, const std::string& zipFile,
const std::string& basePath, const std::string& knobPath);
std::shared_ptr<UiJoystick> findJoystick(const std::string& name);
void resetJoystick(const std::string& name); // сбросить ручку в центр
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool popMenu(); bool popMenu();
void clearMenuStack(); void clearMenuStack();
@ -176,12 +225,14 @@ namespace ZL {
std::shared_ptr<UiNode> root; std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons; std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders; std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiJoystick>> joysticks;
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims; std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName) std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
std::shared_ptr<UiButton> pressedButton; std::shared_ptr<UiButton> pressedButton;
std::shared_ptr<UiSlider> pressedSlider; std::shared_ptr<UiSlider> pressedSlider;
std::shared_ptr<UiJoystick> pressedJoystick;
struct MenuState { struct MenuState {
std::shared_ptr<UiNode> root; std::shared_ptr<UiNode> root;

View File

@ -1,224 +1,234 @@
#include "ClientState.h" #include "ClientState.h"
#include <algorithm> // std::min/max/clamp
#include <cmath> // sinf/cosf/atan2f
// ------------------------------------------------------------
// New control model:
//
// discreteAngle = heading in WORLD XZ plane (0..359)
// discreteMag = throttle 0..1 (joystick magnitude)
// selectedVelocity = optional 0..10 (for compatibility)
//
// Movement is along heading direction.
// Ship rotates to face heading direction (optional).
// ------------------------------------------------------------
void ClientState::simulate_physics(size_t delta) { static inline float DegToRad(float deg) {
if (discreteMag > 0.01f) return deg * static_cast<float>(M_PI) / 180.0f;
{
float rad = static_cast<float>(discreteAngle) * static_cast<float>(M_PI) / 180.0f;
// Целевая угловая скорость (дискретная сила определяет модуль вектора)
// Вектор {cos, sin, 0} дает нам направление отклонения джойстика
Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f);
Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag;
Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity;
float diffLen = diffVel.norm();
if (diffLen > 0.0001f) {
// Вычисляем, на сколько мы можем изменить скорость в этом кадре
float maxChange = ANGULAR_ACCEL * static_cast<float>(delta);
if (diffLen <= maxChange) {
// Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель
currentAngularVelocity = targetAngularVelocity;
}
else {
// Линейно двигаемся в сторону целевого вектора
currentAngularVelocity += (diffVel / diffLen) * maxChange;
}
}
}
else
{
float currentSpeed = currentAngularVelocity.norm();
if (currentSpeed > 0.0001f) {
float drop = ANGULAR_ACCEL * static_cast<float>(delta);
if (currentSpeed <= drop) {
currentAngularVelocity = Eigen::Vector3f::Zero();
}
else {
// Уменьшаем модуль вектора, сохраняя направление
currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop;
}
}
}
float speedScale = currentAngularVelocity.norm();
if (speedScale > 0.0001f) {
// Коэффициент чувствительности вращения
float deltaAlpha = speedScale * static_cast<float>(delta) * ROTATION_SENSITIVITY;
Eigen::Vector3f axis = currentAngularVelocity.normalized();
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis));
rotation = rotation * rotateQuat.toRotationMatrix();
}
// 4. Линейное изменение линейной скорости
float shipDesiredVelocity = selectedVelocity * 100.f;
if (velocity < shipDesiredVelocity)
{
velocity += delta * SHIP_ACCEL;
if (velocity > shipDesiredVelocity)
{
velocity = shipDesiredVelocity;
}
}
else if (velocity > shipDesiredVelocity)
{
velocity -= delta * SHIP_ACCEL;
if (velocity < shipDesiredVelocity)
{
velocity = shipDesiredVelocity;
}
}
if (fabs(velocity) > 0.01f)
{
Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f };
Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection;
position = position + velocityDirectionAdjusted;
}
} }
void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { static inline float Clamp01(float v) {
return (std::max)(0.0f, (std::min)(1.0f, v));
// 2. Вычисляем задержку
long long deltaMs = 0;
if (nowTime > lastUpdateServerTime) {
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
}
// 3. Защита от слишком больших скачков (Clamp)
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
long long final_lag_ms = deltaMs;//min(deltaMs, 500ll);
if (final_lag_ms > 0) {
// Доматываем симуляцию на величину задержки
// Мы предполагаем, что за это время параметры управления не менялись
simulate_physics(final_lag_ms);
}
} }
void ClientState::handle_full_sync(const std::vector<std::string>& parts, int startFrom) { static inline float Approach(float current, float target, float maxDelta) {
// Позиция if (current < target) return (std::min)(target, current + maxDelta);
position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; if (current > target) return (std::max)(target, current - maxDelta);
return current;
}
Eigen::Quaternionf q( void ClientState::simulate_physics(size_t deltaMs)
std::stof(parts[startFrom + 3]), {
std::stof(parts[startFrom + 4]), // dt in seconds
std::stof(parts[startFrom + 5]), const float dt = static_cast<float>(deltaMs) / 1000.0f;
std::stof(parts[startFrom + 6]));
rotation = q.toRotationMatrix();
currentAngularVelocity = Eigen::Vector3f{ // ---------- SETTINGS (tweak these) ----------
std::stof(parts[startFrom + 7]), // Max speed in your world units/sec.
std::stof(parts[startFrom + 8]), // Choose something that makes sense vs projectileSpeed (60 in Game.cpp).
std::stof(parts[startFrom + 9]) }; const float MAX_SPEED = 80.0f;
velocity = std::stof(parts[startFrom + 10]);
selectedVelocity = std::stoi(parts[startFrom + 11]); // Accel/decel in units/sec^2
discreteMag = std::stof(parts[startFrom + 12]); const float ACCEL = 120.0f;
discreteAngle = std::stoi(parts[startFrom + 13]); const float DECEL = 160.0f;
// Rotation smoothing factor (0..1 per frame). Uses your constant as "per ms".
// With ROTATION_SENSITIVITY=0.002 and delta=10ms -> 0.02 (nice).
const float ROT_T = std::clamp(ROTATION_SENSITIVITY * static_cast<float>(deltaMs), 0.0f, 1.0f);
// -------------------------------------------
// Input validity
const bool hasInput = (discreteMag > 0.01f) && (currentAngularVelocity.squaredNorm() > 1e-6f);
// Speed command:
// - Prefer discreteMag (joystick magnitude)
// - Also accept selectedVelocity (0..10) for compatibility
float speed01_fromMag = Clamp01(discreteMag);
float speed01_fromSel = Clamp01(static_cast<float>(selectedVelocity) / 10.0f);
float speed01 = (std::max)(speed01_fromMag, speed01_fromSel);
float targetSpeed = hasInput ? (speed01 * MAX_SPEED) : 0.0f;
// Smooth speed (accelerate/decelerate)
if (targetSpeed > velocity) velocity = Approach(velocity, targetSpeed, ACCEL * dt);
else velocity = Approach(velocity, targetSpeed, DECEL * dt);
// If no direction input — only тормозим
if (!hasInput) {
return;
}
// Heading direction in world XZ plane:
// IMPORTANT: your Game.cpp must set discreteAngle consistently with:
// ang = atan2(worldMove.x, worldMove.z)
// So we reconstruct as:
// x = sin(rad), z = cos(rad)
Eigen::Vector3f moveDirWorld = currentAngularVelocity.normalized();
Eigen::Vector3f veccc(0.0f, 0.0f, -1.0f);
Eigen::Vector3f veccc_rotated = rotation * veccc;
// Move in world
//position += moveDirWorld * (velocity * dt);
position += veccc_rotated * (velocity * dt);
// Rotate ship to face movement direction (optional).
// Ship local forward is (0,0,-1) (as in your fireProjectiles).
// We need yaw so that rotation * (0,0,-1) == moveDirWorld.
// That yaw is: yaw = atan2(-dir.x, -dir.z)
{
Eigen::Vector3f worldUp(0.0f, 1.0f, 0.0f);
// local forward = (0,0,-1) => значит Z-ось мира должна смотреть в -moveDirWorld
Eigen::Vector3f zAxis = -moveDirWorld;
Eigen::Vector3f xAxis = worldUp.cross(zAxis);
if (xAxis.squaredNorm() < 1e-6f) xAxis = Eigen::Vector3f(1, 0, 0);
xAxis.normalize();
Eigen::Vector3f yAxis = zAxis.cross(xAxis).normalized();
Eigen::Matrix3f R;
R.col(0) = xAxis;
R.col(1) = yAxis;
R.col(2) = zAxis;
Eigen::Quaternionf qCur(rotation);
Eigen::Quaternionf qTar(R);
Eigen::Quaternionf qNew = qCur.slerp(ROT_T, qTar).normalized();
rotation = qNew.toRotationMatrix();
}
}
void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime)
{
long long deltaMs = 0;
if (nowTime > lastUpdateServerTime) {
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
}
long long final_lag_ms = deltaMs; // можно clamp, если нужно
if (final_lag_ms > 0) {
simulate_physics(static_cast<size_t>(final_lag_ms));
}
}
void ClientState::handle_full_sync(const std::vector<std::string>& parts, int startFrom)
{
position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
Eigen::Quaternionf q(
std::stof(parts[startFrom + 3]),
std::stof(parts[startFrom + 4]),
std::stof(parts[startFrom + 5]),
std::stof(parts[startFrom + 6]));
rotation = q.toRotationMatrix();
// For compatibility: keep reading it, but we won't use it anymore
currentAngularVelocity = Eigen::Vector3f{
std::stof(parts[startFrom + 7]),
std::stof(parts[startFrom + 8]),
std::stof(parts[startFrom + 9]) };
velocity = std::stof(parts[startFrom + 10]);
selectedVelocity = std::stoi(parts[startFrom + 11]);
discreteMag = std::stof(parts[startFrom + 12]);
discreteAngle = std::stoi(parts[startFrom + 13]);
} }
std::string ClientState::formPingMessageContent() std::string ClientState::formPingMessageContent()
{ {
Eigen::Quaternionf q(rotation); Eigen::Quaternionf q(rotation);
std::string pingMsg = std::to_string(position.x()) + ":" std::string pingMsg =
+ std::to_string(position.y()) + ":" std::to_string(position.x()) + ":" +
+ std::to_string(position.z()) + ":" std::to_string(position.y()) + ":" +
+ std::to_string(q.w()) + ":" std::to_string(position.z()) + ":" +
+ std::to_string(q.x()) + ":" std::to_string(q.w()) + ":" +
+ std::to_string(q.y()) + ":" std::to_string(q.x()) + ":" +
+ std::to_string(q.z()) + ":" std::to_string(q.y()) + ":" +
+ std::to_string(currentAngularVelocity.x()) + ":" std::to_string(q.z()) + ":" +
+ std::to_string(currentAngularVelocity.y()) + ":" std::to_string(currentAngularVelocity.x()) + ":" +
+ std::to_string(currentAngularVelocity.z()) + ":" std::to_string(currentAngularVelocity.y()) + ":" +
+ std::to_string(velocity) + ":" std::to_string(currentAngularVelocity.z()) + ":" +
+ std::to_string(selectedVelocity) + ":" std::to_string(velocity) + ":" +
+ std::to_string(discreteMag) + ":" // Используем те же static переменные из блока ROT std::to_string(selectedVelocity) + ":" +
+ std::to_string(discreteAngle); std::to_string(discreteMag) + ":" +
std::to_string(discreteAngle);
return pingMsg; return pingMsg;
} }
void ClientStateInterval::add_state(const ClientState& state) void ClientStateInterval::add_state(const ClientState& state)
{ {
auto nowTime = std::chrono::system_clock::now(); auto nowTime = std::chrono::system_clock::now();
if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) if (!timedStates.empty() && timedStates.back().lastUpdateServerTime == state.lastUpdateServerTime) {
{ timedStates.back() = state;
timedStates[timedStates.size() - 1] = state; }
} else {
else timedStates.push_back(state);
{ }
timedStates.push_back(state);
}
auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME);
while (!timedStates.empty() && timedStates.front().lastUpdateServerTime < cutoff_time) {
while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) timedStates.erase(timedStates.begin());
{ }
timedStates.erase(timedStates.begin());
}
} }
bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const
{ {
if (timedStates.empty()) if (timedStates.empty()) return false;
{ if (timedStates.front().lastUpdateServerTime > targetTime) return false;
return false; return true;
}
if (timedStates[0].lastUpdateServerTime > targetTime)
{
return false;
}
return true;
} }
ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const
{
if (timedStates.empty()) {
throw std::runtime_error("No timed client states available");
}
if (timedStates.front().lastUpdateServerTime > targetTime) {
throw std::runtime_error("Found time but it is in future");
}
ClientState closestState; ClientState closestState;
if (timedStates.empty()) if (timedStates.size() == 1) {
{ closestState = timedStates[0];
throw std::runtime_error("No timed client states available"); closestState.apply_lag_compensation(targetTime);
return closestState; return closestState;
} }
if (timedStates[0].lastUpdateServerTime > targetTime)
{
throw std::runtime_error("Found time but it is in future");
return closestState;
}
if (timedStates.size() == 1)
{
closestState = timedStates[0];
closestState.apply_lag_compensation(targetTime);
return closestState;
}
for (size_t i = 0; i + 1 < timedStates.size(); ++i) {
const auto& earlierState = timedStates[i];
const auto& laterState = timedStates[i + 1];
for (size_t i = 0; i < timedStates.size() - 1; ++i) if (earlierState.lastUpdateServerTime <= targetTime &&
{ laterState.lastUpdateServerTime >= targetTime)
const auto& earlierState = timedStates[i]; {
const auto& laterState = timedStates[i + 1]; closestState = earlierState;
if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) closestState.apply_lag_compensation(targetTime);
{ return closestState;
closestState = earlierState; }
closestState.apply_lag_compensation(targetTime); }
return closestState;
}
}
closestState = timedStates[timedStates.size() - 1]; closestState = timedStates.back();
closestState.apply_lag_compensation(targetTime); closestState.apply_lag_compensation(targetTime);
return closestState; return closestState;
} }

View File

@ -1,11 +1,12 @@
#include "PlanetObject.h" #include "GameConfig.h"
#include "PlanetObject.h"
#include <random> #include <random>
#include <cmath> #include <cmath>
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include "Environment.h" #include "Environment.h"
#include "StoneObject.h" #include "StoneObject.h"
#include "utils/TaskManager.h" #include "utils/TaskManager.h"
#include <algorithm>
namespace ZL { namespace ZL {
#if defined EMSCRIPTEN || defined __ANDROID__ #if defined EMSCRIPTEN || defined __ANDROID__
@ -57,10 +58,11 @@ namespace ZL {
return rot; return rot;
} }
PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler) PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, Camera& iCamera)
: renderer(iRenderer), : renderer(iRenderer),
taskManager(iTaskManager), taskManager(iTaskManager),
mainThreadHandler(iMainThreadHandler) mainThreadHandler(iMainThreadHandler),
camera(iCamera)
{ {
} }
@ -73,7 +75,7 @@ namespace ZL {
int lodIndex = planetData.getMaxLodIndex(); int lodIndex = planetData.getMaxLodIndex();
planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData;
//planetRenderStruct.data.PositionData.resize(9);
planetRenderStruct.RefreshVBO(); planetRenderStruct.RefreshVBO();
@ -111,34 +113,30 @@ namespace ZL {
// 2. Получаем список видимых треугольников // 2. Получаем список видимых треугольников
auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position); auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position);
std::sort(newIndices.begin(), newIndices.end()); std::sort(newIndices.begin(), newIndices.end());
// 3. Запуск генерации для новых индексов
for (int idx : newIndices) {
if (planetStones.statuses[idx] == ChunkStatus::Empty) {
planetStones.statuses[idx] = ChunkStatus::Generating;
// 3. Анализируем, что нужно загрузить // Запускаем задачу в фоне
for (int triIdx : newIndices) {
if (planetStones.statuses[triIdx] == ChunkStatus::Empty) {
// Помечаем, чтобы не спамить задачами
planetStones.statuses[triIdx] = ChunkStatus::Generating;
// Отправляем тяжелую математику в TaskManager taskManager.EnqueueBackgroundTask([this, idx]() {
taskManager.EnqueueBackgroundTask([this, triIdx]() { // Имитация тяжелой работы (генерация меша)
// Выполняется в фоновом потоке: только генерация геометрии в RAM // Тут мы просто копируем базовый камень + скейл/поворот
float scaleModifier = 1.0f; VertexDataStruct newData = planetStones.inflateOneDataOnly(idx, 0.75f); // 0.75f - scaleModifier
VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier);
// Передаем задачу на загрузку в GPU в очередь главного потока // Когда готово — перекидываем в главный поток для загрузки в GPU
this->mainThreadHandler.EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable { mainThreadHandler.EnqueueMainThreadTask([this, idx, data = std::move(newData)]() mutable {
// Проверяем актуальность: если треугольник всё еще в списке видимых // Проверяем, всё ли еще актуален этот чанк (вдруг игрок улетел)
auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx); // Но пока упростим: всегда грузим, а чистильщик (пункт 4) потом удалит если что
if (it != triangleIndicesToDraw.end()) { if (planetStones.statuses[idx] == ChunkStatus::Generating) {
stonesToRender[triIdx].data = std::move(data); stonesToRender[idx].data = std::move(data);
stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов stonesToRender[idx].RefreshVBO();
planetStones.statuses[triIdx] = ChunkStatus::Live; planetStones.statuses[idx] = ChunkStatus::Live;
} }
else {
// Если уже не нужен — просто сбрасываем статус
planetStones.statuses[triIdx] = ChunkStatus::Empty;
}
});
}); });
});
} }
} }
@ -212,8 +210,7 @@ namespace ZL {
float centerX = (minX + maxX) * 0.5f; float centerX = (minX + maxX) * 0.5f;
float centerY = (minY + maxY) * 0.5f; float centerY = (minY + maxY) * 0.5f;
//width = width * 0.995;
//height = height * 0.995;
renderer.PushProjectionMatrix( renderer.PushProjectionMatrix(
centerX - width*0.5, centerX + width * 0.5, centerX - width*0.5, centerX + width * 0.5,
@ -263,7 +260,7 @@ namespace ZL {
{ {
if (stoneMapFB == nullptr) if (stoneMapFB == nullptr)
{ {
//stoneMapFB = std::make_unique<FrameBuffer>(512, 512, true);
stoneMapFB = std::make_unique<FrameBuffer>(512, 512); stoneMapFB = std::make_unique<FrameBuffer>(512, 512);
} }
stoneMapFB->Bind(); stoneMapFB->Bind();
@ -281,10 +278,11 @@ namespace ZL {
drawPlanet(renderer); drawPlanet(renderer);
drawStones(renderer); #if ENABLE_STONES
drawAtmosphere(renderer); //drawStones(renderer);
#endif
//drawAtmosphere(renderer);
} }
void PlanetObject::drawPlanet(Renderer& renderer) void PlanetObject::drawPlanet(Renderer& renderer)
{ {
static const std::string defaultShaderName = "planetLand"; static const std::string defaultShaderName = "planetLand";
@ -293,7 +291,6 @@ namespace ZL {
static const std::string vColorName = "vColor"; static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal"; static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord"; static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
@ -304,79 +301,69 @@ namespace ZL {
renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray("vBinormal");
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
const float currentZNear = zRange.first; const float currentZNear = zRange.first;
const float currentZFar = zRange.second; const float currentZFar = zRange.second;
// 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, 1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar); currentZNear, currentZFar
);
// View from Camera
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.PushSpecialMatrix(camera.getViewMatrix());
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position); // Planet at world origin (если у тебя есть PLANET_CENTER_OFFSET — добавь Translate туда)
// renderer.TranslateMatrix(PlanetData::PLANET_CENTER_OFFSET);
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("Texture", 0);
renderer.RenderUniform1i("BakedTexture", 1); renderer.RenderUniform1i("BakedTexture", 1);
// Камера (для освещения) — именно позиция камеры, а не shipState.position
Eigen::Vector3f camPos = camera.getPosition();
renderer.RenderUniform3fv("uViewPos", camPos.data());
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
// Позиция камеры (корабля) в мире
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
//renderer.RenderUniform1f("uHeightScale", 0.08f);
renderer.RenderUniform1f("uHeightScale", 0.0f); renderer.RenderUniform1f("uHeightScale", 0.0f);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform1f("uCurrentZFar", currentZFar);
// Направление на солнце в мировом пространстве Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
// Направление от центра планеты к игроку для расчета дня/ночи Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
Vector3f playerDirWorld = Environment::shipState.position.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
// Тот же фактор освещенности игрока float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
//glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
renderer.DrawVertexRenderStruct(planetRenderStruct); renderer.DrawVertexRenderStruct(planetRenderStruct);
CheckGlError(); CheckGlError();
renderer.PopMatrix(); renderer.PopMatrix(); // special matrix
renderer.PopMatrix(); // original push
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray("vTangent"); renderer.DisableVertexAttribArray("vTangent");
renderer.DisableVertexAttribArray("vBinormal"); renderer.DisableVertexAttribArray("vBinormal");
renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void PlanetObject::drawStones(Renderer& renderer) void PlanetObject::drawStones(Renderer& renderer)
{ {
static const std::string defaultShaderName2 = "planetStone"; static const std::string defaultShaderName2 = "planetStone";
@ -384,95 +371,81 @@ namespace ZL {
static const std::string vColorName = "vColor"; static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal"; static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord"; static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2); renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i("Texture", 0);
renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
const float currentZNear = zRange.first; const float currentZNear = zRange.first;
const float currentZFar = zRange.second; const float currentZFar = zRange.second;
renderer.PushPerspectiveProjectionMatrix(
// 2. Применяем динамическую матрицу проекции 1.0 / 1.5,
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar); currentZNear, currentZFar
);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.PushSpecialMatrix(camera.getViewMatrix());
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform1f("uCurrentZFar", currentZFar);
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
//std::cout << "uViewPos" << Environment::shipState.position << std::endl; Eigen::Vector3f camPos = camera.getPosition();
// PlanetObject.cpp, метод drawStones renderer.RenderUniform3fv("uViewPos", camPos.data());
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
Vector3f playerDirWorld = Environment::shipState.position.normalized(); Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); glCullFace(GL_BACK);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
/*
for (int i : triangleIndicesToDraw)
//for (int i = 0; i < stonesToRender.size(); i++)
{
if (stonesToRender[i].data.PositionData.size() > 0)
{
renderer.DrawVertexRenderStruct(stonesToRender[i]);
}
}*/
for (int i : triangleIndicesToDraw) { for (int i : triangleIndicesToDraw) {
// КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ:
// Проверяем, что данные не просто существуют, а загружены в GPU
if (planetStones.statuses[i] == ChunkStatus::Live) { if (planetStones.statuses[i] == ChunkStatus::Live) {
// Дополнительная проверка на наличие данных (на всякий случай) if (!stonesToRender[i].data.PositionData.empty()) {
if (stonesToRender[i].data.PositionData.size() > 0) {
renderer.DrawVertexRenderStruct(stonesToRender[i]); renderer.DrawVertexRenderStruct(stonesToRender[i]);
} }
} }
// Если статус Generating или Empty — мы просто пропускаем этот индекс.
// Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO.
} }
CheckGlError(); CheckGlError();
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
renderer.PopMatrix(); renderer.PopMatrix(); // special
renderer.PopMatrix(); // original
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
glClear(GL_DEPTH_BUFFER_BIT);
} }
void PlanetObject::drawAtmosphere(Renderer& renderer) { void PlanetObject::drawAtmosphere(Renderer& renderer)
{
static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string defaultShaderName = "defaultAtmosphere";
//static const std::string defaultShaderName = "defaultColor";
static const std::string vPositionName = "vPosition"; static const std::string vPositionName = "vPosition";
static const std::string vNormalName = "vNormal"; static const std::string vNormalName = "vNormal";
//glClear(GL_DEPTH_BUFFER_BIT);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
@ -484,90 +457,65 @@ namespace ZL {
float currentZNear = zRange.first; float currentZNear = zRange.first;
float currentZFar = zRange.second; float currentZFar = zRange.second;
if (currentZNear < 200) if (currentZNear < 200) {
{
currentZNear = 200; currentZNear = 200;
currentZFar = currentZNear * 100; currentZFar = currentZNear * 100;
} }
// 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, 1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar); currentZNear, currentZFar
);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.PushSpecialMatrix(camera.getViewMatrix());
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
Vector3f color = { 0.0, 0.5, 1.0 };
renderer.RenderUniform3fv("uColor", color.data());
const Eigen::Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data());
Eigen::Vector3f color = { 0.0f, 0.5f, 1.0f };
renderer.RenderUniform3fv("uColor", color.data());
renderer.RenderUniform3fv("uSkyColor", color.data());
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
// В начале drawAtmosphere или как uniform Eigen::Vector3f worldLightDir = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
//float time = SDL_GetTicks() / 1000.0f;
//renderer.RenderUniform1f("uTime", time);
// В методе PlanetObject::drawAtmosphere
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
// Получаем текущую матрицу вида (ModelView без трансляции объекта)
// В вашем движке это Environment::inverseShipMatrix
Matrix3f viewMatrix2 = Environment::inverseShipMatrix;
// Трансформируем вектор света в пространство вида
Vector3f viewLightDir = viewMatrix2 * worldLightDir;
// Передаем инвертированный вектор (направление НА источник)
Vector3f lightToSource = -viewLightDir.normalized();
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
// В методе Game::drawCubemap
//renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f);
// Направление света в мировом пространстве для освещения облаков
//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data());
// 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) // Свет в view-space: берём матрицу вида (верхний левый 3x3)
Vector3f playerDirWorld = Environment::shipState.position.normalized(); Eigen::Matrix3f viewRot = camera.getViewMatrix().block<3, 3>(0, 0);
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); Eigen::Vector3f viewLightDir = (viewRot * worldLightDir).normalized();
Eigen::Vector3f lightToSource = -viewLightDir;
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
// Насколько игрок на свету Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог
// 2. ОБЯЗАТЕЛЬНО передаем в шейдер
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
// 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было)
renderer.RenderUniform3fv("uSkyColor", color.data());
//Vector3f playerDirWorld = Environment::shipState.position.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
Eigen::Vector3f sunDirWorld = worldLightDir;
float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
renderer.PopMatrix();
renderer.PopMatrix(); // special
renderer.PopMatrix(); // original
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
{ {
return planetData.distanceToPlanetSurfaceFast(viewerPosition); return planetData.distanceToPlanetSurfaceFast(viewerPosition);

View File

@ -18,7 +18,7 @@
#include "render/FrameBuffer.h" #include "render/FrameBuffer.h"
#include <queue> #include <queue>
#include <mutex> #include <mutex>
#include "render/Camera.h"
namespace ZL { namespace ZL {
class TaskManager; class TaskManager;
class MainThreadHandler; class MainThreadHandler;
@ -47,8 +47,9 @@ namespace ZL {
Renderer& renderer; Renderer& renderer;
TaskManager& taskManager; TaskManager& taskManager;
MainThreadHandler& mainThreadHandler; MainThreadHandler& mainThreadHandler;
Camera& camera;
public: public:
PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler); PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, Camera& iCamera);
void init(); void init();
void update(float deltaTimeMs); void update(float deltaTimeMs);

View File

@ -110,18 +110,7 @@ namespace ZL {
v(2) *= scaleFactors(2); v(2) *= scaleFactors(2);
} }
/*
// Случайный поворот (например, вокруг трех осей)
Vector4f qx = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitX()));//QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qy = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitY()));//QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qz = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitZ()));//QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qFinal = slerp(qx, qy, 0.5f); // Простой пример комбинирования
qFinal = slerp(qFinal, qz, 0.5f).normalized();
Matrix3f rotationMatrix = QuatToMatrix(qFinal);
for (Vector3f& v : initialVertices) {
v = MultMatrixVector(rotationMatrix, v);
}*/
// 3. Сглаженные Нормали и Формирование Mesh // 3. Сглаженные Нормали и Формирование Mesh
VertexDataStruct result; VertexDataStruct result;
@ -259,18 +248,7 @@ namespace ZL {
getRandomFloat(engine, SCALE_MIN, SCALE_MAX) getRandomFloat(engine, SCALE_MIN, SCALE_MAX)
}; };
/*
if (tIdx == 0)
{
instance.scale = instance.scale * 0.7f;
}*/
/*
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
instance.rotation = slerp(slerp(qx, qy, 0.5f), qz, 0.5f).normalized();
*/
instance.rotation = Vector4f(0.f, 0.f, 0.f, 1.f); instance.rotation = Vector4f(0.f, 0.f, 0.f, 1.f);
group.allInstances[tIdx].push_back(instance); group.allInstances[tIdx].push_back(instance);
} }
@ -291,9 +269,15 @@ namespace ZL {
return result; return result;
} }
// Helper to get the singleton base stone data
const VertexDataStruct& GetBaseStone() {
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
return baseStone;
}
VertexRenderStruct StoneGroup::inflateOne(int index, float scaleModifier) VertexRenderStruct StoneGroup::inflateOne(int index, float scaleModifier)
{ {
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); const VertexDataStruct& baseStone = GetBaseStone();
VertexRenderStruct result; VertexRenderStruct result;
@ -324,10 +308,11 @@ namespace ZL {
VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier) VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier)
{ {
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); const VertexDataStruct& baseStone = GetBaseStone();
VertexDataStruct result; VertexDataStruct result;
// Pre-reserve logic? Optional but good.
// Since we don't know exact size (depends on stone count), maybe just standard push_back.
for (const auto& inst : allInstances[index]) { for (const auto& inst : allInstances[index]) {
Matrix3f rotMat = inst.rotation.toRotationMatrix(); Matrix3f rotMat = inst.rotation.toRotationMatrix();
@ -342,7 +327,13 @@ namespace ZL {
result.PositionData.push_back(rotMat * p + inst.position); result.PositionData.push_back(rotMat * p + inst.position);
result.NormalData.push_back(rotMat * n); result.NormalData.push_back(rotMat * n);
result.TexCoordData.push_back(baseStone.TexCoordData[j]);
if (j < baseStone.TexCoordData.size()) {
result.TexCoordData.push_back(baseStone.TexCoordData[j]);
}
else {
result.TexCoordData.push_back({ 0.f, 0.f });
}
} }
} }

55
src/render/Camera.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "render/Camera.h"
#include <cmath>
namespace ZL {
using Eigen::Vector3f;
using Eigen::Matrix3f;
using Eigen::Matrix4f;
Camera::Camera()
: position(Vector3f::Zero())
, target(Vector3f::Zero())
, rotation(Matrix3f::Identity())
{
}
void Camera::followOrbit(const Vector3f& targetPos, float yaw, float pitch, float distance, float height)
{
target = targetPos;
float cosP = std::cos(pitch);
float sinP = std::sin(pitch);
float cosY = std::cos(yaw);
float sinY = std::sin(yaw);
Vector3f offset;
offset.x() = distance * sinY * cosP;
offset.y() = height + distance * sinP;
offset.z() = distance * cosY * cosP;
position = target + offset;
// Build camera basis to look at target
Vector3f forward = (target - position).normalized();
Vector3f worldUp(0.0f, 1.0f, 0.0f);
Vector3f right = worldUp.cross(forward).normalized();
Vector3f up = forward.cross(right);
rotation.col(0) = right;
rotation.col(1) = up;
rotation.col(2) = -forward; // OpenGL convention
}
Matrix4f Camera::getViewMatrix() const
{
Matrix3f R_inv = rotation.transpose();
Matrix4f view = Matrix4f::Identity();
view.block<3, 3>(0, 0) = R_inv;
view.block<3, 1>(0, 3) = R_inv * (-position);
return view;
}
} // namespace ZL

23
src/render/Camera.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <Eigen/Dense>
namespace ZL {
class Camera {
public:
Camera();
// Орбитальная камера вокруг targetPos (yaw вокруг Y, pitch вокруг X)
void followOrbit(const Eigen::Vector3f& targetPos, float yaw, float pitch, float distance, float height);
Eigen::Matrix4f getViewMatrix() const;
Eigen::Vector3f getPosition() const { return position; }
Eigen::Vector3f getTarget() const { return target; }
private:
Eigen::Vector3f position;
Eigen::Vector3f target;
Eigen::Matrix3f rotation; // columns: right, up, -forward
};
}