Adapting web version

This commit is contained in:
Vladislav Khorev committed 2026-02-28 20:37:47 +03:00
1 parent 7e5aa22fee
commit 2728ca9646
16 files changed
+234 -207

No files matched your search

+70 -94
View File
@@ -1,100 +1,76 @@
<!doctypehtml> <!DOCTYPE html>
<html lang=en-us> <html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Space Game</title>
<style>
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed; /* Предотвращает pull-to-refresh на Android */
}
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
/* Кнопка Fullscreen */
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<head> <script>
<meta charset=utf-8> var statusElement = document.getElementById("status");
<meta content="text/html; charset=utf-8" http-equiv=Content-Type> var canvas = document.getElementById("canvas");
<title>Space Game</title>
<style> var Module = {
body { canvas: canvas,
font-family: arial; setStatus: function(text) {
margin: 0; statusElement.innerHTML = text;
overflow: hidden; statusElement.style.display = text ? 'block' : 'none';
padding: 0;
min-height: 100vh;
background-color: #000;
} }
};
.emscripten { // Кнопка Fullscreen
padding-right: 0; document.getElementById('fs-button').addEventListener('click', function() {
margin-left: auto; if (!document.fullscreenElement) {
margin-right: auto; document.documentElement.requestFullscreen().catch(e => {
display: block console.error(`Error attempting to enable full-screen mode: ${e.message}`);
});
} else {
document.exitFullscreen();
} }
});
div.emscripten { // Обработка ориентации
text-align: center window.addEventListener("orientationchange", function() {
} // Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
div.emscripten_border { </script>
border: 1px solid #000 <script async src="space-game001.js"></script>
} </body>
</html>
canvas.emscripten {
border: 0 none;
background-color: #000;
width: 90%;
height: 100vh;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0)
}
to {
-webkit-transform: rotate(360deg)
}
}
@-moz-keyframes rotation {
from {
-moz-transform: rotate(0)
}
to {
-moz-transform: rotate(360deg)
}
}
@-o-keyframes rotation {
from {
-o-transform: rotate(0)
}
to {
-o-transform: rotate(360deg)
}
}
@keyframes rotation {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
#status {
display: none;
}
#progress {
height: 20px;
width: 300px
}
</style>
</head>
<body>
<div class=emscripten id=status></div>
<div class=emscripten><progress hidden id=progress max=100 value=0></progress></div>
<div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault()
tabindex=-1></canvas></div>
<script>var statusElement = document.getElementById("status"), progressElement = document.getElementById("progress"), spinnerElement = document.getElementById("spinner"), Module = { print: function () { var e = document.getElementById("output"); return e && (e.value = ""), function (t) { arguments.length > 1 && (t = Array.prototype.slice.call(arguments).join(" ")), console.log(t), e && (e.value += t + "\n", e.scrollTop = e.scrollHeight) } }(), canvas: (() => { var e = document.getElementById("canvas"); return e.addEventListener("webglcontextlost", (e => { alert("WebGL context lost. You will need to reload the page."), e.preventDefault() }), !1), e })(), setStatus: e => { if (Module.setStatus.last || (Module.setStatus.last = { time: Date.now(), text: "" }), e !== Module.setStatus.last.text) { var t = e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/), n = Date.now(); t && n - Module.setStatus.last.time < 30 || (Module.setStatus.last.time = n, Module.setStatus.last.text = e, t ? (e = t[1], progressElement.value = 100 * parseInt(t[2]), progressElement.max = 100 * parseInt(t[4]), progressElement.hidden = !1, spinnerElement.hidden = !1) : (progressElement.value = null, progressElement.max = null, progressElement.hidden = !0, e || (spinnerElement.style.display = "none")), statusElement.innerHTML = e) } }, totalDependencies: 0, monitorRunDependencies: e => { this.totalDependencies = Math.max(this.totalDependencies, e), Module.setStatus(e ? "Preparing... (" + (this.totalDependencies - e) + "/" + this.totalDependencies + ")" : "All downloads complete.") } }; Module.setStatus("Downloading..."), window.onerror = e => { Module.setStatus("Exception thrown, see JavaScript console"), spinnerElement.style.display = "none", Module.setStatus = e => { e && console.error("[post-exception status] " + e) } }</script>
<script async src=space-game001.js></script>
</body>
</html>
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.
+20
View File
@@ -36,5 +36,25 @@ ClientState Environment::shipState;
const float Environment::CONST_Z_NEAR = 5.f; const float Environment::CONST_Z_NEAR = 5.f;
const float Environment::CONST_Z_FAR = 5000.f; const float Environment::CONST_Z_FAR = 5000.f;
float Environment::projectionWidth = 1280.0f;
float Environment::projectionHeight = 720.0f;
void Environment::computeProjectionDimensions()
{
if (width <= 0 || height <= 0) return;
const float refShortSide = 720.0f;
float aspect = (float)width / (float)height;
if (width >= height) {
// Landscape: fix height to 720, scale width to preserve aspect
projectionHeight = refShortSide;
projectionWidth = refShortSide * aspect;
} else {
// Portrait: fix width to 720, scale height to preserve aspect
projectionWidth = refShortSide;
projectionHeight = refShortSide / aspect;
}
}
} // namespace ZL } // namespace ZL
+8 -1
View File
@@ -35,8 +35,15 @@ public:
static const float CONST_Z_NEAR; static const float CONST_Z_NEAR;
static const float CONST_Z_FAR; static const float CONST_Z_FAR;
// Virtual projection dimensions used for all 2D/UI rendering.
// These maintain the screen's actual aspect ratio but normalize the
// height to 720 (landscape) or width to 720 (portrait), giving a
// consistent coordinate space regardless of physical screen resolution.
static float projectionWidth;
static float projectionHeight;
// Call this once at startup and whenever the window is resized.
static void computeProjectionDimensions();
}; };
} // namespace ZL } // namespace ZL
+45 -42
View File
@@ -83,6 +83,8 @@ namespace ZL
void Game::setup() { void Game::setup() {
glContext = SDL_GL_CreateContext(ZL::Environment::window); glContext = SDL_GL_CreateContext(ZL::Environment::window);
Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
ZL::CheckGlError(); ZL::CheckGlError();
renderer.InitOpenGL(); renderer.InitOpenGL();
@@ -99,7 +101,7 @@ namespace ZL
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE)); loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif #endif
loadingMesh.data = CreateRect2D({ Environment::width * 0.5, Environment::height * 0.5 }, { Environment::width * 0.5, Environment::height*0.5 }, 3); loadingMesh.data = CreateRect2D({ Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, { Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, 3);
loadingMesh.RefreshVBO(); loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@@ -141,17 +143,14 @@ namespace ZL
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>(); auto localClient = new LocalClient;
ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
networkClient = std::unique_ptr<INetworkClient>(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; lastTickCount = 0;
spaceGameStarted = 1; spaceGameStarted = 1;
}; };
@@ -160,8 +159,7 @@ namespace ZL
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>();
#ifdef NETWORK
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
@@ -169,19 +167,6 @@ 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
#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) { if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType); std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
@@ -264,8 +249,8 @@ namespace ZL
renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
float width = Environment::width; float width = Environment::projectionWidth;
float height = Environment::height; float height = Environment::projectionHeight;
renderer.PushProjectionMatrix( renderer.PushProjectionMatrix(
0, width, 0, width,
@@ -350,16 +335,18 @@ namespace ZL
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
} }
#if SDL_VERSION_ATLEAST(2,0,5)
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях // Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
Environment::width = event.window.data1; Environment::width = event.window.data1;
Environment::height = event.window.data2; Environment::height = event.window.data2;
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl; Environment::computeProjectionDimensions();
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
space.clearTextRendererCache(); space.clearTextRendererCache();
} }
#endif
#ifdef __ANDROID__ #ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
@@ -368,22 +355,38 @@ namespace ZL
#ifdef __ANDROID__ #ifdef __ANDROID__
if (event.type == SDL_FINGERDOWN) { if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(mx, my); handleDown(mx, my);
} }
else if (event.type == SDL_FINGERUP) { else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(mx, my); handleUp(mx, my);
} }
else if (event.type == SDL_FINGERMOTION) { else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::width); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::height); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(mx, my);
} }
#else #else
if (event.type == SDL_MOUSEBUTTONDOWN) {
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
// Преобразуем экранные пиксели в проекционные единицы
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my);
else handleUp(mx, my);
}
else if (event.type == SDL_MOUSEMOTION) {
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(mx, my);
}
/*if (event.type == SDL_MOUSEBUTTONDOWN) {
int mx = event.button.x; int mx = event.button.x;
int my = event.button.y; int my = event.button.y;
handleDown(mx, my); handleDown(mx, my);
@@ -397,7 +400,7 @@ namespace ZL
int mx = event.motion.x; int mx = event.motion.x;
int my = event.motion.y; int my = event.motion.y;
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;
@@ -454,7 +457,7 @@ namespace ZL
void Game::handleDown(int mx, int my) void Game::handleDown(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseDown(uiX, uiY); menuManager.uiManager.onMouseDown(uiX, uiY);
@@ -482,7 +485,7 @@ namespace ZL
void Game::handleUp(int mx, int my) void Game::handleUp(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseUp(uiX, uiY); menuManager.uiManager.onMouseUp(uiX, uiY);
@@ -497,7 +500,7 @@ namespace ZL
void Game::handleMotion(int mx, int my) void Game::handleMotion(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::height - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseMove(uiX, uiY); menuManager.uiManager.onMouseMove(uiX, uiY);
+27 -27
View File
@@ -159,15 +159,15 @@ namespace ZL
// В пределах экрана? // В пределах экрана?
// (можно оставить, можно клампить) // (можно оставить, можно клампить)
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width; float sx = (ndc.x() * 0.5f + 0.5f) * Environment::projectionWidth;
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height; float sy = (ndc.y() * 0.5f + 0.5f) * Environment::projectionHeight;
outX = sx; outX = sx;
outY = sy; outY = sy;
// Можно отсеять те, что вне: // Можно отсеять те, что вне:
if (sx < -200 || sx > Environment::width + 200) return false; if (sx < -200 || sx > Environment::projectionWidth + 200) return false;
if (sy < -200 || sy > Environment::height + 200) return false; if (sy < -200 || sy > Environment::projectionHeight + 200) return false;
return true; return true;
} }
@@ -296,12 +296,12 @@ namespace ZL
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE) CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
}); });
@@ -859,8 +859,8 @@ namespace ZL
// если ничего не изменилось — не трогаем VBO // если ничего не изменилось — не трогаем VBO
if (crosshairMeshValid && if (crosshairMeshValid &&
crosshairLastW == Environment::width && crosshairLastW == Environment::projectionWidth &&
crosshairLastH == Environment::height && crosshairLastH == Environment::projectionHeight &&
std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f && std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f &&
std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f && std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f &&
std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f && std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f &&
@@ -869,18 +869,18 @@ namespace ZL
return; return;
} }
crosshairLastW = Environment::width; crosshairLastW = Environment::projectionWidth;
crosshairLastH = Environment::height; crosshairLastH = Environment::projectionHeight;
crosshairLastAlpha = crosshairCfg.alpha; crosshairLastAlpha = crosshairCfg.alpha;
crosshairLastThickness = crosshairCfg.thicknessPx; crosshairLastThickness = crosshairCfg.thicknessPx;
crosshairLastGap = crosshairCfg.gapPx; crosshairLastGap = crosshairCfg.gapPx;
crosshairLastScaleMul = crosshairCfg.scaleMul; crosshairLastScaleMul = crosshairCfg.scaleMul;
float cx = Environment::width * 0.5f; float cx = Environment::projectionWidth * 0.5f;
float cy = Environment::height * 0.5f; float cy = Environment::projectionHeight * 0.5f;
// масштаб от reference (стандартно: по высоте) // масштаб от reference (стандартно: по высоте)
float scale = (crosshairCfg.refH > 0) ? (Environment::height / (float)crosshairCfg.refH) : 1.0f; float scale = (crosshairCfg.refH > 0) ? (Environment::projectionHeight / (float)crosshairCfg.refH) : 1.0f;
scale *= crosshairCfg.scaleMul; scale *= crosshairCfg.scaleMul;
float thickness = crosshairCfg.thicknessPx * scale; float thickness = crosshairCfg.thicknessPx * scale;
@@ -940,7 +940,7 @@ namespace ZL
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -1187,15 +1187,15 @@ 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 = 10.0f; // толщина линий (px) float thickness = 2.0f; // толщина линий (px)
float z = 0.0f; // 2D слой float z = 0.0f; // 2D слой
// 5) Если цель в кадре: рисуем скобки // 5) Если цель в кадре: рисуем скобки
if (onScreen) if (onScreen)
{ {
// перевод NDC -> экран (в пикселях) // перевод NDC -> экран (в пикселях)
float sx = (ndcX * 0.5f + 0.5f) * Environment::width; float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
float sy = (ndcY * 0.5f + 0.5f) * Environment::height; float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
// анимация “снаружи внутрь” // анимация “снаружи внутрь”
// targetAcquireAnim растёт к 1, быстро (похоже на захват) // targetAcquireAnim растёт к 1, быстро (похоже на захват)
@@ -1234,7 +1234,7 @@ namespace ZL
glClear(GL_DEPTH_BUFFER_BIT); 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(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@@ -1248,8 +1248,8 @@ namespace ZL
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) {
if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) { if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) {
float lx = (leadNdcX * 0.5f + 0.5f) * Environment::width; float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::height; float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
float distLead = (Environment::shipState.position - leadWorld).norm(); float distLead = (Environment::shipState.position - leadWorld).norm();
float r = 30.0f / (distLead * 0.01f + 1.0f); float r = 30.0f / (distLead * 0.01f + 1.0f);
@@ -1323,8 +1323,8 @@ namespace ZL
float edgeNdcX = dirX * k; float edgeNdcX = dirX * k;
float edgeNdcY = dirY * k; float edgeNdcY = dirY * k;
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width; float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height; float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
float bob = std::sin(t * 6.0f) * 6.0f; float bob = std::sin(t * 6.0f) * 6.0f;
edgeX += dirX * bob; edgeX += dirX * bob;
@@ -1367,7 +1367,7 @@ namespace ZL
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor"); renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
+1 -1
View File
@@ -657,7 +657,7 @@ namespace ZL {
} }
void UiManager::draw(Renderer& renderer) { void UiManager::draw(Renderer& renderer) {
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1); renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
+45 -22
View File
@@ -44,20 +44,34 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
return EM_TRUE; return EM_TRUE;
} }
// Resize the canvas, notify SDL, and push a synthetic SDL_WINDOWEVENT_RESIZED static void applyResize(int logicalW, int logicalH) {
// so Game::update()'s existing handler updates Environment::width/height and clears caches. // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
static void applyResize(int w, int h) { double dpr = emscripten_get_device_pixel_ratio();
if (w <= 0 || h <= 0) return;
// Resize the actual WebGL canvas — without this the rendered pixels stay at // Вычисляем реальные физические пиксели
// the original size no matter what Environment::width/height say. int physicalW = static_cast<int>(logicalW * dpr);
emscripten_set_canvas_element_size("#canvas", w, h); int physicalH = static_cast<int>(logicalH * dpr);
if (ZL::Environment::window)
SDL_SetWindowSize(ZL::Environment::window, w, h); // Устанавливаем размер внутреннего буфера канваса
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
// Сообщаем SDL о новом размере.
// ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели
// для корректной работы последующих вызовов glViewport.
if (ZL::Environment::window) {
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
}
// Обновляем ваши внутренние переменные окружения
ZL::Environment::width = physicalW;
ZL::Environment::height = physicalH;
// Пушим событие, чтобы движок пересчитал матрицы проекции
SDL_Event e = {}; SDL_Event e = {};
e.type = SDL_WINDOWEVENT; e.type = SDL_WINDOWEVENT;
e.window.event = SDL_WINDOWEVENT_RESIZED; e.window.event = SDL_WINDOWEVENT_RESIZED;
e.window.data1 = w; e.window.data1 = physicalW;
e.window.data2 = h; e.window.data2 = physicalH;
SDL_PushEvent(&e); SDL_PushEvent(&e);
} }
@@ -69,17 +83,11 @@ EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*u
} }
EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) { EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) {
if (e->isFullscreen) { // Вместо window.innerWidth, попробуйте запросить размер целевого элемента
// e->screenWidth/screenHeight comes from screen.width/screen.height in JS, // так как после перехода в FS именно он растягивается на весь экран.
// which on mobile browsers returns physical pixels (e.g. 2340x1080), double clientW, clientH;
// causing the canvas to extend far off-screen. window.innerWidth/innerHeight emscripten_get_element_css_size("#canvas", &clientW, &clientH);
// always gives CSS logical pixels and is correct on both desktop and mobile. applyResize(clientW, clientH);
int w = EM_ASM_INT({ return window.innerWidth; });
int h = EM_ASM_INT({ return window.innerHeight; });
applyResize(w, h);
}
// Exiting fullscreen: the browser fires a window resize event next,
// which onWindowResized handles automatically.
return EM_FALSE; return EM_FALSE;
} }
@@ -226,6 +234,21 @@ int main(int argc, char *argv[]) {
// Keep Environment::width/height in sync when the canvas is resized. // Keep Environment::width/height in sync when the canvas is resized.
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized); emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged); emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged);
// 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ:
// Получаем реальные размеры окна браузера на момент запуска
int canvasW = EM_ASM_INT({ return window.innerWidth; });
int canvasH = EM_ASM_INT({ return window.innerHeight; });
// Вызываем вашу функцию — она сама применит DPR, выставит физический размер
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH);
// 3. Создаем игру и вызываем setup (теперь проекция уже будет знать верный size)
g_game = new ZL::Game();
g_game->setup();
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
emscripten_set_main_loop(MainLoop, 0, 1); emscripten_set_main_loop(MainLoop, 0, 1);
#else #else
+4 -3
View File
@@ -362,9 +362,10 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
// 4. Рендеринг // 4. Рендеринг
r->shaderManager.PushShader(shaderName); r->shaderManager.PushShader(shaderName);
// Матрица проекции (экрана) // Матрица проекции — используем виртуальные проекционные размеры,
float W = (float)Environment::width; // чтобы координаты текста были независимы от физического разрешения экрана.
float H = (float)Environment::height; float W = Environment::projectionWidth;
float H = Environment::projectionHeight;
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity(); Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
proj(0, 0) = 2.0f / W; proj(0, 0) = 2.0f / W;
proj(1, 1) = 2.0f / H; proj(1, 1) = 2.0f / H;