Compare commits

...

11 Commits

Author SHA1 Message Date
Vladislav Khorev
be2aa76f8b Working on server optimization 2026-02-26 22:26:43 +03:00
Vladislav Khorev
e8f1786a2a merge 2026-02-26 21:22:38 +03:00
Vladislav Khorev
e30f8a4c70 merge 2026-02-26 21:10:27 +03:00
Vladislav Khorev
f8642efa23 merge 2026-02-26 21:02:16 +03:00
Vlad
74c2f786a1 added choising spaceship type for multiplayer and some fix in respawn 2026-02-25 19:17:42 +06:00
Vlad
5216965496 added choosing spaceship in singleplay 2026-02-24 20:34:01 +06:00
Vlad
ad7294ceea added random spaceship type 2026-02-24 19:51:33 +06:00
Vlad
3893038d9a Merge remote-tracking branch 'origin/main' into spark 2026-02-23 15:30:01 +06:00
Vlad
cb2e8318e7 added multi_menu 2026-02-23 15:29:05 +06:00
Vladislav Khorev
5cca2413fe changes 2026-02-23 11:46:57 +03:00
Vladislav Khorev
db4254e205 working on web 2026-02-23 11:45:55 +03:00
24 changed files with 792 additions and 404 deletions

View File

@ -3,103 +3,156 @@
"type": "LinearLayout", "type": "LinearLayout",
"x": 0, "x": 0,
"y": 0, "y": 0,
"width": 1920, "width": 1280,
"height": 1080, "height": 720,
"orientation": "vertical",
"spacing": 20,
"children": [ "children": [
{ {
"type": "TextView", "type": "Button",
"name": "titleText", "name": "langButton",
"x": 300, "x": 1100,
"y": 100, "y": 580,
"width": 1320, "width": 142,
"height": 100, "height": 96,
"text": "Multiplayer", "textures": {
"fontPath": "resources/fonts/DroidSans.ttf", "normal": "resources/main_menu/lang.png",
"fontSize": 72, "hover": "resources/main_menu/lang.png",
"color": [1, 1, 1, 1], "pressed": "resources/main_menu/lang.png"
"centered": true }
}, },
{ {
"type": "TextView", "type": "Button",
"name": "serverLabel", "name": "titleBtn",
"x": 400, "x": 512,
"y": 250, "y": 500,
"width": 1120, "width": 254,
"height": 50, "height": 35,
"text": "Enter server name or IP:", "textures": {
"fontPath": "resources/fonts/DroidSans.ttf", "normal": "resources/multiplayer_menu/title.png",
"fontSize": 32, "hover": "resources/multiplayer_menu/title.png",
"color": [1, 1, 1, 1], "pressed": "resources/multiplayer_menu/title.png"
"centered": false }
}, },
{
"type": "Button",
"name": "subtitle",
"x": 596.5,
"y": 470,
"width": 87,
"height": 11,
"textures": {
"normal": "resources/multiplayer_menu/JoinServer.png",
"hover": "resources/multiplayer_menu/JoinServer.png",
"pressed": "resources/multiplayer_menu/JoinServer.png"
}
},
{
"type": "Button",
"name": "subtitleBtn",
"x": 450,
"y": 445,
"width": 94,
"height": 9,
"textures": {
"normal": "resources/multiplayer_menu/ServerName.png",
"hover": "resources/multiplayer_menu/ServerName.png",
"pressed": "resources/multiplayer_menu/ServerName.png"
}
},
{ {
"type": "TextField", "type": "TextField",
"name": "serverInputField", "name": "serverInputField",
"x": 400, "x": 449,
"y": 320, "y": 390,
"width": 1120, "width": 382,
"height": 60, "height": 56,
"placeholder": "Enter server name or IP", "placeholder": "Enter server name or IP",
"fontPath": "resources/fonts/DroidSans.ttf", "fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 28, "fontSize": 16,
"maxLength": 256, "maxLength": 256,
"color": [1, 1, 1, 1], "color": [122, 156, 198, 1],
"placeholderColor": [0.6, 0.6, 0.6, 1], "placeholderColor": [122, 156, 198, 1],
"backgroundColor": [0.15, 0.15, 0.15, 1], "backgroundColor": [15, 29, 51, 1],
"borderColor": [0.7, 0.7, 0.7, 1] "borderColor": [15, 29, 51, 1]
}, },
{ {
"type": "LinearLayout", "type": "Button",
"x": 400, "name": "connectButton",
"y": 450, "x": 449,
"width": 1120, "y": 350,
"height": 80, "width": 382,
"orientation": "horizontal", "height": 56,
"spacing": 30, "textures": {
"children": [ "normal": "resources/multiplayer_menu/Filledbuttons.png",
{ "hover": "resources/multiplayer_menu/Filledbuttons.png",
"type": "Button", "pressed": "resources/multiplayer_menu/Filledbuttons.png"
"name": "connectButton", }
"x": 0, },
"y": 0,
"width": 530,
"height": 80,
"textures": {
"normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png",
"pressed": "resources/main_menu/single.png"
}
},
{ {
"type": "Button", "type": "Button",
"name": "backButton", "name": "backButton",
"x": 590, "x": 449,
"y": 0, "y": 280,
"width": 530, "width": 382,
"height": 80, "height": 56,
"textures": { "textures": {
"normal": "resources/main_menu/exit.png", "normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/main_menu/exit.png", "hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/main_menu/exit.png" "pressed": "resources/multiplayer_menu/Backbutton.png"
}
},
{
"type": "Button",
"name": "AvailableServers",
"x": 450,
"y": 240,
"width": 139,
"height": 9,
"textures": {
"normal": "resources/multiplayer_menu/AvailableServers.png",
"hover": "resources/multiplayer_menu/AvailableServers.png",
"pressed": "resources/multiplayer_menu/AvailableServers.png"
}
},
{
"type": "Button",
"name": "SerButton",
"x": 436.5,
"y": 170,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button.png",
"hover": "resources/multiplayer_menu/Button.png",
"pressed": "resources/multiplayer_menu/Button.png"
}
},
{
"type": "Button",
"name": "SerButton2",
"x": 436.5,
"y": 88,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button2.png",
"hover": "resources/multiplayer_menu/Button2.png",
"pressed": "resources/multiplayer_menu/Button2.png"
}
},
{
"type": "Button",
"name": "SerButton3",
"x": 436.5,
"y": 6,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button3.png",
"hover": "resources/multiplayer_menu/Button3.png",
"pressed": "resources/multiplayer_menu/Button3.png"
}
} }
}
] ]
},
{
"type": "TextView",
"name": "statusText",
"x": 400,
"y": 580,
"width": 1120,
"height": 50,
"text": "Ready to connect",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 24,
"color": [0.8, 0.8, 0.8, 1],
"centered": false
}
]
} }
} }

