Compare commits

...
7 Commits
Author SHA1 Message Date
vottozi e799b4ec79 conflict resolution 2026-02-05 01:43:45 +06:00
vottozi c8076b6018 adjust label scale: constant far, smooth growth near 2026-02-05 01:34:14 +06:00
Vlad 204b79bf06 add main menu 2026-01-31 19:25:40 +06:00
Vlad 50232c0816 fix shooting 2026-01-31 16:44:00 +06:00
Vlad e8fb14b809 added sync projectiles/boxes 2026-01-27 19:38:18 +06:00
Vladislav Khorev d425167dce Added planet physics and simple base 2026-01-25 20:55:36 +03:00
Vlad cacc18dc7e added boxes from server 2026-01-19 22:04:33 +06:00
25 changed files with 7187 additions and 346 deletions

No files matched your search

+128
View File
@@ -0,0 +1,128 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"children": [
{
"type": "LinearLayout",
"name": "settingsButtons",
"orientation": "vertical",
"spacing": 10,
"x": 0,
"y": 0,
"width": 300,
"height": 300,
"children": [
{
"type": "Button",
"name": "langButton",
"x": 1100,
"y": 580,
"width": 142,
"height": 96,
"textures": {
"normal": "resources/main_menu/lang.png",
"hover": "resources/main_menu/lang.png",
"pressed": "resources/main_menu/lang.png"
}
},
{
"type": "Button",
"name": "titleBtn",
"x": 473,
"y": 500,
"width": 254,
"height": 35,
"textures": {
"normal": "resources/main_menu/title.png",
"hover": "resources/main_menu/title.png",
"pressed": "resources/main_menu/title.png"
}
},
{
"type": "Button",
"name": "underlineBtn",
"x": 516,
"y": 465,
"width": 168,
"height": 44,
"textures": {
"normal": "resources/main_menu/line.png",
"hover": "resources/main_menu/line.png",
"pressed": "resources/main_menu/line.png"
}
},
{
"type": "Button",
"name": "subtitleBtn",
"x": 528,
"y": 455,
"width": 144,
"height": 11,
"textures": {
"normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png"
}
},
{
"type": "Button",
"name": "singleButton",
"x": 409,
"y": 360,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png",
"pressed": "resources/main_menu/single.png"
}
},
{
"type": "Button",
"name": "multiplayerButton",
"x": 409,
"y": 289,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/multi.png",
"pressed": "resources/main_menu/multi.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": 409,
"y": 218,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/exit.png",
"hover": "resources/main_menu/exit.png",
"pressed": "resources/main_menu/exit.png"
}
},
{
"type": "Button",
"name": "versionLabel",
"x": 559.5,
"y": 170,
"width": 81,
"height": 9,
"textures": {
"normal": "resources/main_menu/version.png",
"hover": "resources/main_menu/version.png",
"pressed": "resources/main_menu/version.png"
}
}
]
}
]
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
+281 -112
View File
@@ -11,16 +11,18 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include <math.h> #include <math.h>
#include "../src/network/ClientState.h" #include "../src/network/ClientState.h"
#include <random>
#include <algorithm>
// Вспомогательный split // Вспомогательный split
std::vector<std::string> split(const std::string& s, char delimiter) { std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
std::string token; std::string token;
std::istringstream tokenStream(s); std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) { while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token); tokens.push_back(token);
} }
return tokens; return tokens;
} }
namespace beast = boost::beast; namespace beast = boost::beast;
@@ -31,152 +33,319 @@ using tcp = net::ip::tcp;
class Session; class Session;
struct ServerBox {
Eigen::Vector3f position;
Eigen::Matrix3f rotation;
float collisionRadius = 2.0f;
};
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<Session>> g_sessions; std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex; std::mutex g_sessions_mutex;
class Session : public std::enable_shared_from_this<Session> { class Session : public std::enable_shared_from_this<Session> {
websocket::stream<beast::tcp_stream> ws_; websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_; beast::flat_buffer buffer_;
int id_; int id_;
bool is_writing_ = false;
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
void process_message(const std::string& msg) { void process_message(const std::string& msg) {
auto now_server = std::chrono::system_clock::now(); auto now_server = std::chrono::system_clock::now();
auto parts = split(msg, ':');
auto parts = split(msg, ':'); if (parts.empty()) {
std::cerr << "Empty message received\n";
return;
}
if (parts.size() < 16) std::string type = parts[0];
{
throw std::runtime_error("Unknown message type received, too small");
}
uint64_t clientTimestamp = std::stoull(parts[1]); if (type == "UPD") {
if (parts.size() < 16) {
std::cerr << "Invalid UPD message: too few parts (" << parts.size() << ")\n";
return;
}
uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{
std::chrono::milliseconds(clientTimestamp)
};
receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2);
retranslateMessage(msg);
timedClientStates.add_state(receivedState);
}
else if (parts[0] == "FIRE") {
if (parts.size() < 8) {
std::cerr << "Invalid FIRE: too few parts\n";
return;
}
ClientState receivedState; uint64_t clientTime = std::stoull(parts[1]);
Eigen::Vector3f pos{
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
};
Eigen::Vector3f dir{
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7])
};
receivedState.id = id_; int shotCount = 1;
if (parts.size() >= 9) {
try {
shotCount = std::stoi(parts[8]);
}
catch (...) {
shotCount = 1;
}
}
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) }; std::string broadcast = "PROJECTILE:" +
receivedState.lastUpdateServerTime = uptime_timepoint; std::to_string(id_) + ":" +
std::to_string(clientTime) + ":" +
std::to_string(pos.x()) + ":" +
std::to_string(pos.y()) + ":" +
std::to_string(pos.z()) + ":" +
std::to_string(dir.x()) + ":" +
std::to_string(dir.y()) + ":" +
std::to_string(dir.z()) + ":" +
std::to_string(shotCount);
if (parts[0] == "UPD") { std::lock_guard<std::mutex> lock(g_sessions_mutex);
receivedState.handle_full_sync(parts, 2); std::cout << "Player " << id_ << " fired " << shotCount << " shots → broadcasting\n";
retranslateMessage(msg);
}
else
{
throw std::runtime_error("Unknown message type received: " + parts[0]);
}
timedClientStates.add_state(receivedState); for (auto& session : g_sessions) {
} if (session->get_id() != id_) {
session->send_message(broadcast);
}
}
}
else {
std::cerr << "Unknown message type: " << type << "\n";
}
}
void retranslateMessage(const std::string& msg) void retranslateMessage(const std::string& msg)
{ {
std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg;
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { // Не шлем отправителю
session->send_message(event_msg);
}
}
}
void sendBoxesToClient() {
std::lock_guard<std::mutex> lock(g_boxes_mutex);
std::string boxMsg = "BOXES:";
for (const auto& box : g_serverBoxes) {
Eigen::Quaternionf q(box.rotation);
boxMsg += std::to_string(box.position.x()) + ":" +
std::to_string(box.position.y()) + ":" +
std::to_string(box.position.z()) + ":" +
std::to_string(q.w()) + ":" +
std::to_string(q.x()) + ":" +
std::to_string(q.y()) + ":" +
std::to_string(q.z()) + "|";
}
if (!boxMsg.empty() && boxMsg.back() == '|') {
boxMsg.pop_back();
}
send_message(boxMsg);
}
void send_message(std::string msg) {
auto ss = std::make_shared<std::string>(std::move(msg));
if (is_writing_) {
ws_.async_write(net::buffer(*ss),
[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
}
});
}
else {
is_writing_ = true;
ws_.async_write(net::buffer(*ss),
[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
self->is_writing_ = false;
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
}
});
}
}
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { // Не шлем отправителю
session->send_message(event_msg);
}
}
}
public: public:
explicit Session(tcp::socket&& socket, int id) explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) { : ws_(std::move(socket)), id_(id) {
} }
void init() void init()
{ {
} sendBoxesToClient();
void run() { auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
if (!ec) {
self->send_message("ID:" + std::to_string(self->id_));
self->do_read();
}
});
}
{ void run() {
std::lock_guard<std::mutex> lock(g_sessions_mutex);
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.push_back(shared_from_this()); g_sessions.push_back(shared_from_this());
} }
ws_.async_accept([self = shared_from_this()](beast::error_code ec) { ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
if (ec) return; if (ec) return;
std::cout << "Client " << self->id_ << " connected\n"; std::cout << "Client " << self->id_ << " connected\n";
self->init(); self->init();
self->send_message("ID:" + std::to_string(self->id_)); // self->send_message("ID:" + std::to_string(self->id_));
self->do_read(); // self->do_read();
}); });
} }
void send_message(std::string msg) { /*void send_message(std::string msg) {
auto ss = std::make_shared<std::string>(std::move(msg)); auto ss = std::make_shared<std::string>(std::move(msg));
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
} }*/
int get_id() const { int get_id() const {
return id_; return id_;
} }
private: private:
void do_read() { void do_read() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
if (ec) if (ec)
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
[self](const std::shared_ptr<Session>& session) { [self](const std::shared_ptr<Session>& session) {
return session.get() == self.get(); return session.get() == self.get();
}), g_sessions.end()); }), g_sessions.end());
return; return;
} }
std::string msg = beast::buffers_to_string(self->buffer_.data()); std::string msg = beast::buffers_to_string(self->buffer_.data());
self->process_message(msg); self->process_message(msg);
self->buffer_.consume(self->buffer_.size()); self->buffer_.consume(self->buffer_.size());
self->do_read(); self->do_read();
}); });
} }
}; };
void update_world(net::steady_timer& timer, net::io_context& ioc) { void update_world(net::steady_timer& timer, net::io_context& ioc) {
// TODO: Renew game state // TODO: Renew game state
timer.expires_after(std::chrono::milliseconds(50)); timer.expires_after(std::chrono::milliseconds(50));
timer.async_wait([&](const boost::system::error_code& ec) { timer.async_wait([&](const boost::system::error_code& ec) {
if (!ec) update_world(timer, ioc); if (!ec) update_world(timer, ioc);
}); });
} }
std::vector<ServerBox> generateServerBoxes(int count) {
std::vector<ServerBox> boxes;
std::random_device rd;
std::mt19937 gen(rd());
const float MIN_COORD = -100.0f;
const float MAX_COORD = 100.0f;
const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const int MAX_ATTEMPTS = 1000;
std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);
for (int i = 0; i < count; i++) {
bool accepted = false;
int attempts = 0;
while (!accepted && attempts < MAX_ATTEMPTS) {
ServerBox box;
box.position = Eigen::Vector3f(
(float)posDistrib(gen),
(float)posDistrib(gen),
(float)posDistrib(gen)
);
accepted = true;
for (const auto& existingBox : boxes) {
Eigen::Vector3f diff = box.position - existingBox.position;
if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
accepted = false;
break;
}
}
if (accepted) {
float randomAngle = (float)angleDistrib(gen);
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
boxes.push_back(box);
}
attempts++;
}
}
return boxes;
}
int main() { int main() {
try { try {
net::io_context ioc; {
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; std::lock_guard<std::mutex> lock(g_boxes_mutex);
int next_id = 0; g_serverBoxes = generateServerBoxes(50);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
}
net::io_context ioc;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
int next_id = 0;
std::cout << "Server started on port 8080...\n"; std::cout << "Server started on port 8080...\n";
auto do_accept = [&](auto& self_fn) -> void { auto do_accept = [&](auto& self_fn) -> void {
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) { acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
if (!ec) { if (!ec) {
std::make_shared<Session>(std::move(socket), next_id++)->run(); std::make_shared<Session>(std::move(socket), next_id++)->run();
} }
self_fn(self_fn); self_fn(self_fn);
}); });
}; };
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept); net::steady_timer timer(ioc);
ioc.run(); update_world(timer, ioc);
}
catch (std::exception const& e) { do_accept(do_accept);
std::cerr << "Error: " << e.what() << std::endl; ioc.run();
} }
return 0; catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
} }
+209 -80
View File
@@ -10,6 +10,7 @@
#include <random> #include <random>
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <functional>
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <android/log.h> #include <android/log.h>
#endif #endif
@@ -268,83 +269,119 @@ namespace ZL
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE); bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>()); explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
projectileEmitter.setEmissionPoints(std::vector<Vector3f>()); projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
uiManager.startAnimationOnNode("backgroundNode", "bgScroll"); uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
static bool isExitButtonAnimating = false; std::function<void()> loadGameplayUI;
uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() { loadGameplayUI = [this]() {
std::cerr << "Settings button animation finished -> переход в настройки" << std::endl; uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
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", []() { uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
std::cerr << "Exit button bgScroll animation finished" << std::endl; static bool isExitButtonAnimating = false;
g_exitBgAnimating = 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)) {
// Set UI button callbacks uiManager.setButtonCallback("Opt1", [this](const std::string& n) {
uiManager.setButtonCallback("playButton", [this](const std::string& name) { std::cerr << "Opt1 pressed: " << n << std::endl;
std::cerr << "Play button pressed: " << name << std::endl; });
uiManager.setButtonCallback("Opt2", [this](const std::string& n) {
}); std::cerr << "Opt2 pressed: " << n << std::endl;
});
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) { uiManager.setButtonCallback("backButton", [this](const std::string& n) {
std::cerr << "Settings button pressed: " << name << std::endl; uiManager.stopAllAnimations();
uiManager.startAnimationOnNode("playButton", "buttonsExit"); uiManager.popMenu();
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();
} }
} else {
}); std::cerr << "Failed to open settings menu after animations" << std::endl;
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) { uiManager.setAnimationCallback("exitButton", "bgScroll", []() {
int newVel = roundf(value * 10); std::cerr << "Exit button bgScroll animation finished" << std::endl;
if (newVel != Environment::shipState.selectedVelocity) { g_exitBgAnimating = false;
newShipVelocity = newVel; });
}
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
std::cerr << "Play button pressed: " << name << std::endl;
});
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
std::cerr << "Settings button pressed: " << name << std::endl;
uiManager.startAnimationOnNode("playButton", "buttonsExit");
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
});
uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
std::cerr << "Exit button pressed: " << name << std::endl;
if (!g_exitBgAnimating) {
std::cerr << "start repeat anim bgScroll on exitButton" << std::endl;
g_exitBgAnimating = true;
uiManager.startAnimationOnNode("exitButton", "bgScroll");
}
else {
std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl;
g_exitBgAnimating = false;
uiManager.stopAnimationOnNode("exitButton", "bgScroll");
auto exitButton = uiManager.findButton("exitButton");
if (exitButton) {
exitButton->animOffsetX = 0.0f;
exitButton->animOffsetY = 0.0f;
exitButton->animScaleX = 1.0f;
exitButton->animScaleY = 1.0f;
exitButton->buildMesh();
}
}
});
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
uint64_t now = SDL_GetTicks64();
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
lastProjectileFireTime = now;
this->fireProjectiles();
Eigen::Vector3f localForward = { 0, 0, -1 };
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
Eigen::Vector3f centerPos = Environment::shipState.position +
Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };
std::string fireMsg = "FIRE:" +
std::to_string(now) + ":" +
std::to_string(centerPos.x()) + ":" +
std::to_string(centerPos.y()) + ":" +
std::to_string(centerPos.z()) + ":" +
std::to_string(worldForward.x()) + ":" +
std::to_string(worldForward.y()) + ":" +
std::to_string(worldForward.z()) + ":" +
"2";
networkClient->Send(fireMsg);
}
});
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10);
if (newVel != Environment::shipState.selectedVelocity) {
newShipVelocity = newVel;
}
});
};
uiManager.setButtonCallback("singleButton", [loadGameplayUI](const std::string& name) {
std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n";
loadGameplayUI();
});
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n";
loadGameplayUI();
});
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
Environment::exitGameLoop = true;
}); });
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
@@ -540,6 +577,9 @@ namespace ZL
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
//glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID());
//renderer.DrawVertexRenderStruct(basePlatform);
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
@@ -692,6 +732,27 @@ namespace ZL
latestRemotePlayers = networkClient->getRemotePlayers(); latestRemotePlayers = networkClient->getRemotePlayers();
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
if (!serverBoxesApplied && networkClient) {
auto sboxes = networkClient->getServerBoxes();
if (!sboxes.empty()) {
boxCoordsArr.clear();
for (auto& b : sboxes) {
BoxCoords bc;
bc.pos = b.first;
bc.m = b.second;
boxCoordsArr.push_back(bc);
}
boxRenderArr.resize(boxCoordsArr.size());
for (int i = 0; i < (int)boxCoordsArr.size(); ++i) {
boxRenderArr[i].AssignFrom(boxBase);
boxRenderArr[i].RefreshVBO();
}
boxAlive.assign(boxCoordsArr.size(), true);
serverBoxesApplied = true;
}
}
// Итерируемся по актуальным данным из extrapolateRemotePlayers // Итерируемся по актуальным данным из extrapolateRemotePlayers
for (auto const& [id, remotePlayer] : latestRemotePlayers) { for (auto const& [id, remotePlayer] : latestRemotePlayers) {
@@ -701,7 +762,7 @@ namespace ZL
} }
ClientState playerState = remotePlayer.fetchClientStateAtTime(now); ClientState playerState = remotePlayer.fetchClientStateAtTime(now);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -770,7 +831,16 @@ namespace ZL
continue; continue;
float uiX = sx, uiY = sy; float uiX = sx, uiY = sy;
float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); //float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);
float scale;
if (dist > CLOSE_DIST) {
scale = 0.4f;
}
else {
float t = 1.0f - (dist / CLOSE_DIST);
scale = 0.4f + (MAX_SCALE - 0.4f) * t;
}
scale = std::clamp(scale, MIN_SCALE, MAX_SCALE);
// Дефолтный лейбл // Дефолтный лейбл
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m";
@@ -854,7 +924,7 @@ namespace ZL
float radians = atan2f(diffy, diffx); float radians = atan2f(diffy, diffx);
discreteAngle = static_cast<int>(radians * 180.0f / M_PI); discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
if (discreteAngle < 0) discreteAngle += 360; if (discreteAngle < 0) discreteAngle += 360;
} }
else else
{ {
@@ -868,7 +938,7 @@ namespace ZL
discreteMag = 0.0f; discreteMag = 0.0f;
} }
if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) {
Environment::shipState.discreteAngle = discreteAngle; Environment::shipState.discreteAngle = discreteAngle;
Environment::shipState.discreteMag = discreteMag; Environment::shipState.discreteMag = discreteMag;
@@ -1230,7 +1300,7 @@ namespace ZL
handleMotion(mx, my); handleMotion(mx, my);
} }
/*
if (event.type == SDL_MOUSEWHEEL) { if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f; static const float zoomstep = 2.0f;
if (event.wheel.y > 0) { if (event.wheel.y > 0) {
@@ -1247,14 +1317,73 @@ namespace ZL
{ {
if (event.key.keysym.sym == SDLK_i) if (event.key.keysym.sym == SDLK_i)
{ {
x = x + 1;
} }
}*/ if (event.key.keysym.sym == SDLK_k)
{
x = x - 1;
}
if (event.key.keysym.sym == SDLK_a)
{
Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
}
}
#endif #endif
} }
render(); render();
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();
networkClient->Poll(); networkClient->Poll();
if (networkClient) {
auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) {
const float projectileSpeed = 60.0f;
const float lifeMs = 5000.0f;
const float size = 0.5f;
auto remotePlayersSnapshot = networkClient->getRemotePlayers();
for (const auto& pi : pending) {
Eigen::Vector3f dir = pi.direction;
float len = dir.norm();
if (len <= 1e-6f) continue;
dir /= len;
Eigen::Matrix3f shooterRot = Eigen::Matrix3f::Identity();
float shooterVel = 0.0f;
auto it = remotePlayersSnapshot.find(pi.shooterId);
if (it != remotePlayersSnapshot.end()) {
std::chrono::system_clock::time_point pktTime{ std::chrono::milliseconds(pi.clientTime) };
if (it->second.canFetchClientStateAtTime(pktTime)) {
ClientState shooterState = it->second.fetchClientStateAtTime(pktTime);
shooterRot = shooterState.rotation;
shooterVel = shooterState.velocity;
}
}
float speedWithOwner = projectileSpeed + shooterVel;
Eigen::Vector3f baseVel = dir * speedWithOwner;
int shotCount = 2;
std::vector<Eigen::Vector3f> localOffsets = {
{-1.5f, 0.9f, 5.0f},
{ 1.5f, 0.9f, 5.0f}
};
for (int i = 0; i < shotCount; ++i) {
Eigen::Vector3f rotatedOffset = shooterRot * localOffsets[i];
Eigen::Vector3f shotPos = pi.position + rotatedOffset;
for (auto& p : projectiles) {
if (!p->isActive()) {
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
break;
}
}
}
}
}
}
} }
void Game::handleDown(int mx, int my) void Game::handleDown(int mx, int my)
+4 -1
View File
@@ -86,6 +86,7 @@ namespace ZL {
VertexDataStruct spaceshipBase; VertexDataStruct spaceshipBase;
VertexRenderStruct spaceship; VertexRenderStruct spaceship;
VertexRenderStruct cubemap; VertexRenderStruct cubemap;
std::shared_ptr<Texture> boxTexture; std::shared_ptr<Texture> boxTexture;
@@ -121,7 +122,9 @@ namespace ZL {
static constexpr float BASE_SCALE = 140.f; static constexpr float BASE_SCALE = 140.f;
static constexpr float PERSPECTIVE_K = 0.05f; // Tune static constexpr float PERSPECTIVE_K = 0.05f; // Tune
static constexpr float MIN_SCALE = 0.4f; static constexpr float MIN_SCALE = 0.4f;
static constexpr float MAX_SCALE = 1.5f; static constexpr float MAX_SCALE = 0.8f;
static constexpr float CLOSE_DIST = 600.0f;
bool serverBoxesApplied = false;
}; };
+63
View File
@@ -45,6 +45,69 @@ void ClientState::simulate_physics(size_t delta) {
} }
} }
float distToCenter = position.norm(); // Расстояние до {0,0,0}
float landingZone = PLANET_RADIUS * PLANET_ALIGN_ZONE;
if (distToCenter <= landingZone) {
Eigen::Vector3f planetNormal = position.normalized();
// --- 1. ВЫРАВНИВАНИЕ КРЕНА (Roll - ось Z) ---
Eigen::Vector3f localX = rotation.col(0);
float rollError = localX.dot(planetNormal);
if (std::abs(rollError) > 0.001f) {
currentAngularVelocity.z() -= rollError * PLANET_ANGULAR_ACCEL * delta;
currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY));
}
// --- 2. ОГРАНИЧЕНИЕ ТАНГАЖА (Pitch - ось X) ---
// Нос корабля в локальных координатах — это -Z (третий столбец матрицы)
Eigen::Vector3f forwardDir = -rotation.col(2);
// В твоем случае dot < 0 означает, что нос направлен К планете
float pitchSin = forwardDir.dot(planetNormal);
float currentPitchAngle = asinf(std::clamp(pitchSin, -1.0f, 1.0f));
// Лимит у нас M_PI / 6.0 (примерно 0.523)
// По твоим данным: -0.89 < -0.523, значит мы превысили наклон вниз
if (currentPitchAngle < -PITCH_LIMIT) {
// Вычисляем ошибку (насколько мы ушли "ниже" лимита)
// -0.89 - (-0.52) = -0.37
float pitchError = currentPitchAngle + PITCH_LIMIT;
// Теперь важно: нам нужно ПОДНЯТЬ нос.
// Если pitchError отрицательный, а нам нужно уменьшить вращение,
// пробуем прибавлять или вычитать в зависимости от твоей оси X.
// Судя по стандартной логике Eigen, нам нужно ПЛЮСОВАТЬ:
currentAngularVelocity.x() -= pitchError * PLANET_ANGULAR_ACCEL * delta;
}
}
else {
// Вне зоны: тормозим Z (крен) И X (тангаж), если они были активны
float drop = ANGULAR_ACCEL * delta;
if (std::abs(currentAngularVelocity[2]) > 0.0001f) {
if (std::abs(currentAngularVelocity[2]) <= drop) {
currentAngularVelocity[2] = 0.0f;
}
else {
currentAngularVelocity[2] -= (currentAngularVelocity[2] > 0 ? 1.0f : -1.0f) * drop;
}
}
}
// Ограничение скорости
currentAngularVelocity.x() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
std::min(currentAngularVelocity.x(), PLANET_MAX_ANGULAR_VELOCITY));
// Ограничение скорости
currentAngularVelocity.y() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
std::min(currentAngularVelocity.y(), PLANET_MAX_ANGULAR_VELOCITY));
// Ограничение скорости
currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY));
float speedScale = currentAngularVelocity.norm(); float speedScale = currentAngularVelocity.norm();
if (speedScale > 0.0001f) { if (speedScale > 0.0001f) {
// Коэффициент чувствительности вращения // Коэффициент чувствительности вращения
+7 -1
View File
@@ -13,8 +13,14 @@ constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f;
constexpr float SHIP_ACCEL = 1.0f * 1000.0f; constexpr float SHIP_ACCEL = 1.0f * 1000.0f;
constexpr float ROTATION_SENSITIVITY = 0.002f; constexpr float ROTATION_SENSITIVITY = 0.002f;
constexpr float PLANET_RADIUS = 20000.f;
constexpr float PLANET_ALIGN_ZONE = 1.05f;
constexpr float PLANET_ANGULAR_ACCEL = 0.01f; // Подбери под динамику
constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f;
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
constexpr long long SERVER_DELAY = 0; //ms constexpr long long SERVER_DELAY = 0; //ms
constexpr long long CLIENT_DELAY = 200; //ms constexpr long long CLIENT_DELAY = 1000; //ms
constexpr long long CUTOFF_TIME = 5000; //ms constexpr long long CUTOFF_TIME = 5000; //ms
struct ClientState { struct ClientState {
+9 -7
View File
@@ -4,15 +4,17 @@
namespace ZL { namespace ZL {
void LocalClient::Connect(const std::string& host, uint16_t port) { void LocalClient::Connect(const std::string& host, uint16_t port) {
} }
void LocalClient::Poll() { void LocalClient::Poll() {
} }
void LocalClient::Send(const std::string& message) { void LocalClient::Send(const std::string& message) {
}
}
std::vector<ProjectileInfo> LocalClient::getPendingProjectiles() {
return {};
}
} }
+5
View File
@@ -17,9 +17,14 @@ namespace ZL {
bool IsConnected() const override { return true; } bool IsConnected() const override { return true; }
int GetClientId() const { return 1; } int GetClientId() const { return 1; }
std::vector<ProjectileInfo> getPendingProjectiles();
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override { std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
return std::unordered_map<int, ClientStateInterval>(); return std::unordered_map<int, ClientStateInterval>();
} }
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
return {};
}
}; };
} }
+11
View File
@@ -7,6 +7,13 @@
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений // NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
namespace ZL { namespace ZL {
struct ProjectileInfo {
int shooterId = -1;
uint64_t clientTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
Eigen::Vector3f direction = Eigen::Vector3f::Zero();
};
class INetworkClient { class INetworkClient {
public: public:
virtual ~INetworkClient() = default; virtual ~INetworkClient() = default;
@@ -15,5 +22,9 @@ namespace ZL {
virtual bool IsConnected() const = 0; virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0; virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0;
virtual std::vector<ProjectileInfo> getPendingProjectiles() = 0;
}; };
} }
+195 -126
View File
@@ -6,170 +6,239 @@
// Вспомогательный split // Вспомогательный split
std::vector<std::string> split(const std::string& s, char delimiter) { std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
std::string token; std::string token;
std::istringstream tokenStream(s); std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) { while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token); tokens.push_back(token);
} }
return tokens; return tokens;
} }
namespace ZL { namespace ZL {
void WebSocketClient::Connect(const std::string& host, uint16_t port) { void WebSocketClient::Connect(const std::string& host, uint16_t port) {
try { try {
boost::asio::ip::tcp::resolver resolver(ioc_); boost::asio::ip::tcp::resolver resolver(ioc_);
auto const results = resolver.resolve(host, std::to_string(port)); auto const results = resolver.resolve(host, std::to_string(port));
ws_ = std::make_unique<boost::beast::websocket::stream<boost::beast::tcp_stream>>(ioc_); ws_ = std::make_unique<boost::beast::websocket::stream<boost::beast::tcp_stream>>(ioc_);
// Выполняем синхронный коннект и handshake для простоты старта // Выполняем синхронный коннект и handshake для простоты старта
boost::beast::get_lowest_layer(*ws_).connect(results); boost::beast::get_lowest_layer(*ws_).connect(results);
ws_->handshake(host, "/"); ws_->handshake(host, "/");
connected = true; connected = true;
// Запускаем асинхронное чтение в пуле потоков TaskManager // Запускаем асинхронное чтение в пуле потоков TaskManager
startAsyncRead(); startAsyncRead();
} }
catch (std::exception& e) { catch (std::exception& e) {
std::cerr << "Network Error: " << e.what() << std::endl; std::cerr << "Network Error: " << e.what() << std::endl;
} }
} }
void WebSocketClient::startAsyncRead() { void WebSocketClient::startAsyncRead() {
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
if (!ec) { if (!ec) {
std::string msg = boost::beast::buffers_to_string(buffer_.data()); std::string msg = boost::beast::buffers_to_string(buffer_.data());
buffer_.consume(bytes); buffer_.consume(bytes);
processIncomingMessage(msg); processIncomingMessage(msg);
startAsyncRead(); startAsyncRead();
} }
else { else {
connected = false; connected = false;
} }
}); });
} }
void WebSocketClient::processIncomingMessage(const std::string& msg) { void WebSocketClient::processIncomingMessage(const std::string& msg) {
// Логика парсинга... // Логика парсинга...
if (msg.rfind("ID:", 0) == 0) { if (msg.rfind("ID:", 0) == 0) {
clientId = std::stoi(msg.substr(3)); clientId = std::stoi(msg.substr(3));
} }
// Безопасно кладем в очередь для главного потока // Безопасно кладем в очередь для главного потока
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
messageQueue.push(msg); messageQueue.push(msg);
} }
void WebSocketClient::Poll() { std::vector<ProjectileInfo> WebSocketClient::getPendingProjectiles() {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(projMutex_);
auto copy = pendingProjectiles_;
pendingProjectiles_.clear();
return copy;
}
while (!messageQueue.empty()) { void WebSocketClient::Poll() {
std::lock_guard<std::mutex> lock(queueMutex);
auto nowTime = std::chrono::system_clock::now(); while (!messageQueue.empty()) {
//Apply server delay: auto nowTime = std::chrono::system_clock::now();
nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( //Apply server delay:
nowTime.time_since_epoch() nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
).count();
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
nowTime.time_since_epoch()
).count();
std::string msg = messageQueue.front(); std::string msg = messageQueue.front();
messageQueue.pop(); messageQueue.pop();
if (msg.rfind("EVENT:", 0) == 0) { // Обработка списка коробок от сервера
auto parts = split(msg, ':'); if (msg.rfind("BOXES:", 0) == 0) {
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... std::string payload = msg.substr(6); // после "BOXES:"
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> parsedBoxes;
if (!payload.empty()) {
auto items = split(payload, '|');
for (auto& item : items) {
if (item.empty()) continue;
auto parts = split(item, ':');
if (parts.size() < 7) continue;
try {
float px = std::stof(parts[0]);
float py = std::stof(parts[1]);
float pz = std::stof(parts[2]);
Eigen::Quaternionf q(
std::stof(parts[3]),
std::stof(parts[4]),
std::stof(parts[5]),
std::stof(parts[6])
);
Eigen::Matrix3f rot = q.toRotationMatrix();
parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot);
}
catch (...) {
// пропускаем некорректную запись
continue;
}
}
}
{
std::lock_guard<std::mutex> bLock(boxesMutex);
serverBoxes_ = std::move(parsedBoxes);
}
continue;
}
int remoteId = std::stoi(parts[1]); if (msg.rfind("PROJECTILE:", 0) == 0) {
std::string subType = parts[2]; auto parts = split(msg, ':');
uint64_t sentTime = std::stoull(parts[3]); if (parts.size() >= 9) {
try {
ProjectileInfo pi;
pi.shooterId = std::stoi(parts[1]);
pi.clientTime = std::stoull(parts[2]);
pi.position = Eigen::Vector3f(
std::stof(parts[3]),
std::stof(parts[4]),
std::stof(parts[5])
);
pi.direction = Eigen::Vector3f(
std::stof(parts[6]),
std::stof(parts[7]),
std::stof(parts[8])
);
std::lock_guard<std::mutex> pl(projMutex_);
pendingProjectiles_.push_back(pi);
}
catch (...) {
}
}
continue;
}
ClientState remoteState; if (msg.rfind("EVENT:", 0) == 0) {
auto parts = split(msg, ':');
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
int remoteId = std::stoi(parts[1]);
std::string subType = parts[2];
uint64_t sentTime = std::stoull(parts[3]);
ClientState remoteState;
remoteState.id = remoteId; remoteState.id = remoteId;
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(sentTime)) }; std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(sentTime)) };
remoteState.lastUpdateServerTime = uptime_timepoint; remoteState.lastUpdateServerTime = uptime_timepoint;
if (subType == "UPD") {
int startFrom = 4;
remoteState.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]));
remoteState.rotation = q.toRotationMatrix();
remoteState.currentAngularVelocity = Eigen::Vector3f{ if (subType == "UPD") {
std::stof(parts[startFrom + 7]), int startFrom = 4;
std::stof(parts[startFrom + 8]), remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
std::stof(parts[startFrom + 9]) }; Eigen::Quaternionf q(
remoteState.velocity = std::stof(parts[startFrom + 10]); std::stof(parts[startFrom + 3]),
remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); std::stof(parts[startFrom + 4]),
remoteState.discreteMag = std::stof(parts[startFrom + 12]); std::stof(parts[startFrom + 5]),
remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); std::stof(parts[startFrom + 6]));
} remoteState.rotation = q.toRotationMatrix();
else
{ remoteState.currentAngularVelocity = Eigen::Vector3f{
std::stof(parts[startFrom + 7]),
std::stof(parts[startFrom + 8]),
std::stof(parts[startFrom + 9]) };
remoteState.velocity = std::stof(parts[startFrom + 10]);
remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]);
remoteState.discreteMag = std::stof(parts[startFrom + 12]);
remoteState.discreteAngle = std::stoi(parts[startFrom + 13]);
}
else
{
throw std::runtime_error("Unknown EVENT subtype: " + subType); throw std::runtime_error("Unknown EVENT subtype: " + subType);
} }
{ {
std::lock_guard<std::mutex> pLock(playersMutex); std::lock_guard<std::mutex> pLock(playersMutex);
auto& rp = remotePlayers[remoteId]; auto& rp = remotePlayers[remoteId];
rp.add_state(remoteState); rp.add_state(remoteState);
} }
} }
} }
} }
void WebSocketClient::Send(const std::string& message) { void WebSocketClient::Send(const std::string& message) {
if (!connected) return; if (!connected) return;
auto ss = std::make_shared<std::string>(message); auto ss = std::make_shared<std::string>(message);
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.push(ss); writeQueue_.push(ss);
// Если сейчас ничего не записывается, инициируем первую запись // Если сейчас ничего не записывается, инициируем первую запись
if (!isWriting_) { if (!isWriting_) {
doWrite(); doWrite();
} }
} }
void WebSocketClient::doWrite() { void WebSocketClient::doWrite() {
// Эта функция всегда вызывается под мьютексом или из колбэка // Эта функция всегда вызывается под мьютексом или из колбэка
if (writeQueue_.empty()) { if (writeQueue_.empty()) {
isWriting_ = false; isWriting_ = false;
return; return;
} }
isWriting_ = true; isWriting_ = true;
auto message = writeQueue_.front(); auto message = writeQueue_.front();
// Захватываем self (shared_from_this), чтобы объект не удалился во время записи // Захватываем self (shared_from_this), чтобы объект не удалился во время записи
ws_->async_write( ws_->async_write(
boost::asio::buffer(*message), boost::asio::buffer(*message),
[this, message](boost::beast::error_code ec, std::size_t) { [this, message](boost::beast::error_code ec, std::size_t) {
if (ec) { if (ec) {
connected = false; connected = false;
return; return;
} }
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.pop(); // Удаляем отправленное сообщение writeQueue_.pop(); // Удаляем отправленное сообщение
doWrite(); // Проверяем следующее doWrite(); // Проверяем следующее
} }
); );
} }
} }
#endif #endif
+14
View File
@@ -34,6 +34,13 @@ namespace ZL {
std::unordered_map<int, ClientStateInterval> remotePlayers; std::unordered_map<int, ClientStateInterval> remotePlayers;
std::mutex playersMutex; std::mutex playersMutex;
// Серверные коробки
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
std::mutex boxesMutex;
std::vector<ProjectileInfo> pendingProjectiles_;
std::mutex projMutex_;
void startAsyncRead(); void startAsyncRead();
void processIncomingMessage(const std::string& msg); void processIncomingMessage(const std::string& msg);
@@ -54,6 +61,13 @@ namespace ZL {
std::lock_guard<std::mutex> lock(playersMutex); std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers; return remotePlayers;
} }
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
std::lock_guard<std::mutex> lock(boxesMutex);
return serverBoxes_;
}
std::vector<ProjectileInfo> getPendingProjectiles() override;
}; };
} }
#endif #endif
+28 -7
View File
@@ -6,6 +6,7 @@
namespace ZL { namespace ZL {
Matrix3f GetRotationForTriangle(const Triangle& tri);
const float PlanetData::PLANET_RADIUS = 20000.f; const float PlanetData::PLANET_RADIUS = 20000.f;
const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f };
@@ -29,9 +30,9 @@ namespace ZL {
PlanetData::PlanetData() PlanetData::PlanetData()
: perlin(77777) : perlin(77777)
, colorPerlin(123123) , colorPerlin(123123)
, currentLod(0) //, currentLod(0)
{ {
currentLod = planetMeshLods.size() - 1; // Start with max LOD // currentLod = planetMeshLods.size() - 1; // Start with max LOD
/* /*
initialVertexMap = { initialVertexMap = {
{{ 0.0f, 1.0f, 0.0f}, "A"}, {{ 0.0f, 1.0f, 0.0f}, "A"},
@@ -54,10 +55,29 @@ namespace ZL {
planetAtmosphereLod = generateSphere(5, 0); planetAtmosphereLod = generateSphere(5, 0);
planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03); planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
planetAtmosphereLod.Move(PLANET_CENTER_OFFSET); planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);
const auto& lodLevel = getLodLevel();
for (size_t i = 0; i < lodLevel.triangles.size(); i++)
{
if (i % 100 == 0)
{
PlanetCampObject campObject;
campObject.position = (lodLevel.triangles[i].data[0] +
lodLevel.triangles[i].data[1] +
lodLevel.triangles[i].data[2]) / 3.0f;
auto newM = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitX())).toRotationMatrix();
campObject.rotation = GetRotationForTriangle(lodLevel.triangles[i]).inverse() * newM;
campObjects.push_back(campObject);
}
}
} }
const LodLevel& PlanetData::getLodLevel(int level) const { const LodLevel& PlanetData::getLodLevel() const {
return planetMeshLods.at(level); return planetMeshLods.at(MAX_LOD_LEVELS-1);
} }
@@ -65,13 +85,14 @@ namespace ZL {
return planetAtmosphereLod; return planetAtmosphereLod;
} }
/*
int PlanetData::getCurrentLodIndex() const { int PlanetData::getCurrentLodIndex() const {
return currentLod; return currentLod;
} }
int PlanetData::getMaxLodIndex() const { int PlanetData::getMaxLodIndex() const {
return static_cast<int>(planetMeshLods.size() - 1); return static_cast<int>(planetMeshLods.size() - 1);
} }*/
std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) { std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) {
@@ -138,7 +159,7 @@ namespace ZL {
std::vector<int> PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) { std::vector<int> PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) {
const LodLevel& finalLod = planetMeshLods[currentLod]; // Работаем с текущим активным LOD const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1]; // Работаем с текущим активным LOD
Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized(); Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized();
int bestTriangle = -1; int bestTriangle = -1;
@@ -167,7 +188,7 @@ namespace ZL {
} }
std::vector<int> PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) { std::vector<int> PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) {
const LodLevel& finalLod = planetMeshLods[currentLod]; const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1];
Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET; Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET;
float currentDist = shipLocal.norm(); float currentDist = shipLocal.norm();
Vector3f targetDir = shipLocal.normalized(); Vector3f targetDir = shipLocal.normalized();
+19 -6
View File
@@ -75,6 +75,20 @@ namespace ZL {
} }
}; };
struct PlanetCampObject
{
Vector3f position;
Matrix3f rotation;
std::array<Vector3f, 5> platformPos = {
Vector3f{ 0.f, 0.f,-38.f },
Vector3f{ 20.f, 0.f,-18.f },
Vector3f{ 20.f, 0.f,-58.f },
Vector3f{ -20.f, 0.f,-58.f },
Vector3f{ -20.f, 0.f,-18.f }
};
};
class PlanetData { class PlanetData {
public: public:
static const float PLANET_RADIUS; static const float PLANET_RADIUS;
@@ -87,7 +101,7 @@ namespace ZL {
std::array<LodLevel, MAX_LOD_LEVELS> planetMeshLods; std::array<LodLevel, MAX_LOD_LEVELS> planetMeshLods;
LodLevel planetAtmosphereLod; LodLevel planetAtmosphereLod;
int currentLod; // Логический текущий уровень детализации //int currentLod; // Логический текущий уровень детализации
//std::map<Vector3f, VertexID, Vector3fComparator> initialVertexMap; //std::map<Vector3f, VertexID, Vector3fComparator> initialVertexMap;
@@ -100,12 +114,8 @@ namespace ZL {
PlanetData(); PlanetData();
void init(); void init();
const LodLevel& getLodLevel() const;
// Методы доступа к данным (для рендерера)
const LodLevel& getLodLevel(int level) const;
const LodLevel& getAtmosphereLod() const; const LodLevel& getAtmosphereLod() const;
int getCurrentLodIndex() const;
int getMaxLodIndex() const;
// Логика // Логика
std::pair<float, float> calculateZRange(float distanceToSurface); std::pair<float, float> calculateZRange(float distanceToSurface);
@@ -116,6 +126,9 @@ namespace ZL {
std::vector<int> getTrianglesUnderCameraNew2(const Vector3f& viewerPosition); std::vector<int> getTrianglesUnderCameraNew2(const Vector3f& viewerPosition);
void applySphericalRelaxation(LodLevel& lod, int iterations); void applySphericalRelaxation(LodLevel& lod, int iterations);
std::vector<PlanetCampObject> campObjects;
}; };
} // namespace ZL } // namespace ZL
+103 -6
View File
@@ -5,9 +5,12 @@
#include "Environment.h" #include "Environment.h"
#include "StoneObject.h" #include "StoneObject.h"
#include "utils/TaskManager.h" #include "utils/TaskManager.h"
#include "TextModel.h"
namespace ZL { namespace ZL {
extern float x;
#if defined EMSCRIPTEN || defined __ANDROID__ #if defined EMSCRIPTEN || defined __ANDROID__
using std::min; using std::min;
using std::max; using std::max;
@@ -70,8 +73,7 @@ namespace ZL {
// 2. Забираем данные для VBO // 2. Забираем данные для VBO
// Берем максимальный LOD для начальной отрисовки // Берем максимальный LOD для начальной отрисовки
int lodIndex = planetData.getMaxLodIndex(); planetRenderStruct.data = planetData.getLodLevel().vertexData;
planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData;
//planetRenderStruct.data.PositionData.resize(9); //planetRenderStruct.data.PositionData.resize(9);
planetRenderStruct.RefreshVBO(); planetRenderStruct.RefreshVBO();
@@ -87,13 +89,20 @@ namespace ZL {
planetAtmosphereRenderStruct.RefreshVBO(); planetAtmosphereRenderStruct.RefreshVBO();
} }
planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); planetStones = CreateStoneGroupData(778, planetData.getLodLevel());
//stonesToRender = planetStones.inflate(planetStones.allInstances.size()); //stonesToRender = planetStones.inflate(planetStones.allInstances.size());
stonesToRender.resize(planetStones.allInstances.size()); stonesToRender.resize(planetStones.allInstances.size());
planetStones.initStatuses(); planetStones.initStatuses();
stoneToBake = planetStones.inflateOne(0, 0.75); stoneToBake = planetStones.inflateOne(0, 0.75);
campPlatform.data = LoadFromTextFile02("resources/platform1.txt", CONST_ZIP_FILE);
campPlatform.RefreshVBO();
campPlatformTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/platform_base.png", CONST_ZIP_FILE));
} }
@@ -187,7 +196,7 @@ namespace ZL {
renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; Triangle tr = planetData.getLodLevel().triangles[0];
// 1. Получаем матрицу вращения (оси в столбцах) // 1. Получаем матрицу вращения (оси в столбцах)
Matrix3f mr = GetRotationForTriangle(tr); Matrix3f mr = GetRotationForTriangle(tr);
@@ -282,6 +291,8 @@ namespace ZL {
drawPlanet(renderer); drawPlanet(renderer);
drawStones(renderer); drawStones(renderer);
drawCamp(renderer);
glClear(GL_DEPTH_BUFFER_BIT);
drawAtmosphere(renderer); drawAtmosphere(renderer);
} }
@@ -328,7 +339,7 @@ namespace ZL {
renderer.RenderUniform1i("BakedTexture", 1); renderer.RenderUniform1i("BakedTexture", 1);
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
// Позиция камеры (корабля) в мире // Позиция камеры (корабля) в мире
@@ -464,7 +475,7 @@ namespace ZL {
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
glClear(GL_DEPTH_BUFFER_BIT);
} }
void PlanetObject::drawAtmosphere(Renderer& renderer) { void PlanetObject::drawAtmosphere(Renderer& renderer) {
@@ -568,6 +579,92 @@ namespace ZL {
} }
void PlanetObject::drawCamp(Renderer& renderer)
{
static const std::string defaultShaderName2 = "default";
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist);
const float currentZNear = zRange.first;
const float currentZFar = zRange.second;
// 2. Применяем динамическую матрицу проекции
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
//std::cout << "uViewPos" << Environment::shipState.position << std::endl;
// PlanetObject.cpp, метод drawStones
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
Vector3f playerDirWorld = Environment::shipState.position.normalized();
float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (int i = 0; i < planetData.campObjects.size(); i++)
{
renderer.PushMatrix();
for (int j = 0; j < 5; j++)
{
renderer.PushMatrix();
renderer.TranslateMatrix(planetData.campObjects[i].position);
renderer.RotateMatrix(planetData.campObjects[i].rotation);
renderer.ScaleMatrix(Vector3f{ 2.0f, 2.0f, 2.0f });
renderer.TranslateMatrix(planetData.campObjects[i].platformPos[j]);
glBindTexture(GL_TEXTURE_2D, campPlatformTexture->getTexID());
renderer.DrawVertexRenderStruct(campPlatform);
renderer.PopMatrix();
}
renderer.PopMatrix();
}
CheckGlError();
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
glClear(GL_DEPTH_BUFFER_BIT);
}
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
{ {
return planetData.distanceToPlanetSurfaceFast(viewerPosition); return planetData.distanceToPlanetSurfaceFast(viewerPosition);
+5
View File
@@ -41,6 +41,10 @@ namespace ZL {
std::unique_ptr<FrameBuffer> stoneMapFB; std::unique_ptr<FrameBuffer> stoneMapFB;
VertexRenderStruct campPlatform;
std::shared_ptr<Texture> campPlatformTexture;
Vector3f lastUpdatePos; Vector3f lastUpdatePos;
// External items, set outside // External items, set outside
@@ -57,6 +61,7 @@ namespace ZL {
void drawStones(Renderer& renderer); void drawStones(Renderer& renderer);
void drawPlanet(Renderer& renderer); void drawPlanet(Renderer& renderer);
void drawAtmosphere(Renderer& renderer); void drawAtmosphere(Renderer& renderer);
void drawCamp(Renderer& renderer);
float distanceToPlanetSurface(const Vector3f& viewerPosition); float distanceToPlanetSurface(const Vector3f& viewerPosition);
}; };