View File

@ -0,0 +1,64 @@
{
"root": {
"name": "shipSelectionRoot",
"type": "node",
"children": [
{
"type": "TextField",
"name": "nicknameInput",
"x": 400,
"y": 150,
"width": 400,
"height": 50,
"placeholder": "Enter your nickname",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 16,
"maxLength": 256,
"color": [122, 156, 198, 1],
"placeholderColor": [122, 156, 198, 1],
"backgroundColor": [15, 29, 51, 1],
"borderColor": [15, 29, 51, 1]
},
{
"type": "Button",
"name": "spaceshipButton",
"x": 300,
"y": 320,
"width": 200,
"height": 80,
"textures": {
"normal": "resources/multiplayer_menu/JoinServer.png",
"hover": "resources/multiplayer_menu/JoinServer.png",
"pressed": "resources/multiplayer_menu/JoinServer.png"
}
},
{
"type": "Button",
"name": "cargoshipButton",
"x": 700,
"y": 320,
"width": 200,
"height": 80,
"textures": {
"normal": "resources/multiplayer_menu/JoinServer.png",
"hover": "resources/multiplayer_menu/JoinServer.png",
"pressed": "resources/multiplayer_menu/JoinServer.png"
}
},
{
"type": "Button",
"name": "backButton",
"x": 449,
"y": 280,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/multiplayer_menu/Backbutton.png"
}
}
]
}
}

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -93,6 +93,9 @@ class Session : public std::enable_shared_from_this<Session> {
public: public:
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
std::string nickname = "Player";
int shipType = 0;
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) {
} }
@ -205,7 +208,7 @@ public:
return latest; return latest;
} }
void doWrite() { void doWrite() {
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
if (is_writing_ || writeQueue_.empty()) { if (is_writing_ || writeQueue_.empty()) {
@ -253,7 +256,6 @@ private:
void process_message(const std::string& msg) { void process_message(const std::string& msg) {
if (!IsMessageValid(msg)) { if (!IsMessageValid(msg)) {
// Логируем попытку подмены и просто выходим из обработки
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return; return;
} }
@ -266,7 +268,40 @@ private:
std::string type = parts[0]; std::string type = parts[0];
if (type == "UPD") { if (type == "JOIN") {
std::string nick = "Player";
int sType = 0;
if (parts.size() >= 2) nick = parts[1];
if (parts.size() >= 3) {
try { sType = std::stoi(parts[2]); }
catch (...) { sType = 0; }
}
this->nickname = nick;
this->shipType = sType;
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
session->send_message(info);
}
}
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
// Отправляем именно новому клиенту
this->send_message(otherInfo);
}
}
}
else if (type == "UPD") {
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(id_) != g_dead_players.end()) { if (g_dead_players.find(id_) != g_dead_players.end()) {
@ -284,20 +319,47 @@ private:
}; };
receivedState.lastUpdateServerTime = uptime_timepoint; receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2); receivedState.handle_full_sync(parts, 2);
receivedState.nickname = this->nickname;
receivedState.shipType = this->shipType;
timedClientStates.add_state(receivedState); timedClientStates.add_state(receivedState);
retranslateMessage(cleanMessage);
} }
else if (parts[0] == "RESPAWN") { else if (type == "RESPAWN") {
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
g_dead_players.erase(id_); g_dead_players.erase(id_);
} }
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); {
broadcastToAll(respawnMsg); auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
std::cout << "Server: Player " << id_ << " respawned\n"; ClientState st;
st.id = id_;
st.position = Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
broadcastToAll(respawnMsg);
std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
broadcastToAll(playerInfo);
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
broadcastToAll(eventMsg);
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
}
} }
else if (parts[0] == "FIRE") { else if (parts[0] == "FIRE") {
if (parts.size() < 10) return; if (parts.size() < 10) return;
@ -344,8 +406,8 @@ private:
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f) Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
}; };
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch()).count(); std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex); std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) { for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {
@ -370,16 +432,6 @@ private:
} }
} }
void retranslateMessage(const std::string& 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 broadcastToAll(const std::string& message) { void broadcastToAll(const std::string& message) {
@ -391,273 +443,257 @@ void broadcastToAll(const std::string& message) {
void update_world(net::steady_timer& timer, net::io_context& ioc) { void update_world(net::steady_timer& timer, net::io_context& ioc) {
static auto last_snapshot_time = std::chrono::steady_clock::now(); static auto last_snapshot_time = std::chrono::system_clock::now();
auto now = std::chrono::steady_clock::now();
/*static uint64_t lastTickCount = 0;
if (lastTickCount == 0) { auto now = std::chrono::system_clock::now();
//lastTickCount = SDL_GetTicks64(); uint64_t now_ms = static_cast<uint64_t>(
lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
std::chrono::system_clock::now().time_since_epoch()
).count();
lastTickCount = (lastTickCount / 50) * 50; // --- Snapshot every 500ms ---
/*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) {
return;
}
auto newTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
newTickCount = (newTickCount / 50) * 50;
int64_t deltaMs = static_cast<int64_t>(newTickCount - lastTickCount);
std::chrono::system_clock::time_point nowRounded = std::chrono::system_clock::time_point(std::chrono::milliseconds(newTickCount));
*/
// For each player
// Get letest state + add time (until newTickCount)
// Calculate if collisions with boxes
// Рассылка Snapshot раз в 1000мс
/*
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 1000) {
last_snapshot_time = now; last_snapshot_time = now;
auto system_now = std::chrono::system_clock::now(); std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms);
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(
system_now.time_since_epoch()).count()
);
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
// Формируем общую строку состояний всех игроков
for (auto& session : g_sessions) { for (auto& session : g_sessions) {
ClientState st = session->get_latest_state(system_now); ClientState st = session->get_latest_state(now);
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
} }
for (auto& session : g_sessions) { for (auto& session : g_sessions) {
session->send_message(snapshot_msg); session->send_message(snapshot_msg);
} }
}*/ }*/
const std::chrono::milliseconds interval(50); // --- Tick: broadcast each player's latest state to all others (20Hz) ---
timer.expires_after(interval); // Send the raw last-known state with its original timestamp, NOT an extrapolated one.
// Extrapolating here causes snap-back: if a player stops rotating, the server would
// keep sending over-rotated positions until the new state arrives, then B snaps back.
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& sender : g_sessions) {
if (sender->timedClientStates.timedStates.empty()) continue;
timer.async_wait([&](const boost::system::error_code& ec) { const ClientState& st = sender->timedClientStates.timedStates.back();
if (ec) return; uint64_t stateTime = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
st.lastUpdateServerTime.time_since_epoch()).count());
auto now = std::chrono::system_clock::now(); std::string event_msg = "EVENT:" + std::to_string(sender->get_id()) +
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); ":UPD:" + std::to_string(stateTime) + ":" + st.formPingMessageContent();
std::vector<DeathInfo> deathEvents; for (auto& receiver : g_sessions) {
if (receiver->get_id() != sender->get_id()) {
{ receiver->send_message(event_msg);
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
std::vector<int> indicesToRemove;
float dt = 50.0f / 1000.0f;
for (size_t i = 0; i < g_projectiles.size(); ++i) {
auto& pr = g_projectiles[i];
pr.pos += pr.vel * dt;
if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
indicesToRemove.push_back(static_cast<int>(i));
continue;
}
bool hitDetected = false;
{
std::lock_guard<std::mutex> lm(g_sessions_mutex);
std::lock_guard<std::mutex> gd(g_dead_mutex);
for (auto& session : g_sessions) {
int targetId = session->get_id();
if (targetId == pr.shooterId) continue;
if (g_dead_players.find(targetId) != g_dead_players.end()) continue;
ClientState targetState;
if (!session->fetchStateAtTime(now, targetState)) continue;
Eigen::Vector3f diff = pr.pos - targetState.position;
const float shipRadius = 15.0f;
const float projectileRadius = 1.5f;
float combinedRadius = shipRadius + projectileRadius;
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
DeathInfo death;
death.targetId = targetId;
death.serverTime = now_ms;
death.position = pr.pos;
death.killerId = pr.shooterId;
deathEvents.push_back(death);
g_dead_players.insert(targetId);
indicesToRemove.push_back(static_cast<int>(i));
hitDetected = true;
std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
std::cout << "Server: Projectile at ("
<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
<< ") hit player " << targetId << std::endl;
break;
}
}
}
if (hitDetected) continue;
}
if (!indicesToRemove.empty()) {
std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
for (int idx : indicesToRemove) {
if (idx >= 0 && idx < (int)g_projectiles.size()) {
g_projectiles.erase(g_projectiles.begin() + idx);
}
} }
} }
} }
}
{ // --- Tick: projectile movement and hit detection ---
std::lock_guard<std::mutex> bm(g_boxes_mutex); const float dt = 50.0f / 1000.0f;
//const float projectileHitRadius = 1.5f; std::vector<DeathInfo> deathEvents;
const float projectileHitRadius = 5.0f;
const float boxCollisionRadius = 2.0f;
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions; {
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
std::vector<int> indicesToRemove;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { for (size_t i = 0; i < g_projectiles.size(); ++i) {
if (g_serverBoxes[bi].destroyed) continue; auto& pr = g_projectiles[i];
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); pr.pos += pr.vel * dt;
for (size_t pi = 0; pi < g_projectiles.size(); ++pi) { if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
const auto& pr = g_projectiles[pi]; indicesToRemove.push_back(static_cast<int>(i));
Eigen::Vector3f diff = pr.pos - boxWorld; continue;
//std::cout << "diff norm is " << diff.norm() << std::endl;
float thresh = boxCollisionRadius + projectileHitRadius;
if (diff.squaredNorm() <= thresh * thresh) {
boxProjectileCollisions.push_back({ bi, pi });
}
}
} }
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { bool hitDetected = false;
g_serverBoxes[boxIdx].destroyed = true;
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); {
std::lock_guard<std::mutex> lm(g_sessions_mutex);
BoxDestroyedInfo destruction; std::lock_guard<std::mutex> gd(g_dead_mutex);
destruction.boxIndex = static_cast<int>(boxIdx);
destruction.serverTime = now_ms;
destruction.position = boxWorld;
destruction.destroyedBy = g_projectiles[projIdx].shooterId;
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
g_boxDestructions.push_back(destruction);
}
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
<< g_projectiles[projIdx].shooterId << std::endl;
}
}
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
std::lock_guard<std::mutex> lm(g_sessions_mutex);
const float shipCollisionRadius = 15.0f;
const float boxCollisionRadius = 2.0f;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue;
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
for (auto& session : g_sessions) { for (auto& session : g_sessions) {
{ int targetId = session->get_id();
std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
continue;
}
}
ClientState shipState; if (targetId == pr.shooterId) continue;
if (!session->fetchStateAtTime(now, shipState)) continue; if (g_dead_players.find(targetId) != g_dead_players.end()) continue;
Eigen::Vector3f diff = shipState.position - boxWorld; ClientState targetState;
float thresh = shipCollisionRadius + boxCollisionRadius; if (!session->fetchStateAtTime(now, targetState)) continue;
if (diff.squaredNorm() <= thresh * thresh) { Eigen::Vector3f diff = pr.pos - targetState.position;
g_serverBoxes[bi].destroyed = true; const float shipRadius = 15.0f;
const float projectileRadius = 1.5f;
float combinedRadius = shipRadius + projectileRadius;
BoxDestroyedInfo destruction; if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
destruction.boxIndex = static_cast<int>(bi); DeathInfo death;
destruction.serverTime = now_ms; death.targetId = targetId;
destruction.position = boxWorld; death.serverTime = now_ms;
destruction.destroyedBy = session->get_id(); death.position = pr.pos;
death.killerId = pr.shooterId;
{ deathEvents.push_back(death);
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex); g_dead_players.insert(targetId);
g_boxDestructions.push_back(destruction); indicesToRemove.push_back(static_cast<int>(i));
} hitDetected = true;
std::cout << "Server: Box " << bi << " destroyed by ship collision with player " std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
<< session->get_id() << std::endl; std::cout << "Server: Projectile at ("
<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
<< ") hit player " << targetId << std::endl;
break; break;
} }
} }
} }
if (hitDetected) continue;
} }
if (!deathEvents.empty()) { if (!indicesToRemove.empty()) {
for (const auto& death : deathEvents) { std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
std::string deadMsg = "DEAD:" + for (int idx : indicesToRemove) {
std::to_string(death.serverTime) + ":" + if (idx >= 0 && idx < (int)g_projectiles.size()) {
std::to_string(death.targetId) + ":" + g_projectiles.erase(g_projectiles.begin() + idx);
std::to_string(death.position.x()) + ":" + }
std::to_string(death.position.y()) + ":" + }
std::to_string(death.position.z()) + ":" + }
std::to_string(death.killerId); }
broadcastToAll(deadMsg); // --- Tick: box-projectile collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
const float projectileHitRadius = 5.0f;
const float boxCollisionRadius = 2.0f;
std::cout << "Server: Sent DEAD event - Player " << death.targetId std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
<< " killed by " << death.killerId << std::endl;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue;
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
for (size_t pi = 0; pi < g_projectiles.size(); ++pi) {
const auto& pr = g_projectiles[pi];
Eigen::Vector3f diff = pr.pos - boxWorld;
float thresh = boxCollisionRadius + projectileHitRadius;
if (diff.squaredNorm() <= thresh * thresh) {
boxProjectileCollisions.push_back({ bi, pi });
}
} }
} }
{ for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex); g_serverBoxes[boxIdx].destroyed = true;
for (const auto& destruction : g_boxDestructions) {
std::string boxMsg = "BOX_DESTROYED:" +
std::to_string(destruction.boxIndex) + ":" +
std::to_string(destruction.serverTime) + ":" +
std::to_string(destruction.position.x()) + ":" +
std::to_string(destruction.position.y()) + ":" +
std::to_string(destruction.position.z()) + ":" +
std::to_string(destruction.destroyedBy);
broadcastToAll(boxMsg); Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
BoxDestroyedInfo destruction;
destruction.boxIndex = static_cast<int>(boxIdx);
destruction.serverTime = now_ms;
destruction.position = boxWorld;
destruction.destroyedBy = g_projectiles[projIdx].shooterId;
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
g_boxDestructions.push_back(destruction);
} }
g_boxDestructions.clear();
}
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
<< g_projectiles[projIdx].shooterId << std::endl;
}
}
// --- Tick: box-ship collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
std::lock_guard<std::mutex> lm(g_sessions_mutex);
const float shipCollisionRadius = 15.0f;
const float boxCollisionRadius = 2.0f;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue;
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
for (auto& session : g_sessions) {
{
std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
continue;
}
}
ClientState shipState;
if (!session->fetchStateAtTime(now, shipState)) continue;
Eigen::Vector3f diff = shipState.position - boxWorld;
float thresh = shipCollisionRadius + boxCollisionRadius;
if (diff.squaredNorm() <= thresh * thresh) {
g_serverBoxes[bi].destroyed = true;
BoxDestroyedInfo destruction;
destruction.boxIndex = static_cast<int>(bi);
destruction.serverTime = now_ms;
destruction.position = boxWorld;
destruction.destroyedBy = session->get_id();
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
g_boxDestructions.push_back(destruction);
}
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
<< session->get_id() << std::endl;
break;
}
}
}
}
// --- Broadcast deaths ---
for (const auto& death : deathEvents) {
std::string deadMsg = "DEAD:" +
std::to_string(death.serverTime) + ":" +
std::to_string(death.targetId) + ":" +
std::to_string(death.position.x()) + ":" +
std::to_string(death.position.y()) + ":" +
std::to_string(death.position.z()) + ":" +
std::to_string(death.killerId);
broadcastToAll(deadMsg);
std::cout << "Server: Sent DEAD event - Player " << death.targetId
<< " killed by " << death.killerId << std::endl;
}
// --- Broadcast box destructions ---
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
for (const auto& destruction : g_boxDestructions) {
std::string boxMsg = "BOX_DESTROYED:" +
std::to_string(destruction.boxIndex) + ":" +
std::to_string(destruction.serverTime) + ":" +
std::to_string(destruction.position.x()) + ":" +
std::to_string(destruction.position.y()) + ":" +
std::to_string(destruction.position.z()) + ":" +
std::to_string(destruction.destroyedBy);
broadcastToAll(boxMsg);
std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
}
g_boxDestructions.clear();
}
// --- Schedule next tick in 50ms ---
timer.expires_after(std::chrono::milliseconds(50));
timer.async_wait([&timer, &ioc](const boost::system::error_code& ec) {
if (ec) return;
update_world(timer, ioc); update_world(timer, ioc);
}); });
} }

View File

@ -24,6 +24,7 @@
#endif #endif
#include "network/LocalClient.h" #include "network/LocalClient.h"
#include "network/ClientState.h"
namespace ZL namespace ZL
@ -103,14 +104,30 @@ namespace ZL
menuManager.setupMenu(); menuManager.setupMenu();
menuManager.onSingleplayerPressed = [this]() { menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>(); networkClient = std::make_unique<LocalClient>();
networkClient->Connect("", 0); networkClient->Connect("", 0);
#ifndef NETWORK
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
if (localClient) {
ZL::ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
}
#endif
lastTickCount = 0;
spaceGameStarted = 1; spaceGameStarted = 1;
}; };
menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType;
menuManager.onMultiplayerPressed = [this]() { networkClient = std::make_unique<LocalClient>();
#ifdef NETWORK #ifdef NETWORK
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
@ -119,7 +136,26 @@ namespace ZL
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext()); networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
#endif #endif
#else
networkClient->Connect("", 0);
#endif #endif
#ifndef NETWORK
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
if (localClient) {
ZL::ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
}
#endif
if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
networkClient->Send(joinMsg);
std::cerr << "Sent JOIN: " << joinMsg << std::endl;
}
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1; spaceGameStarted = 1;
}; };

View File

@ -21,6 +21,7 @@ namespace ZL {
gameOverSavedRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE); gameOverSavedRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE);
auto shipSelectionRoot = loadUiFromFile("resources/config/ship_selection_menu.json", renderer, CONST_ZIP_FILE);
std::function<void()> loadGameplayUI; std::function<void()> loadGameplayUI;
loadGameplayUI = [this]() { loadGameplayUI = [this]() {
uiManager.replaceRoot(uiSavedRoot); uiManager.replaceRoot(uiSavedRoot);
@ -103,10 +104,10 @@ 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 > 2) if (newVel > 2)
{ {
newVel = 2; newVel = 2;
}*/ }
if (newVel != Environment::shipState.selectedVelocity) { if (newVel != Environment::shipState.selectedVelocity) {
onVelocityChanged(newVel); onVelocityChanged(newVel);
@ -114,15 +115,72 @@ namespace ZL {
}); });
}; };
uiManager.setButtonCallback("singleButton", [loadGameplayUI, this](const std::string& name) { uiManager.setButtonCallback("singleButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n"; std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n";
loadGameplayUI(); if (!shipSelectionRoot) {
onSingleplayerPressed(); std::cerr << "Failed to load ship selection UI\n";
return;
}
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) {
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) {
uiManager.popMenu();
});
}
else {
std::cerr << "Failed to push ship selection menu\n";
}
}); });
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI, this](const std::string& name) {
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n"; uiManager.setButtonCallback("multiplayerButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
loadGameplayUI(); std::cerr << "Multiplayer button pressed: " << name << " -> open ship selection UI\n";
onMultiplayerPressed(); if (!shipSelectionRoot) {
std::cerr << "Failed to load ship selection UI\n";
return;
}
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) {
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI();
if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) {
uiManager.popMenu();
});
}
else {
std::cerr << "Failed to push ship selection menu\n";
}
}); });
uiManager.setButtonCallback("multiplayerButton2", [this](const std::string& name) { uiManager.setButtonCallback("multiplayerButton2", [this](const std::string& name) {
@ -135,7 +193,6 @@ namespace ZL {
if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) { if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) {
// Callback для кнопки подключения
uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) { uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) {
std::string serverAddress = uiManager.getTextFieldValue("serverInputField"); std::string serverAddress = uiManager.getTextFieldValue("serverInputField");
@ -147,16 +204,12 @@ namespace ZL {
uiManager.setText("statusText", "Connecting to " + serverAddress + "..."); uiManager.setText("statusText", "Connecting to " + serverAddress + "...");
std::cerr << "Connecting to server: " << serverAddress << std::endl; std::cerr << "Connecting to server: " << serverAddress << std::endl;
// Здесь добавить вашу логику подключения к серверу
// connectToServer(serverAddress);
}); });
// Callback для кнопки назад
uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) { uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) {
uiManager.popMenu(); uiManager.popMenu();
}); });
// Callback для отслеживания ввода текста
uiManager.setTextFieldCallback("serverInputField", uiManager.setTextFieldCallback("serverInputField",
[this](const std::string& fieldName, const std::string& newText) { [this](const std::string& fieldName, const std::string& newText) {
std::cout << "Server input field changed to: " << newText << std::endl; std::cout << "Server input field changed to: " << newText << std::endl;

View File

@ -34,8 +34,8 @@ namespace ZL {
std::function<void(float)> onVelocityChanged; std::function<void(float)> onVelocityChanged;
std::function<void()> onFirePressed; std::function<void()> onFirePressed;
std::function<void()> onSingleplayerPressed; std::function<void(const std::string&, int)> onSingleplayerPressed;
std::function<void()> onMultiplayerPressed; std::function<void(const std::string&, int)> onMultiplayerPressed;
}; };
}; };

View File

@ -267,6 +267,16 @@ namespace ZL
Environment::zoom = DEFAULT_ZOOM; Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false; Environment::tapDownHold = false;
if (networkClient) {
try {
networkClient->Send(std::string("RESPAWN"));
std::cout << "Client: Sent RESPAWN to server\n";
}
catch (...) {
std::cerr << "Client: Failed to send RESPAWN\n";
}
}
std::cerr << "Game restarted\n"; std::cerr << "Game restarted\n";
}; };
@ -310,6 +320,21 @@ namespace ZL
spaceship.AssignFrom(spaceshipBase); spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO(); spaceship.RefreshVBO();
// Load cargo
cargoTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/Cargo_Base_color_sRGB.png", CONST_ZIP_FILE));
cargoBase = LoadFromTextFile02("resources/cargoship001.txt", CONST_ZIP_FILE);
auto quat = Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitZ()));
auto rotMatrix = quat.toRotationMatrix();
cargoBase.RotateByMatrix(rotMatrix);
auto quat2 = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY()));
auto rotMatrix2 = quat2.toRotationMatrix();
cargoBase.RotateByMatrix(rotMatrix2);
//cargoBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());
cargoBase.Move(Vector3f{ 1.2, 0, -5 });
cargo.AssignFrom(cargoBase);
cargo.RefreshVBO();
//Boxes //Boxes
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE)); boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
@ -452,8 +477,14 @@ namespace ZL
renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
if (shipAlive) { if (shipAlive) {
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); if (Environment::shipState.shipType == 1 && cargoTexture) {
renderer.DrawVertexRenderStruct(spaceship); glBindTexture(GL_TEXTURE_2D, cargoTexture->getTexID());
renderer.DrawVertexRenderStruct(cargo);
}
else {
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship);
}
} }
renderer.PopMatrix(); renderer.PopMatrix();
glEnable(GL_BLEND); glEnable(GL_BLEND);
@ -602,8 +633,8 @@ 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);
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга) // Биндим ÑекÑ<C2BA>Ñуру кораблÑ<C2BB> один раз длÑ<C2BB> Ð?Ñ<>ех правильных игроков
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); // ?????????: ?????? ???????? ?????????? ?????? ????? ? ??????????? ?? ClientState.shipType
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации // Если сервер прислал коробки, применяем их однократно вместо локальной генерации
if (!serverBoxesApplied && networkClient) { if (!serverBoxesApplied && networkClient) {
@ -647,7 +678,14 @@ namespace ZL
// 3. Поворот врага // 3. Поворот врага
renderer.RotateMatrix(playerState.rotation); renderer.RotateMatrix(playerState.rotation);
renderer.DrawVertexRenderStruct(spaceship); if (playerState.shipType == 1 && cargoTexture) {
glBindTexture(GL_TEXTURE_2D, cargoTexture->getTexID());
renderer.DrawVertexRenderStruct(cargo);
}
else {
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship);
}
renderer.PopMatrix(); renderer.PopMatrix();
} }
@ -663,8 +701,6 @@ namespace ZL
{ {
if (!textRenderer) return; if (!textRenderer) return;
//#ifdef NETWORK
// 2D поверх 3D
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -674,18 +710,12 @@ namespace ZL
if (deadRemotePlayers.count(id)) continue; if (deadRemotePlayers.count(id)) continue;
const ClientState& st = remotePlayer; const ClientState& st = remotePlayer;
// Позиция корабля в мире
Vector3f shipWorld = st.position; Vector3f shipWorld = st.position;
float distSq = (Environment::shipState.position - shipWorld).squaredNorm(); float distSq = (Environment::shipState.position - shipWorld).squaredNorm();
/*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма
continue;*/
float dist = sqrt(distSq); float dist = sqrt(distSq);
float alpha = 1.0f; // постоянная видимость float alpha = 1.0f;
/*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f };
if (alpha < 0.01f)
continue; */
Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты
float sx, sy, depth; float sx, sy, depth;
if (!worldToScreen(labelWorld, sx, sy, depth)) if (!worldToScreen(labelWorld, sx, sy, depth))
continue; continue;
@ -693,18 +723,21 @@ namespace ZL
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);
// Дефолтный лейбл std::string displayName;
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; if (!st.nickname.empty() && st.nickname != "Player") {
displayName = st.nickname;
}
else {
displayName = "Player (" + std::to_string(st.id) + ")";
}
std::string label = displayName + " " + std::to_string((int)dist) + "m";
// TODO: nickname sync textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha });
textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); // color param
textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha });
} }
glDisable(GL_BLEND); glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
//#endif
} }
// хелпер прицела: добавляет повернутую 2D-линию в меш прицела // хелпер прицела: добавляет повернутую 2D-линию в меш прицела
@ -1054,11 +1087,16 @@ namespace ZL
// defaultColor shader likely uses vColor (vec3), но нам нужен alpha. // defaultColor shader likely uses vColor (vec3), но нам нужен alpha.
// У тебя в Renderer есть RenderUniform4fv, но шейдер может брать vColor. // У тебя в Renderer есть RenderUniform4fv, но шейдер может брать vColor.
// Поэтому: сделаем ColorData vec3, а alpha дадим через uniform uColor, если есть. // ПоÑ<C2BE>Ñому: Ñ<>делаем ColorData vec3, а alpha будем задавать uniform'ом отдельно.
// Если в defaultColor нет uniform uColor — тогда alpha будет 1.0.
// Для совместимости: кладём RGB, alpha будем задавать uniform'ом отдельно.
Vector3f rgb{ rgba.x(), rgba.y(), rgba.z() }; Vector3f rgb{ rgba.x(), rgba.y(), rgba.z() };
v.ColorData = { rgb, rgb, rgb, rgb, rgb, rgb }; v.ColorData = { rgb, rgb, rgb, rgb, rgb, rgb };
// defaultColor vertex shader expects vNormal and vTexCoord; provide valid values
// so WebGL/GLSL doesn't get NaN from normalize(vec3(0,0,0)).
const Vector3f n{ 0.f, 0.f, 1.f };
v.NormalData = { n, n, n, n, n, n };
const Vector2f uv{ 0.f, 0.f };
v.TexCoordData = { uv, uv, uv, uv, uv, uv };
return v; return v;
} }
@ -1150,7 +1188,7 @@ namespace ZL
// 4) Настройки стиля // 4) Настройки стиля
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
float thickness = 2.0f; // толщина линий (px) float thickness = 10.0f; // толщина линий (px)
float z = 0.0f; // 2D слой float z = 0.0f; // 2D слой
// 5) Если цель в кадре: рисуем скобки // 5) Если цель в кадре: рисуем скобки
@ -1194,17 +1232,19 @@ namespace ZL
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
// базовый цвет для HUD (скобки/стрелка) — непрозрачный renderer.EnableVertexAttribArray("vPosition");
Eigen::Vector4f hudColor = enemyColor; Eigen::Vector4f hudColor = enemyColor;
renderer.RenderUniform4fv("uColor", hudColor.data()); renderer.RenderUniform4fv("uColor", hudColor.data());
// рисуем кружок упреждения (только если есть решение)
if (haveLead) { if (haveLead) {
float leadNdcX, leadNdcY, leadNdcZ, leadClipW; float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) { if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
@ -1219,8 +1259,6 @@ namespace ZL
float thicknessPx = 2.5f; float thicknessPx = 2.5f;
float innerR = max(1.0f, r - thicknessPx); float innerR = max(1.0f, r - thicknessPx);
float outerR = r + thicknessPx; float outerR = r + thicknessPx;
// для lead indicator alpha: 1.0 если движется, 0.5 если стоит
Eigen::Vector4f leadColor = enemyColor; Eigen::Vector4f leadColor = enemyColor;
leadColor.w() = leadAlpha; leadColor.w() = leadAlpha;
renderer.RenderUniform4fv("uColor", leadColor.data()); renderer.RenderUniform4fv("uColor", leadColor.data());
@ -1234,22 +1272,23 @@ namespace ZL
} }
} }
// верх-лево: горизонт + вертикаль renderer.EnableVertexAttribArray("vPosition");
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness); drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen); drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
// верх-право
drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness); drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen); drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen);
// низ-лево
drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness); drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness);
drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen); drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen);
// низ-право
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness); drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen); drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1261,12 +1300,9 @@ namespace ZL
return; return;
} }
// 6) Если цель offscreen: рисуем стрелку на краю
// dir: куда “смотреть” в NDC
float dirX = ndcX; float dirX = ndcX;
float dirY = ndcY; float dirY = ndcY;
// если позади камеры — разворачиваем направление
if (behind) { if (behind) {
dirX = -dirX; dirX = -dirX;
dirY = -dirY; dirY = -dirY;
@ -1277,7 +1313,6 @@ namespace ZL
dirX /= len; dirX /= len;
dirY /= len; dirY /= len;
// пересечение луча с прямоугольником [-1..1] с отступом
float marginNdc = 0.08f; float marginNdc = 0.08f;
float maxX = 1.0f - marginNdc; float maxX = 1.0f - marginNdc;
float maxY = 1.0f - marginNdc; float maxY = 1.0f - marginNdc;
@ -1292,16 +1327,13 @@ namespace ZL
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width; float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height; float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
// лёгкая анимация “зова”: смещение по направлению
float bob = std::sin(t * 6.0f) * 6.0f; float bob = std::sin(t * 6.0f) * 6.0f;
edgeX += dirX * bob; edgeX += dirX * bob;
edgeY += dirY * bob; edgeY += dirY * bob;
// стрелка как треугольник + маленький “хвост”
float arrowLen = 26.0f; float arrowLen = 26.0f;
float arrowWid = 14.0f; float arrowWid = 14.0f;
// перпендикуляр
float px = -dirY; float px = -dirY;
float py = dirX; float py = dirX;
@ -1315,6 +1347,11 @@ namespace ZL
v.PositionData = { a, b, c }; v.PositionData = { a, b, c };
Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() }; Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() };
v.ColorData = { rgb, rgb, rgb }; v.ColorData = { rgb, rgb, rgb };
// defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL).
const Vector3f n{ 0.f, 0.f, 1.f };
v.NormalData = { n, n, n };
const Vector2f uv{ 0.f, 0.f };
v.TexCoordData = { uv, uv, uv };
hudTempMesh.AssignFrom(v); hudTempMesh.AssignFrom(v);
renderer.DrawVertexRenderStruct(hudTempMesh); renderer.DrawVertexRenderStruct(hudTempMesh);
}; };
@ -1335,23 +1372,18 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
// треугольник-стрелка
drawTri(tip, left, right); drawTri(tip, left, right);
// “хвост” (короткая черта)
float tailLen = 14.0f; float tailLen = 14.0f;
float tailX = edgeX - dirX * 6.0f; float tailX = edgeX - dirX * 6.0f;
float tailY = edgeY - dirY * 6.0f; float tailY = edgeY - dirY * 6.0f;
// хвост рисуем как тонкий прямоугольник, ориентированный примерно по направлению:
// (упрощение: горизонт/вертикаль не поворачиваем, но выглядит ок. Хочешь — сделаем поворот матрицей)
drawBar(tailX, tailY, max(thickness, tailLen), thickness); drawBar(tailX, tailY, max(thickness, tailLen), thickness);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
// дистанция рядом со стрелкой
// (у тебя ещё будет “статично под прицелом” — это просто другой TextView / drawText)
{ {
std::string d = std::to_string((int)dist) + "m"; std::string d = std::to_string((int)dist) + "m";
float tx = edgeX + px * 18.0f; float tx = edgeX + px * 18.0f;
@ -1489,6 +1521,10 @@ namespace ZL
for (auto const& [id, remotePlayer] : latestRemotePlayers) { for (auto const& [id, remotePlayer] : latestRemotePlayers) {
if (networkClient && id == networkClient->GetClientId()) {
continue;
}
if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay)) if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay))
{ {
continue; continue;
@ -1497,7 +1533,6 @@ namespace ZL
ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay); ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay);
remotePlayerStates[id] = playerState; remotePlayerStates[id] = playerState;
} }
for (auto& p : projectiles) { for (auto& p : projectiles) {

View File

@ -78,6 +78,9 @@ namespace ZL {
VertexDataStruct spaceshipBase; VertexDataStruct spaceshipBase;
VertexRenderStruct spaceship; VertexRenderStruct spaceship;
std::shared_ptr<Texture> cargoTexture;
VertexDataStruct cargoBase;
VertexRenderStruct cargo;
VertexRenderStruct cubemap; VertexRenderStruct cubemap;
@ -133,7 +136,7 @@ namespace ZL {
// helpers // helpers
void drawTargetHud(); // рисует рамку или стрелку void drawTargetHud(); // рисует рамку или стрелку
int pickTargetId() const; // выбирает цель (пока: ближайший живой удаленный игрок) int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
void clearTextRendererCache(); void clearTextRendererCache();

View File

@ -906,22 +906,28 @@ namespace ZL {
} }
void UiManager::onMouseUp(int x, int y) { void UiManager::onMouseUp(int x, int y) {
std::vector<std::shared_ptr<UiButton>> clicked;
for (auto& b : buttons) { for (auto& b : buttons) {
if (!b) continue;
bool contains = b->rect.contains((float)x, (float)y); bool contains = b->rect.contains((float)x, (float)y);
if (b->state == ButtonState::Pressed) { if (b->state == ButtonState::Pressed) {
if (contains && pressedButton == b) { if (contains && pressedButton == b) {
if (b->onClick) { clicked.push_back(b);
b->onClick(b->name);
}
} }
b->state = contains ? ButtonState::Hover : ButtonState::Normal; b->state = contains ? ButtonState::Hover : ButtonState::Normal;
} }
} }
pressedButton.reset();
if (pressedSlider) { for (auto& b : clicked) {
pressedSlider.reset(); if (b->onClick) {
b->onClick(b->name);
}
} }
pressedButton.reset();
if (pressedSlider) pressedSlider.reset();
} }
void UiManager::onKeyPress(unsigned char key) { void UiManager::onKeyPress(unsigned char key) {

View File

@ -169,7 +169,7 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n
while (deltaMsLeftover > 0) while (deltaMsLeftover > 0)
{ {
long long miniDelta = 50; long long miniDelta = std::min(50LL, deltaMsLeftover);
simulate_physics(miniDelta); simulate_physics(miniDelta);
deltaMsLeftover -= miniDelta; deltaMsLeftover -= miniDelta;
} }
@ -207,7 +207,7 @@ void ClientState::handle_full_sync(const std::vector<std::string>& parts, int st
discreteAngle = std::stoi(parts[startFrom + 13]); discreteAngle = std::stoi(parts[startFrom + 13]);
} }
std::string ClientState::formPingMessageContent() std::string ClientState::formPingMessageContent() const
{ {
Eigen::Quaternionf q(rotation); Eigen::Quaternionf q(rotation);

View File

@ -4,6 +4,7 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include <math.h> #include <math.h>
#include <iostream> #include <iostream>
#include <string>
using std::min; using std::min;
@ -13,12 +14,12 @@ constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f";
#define ENABLE_NETWORK_CHECKSUM #define ENABLE_NETWORK_CHECKSUM
constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; 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_RADIUS = 20000.f;
constexpr float PLANET_ALIGN_ZONE = 1.05f; constexpr float PLANET_ALIGN_ZONE = 1.05f;
constexpr float PLANET_ANGULAR_ACCEL = 0.01f; // Подбери под динамику constexpr float PLANET_ANGULAR_ACCEL = 0.01f; // ??????? ??? ????????
constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f; constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f;
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f; constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
@ -38,7 +39,9 @@ struct ClientState {
float discreteMag = 0; float discreteMag = 0;
int discreteAngle = -1; int discreteAngle = -1;
// Для расчета лага std::string nickname = "Player";
int shipType = 0;
// ??? ??????? ????
std::chrono::system_clock::time_point lastUpdateServerTime; std::chrono::system_clock::time_point lastUpdateServerTime;
void simulate_physics(size_t delta); void simulate_physics(size_t delta);
@ -47,7 +50,7 @@ struct ClientState {
void handle_full_sync(const std::vector<std::string>& parts, int startFrom); void handle_full_sync(const std::vector<std::string>& parts, int startFrom);
std::string formPingMessageContent(); std::string formPingMessageContent() const;
}; };
struct ClientStateInterval struct ClientStateInterval

View File

@ -79,6 +79,10 @@ namespace ZL {
void LocalClient::initializeNPCs() { void LocalClient::initializeNPCs() {
npcs.clear(); npcs.clear();
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> typeDistrib(0, 1); // 0 = default ship, 1 = cargo
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
LocalNPC npc; LocalNPC npc;
npc.id = 100 + i; npc.id = 100 + i;
@ -91,6 +95,11 @@ namespace ZL {
npc.currentState.discreteAngle = -1; npc.currentState.discreteAngle = -1;
npc.currentState.currentAngularVelocity = Eigen::Vector3f::Zero(); npc.currentState.currentAngularVelocity = Eigen::Vector3f::Zero();
// random
int shipType = typeDistrib(gen);
npc.shipType = shipType;
npc.currentState.shipType = shipType;
npc.targetPosition = generateRandomPosition(); npc.targetPosition = generateRandomPosition();
npc.lastStateUpdateMs = std::chrono::duration_cast<std::chrono::milliseconds>( npc.lastStateUpdateMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count(); std::chrono::system_clock::now().time_since_epoch()).count();

View File

@ -31,6 +31,7 @@ namespace ZL {
Eigen::Vector3f targetPosition; Eigen::Vector3f targetPosition;
uint64_t lastStateUpdateMs = 0; uint64_t lastStateUpdateMs = 0;
bool destroyed = false; bool destroyed = false;
int shipType = 0;
}; };
class LocalClient : public INetworkClient { class LocalClient : public INetworkClient {

View File

@ -79,7 +79,6 @@ namespace ZL {
try { try {
int respawnedPlayerId = std::stoi(parts[1]); int respawnedPlayerId = std::stoi(parts[1]);
pendingRespawns_.push_back(respawnedPlayerId); pendingRespawns_.push_back(respawnedPlayerId);
remotePlayers.erase(respawnedPlayerId);
std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl; std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl;
} }
catch (...) {} catch (...) {}
@ -202,6 +201,11 @@ namespace ZL {
{ {
auto& rp = remotePlayers[remoteId]; auto& rp = remotePlayers[remoteId];
if (!rp.timedStates.empty()) {
const ClientState& last = rp.timedStates.back();
remoteState.nickname = last.nickname;
remoteState.shipType = last.shipType;
}
rp.add_state(remoteState); rp.add_state(remoteState);
} }
} }
@ -218,7 +222,7 @@ namespace ZL {
if (playerParts.size() < 15) return; // ID + 14 полей ClientState if (playerParts.size() < 15) return; // ID + 14 полей ClientState
int rId = std::stoi(playerParts[0]); int rId = std::stoi(playerParts[0]);
if (rId == clientId) return; // Свое состояние игрок знает лучше всех (Client Side Prediction) if (rId == clientId) return; // Свое состояние игрок знает лучше всех, (Client Side Prediction)
ClientState remoteState; ClientState remoteState;
remoteState.id = rId; remoteState.id = rId;
@ -230,6 +234,40 @@ namespace ZL {
remotePlayers[rId].add_state(remoteState); remotePlayers[rId].add_state(remoteState);
} }
} }
if (msg.rfind("PLAYERINFO:", 0) == 0) {
if (parts.size() >= 4) {
try {
int pid = std::stoi(parts[1]);
if (pid == clientId) {
return;
}
std::string nick = parts[2];
int st = std::stoi(parts[3]);
auto it = remotePlayers.find(pid);
if (it != remotePlayers.end() && !it->second.timedStates.empty()) {
auto& states = it->second.timedStates;
states.back().nickname = nick;
states.back().shipType = st;
}
else {
ClientState cs;
cs.id = pid;
cs.nickname = nick;
cs.shipType = st;
cs.lastUpdateServerTime = std::chrono::system_clock::now();
remotePlayers[pid].add_state(cs);
}
std::cout << "Client: PLAYERINFO received. id=" << pid << " nick=" << nick << " shipType=" << st << std::endl;
}
catch (...) {
}
}
return;
}
} }
std::string WebSocketClientBase::SignMessage(const std::string& msg) { std::string WebSocketClientBase::SignMessage(const std::string& msg) {

View File

@ -895,41 +895,65 @@ namespace ZL {
static const std::string vColor("vColor"); static const std::string vColor("vColor");
static const std::string vTexCoord("vTexCoord"); static const std::string vTexCoord("vTexCoord");
static const std::string vPosition("vPosition"); static const std::string vPosition("vPosition");
//glBindVertexArray(VertexRenderStruct.vao->getBuffer());
//Check if main thread, check if data is not empty... // On WebGL (and when not using VAO), vertex attribute arrays must be explicitly
// enabled before drawing. Desktop with VAO can rely on stored state; WebGL cannot.
if (VertexRenderStruct.data.NormalData.size() > 0) if (VertexRenderStruct.data.NormalData.size() > 0)
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer());
VertexAttribPointer3fv(vNormal, 0, NULL); VertexAttribPointer3fv(vNormal, 0, NULL);
EnableVertexAttribArray(vNormal);
}
else
{
DisableVertexAttribArray(vNormal);
} }
if (VertexRenderStruct.data.TangentData.size() > 0) if (VertexRenderStruct.data.TangentData.size() > 0)
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer());
VertexAttribPointer3fv(vTangent, 0, NULL); VertexAttribPointer3fv(vTangent, 0, NULL);
EnableVertexAttribArray(vTangent);
}
else
{
DisableVertexAttribArray(vTangent);
} }
if (VertexRenderStruct.data.BinormalData.size() > 0) if (VertexRenderStruct.data.BinormalData.size() > 0)
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer());
VertexAttribPointer3fv(vBinormal, 0, NULL); VertexAttribPointer3fv(vBinormal, 0, NULL);
EnableVertexAttribArray(vBinormal);
}
else
{
DisableVertexAttribArray(vBinormal);
} }
if (VertexRenderStruct.data.ColorData.size() > 0) if (VertexRenderStruct.data.ColorData.size() > 0)
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer());
VertexAttribPointer3fv(vColor, 0, NULL); VertexAttribPointer3fv(vColor, 0, NULL);
EnableVertexAttribArray(vColor);
}
else
{
DisableVertexAttribArray(vColor);
} }
if (VertexRenderStruct.data.TexCoordData.size() > 0) if (VertexRenderStruct.data.TexCoordData.size() > 0)
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer());
VertexAttribPointer2fv(vTexCoord, 0, NULL); VertexAttribPointer2fv(vTexCoord, 0, NULL);
EnableVertexAttribArray(vTexCoord);
}
else
{
DisableVertexAttribArray(vTexCoord);
} }
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
VertexAttribPointer3fv(vPosition, 0, NULL); VertexAttribPointer3fv(vPosition, 0, NULL);
EnableVertexAttribArray(vPosition);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size())); glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
} }
void worldToScreenCoordinates(Vector3f objectPos, void worldToScreenCoordinates(Vector3f objectPos,