From a59bcc0c4b04a83f200a6241147522ebe06578f3 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sun, 8 Feb 2026 22:38:24 +0300 Subject: [PATCH] Adapting for web --- proj-web/CMakeLists.txt | 54 ++++- resources/config/spark_config.json | 4 +- resources/main_menu/exit.png | 4 +- resources/main_menu/lang.png | 4 +- resources/main_menu/line.png | 4 +- resources/main_menu/multi.png | 4 +- resources/main_menu/single.png | 4 +- resources/main_menu/subtitle.png | 4 +- resources/main_menu/title.png | 4 +- resources/main_menu/version.png | 4 +- .../defaultAtmosphere_desktop.fragment | 125 ++++++++++ .../shaders/defaultAtmosphere_web.fragment | 2 +- .../shaders/defaultColor_desktop.fragment | 9 + resources/shaders/defaultColor_web.fragment | 2 +- resources/shaders/default_desktop.fragment | 12 + .../shaders/default_env_desktop.fragment | 8 + resources/shaders/default_env_web.fragment | 2 +- ...gment => default_texture_desktop.fragment} | 0 .../shaders/default_texture_web.fragment | 2 +- resources/shaders/default_web.fragment | 2 +- resources/shaders/env_sky_desktop.fragment | 37 +++ resources/shaders/env_sky_web.fragment | 2 +- .../shaders/planet_bake_desktop.fragment | 14 ++ resources/shaders/planet_bake_web.fragment | 2 +- .../shaders/planet_land_desktop.fragment | 116 +++++++++ resources/shaders/planet_land_web.fragment | 4 +- .../shaders/planet_stone_desktop.fragment | 102 ++++++++ resources/shaders/planet_stone_web.fragment | 2 +- resources/shaders/spark_desktop.fragment | 9 + resources/shaders/spark_web.fragment | 9 + resources/shaders/text2d.vertex | 9 +- ...ext2d.fragment => text2d_desktop.fragment} | 0 resources/shaders/text2d_web.fragment | 19 ++ src/Game.cpp | 24 +- src/SparkEmitter.cpp | 51 ++-- src/UiManager.cpp | 4 +- src/network/LocalClient.h | 5 + src/render/TextRenderer.cpp | 224 +++++++++++------- src/render/TextRenderer.h | 15 +- src/render/TextureManager.cpp | 149 ++++++------ src/render/TextureManager.h | 23 +- src/utils/Utils.cpp | 63 ++--- 42 files changed, 843 insertions(+), 294 deletions(-) create mode 100644 resources/shaders/defaultAtmosphere_desktop.fragment create mode 100644 resources/shaders/defaultColor_desktop.fragment create mode 100644 resources/shaders/default_desktop.fragment create mode 100644 resources/shaders/default_env_desktop.fragment rename resources/shaders/{spark.fragment => default_texture_desktop.fragment} (100%) create mode 100644 resources/shaders/env_sky_desktop.fragment create mode 100644 resources/shaders/planet_bake_desktop.fragment create mode 100644 resources/shaders/planet_land_desktop.fragment create mode 100644 resources/shaders/planet_stone_desktop.fragment create mode 100644 resources/shaders/spark_desktop.fragment create mode 100644 resources/shaders/spark_web.fragment rename resources/shaders/{text2d.fragment => text2d_desktop.fragment} (100%) create mode 100644 resources/shaders/text2d_web.fragment diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index 513de7b..b613506 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -21,23 +21,52 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake") set(SOURCES ../src/main.cpp ../src/Game.cpp + ../src/Game.h ../src/Environment.cpp - ../src/BoneAnimatedModel.cpp - ../src/TextModel.cpp - ../src/Projectile.cpp - ../src/SparkEmitter.cpp - ../src/UiManager.cpp + ../src/Environment.h ../src/render/Renderer.cpp + ../src/render/Renderer.h ../src/render/ShaderManager.cpp + ../src/render/ShaderManager.h ../src/render/TextureManager.cpp - ../src/render/FrameBuffer.cpp + ../src/render/TextureManager.h + ../src/TextModel.cpp + ../src/TextModel.h + ../src/AudioPlayerAsync.cpp + ../src/AudioPlayerAsync.h + ../src/BoneAnimatedModel.cpp + ../src/BoneAnimatedModel.h ../src/render/OpenGlExtensions.cpp + ../src/render/OpenGlExtensions.h ../src/utils/Utils.cpp - ../src/utils/TaskManager.cpp - ../src/utils/Perlin.cpp - ../src/planet/PlanetData.cpp - ../src/planet/PlanetObject.cpp - ../src/planet/StoneObject.cpp + ../src/utils/Utils.h + ../src/SparkEmitter.cpp + ../src/SparkEmitter.h + ../src/planet/PlanetObject.cpp + ../src/planet/PlanetObject.h + ../src/planet/PlanetData.cpp + ../src/planet/PlanetData.h + ../src/utils/Perlin.cpp + ../src/utils/Perlin.h + ../src/utils/TaskManager.cpp + ../src/utils/TaskManager.h + ../src/planet/StoneObject.cpp + ../src/planet/StoneObject.h + ../src/render/FrameBuffer.cpp + ../src/render/FrameBuffer.h + ../src/UiManager.cpp + ../src/UiManager.h + ../src/Projectile.h + ../src/Projectile.cpp + ../src/network/NetworkInterface.h + ../src/network/LocalClient.h + ../src/network/LocalClient.cpp + ../src/network/ClientState.h + ../src/network/ClientState.cpp + ../src/network/WebSocketClient.h + ../src/network/WebSocketClient.cpp + ../src/render/TextRenderer.h + ../src/render/TextRenderer.cpp ) add_executable(space-game001 ${SOURCES}) @@ -68,7 +97,8 @@ set(EMSCRIPTEN_FLAGS "-sUSE_SDL=2" "-sUSE_SDL_IMAGE=2" "-sUSE_LIBPNG=1" - "-sUSE_ZLIB=1" # Добавили zlib порт + "-sUSE_ZLIB=1" + "-sUSE_SDL_TTF=2" "-pthread" "-sUSE_PTHREADS=1" "-fexceptions" diff --git a/resources/config/spark_config.json b/resources/config/spark_config.json index edc73ee..cbe6c47 100644 --- a/resources/config/spark_config.json +++ b/resources/config/spark_config.json @@ -16,7 +16,5 @@ "scaleRange": [0.8, 1.2], "lifeTimeRange": [600.0, 1400.0], "texture": "resources/spark.png", - "shaderProgramName": "default", - "vertexShader": "resources/shaders/spark.vertex", - "fragmentShader": "resources/shaders/spark.fragment" + "shaderProgramName": "spark" } \ No newline at end of file diff --git a/resources/main_menu/exit.png b/resources/main_menu/exit.png index d2e10c7..e51c398 100644 --- a/resources/main_menu/exit.png +++ b/resources/main_menu/exit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd2def6829ad74a9a6f466259f56001261161e44e04d54452b20537f02100510 -size 1705 +oid sha256:d025a8a8ca00d6a7521d5cf60a7ffe1a89cd17ee8ef9236248f7a8aa36cffdf0 +size 8039 diff --git a/resources/main_menu/lang.png b/resources/main_menu/lang.png index 6ab2a9d..63ab2a9 100644 --- a/resources/main_menu/lang.png +++ b/resources/main_menu/lang.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:063b260ec102139ba2afe878ceff30343c1d3debe82cfed2bf80c4681d9f97e1 -size 9811 +oid sha256:92f0461b2814ffcbce7aecfcdf466c02947b38419e1069ca8f7f94ebe561a733 +size 29661 diff --git a/resources/main_menu/line.png b/resources/main_menu/line.png index 73e47a5..349db54 100644 --- a/resources/main_menu/line.png +++ b/resources/main_menu/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:268a7cb9f878a44b0e170655f6ef88ef6ba55e422458a87e05a16a121a5ed2f3 -size 5774 +oid sha256:2b8df673b29ad159ca43972d9f2244e614f42c5c11c54b18298444a76948ec06 +size 22360 diff --git a/resources/main_menu/multi.png b/resources/main_menu/multi.png index 8ed569b..d86a3d3 100644 --- a/resources/main_menu/multi.png +++ b/resources/main_menu/multi.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44549cc8ac38f4ee24ebf8feedd11a5c0dfb156ad1f65c84d290980d5b0f44fb -size 1909 +oid sha256:b564777ff80375bdceefc35846ec93962d69f83ef7aa710d3b85b865abb02280 +size 5637 diff --git a/resources/main_menu/single.png b/resources/main_menu/single.png index ee1595e..9efed56 100644 --- a/resources/main_menu/single.png +++ b/resources/main_menu/single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34926668c8f681d6fa9527343bf47169f0f6683a281591ee0d8106b65ad2e758 -size 2036 +oid sha256:c5b030b23dd7009e0d189d758a7e002afc771b83b5dd49208a36cd1ffebcfde7 +size 11681 diff --git a/resources/main_menu/subtitle.png b/resources/main_menu/subtitle.png index 4955406..0f2ea2b 100644 --- a/resources/main_menu/subtitle.png +++ b/resources/main_menu/subtitle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef4a84b8462285ee4038b85b5474059c5dc77b114a93e35ce09dd71f74c9ed60 -size 1652 +oid sha256:356b162dc2f0e690ff8f77fb64d6937084ceafca5590d135d50a2b88134b2cd8 +size 6723 diff --git a/resources/main_menu/title.png b/resources/main_menu/title.png index 81d446a..70058a0 100644 --- a/resources/main_menu/title.png +++ b/resources/main_menu/title.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efaa78ed8e564bc34e0f2d154f92b5a0e4cc18da733db729925967d1b1c4e482 -size 2023 +oid sha256:3cfd73ee7e067e0298f461ea0a01f1e2fc4b093cf2d22c665654f030867463b7 +size 7191 diff --git a/resources/main_menu/version.png b/resources/main_menu/version.png index daaa9ce..bb5d013 100644 --- a/resources/main_menu/version.png +++ b/resources/main_menu/version.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9570c36786c210f39fc6e44a428b7fbeecfa91d564b8982e9cc479aa8a2d545f -size 1157 +oid sha256:6325da7b88540fed722f53297569f359c95710ec8d6eab4fc6477f95aa69416b +size 4110 diff --git a/resources/shaders/defaultAtmosphere_desktop.fragment b/resources/shaders/defaultAtmosphere_desktop.fragment new file mode 100644 index 0000000..718e19b --- /dev/null +++ b/resources/shaders/defaultAtmosphere_desktop.fragment @@ -0,0 +1,125 @@ +//precisionhighp float; + +// Фрагментный шейдер: +uniform vec3 uColor; +uniform float uDistanceToPlanetSurface; // Расстояние корабля до поверхности + +// Константы затухания, определенные прямо в шейдере +const float DIST_FOG_MAX = 2000.0; +const float DIST_FOG_MIN = 1000.0; + +varying vec3 vWorldNormal; +varying vec3 vViewNormal; +varying vec3 vViewPosition; + + +// Добавь эти uniform-ы +//uniform float uTime; +uniform vec3 uLightDirView; +uniform vec3 uPlayerDirWorld; + +// Простая функция псевдослучайного шума +float hash(float n) { return fract(sin(n) * 43758.5453123); } + +float noise(vec3 x) { + vec3 p = floor(x); + vec3 f = fract(x); + f = f * f * (3.0 - 2.0 * f); + float n = p.x + p.y * 57.0 + 113.0 * p.z; + return mix(mix(mix(hash(n + 0.0), hash(n + 1.0), f.x), + mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y), + mix(mix(hash(n + 113.0), hash(n + 114.0), f.x), + mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y), f.z); +} + +// Fractal Brownian Motion для "кучевости" облаков +float fbm(vec3 p) { + float f = 0.5000 * noise(p); p *= 2.02; + f += 0.2500 * noise(p); p *= 2.03; + f += 0.1250 * noise(p); + return f; +} + +void main() +{ +//gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5); + + vec3 normal = normalize(vViewNormal); + vec3 viewVector = normalize(-vViewPosition); + float NdotV = dot(normal, viewVector); + + // Вектор направления от центра планеты к текущему фрагменту атмосферы (Мировой) + vec3 fragmentDir = normalize(vWorldNormal); + // Вектор направления от центра планеты к игроку (нужно передать как uniform) + // Передай его в C++: renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); + + + // --- 1. Плавное отсечение за горизонтом (Horizon Mask) --- + // Считаем косинус угла между игроком и точкой атмосферы + float cosAngle = dot(fragmentDir, uPlayerDirWorld); + // Плавно затухаем от 0.0 (горизонт) до 0.2 (над головой) + //float horizonMask = smoothstep(0.0, 0.4, cosAngle); + float horizonMask = smoothstep(0.93, 1.0, cosAngle); + + + // --- 2. Плавный переход при прохождении сквозь слой (Transition Mask) --- + // Определяем "высоту" игрока относительно слоя (напр. слой на 1.03 * R) + // uDistanceToPlanetSurface уже вычислен в PlanetData + float layerHeight = 600.0; // Примерная толщина слоя атмосферы (3% от 20000) + + // Делаем прозрачным при приближении к границе (dist около layerHeight) + // и снова видимым, когда спустились ниже + float distToLayer = abs(uDistanceToPlanetSurface - layerHeight); + float transitionMask = smoothstep(0.0, 200.0, distToLayer); + + // --- 3. Освещение и облака --- + float diff = max(dot(normal, uLightDirView), 0.0); + if (uDistanceToPlanetSurface < layerHeight) { + diff = max(dot(-normal, uLightDirView), 0.0); // Инверсия для взгляда снизу + } + float lightIntensity = diff + 0.05; + + vec3 cloudCoord = fragmentDir * 6.0; + //cloudCoord.x += uTime * 0.03; + float n = fbm(cloudCoord); + float cloudMask = smoothstep(0.4, 0.65, n); + + // --- 4. Финальный расчет альфы --- + float atmosphereDensity = pow(1.0 - abs(NdotV), 5.0); + float distanceFactor = clamp((uDistanceToPlanetSurface - DIST_FOG_MIN) / (DIST_FOG_MAX - DIST_FOG_MIN), 0.0, 1.0); + + // Базовая прозрачность облаков + float cloudAlpha = cloudMask * 0.8; + + // Применяем маски: + // В космосе важен distanceFactor, на планете важен horizonMask + float finalCloudAlpha = cloudAlpha * transitionMask; + if (uDistanceToPlanetSurface < layerHeight) { + finalCloudAlpha *= horizonMask; // На планете скрываем то, что под ногами + } else { + finalCloudAlpha *= distanceFactor; // В космосе скрываем по вашей старой логике + if (NdotV <=0.0) + { + //finalCloudAlpha = 0.0; + discard; + } + } + + vec3 currentAtmo = mix(vec3(0.01, 0.02, 0.1), uColor, lightIntensity); + vec3 currentCloud = mix(vec3(0.1, 0.1, 0.15), vec3(1.0, 1.0, 1.0), lightIntensity); + vec3 finalRGB = mix(currentAtmo, currentCloud, cloudMask); + //vec3 finalRGB = currentAtmo; + + // Для дымки (atmo) оставляем затухание при посадке + float atmoAlpha = atmosphereDensity * distanceFactor; + //float atmoAlpha = distanceFactor; + + float finalAtmoAlpha = atmoAlpha; + + if (uDistanceToPlanetSurface < layerHeight) { + finalCloudAlpha *= horizonMask; + finalAtmoAlpha *= horizonMask; // Принудительно гасим дымку под горизонтом + } + + gl_FragColor = vec4(finalRGB, max(finalAtmoAlpha, finalCloudAlpha)); +} \ No newline at end of file diff --git a/resources/shaders/defaultAtmosphere_web.fragment b/resources/shaders/defaultAtmosphere_web.fragment index 718e19b..8ad78af 100644 --- a/resources/shaders/defaultAtmosphere_web.fragment +++ b/resources/shaders/defaultAtmosphere_web.fragment @@ -1,4 +1,4 @@ -//precisionhighp float; +precision highp float; // Фрагментный шейдер: uniform vec3 uColor; diff --git a/resources/shaders/defaultColor_desktop.fragment b/resources/shaders/defaultColor_desktop.fragment new file mode 100644 index 0000000..9dfd488 --- /dev/null +++ b/resources/shaders/defaultColor_desktop.fragment @@ -0,0 +1,9 @@ +//precisionmediump float; +varying vec3 color; + +void main() +{ + //gl_FragColor = vec4(color, 1.0); + gl_FragColor = vec4(1.0, 1.0, 0.5, 1.0); + +} \ No newline at end of file diff --git a/resources/shaders/defaultColor_web.fragment b/resources/shaders/defaultColor_web.fragment index 9dfd488..1e3d8c1 100644 --- a/resources/shaders/defaultColor_web.fragment +++ b/resources/shaders/defaultColor_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; varying vec3 color; void main() diff --git a/resources/shaders/default_desktop.fragment b/resources/shaders/default_desktop.fragment new file mode 100644 index 0000000..7548e9e --- /dev/null +++ b/resources/shaders/default_desktop.fragment @@ -0,0 +1,12 @@ +//precisionmediump float; +uniform sampler2D Texture; +varying vec2 texCoord; + +void main() +{ + vec4 color = texture2D(Texture,texCoord).rgba; + //gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0); + + gl_FragColor = color; + +} \ No newline at end of file diff --git a/resources/shaders/default_env_desktop.fragment b/resources/shaders/default_env_desktop.fragment new file mode 100644 index 0000000..be588de --- /dev/null +++ b/resources/shaders/default_env_desktop.fragment @@ -0,0 +1,8 @@ +//precisionmediump float; +uniform samplerCube Texture; + +varying vec3 dir; + +void main(){ + gl_FragColor = textureCube(Texture, normalize(dir)); +} \ No newline at end of file diff --git a/resources/shaders/default_env_web.fragment b/resources/shaders/default_env_web.fragment index be588de..505814f 100644 --- a/resources/shaders/default_env_web.fragment +++ b/resources/shaders/default_env_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; uniform samplerCube Texture; varying vec3 dir; diff --git a/resources/shaders/spark.fragment b/resources/shaders/default_texture_desktop.fragment similarity index 100% rename from resources/shaders/spark.fragment rename to resources/shaders/default_texture_desktop.fragment diff --git a/resources/shaders/default_texture_web.fragment b/resources/shaders/default_texture_web.fragment index f59b4f7..1c3c571 100644 --- a/resources/shaders/default_texture_web.fragment +++ b/resources/shaders/default_texture_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; uniform sampler2D Texture; varying vec2 texCoord; diff --git a/resources/shaders/default_web.fragment b/resources/shaders/default_web.fragment index 7548e9e..80c01d7 100644 --- a/resources/shaders/default_web.fragment +++ b/resources/shaders/default_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; uniform sampler2D Texture; varying vec2 texCoord; diff --git a/resources/shaders/env_sky_desktop.fragment b/resources/shaders/env_sky_desktop.fragment new file mode 100644 index 0000000..18a1169 --- /dev/null +++ b/resources/shaders/env_sky_desktop.fragment @@ -0,0 +1,37 @@ +//precisionmediump float; +uniform samplerCube Texture; +uniform float skyPercent; +uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока +uniform vec3 uSkyColor; + +varying vec3 vViewDir; + +void main() { + vec3 viewDir = normalize(vViewDir); + + // 1. Получаем звезды + vec4 starsColor = textureCube(Texture, viewDir); + + // 2. Цвета атмосферы + vec3 dayColor = uSkyColor; + vec3 nightColor = vec3(0.01, 0.01, 0.04); + + // 3. Теперь всё небо окрашивается одинаково в зависимости от положения игрока + // Мы плавно смешиваем ночной и дневной купол + vec3 atmosphericColor = mix(nightColor, dayColor, uPlayerLightFactor); + + // 4. Логика видимости звезд + // Звезды видны в космосе (skyPercent=0) + // И в атмосфере, если сейчас ночь (uPlayerLightFactor -> 0) + float starsVisibility = (1.0 - skyPercent) + (skyPercent * (1.0 - uPlayerLightFactor)); + + // Делаем звезды чуть ярче на ночном небе + starsVisibility = pow(starsVisibility, 1.5); + + // 5. Итоговый цвет + // Добавляем слой атмосферы к звездам + vec3 skyLayer = atmosphericColor * skyPercent; + vec3 finalColor = (starsColor.rgb * starsVisibility) + skyLayer; + + gl_FragColor = vec4(finalColor, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/env_sky_web.fragment b/resources/shaders/env_sky_web.fragment index 18a1169..971d78c 100644 --- a/resources/shaders/env_sky_web.fragment +++ b/resources/shaders/env_sky_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; uniform samplerCube Texture; uniform float skyPercent; uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока diff --git a/resources/shaders/planet_bake_desktop.fragment b/resources/shaders/planet_bake_desktop.fragment new file mode 100644 index 0000000..cd404ba --- /dev/null +++ b/resources/shaders/planet_bake_desktop.fragment @@ -0,0 +1,14 @@ +//precisionmediump float; +varying vec2 TexCoord; +varying float vHeight; + +uniform sampler2D Texture; + +void main() +{ + vec4 stoneColor = texture2D(Texture, TexCoord); + + gl_FragColor = vec4(stoneColor.rgb, vHeight); + //gl_FragColor = vec4(vHeight, vHeight, vHeight, vHeight); + //gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/planet_bake_web.fragment b/resources/shaders/planet_bake_web.fragment index cd404ba..c08c976 100644 --- a/resources/shaders/planet_bake_web.fragment +++ b/resources/shaders/planet_bake_web.fragment @@ -1,4 +1,4 @@ -//precisionmediump float; +precision mediump float; varying vec2 TexCoord; varying float vHeight; diff --git a/resources/shaders/planet_land_desktop.fragment b/resources/shaders/planet_land_desktop.fragment new file mode 100644 index 0000000..a569efa --- /dev/null +++ b/resources/shaders/planet_land_desktop.fragment @@ -0,0 +1,116 @@ +//precisionhighp float; +varying vec2 TexCoord; +varying vec3 vViewDirTangent; +varying vec3 Color; +varying vec3 worldPosition; +varying vec3 vWorldNormal; + +uniform sampler2D Texture; +uniform sampler2D BakedTexture; +uniform float uHeightScale; +uniform float uDistanceToPlanetSurface; +uniform float uCurrentZFar; + +uniform vec3 uViewPos; +const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман + + +uniform vec3 uLightDirWorld; // Направление ЛУЧЕЙ (1, -1, -1) +uniform float uPlayerLightFactor; +uniform vec3 uPlayerDirWorld; + +void main() { + // --- 1. Расчет освещения поверхности --- + // Направление НА источник света + vec3 lightToSource = normalize(-uLightDirWorld); + + vec3 fragmentDir = normalize(vWorldNormal); + + // Используем vNormal (нормаль в мировом пространстве, так как vPosition тоже в мировом) + // Важно: если vNormal не нормализована в вершинном, делаем это здесь + vec3 normal = normalize(fragmentDir); // На планете-сфере нормаль совпадает с направлением от центра + + float diff = max(dot(normal, lightToSource), 0.0); + float ambient = 0.3; // Базовая освещенность ночной стороны + float surfaceLightIntensity = diff + ambient; + + // --- 2. Динамический цвет тумана --- + // Синхронизируем туман с атмосферой: днем голубой, ночью темно-синий + vec3 dayFog = vec3(0.0, 0.5, 1.0); + vec3 nightFog = vec3(0.01, 0.01, 0.04); + vec3 dynamicFogColor = mix(nightFog, dayFog, uPlayerLightFactor); + + // --- 3. Основная логика текстур (твой код) --- + vec3 viewDir = normalize(vViewDirTangent); + float height = texture2D(Texture, TexCoord).a; + vec2 p = vec2(viewDir.x, -viewDir.y) * (height * uHeightScale); + vec2 finalTexCoord = TexCoord + p; + + float realDist = distance(worldPosition, uViewPos); + float textureMixFactor = clamp((2000.0 - realDist) / 500.0, 0.0, 1.0); + + vec4 bakedTextureColor = texture2D(BakedTexture, finalTexCoord); + vec4 textureColor = texture2D(Texture, TexCoord); + vec4 textureFlavoredColor = vec4(textureColor.rgb * Color, 1.0); + + if (bakedTextureColor.x < 0.01 && bakedTextureColor.y < 0.01 && bakedTextureColor.z < 0.01) { + textureMixFactor = 1.0; + } + + vec4 finalColor = textureMixFactor * textureFlavoredColor + (1.0 - textureMixFactor) * bakedTextureColor; + + // --- 4. ПРИМЕНЕНИЕ ОСВЕЩЕНИЯ --- + // Умножаем результат текстурирования на освещенность + finalColor.rgb *= surfaceLightIntensity; + + // --- 5. Расчет тумана (с использованием динамического цвета) --- + float h = uDistanceToPlanetSurface; + float fogStart, fogEnd; + + if (h >= 1000.0) { + gl_FragColor = vec4(finalColor.rgb, 1.0); + } + else + { + if (h > 100.0) { + // Нормализуем высоту от 100 до 1000 (t: 0 -> 1) + float t = (h - 100.0) / 900.0; + + // На высоте 100 туман начинается со 100. + // К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает. + fogStart = mix(1500.0, 15000.0, t); + + // Держим ширину зоны тумана в пределах 400-600 единиц + float fogRange = mix(1000.0, 6000.0, t); + fogEnd = fogStart + fogRange; + } + else if (h > 40.0) { + float t = (h - 40.0) / 60.0; + + // На высоте 100 туман начинается со 100. + // К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает. + fogStart = mix(800.0, 1500.0, t); + + fogEnd = mix(1000.0, 2500.0, t); + } + else if (h > 20.0) { + float t = (h - 20.0) / 20.0; + fogStart = mix(200.0, 800.0, t); + fogEnd = mix(500.0, 1000.0, t); + } + else { + // Минимумы при h = 0: start 25, end 125 + float t = clamp(h / 20.0, 0.0, 1.0); + fogStart = mix(100.0, 200.0, t); + fogEnd = mix(250.0, 500.0, t); + } + + float fogRange = max(fogEnd - fogStart, 1.0); + float fogFactor = clamp((realDist - fogStart) / fogRange, 0.0, 1.0); + + // Смешиваем с динамическим цветом тумана + vec3 mixedColor = mix(finalColor.rgb, dynamicFogColor, fogFactor); + + gl_FragColor = vec4(mixedColor, 1.0); + } +} \ No newline at end of file diff --git a/resources/shaders/planet_land_web.fragment b/resources/shaders/planet_land_web.fragment index a569efa..8c4b00d 100644 --- a/resources/shaders/planet_land_web.fragment +++ b/resources/shaders/planet_land_web.fragment @@ -1,4 +1,4 @@ -//precisionhighp float; +precision highp float; varying vec2 TexCoord; varying vec3 vViewDirTangent; varying vec3 Color; @@ -11,7 +11,7 @@ uniform float uHeightScale; uniform float uDistanceToPlanetSurface; uniform float uCurrentZFar; -uniform vec3 uViewPos; +uniform highp vec3 uViewPos; const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман diff --git a/resources/shaders/planet_stone_desktop.fragment b/resources/shaders/planet_stone_desktop.fragment new file mode 100644 index 0000000..e394f56 --- /dev/null +++ b/resources/shaders/planet_stone_desktop.fragment @@ -0,0 +1,102 @@ +//precisionhighp float; +// planetStone фрагментный шейдер + +varying vec2 TexCoord; +varying vec3 worldPosition; +varying vec3 vWorldNormal; + +uniform sampler2D Texture; +uniform float uDistanceToPlanetSurface; +uniform vec3 uViewPos; +uniform vec3 uLightDirWorld; +uniform float uPlayerLightFactor; + +void main() +{ + // --- 1. Подготовка векторов --- + vec3 normal = normalize(vWorldNormal); + vec3 lightToSource = normalize(-uLightDirWorld); + + // Вектор от центра планеты к камню (нормаль самой поверхности планеты под камнем) + // Предполагаем, что центр планеты в (0,0,0) + vec3 surfaceNormal = normalize(worldPosition); + + // --- 2. Расчет Shadow Mask --- + // Проверяем, освещена ли точка планеты, на которой стоит камень + float shadowMask = clamp(dot(surfaceNormal, lightToSource) * 5.0, 0.0, 1.0); + // Коэффициент 5.0 делает переход на терминаторе более четким + + // --- 3. Освещение камня --- + float diff = max(dot(normal, lightToSource), 0.0); + + // Применяем shadowMask к диффузной составляющей + // Если точка на планете в тени, то прямой свет (diff) обнуляется + float ambient = 0.3; + float lightIntensity = (diff * shadowMask); + + lightIntensity *= mix(0.2, 1.0, shadowMask); + + lightIntensity += ambient; + + // --- 4. Остальная логика (цвета и туман) --- + vec3 dayFog = vec3(0.0, 0.5, 1.0); + vec3 nightFog = vec3(0.01, 0.01, 0.04); + vec3 dynamicFogColor = mix(nightFog, dayFog, uPlayerLightFactor); + + vec4 textureColor = texture2D(Texture, TexCoord); + vec3 litColor = textureColor.rgb * lightIntensity; + + float realDist = distance(worldPosition, uViewPos); + float h = uDistanceToPlanetSurface; + float alphaFactor = clamp((2000.0 - realDist) / 500.0, 0.0, 1.0); + + float fogStart, fogEnd; + + // --- Логика расчета границ тумана --- + if (h >= 1000.0) { + gl_FragColor = vec4(litColor, alphaFactor); + } + else + { + if (h > 100.0) { + // Нормализуем высоту от 100 до 1000 (t: 0 -> 1) + float t = (h - 100.0) / 900.0; + + // На высоте 100 туман начинается со 100. + // К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает. + fogStart = mix(1500.0, 15000.0, t); + + // Держим ширину зоны тумана в пределах 400-600 единиц + float fogRange = mix(1000.0, 6000.0, t); + fogEnd = fogStart + fogRange; + } + else if (h > 40.0) { + float t = (h - 40.0) / 60.0; + + // На высоте 100 туман начинается со 100. + // К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает. + fogStart = mix(800.0, 1500.0, t); + + fogEnd = mix(1000.0, 2500.0, t); + } + else if (h > 20.0) { + float t = (h - 20.0) / 20.0; + fogStart = mix(200.0, 800.0, t); + fogEnd = mix(500.0, 1000.0, t); + } + else { + // Минимумы при h = 0: start 25, end 125 + float t = clamp(h / 20.0, 0.0, 1.0); + fogStart = mix(100.0, 200.0, t); + fogEnd = mix(250.0, 500.0, t); + } + + // Расчет фактора тумана + float fogRange = max(fogEnd - fogStart, 1.0); + float fogFactor = clamp((realDist - fogStart) / fogRange, 0.0, 1.0); + + // Смешивание освещенного камня с динамическим туманом + vec3 mixedColor = mix(litColor, dynamicFogColor, fogFactor); + gl_FragColor = vec4(mixedColor, alphaFactor); + } +} \ No newline at end of file diff --git a/resources/shaders/planet_stone_web.fragment b/resources/shaders/planet_stone_web.fragment index e394f56..64333e4 100644 --- a/resources/shaders/planet_stone_web.fragment +++ b/resources/shaders/planet_stone_web.fragment @@ -1,4 +1,4 @@ -//precisionhighp float; +precision highp float; // planetStone фрагментный шейдер varying vec2 TexCoord; diff --git a/resources/shaders/spark_desktop.fragment b/resources/shaders/spark_desktop.fragment new file mode 100644 index 0000000..f59b4f7 --- /dev/null +++ b/resources/shaders/spark_desktop.fragment @@ -0,0 +1,9 @@ +//precisionmediump float; +uniform sampler2D Texture; +varying vec2 texCoord; + +void main() +{ + vec4 color = texture2D(Texture,texCoord).rgba; + gl_FragColor = color; +} \ No newline at end of file diff --git a/resources/shaders/spark_web.fragment b/resources/shaders/spark_web.fragment new file mode 100644 index 0000000..1c3c571 --- /dev/null +++ b/resources/shaders/spark_web.fragment @@ -0,0 +1,9 @@ +precision mediump float; +uniform sampler2D Texture; +varying vec2 texCoord; + +void main() +{ + vec4 color = texture2D(Texture,texCoord).rgba; + gl_FragColor = color; +} \ No newline at end of file diff --git a/resources/shaders/text2d.vertex b/resources/shaders/text2d.vertex index 7483062..76c68ae 100644 --- a/resources/shaders/text2d.vertex +++ b/resources/shaders/text2d.vertex @@ -1,12 +1,11 @@ -#version 330 core -in vec2 vPosition; -in vec2 vTexCoord; +attribute vec2 vPosition; +attribute vec2 vTexCoord; -out vec2 TexCoord; +varying vec2 TexCoord; uniform mat4 uProjection; void main() { TexCoord = vTexCoord; - gl_Position = uProjection * vec4(vPosition.xy, 0.0, 1.0); + gl_Position = uProjection * vec4(vPosition, 0.0, 1.0); } \ No newline at end of file diff --git a/resources/shaders/text2d.fragment b/resources/shaders/text2d_desktop.fragment similarity index 100% rename from resources/shaders/text2d.fragment rename to resources/shaders/text2d_desktop.fragment diff --git a/resources/shaders/text2d_web.fragment b/resources/shaders/text2d_web.fragment new file mode 100644 index 0000000..fadd1aa --- /dev/null +++ b/resources/shaders/text2d_web.fragment @@ -0,0 +1,19 @@ +precision mediump float; + +varying vec2 TexCoord; + +uniform sampler2D uText; +uniform vec4 uColor; +//uniform float uOutlineWidth; // Убрано дефолтное значение + +void main() { + float dist = texture2D(uText, TexCoord).r; + + float uOutlineWidth = 0.7; + + float outline = smoothstep(0.5 - uOutlineWidth, 0.5 + uOutlineWidth, dist); + float text = smoothstep(0.5, 0.6, dist); // 0.5 + 0.1 вычислено заранее + float glow = exp(-pow(dist - 0.5, 2.0) / 0.02); + + gl_FragColor = vec4(uColor.rgb * (text + glow * 0.6), uColor.a * outline); +} \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index ec7a61d..5ce7e6c 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -26,7 +26,8 @@ namespace ZL #ifdef EMSCRIPTEN const char* CONST_ZIP_FILE = "resources.zip"; #else - const char* CONST_ZIP_FILE = ""; + const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip"; + //const char* CONST_ZIP_FILE = ""; #endif static bool g_exitBgAnimating = false; @@ -245,8 +246,10 @@ namespace ZL ZL::BindOpenGlFunctions(); ZL::CheckGlError(); - //#ifndef SIMPLIFIED + + +#ifdef EMSCRIPTEN renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); @@ -254,7 +257,18 @@ namespace ZL renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE); +#else + renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE); +#endif /*#else renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); @@ -450,10 +464,10 @@ namespace ZL } boxAlive.resize(boxCoordsArr.size(), true); - + ZL::CheckGlError(); textRenderer = std::make_unique(); - textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32); - + textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE); + ZL::CheckGlError(); boxLabels.clear(); boxLabels.reserve(boxCoordsArr.size()); for (size_t i = 0; i < boxCoordsArr.size(); ++i) { diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index 7d317bb..05f686c 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -127,7 +127,7 @@ namespace ZL { void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) { if (!configured) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 1!"); } if (getActiveParticleCount() == 0) { @@ -135,7 +135,7 @@ namespace ZL { } if (!texture) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 2!"); } prepareDrawData(); @@ -183,7 +183,7 @@ namespace ZL { void SparkEmitter::update(float deltaTimeMs) { if (!configured) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 3!"); } auto currentTime = std::chrono::steady_clock::now(); @@ -233,7 +233,7 @@ namespace ZL { void SparkEmitter::emit() { if (!configured) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 4!"); } if (emissionPoints.empty()) return; @@ -287,7 +287,7 @@ namespace ZL { void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) { if (!configured) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 5!"); } particle.velocity = getRandomVelocity(emitterIndex); @@ -303,7 +303,7 @@ namespace ZL { Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) { if (!configured) { - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 6!"); } static std::random_device rd; @@ -359,14 +359,14 @@ namespace ZL { auto buf = readFileFromZIP(path, zipFile); if (buf.empty()) { std::cerr << "Failed to read JSON from zip: " << path << " in " << zipFile << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 7!"); } content.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { std::cerr << "Failed to open JSON file: " << path << " : " << e.what() << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 8!"); } json j; @@ -376,14 +376,14 @@ namespace ZL { } catch (const std::exception& e) { std::cerr << "JSON parse error: " << e.what() << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 9!"); } std::cout << "JSON content: " << j.dump(2) << std::endl; auto requireKey = [&](const std::string& key) { if (!j.contains(key)) { std::cerr << "Missing required key in spark JSON: " << key << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 9!"); } }; @@ -449,12 +449,12 @@ namespace ZL { } else { std::cerr << "Emission points parsed but empty" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 10!"); } } else { std::cerr << "emissionPoints is not array" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 11!"); } // Ranges @@ -466,7 +466,7 @@ namespace ZL { } else { std::cerr << "speedRange missing or invalid" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 12!"); } if (j.contains("zSpeedRange") && j["zSpeedRange"].is_array()) { @@ -477,7 +477,7 @@ namespace ZL { } else { std::cerr << "zSpeedRange missing or invalid" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 13!"); } if (j.contains("scaleRange") && j["scaleRange"].is_array()) { @@ -488,7 +488,7 @@ namespace ZL { } else { std::cerr << "scaleRange missing or invalid" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 14!"); } if (j.contains("lifeTimeRange") && j["lifeTimeRange"].is_array()) { @@ -499,29 +499,33 @@ namespace ZL { } else { std::cerr << "lifeTimeRange missing or invalid" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 15!"); } // texture if (j.contains("texture") && j["texture"].is_string()) { std::string texPath = j["texture"].get(); - std::cout << "Loading texture: " << texPath << std::endl; + std::cout << "Loading texture: " << texPath << " From zip file: " << zipFile << std::endl; try { + std::cout << "Loading texture step 1" << std::endl; auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str()); + std::cout << "Loading texture step 2" << std::endl; texture = std::make_shared(texData); std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl; } catch (const std::exception& e) { + std::cout << "Texture load error: " << e.what() << std::endl; std::cerr << "Texture load error: " << e.what() << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 16!"); } } else { std::cerr << "No texture specified or invalid type in JSON" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 17!"); } + std::cout << "Working with shaders 1" << std::endl; // shaders if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) { shaderProgramName = j["shaderProgramName"].get(); @@ -529,9 +533,12 @@ namespace ZL { } else { std::cerr << "shaderProgramName missing or invalid" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 18!"); } + + std::cout << "Working with shaders 2" << std::endl; + /* if (j.contains("vertexShader") && j.contains("fragmentShader") && j["vertexShader"].is_string() && j["fragmentShader"].is_string()) { std::string v = j["vertexShader"].get(); @@ -544,9 +551,9 @@ namespace ZL { } catch (const std::exception& e) { std::cerr << "Shader load error: " << e.what() << std::endl; - throw std::runtime_error("Failed to load spark emitter config file!"); + throw std::runtime_error("Failed to load spark emitter config file 19!"); } - } + }*/ drawDataDirty = true; configured = true; diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 86a4fb5..70f1de4 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -247,6 +247,7 @@ namespace ZL { if (!t.contains(key) || !t[key].is_string()) return nullptr; std::string path = t[key].get(); try { + std::cout << "UiManager: loading texture for button '" << btn->name << "' : " << path << " Zip file: " << zipFile << std::endl; auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str()); return std::make_shared(data); } @@ -276,6 +277,7 @@ namespace ZL { if (!t.contains(key) || !t[key].is_string()) return nullptr; std::string path = t[key].get(); try { + std::cout << "UiManager: --loading texture for button '" << "' : " << path << " Zip file: " << zipFile << std::endl; auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str()); return std::make_shared(data); } @@ -345,7 +347,7 @@ namespace ZL { if (j.contains("centered")) tv->centered = j["centered"].get(); tv->textRenderer = std::make_unique(); - if (!tv->textRenderer->init(renderer, tv->fontPath, tv->fontSize)) { + if (!tv->textRenderer->init(renderer, tv->fontPath, tv->fontSize, zipFile)) { std::cerr << "Failed to init TextRenderer for TextView: " << tv->name << std::endl; } diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h index 8d72353..ba2fbe7 100644 --- a/src/network/LocalClient.h +++ b/src/network/LocalClient.h @@ -33,5 +33,10 @@ namespace ZL { std::vector getPendingRespawns() override { return {}; } + + std::vector getPendingBoxDestructions() override + { + return {}; + } }; } \ No newline at end of file diff --git a/src/render/TextRenderer.cpp b/src/render/TextRenderer.cpp index e081bef..067086e 100644 --- a/src/render/TextRenderer.cpp +++ b/src/render/TextRenderer.cpp @@ -1,4 +1,4 @@ -#include "render/TextRenderer.h" +#include "render/TextRenderer.h" #include #include FT_FREETYPE_H @@ -11,53 +11,87 @@ namespace ZL { TextRenderer::~TextRenderer() { - for (auto& kv : glyphs) { + /*for (auto& kv : glyphs) { if (kv.second.texID) glDeleteTextures(1, &kv.second.texID); - } + }*/ glyphs.clear(); + textMesh.positionVBO.reset(); + /* if (vbo) glDeleteBuffers(1, &vbo); // if (vao) glDeleteVertexArrays(1, &vao); vao = 0; - vbo = 0; + vbo = 0;*/ } -bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixelSize) +bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename) { r = &renderer; +#ifdef EMSCRIPTEN r->shaderManager.AddShaderFromFiles(shaderName, "resources/shaders/text2d.vertex", - "resources/shaders/text2d.fragment", - "" // ZIP пустой + "resources/shaders/text2d_web.fragment", + zipfilename ); +#else + r->shaderManager.AddShaderFromFiles(shaderName, + "resources/shaders/text2d.vertex", + "resources/shaders/text2d_desktop.fragment", + zipfilename + ); +#endif + ZL::CheckGlError(); + if (!loadGlyphs(ttfPath, pixelSize, zipfilename)) return false; + ZL::CheckGlError(); - if (!loadGlyphs(ttfPath, pixelSize)) return false; - + textMesh.data.PositionData.resize(6, Eigen::Vector3f(0, 0, 0)); + textMesh.RefreshVBO(); // VAO/VBO для 6 вершин (2 треугольника) * (pos.xy + uv.xy) = 4 float // glGenVertexArrays(1, &vao); - glGenBuffers(1, &vbo); + /* glGenBuffers(1, &vbo); //glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); - - + */ + ZL::CheckGlError(); return true; } -bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize) +bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename) { + // 1. Загружаем сырые данные из ZIP + std::vector fontData; + try { + fontData = !zipfilename.empty() + ? readFileFromZIP(ttfPath, zipfilename) + : readFile(ttfPath); // Предполагаем наличие функции readFile + } + catch (const std::exception& e) { + std::cerr << "TextRenderer: failed to read font data: " << e.what() << "\n"; + return false; + } + + if (fontData.empty()) return false; + FT_Library ft; if (FT_Init_FreeType(&ft)) { std::cerr << "FreeType: FT_Init_FreeType failed\n"; return false; } + // 2. Инициализируем Face из памяти FT_Face face; - if (FT_New_Face(ft, ttfPath.c_str(), 0, &face)) { - std::cerr << "FreeType: failed to load font: " << ttfPath << "\n"; + // Важно: передаем указатель на буфер и его размер + if (FT_New_Memory_Face(ft, + reinterpret_cast(fontData.data()), + static_cast(fontData.size()), + 0, + &face)) + { + std::cerr << "FreeType: failed to load font from memory: " << ttfPath << "\n"; FT_Done_FreeType(ft); return false; } @@ -67,52 +101,50 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize) glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glyphs.clear(); + // Проходим по стандартным ASCII символам for (unsigned char c = 32; c < 128; ++c) { - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { - continue; - } + + FT_Load_Char(face, c, FT_LOAD_RENDER); - unsigned int tex; - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D( - GL_TEXTURE_2D, - 0, - GL_RED, - face->glyph->bitmap.width, - face->glyph->bitmap.rows, - 0, - GL_RED, - GL_UNSIGNED_BYTE, - face->glyph->bitmap.buffer - ); + TextureDataStruct glyphData; + glyphData.width = face->glyph->bitmap.width; + glyphData.height = face->glyph->bitmap.rows; + glyphData.format = TextureDataStruct::R8; + glyphData.mipmap = TextureDataStruct::NONE; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // Копируем буфер FreeType в вектор данных + size_t dataSize = glyphData.width * glyphData.height; + glyphData.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize); - GlyphInfo g; - g.texID = tex; + // Теперь создание текстуры — это одна строка! + auto tex = std::make_shared(glyphData); + + GlyphInfo g; + g.texture = tex; g.size = Eigen::Vector2f((float)face->glyph->bitmap.width, (float)face->glyph->bitmap.rows); g.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top); + // Advance во FreeType измеряется в 1/64 пикселя g.advance = (unsigned int)face->glyph->advance.x; glyphs.emplace((char)c, g); } + // Очистка FT_Done_Face(face); FT_Done_FreeType(ft); + // После FT_Done_Face данные из fontData больше не нужны, + // вектор сам очистится при выходе из функции. + glBindTexture(GL_TEXTURE_2D, 0); return true; } void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array color) { - if (!r) return; + if (!r || text.empty()) return; - // Считаем ширину строки для центрирования + // 1. Считаем ширину для центрирования float totalW = 0.0f; if (centered) { for (char ch : text) { @@ -123,35 +155,8 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca x -= totalW * 0.5f; } - r->shaderManager.PushShader(shaderName); - - // Орто-проекция в пикселях: (0..W, 0..H) - float W = (float)Environment::width; - float H = (float)Environment::height; - - Eigen::Matrix4f proj = Eigen::Matrix4f::Identity(); - proj(0,0) = 2.0f / W; - proj(1,1) = 2.0f / H; - proj(0,3) = -1.0f; - proj(1,3) = -1.0f; - - // uProjection - r->RenderUniformMatrix4fv("uProjection", false, proj.data()); - r->RenderUniform1i("uText", 0); - - r->RenderUniform4fv("uColor", color.data()); - - glActiveTexture(GL_TEXTURE0); - // glBindVertexArray(vao); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - - r->EnableVertexAttribArray("vPosition"); - r->EnableVertexAttribArray("vTexCoord"); - - r->VertexAttribPointer2fv("vPosition", 4 * sizeof(float), (const char*)0); - r->VertexAttribPointer2fv("vTexCoord", 4 * sizeof(float), (const char*)(2 * sizeof(float))); - + // 2. Подготовка данных (аналог CreateRect2D, но для всей строки) + VertexDataStruct textData; float penX = x; float penY = y; @@ -163,36 +168,79 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca float xpos = penX + g.bearing.x() * scale; float ypos = penY - (g.size.y() - g.bearing.y()) * scale; - float w = g.size.x() * scale; float h = g.size.y() * scale; - // 2 треугольника - float verts[6][4] = { - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos, ypos, 0.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, + // Добавляем 2 треугольника (6 вершин) для текущего символа + // Координаты Z ставим 0.0f, так как это 2D + textData.PositionData.push_back({ xpos, ypos + h, 0.0f }); + textData.PositionData.push_back({ xpos, ypos, 0.0f }); + textData.PositionData.push_back({ xpos + w, ypos, 0.0f }); + textData.PositionData.push_back({ xpos, ypos + h, 0.0f }); + textData.PositionData.push_back({ xpos + w, ypos, 0.0f }); + textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f }); - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - { xpos + w, ypos + h, 1.0f, 0.0f } - }; - - glBindTexture(GL_TEXTURE_2D, g.texID); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts); - glDrawArrays(GL_TRIANGLES, 0, 6); + // UV-координаты (здесь есть нюанс с атласом, ниже поясню) + textData.TexCoordData.push_back({ 0.0f, 0.0f }); + textData.TexCoordData.push_back({ 0.0f, 1.0f }); + textData.TexCoordData.push_back({ 1.0f, 1.0f }); + textData.TexCoordData.push_back({ 0.0f, 0.0f }); + textData.TexCoordData.push_back({ 1.0f, 1.0f }); + textData.TexCoordData.push_back({ 1.0f, 0.0f }); penX += (g.advance >> 6) * scale; } - // cleanup - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + // 3. Обновляем VBO через наш стандартный механизм + // Примечание: для текста лучше использовать GL_DYNAMIC_DRAW, + // но RefreshVBO сейчас жестко зашит на GL_STATIC_DRAW. + // Для UI это обычно не критично, если строк не тысячи. + textMesh.AssignFrom(textData); + + // 4. Рендеринг + r->shaderManager.PushShader(shaderName); + + // Матрица проекции (экрана) + float W = (float)Environment::width; + float H = (float)Environment::height; + Eigen::Matrix4f proj = Eigen::Matrix4f::Identity(); + proj(0, 0) = 2.0f / W; + proj(1, 1) = 2.0f / H; + proj(0, 3) = -1.0f; + proj(1, 3) = -1.0f; + + r->RenderUniformMatrix4fv("uProjection", false, proj.data()); + r->RenderUniform1i("uText", 0); + r->RenderUniform4fv("uColor", color.data()); + + glActiveTexture(GL_TEXTURE0); + + // ВНИМАНИЕ: Так как у тебя каждый символ — это отдельная текстура, + // нам всё равно придется делать glDrawArrays в цикле, ЛИБО использовать атлас. + // Если оставляем текущую систему с разными текстурами: + + r->EnableVertexAttribArray("vPosition"); + r->EnableVertexAttribArray("vTexCoord"); + + for (size_t i = 0; i < text.length(); ++i) { + auto it = glyphs.find(text[i]); + if (it == glyphs.end()) continue; + + glBindTexture(GL_TEXTURE_2D, it->second.texture->getTexID()); + + // Отрисовываем по 6 вершин за раз + // Нам нужно вручную биндить VBO, так как DrawVertexRenderStruct рисует всё сразу + glBindBuffer(GL_ARRAY_BUFFER, textMesh.positionVBO->getBuffer()); + r->VertexAttribPointer3fv("vPosition", 0, (const char*)(i * 6 * sizeof(Vector3f))); + + glBindBuffer(GL_ARRAY_BUFFER, textMesh.texCoordVBO->getBuffer()); + r->VertexAttribPointer2fv("vTexCoord", 0, (const char*)(i * 6 * sizeof(Vector2f))); + + glDrawArrays(GL_TRIANGLES, 0, 6); + } r->DisableVertexAttribArray("vPosition"); r->DisableVertexAttribArray("vTexCoord"); - r->shaderManager.PopShader(); } - } // namespace ZL \ No newline at end of file diff --git a/src/render/TextRenderer.h b/src/render/TextRenderer.h index 7064f7e..4f78efc 100644 --- a/src/render/TextRenderer.h +++ b/src/render/TextRenderer.h @@ -1,17 +1,18 @@ -#pragma once +#pragma once #include #include #include #include #include #include "render/Renderer.h" +#include "render/TextureManager.h" #include namespace ZL { struct GlyphInfo { - unsigned int texID = 0; // GL texture for glyph + std::shared_ptr texture; // Texture for glyph Eigen::Vector2f size; // glyph size in pixels Eigen::Vector2f bearing; // offset from baseline unsigned int advance = 0; // advance.x in 1/64 pixels @@ -22,19 +23,21 @@ public: TextRenderer() = default; ~TextRenderer(); - bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize); + bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename); void drawText(const std::string& text, float x, float y, float scale, bool centered, std::array color = { 1.f,1.f,1.f,1.f }); private: - bool loadGlyphs(const std::string& ttfPath, int pixelSize); + bool loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename); Renderer* r = nullptr; std::unordered_map glyphs; // OpenGL objects for a dynamic quad - unsigned int vao = 0; - unsigned int vbo = 0; + //unsigned int vao = 0; + //unsigned int vbo = 0; + + VertexRenderStruct textMesh; std::string shaderName = "text2d"; }; diff --git a/src/render/TextureManager.cpp b/src/render/TextureManager.cpp index 9e9d878..e5373bf 100644 --- a/src/render/TextureManager.cpp +++ b/src/render/TextureManager.cpp @@ -1,4 +1,4 @@ -#include "render/TextureManager.h" +#include "render/TextureManager.h" #include "render/OpenGlExtensions.h" #ifdef PNG_ENABLED #include "png.h" @@ -11,48 +11,68 @@ namespace ZL Texture::Texture(const TextureDataStruct& texData) { width = texData.width; height = texData.height; - glGenTextures(1, &texID); - if (texID == 0) { - throw std::runtime_error("glGenTextures did not work"); + glBindTexture(GL_TEXTURE_2D, texID); + + GLint internalFormat; + GLenum externalFormat; + + switch (texData.format) { + case TextureDataStruct::R8: + // Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED + // Если используешь WebGL 2.0, можно оставить GL_RED +#if defined(EMSCRIPTEN) || defined(__ANDROID__) + internalFormat = GL_LUMINANCE; + externalFormat = GL_LUMINANCE; +#else + internalFormat = GL_RED; + externalFormat = GL_RED; +#endif + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // Исправлено: используем GL_TEXTURE_2D + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + break; + + case TextureDataStruct::RGB: + internalFormat = GL_RGB; + externalFormat = GL_RGB; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // RGB может быть не выровнен по 4 байта + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + break; + + default: // RGBA + internalFormat = GL_RGBA; + externalFormat = GL_RGBA; + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + break; } - glBindTexture(GL_TEXTURE_2D, texID); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, + static_cast(width), static_cast(height), + 0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data()); CheckGlError(); - // ��������� ���������� � ����������� �� ���������� ���� - if (texData.mipmap == TextureDataStruct::GENERATE) { - // ���������� ����������� ���������� ��� ���������� �������� + // 3. Фильтрация и Мип-мапы + // ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет! + if (texData.mipmap == TextureDataStruct::GENERATE && texData.format != TextureDataStruct::R8) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glGenerateMipmap(GL_TEXTURE_2D); } else { - // ������� �������� ���������� (����� ������� �� ��������) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - CheckGlError(); - - // �������� ������ �������� ������ (Level 0) - GLint format = (texData.bitSize == TextureDataStruct::BS_24BIT) ? GL_RGB : GL_RGBA; - - glTexImage2D(GL_TEXTURE_2D, 0, format, - static_cast(width), - static_cast(height), - 0, format, GL_UNSIGNED_BYTE, texData.data.data()); CheckGlError(); - - // ��������� ���-�����, ���� ��� ���������� - if (texData.mipmap == TextureDataStruct::GENERATE) { - glGenerateMipmap(GL_TEXTURE_2D); - CheckGlError(); - } + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } Texture::Texture(const std::array& texDataArray) { - // ��������, ��� ��� ����� ����� ���������� ������� width = texDataArray[0].width; height = texDataArray[0].height; @@ -73,36 +93,28 @@ namespace ZL CheckGlError(); - // ��������� ���������� ��� Cubemap + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // ���������� GL_LINEAR ��� MIN_FILTER, ��� ��� ������� ����� �� ������������ - // ���� �� �������������� ������� (e.g., GL_LINEAR_MIPMAP_LINEAR), ����� ���� �� ������� glGenerateMipmap. glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - // ������������ ��������� ������� ��� Cubemap glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - // GL_TEXTURE_WRAP_R �� �������������� � WebGL 1.0/OpenGL ES 2.0 � �������� ������. - // ������������ ��� ����� ������ ��� ���������� ��������. #ifndef EMSCRIPTEN #ifndef __ANDROID__ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); #endif #endif - CheckGlError(); // �������� ����� ��������� ���������� - - // �������� ������ ��� ������ �� 6 ������ - // GL_TEXTURE_CUBE_MAP_POSITIVE_X + i ���� �����: +X (0), -X (1), +Y (2), -Y (3), +Z (4), -Z (5) + CheckGlError(); + for (int i = 0; i < 6; ++i) { GLint internalFormat; GLenum format; - // � WebGL 1.0/OpenGL ES 2.0 ���������� ������ (internalFormat) - // ������ ������ ��������������� ������� ������ (format). - if (texDataArray[i].bitSize == TextureDataStruct::BS_24BIT) + + if (texDataArray[i].format == TextureDataStruct::RGB) { internalFormat = GL_RGB; // internalFormat format = GL_RGB; // format @@ -114,20 +126,19 @@ namespace ZL } glTexImage2D( - GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // ������� ����� - 0, // ������� MIP-�������� - internalFormat, // ���������� ������ (������ ��������� � ��������) + GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, + 0, + internalFormat, static_cast(width), static_cast(height), - 0, // ������� (������ 0) - format, // ������ �������� ������ - GL_UNSIGNED_BYTE, // ��� ������ - texDataArray[i].data.data() // ��������� �� ������ + 0, + format, + GL_UNSIGNED_BYTE, + texDataArray[i].data.data() ); CheckGlError(); } - // ������� �������� ��� ������� glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } @@ -173,7 +184,7 @@ namespace ZL texData.width = *reinterpret_cast(&fileArr[18]); texData.height = *reinterpret_cast(&fileArr[22]); - texData.bitSize = TextureDataStruct::BS_24BIT; + texData.format = TextureDataStruct::RGB; size_t dataSize = texData.width * texData.height * 3; @@ -222,7 +233,7 @@ namespace ZL texData.width = *reinterpret_cast(&fileArr[18]); texData.height = *reinterpret_cast(&fileArr[22]); - texData.bitSize = TextureDataStruct::BS_32BIT; + texData.format = TextureDataStruct::RGBA; size_t dataSize = texData.width * texData.height * 4; @@ -255,48 +266,33 @@ namespace ZL #ifdef PNG_ENABLED - // ��������� ��� �������� ������ � �����/������� � ������� ������� ������ struct png_data_t { const char* data; size_t size; size_t offset; }; - // ���������������� ������� ������ ��� libpng - // 'png_ptr' - ��������� �� ��������� png - // 'out_ptr' - ���� ���������� ����������� ������ - // 'bytes_to_read' - ������� ���� ����� ��������� void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) { - // �������� ��������� �� ���� ��������� png_data_t, ������� �� ���������� � ������� png_set_read_fn png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr); if (data->offset + bytes_to_read > data->size) { - // ������� ��������� ������, ��� ���� � �������. - // ������ ������ ����������� ������, �� ����� ������ ��������� ������� ��� ������� ������. - // � ���� ������ �� ������� ������ libpng. png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer."); - bytes_to_read = data->size - data->offset; // �������������, ����� ��������� ���������� + bytes_to_read = data->size - data->offset; } - // �������� ������ �� ������ ������� � ����� libpng std::memcpy(out_ptr, data->data + data->offset, bytes_to_read); - // ��������� �������� data->offset += bytes_to_read; } - // ���������������� ������� �������������� (�� �������, ����� ������������ nullptr) void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { - // ����� ����� ����������� ����������� �������������� - //throw std::runtime_error(); std::cout << "PNG Warning: " << warning_msg << std::endl; } - // ���������������� ������� ������ (����������� ��� setjmp) + void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { - // ����� ����� ����������� ����������� ������ + std::cout << "PNG Error: " << error_msg << std::endl; - // ����������� �������� longjmp ��� ������ �� �������� ������/������ PNG longjmp(png_jmpbuf(png_ptr), 1); } @@ -304,7 +300,6 @@ namespace ZL { TextureDataStruct texData; - // ��������� ��� ���������� ������� �� ������� png_data_t png_data = { fileArr.data(), fileArr.size(), 0 }; png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); @@ -318,21 +313,15 @@ namespace ZL throw std::runtime_error("Could not create PNG info structure"); } - // === ��������� ���������������� ������� ������ � ��������� ������ === - // 1. ��������� ����������� ������ � longjmp if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, &info, nullptr); throw std::runtime_error("Error during PNG read (longjmp was executed)"); } - // 2. ��������� ���������������� ������� ��� ��������� ������ � �������������� - // ������ nullptr � error_ptr � warning_ptr ����� �������� ��������� �� ���� ��������� ������, ���� ���������� png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn); - // 3. ��������� ���������������� ������� ������ � �������� �� ����� ��������� png_data png_set_read_fn(png, &png_data, user_read_data); - // =================================================================== - + png_read_info(png, info); texData.width = png_get_image_width(png, info); @@ -340,7 +329,6 @@ namespace ZL png_byte color_type = png_get_color_type(png, info); png_byte bit_depth = png_get_bit_depth(png, info); - // === ���� �������������� (�������� ��� ���������) === if (bit_depth == 16) png_set_strip_16(png); @@ -363,9 +351,7 @@ namespace ZL png_set_gray_to_rgb(png); png_read_update_info(png, info); - // ==================================================== - - // === ������ �������� (�������� ��� ���������) === + png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height); for (int y = 0; y < texData.height; y++) { row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); @@ -379,11 +365,11 @@ namespace ZL if (has_alpha) { - texData.bitSize = TextureDataStruct::BS_32BIT; + texData.format = TextureDataStruct::RGBA; } else { - texData.bitSize = TextureDataStruct::BS_24BIT; + texData.format = TextureDataStruct::RGB; } int channels = has_alpha ? 4 : 3; @@ -424,7 +410,6 @@ namespace ZL throw std::runtime_error("Could not read file data into memory"); } - // �������� ����� �������, ������� �������� � �������� ���� return CreateTextureDataFromPng(fileArr); } diff --git a/src/render/TextureManager.h b/src/render/TextureManager.h index b0d6f74..ea453b1 100644 --- a/src/render/TextureManager.h +++ b/src/render/TextureManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/OpenGlExtensions.h" #include "utils/Utils.h" @@ -15,23 +15,20 @@ namespace ZL size_t height; std::vector data; - enum BitSize { - BS_24BIT, - BS_32BIT + enum Format { + R8, // Для шрифтов (GL_RED) + RGB, // BS_24BIT + RGBA // BS_32BIT }; + - // ����� ������������ ��� ���������� ���-��������� enum MipmapType { - NONE, // ��� ���-����� (GL_LINEAR) - GENERATE // �������������� ��������� (GL_LINEAR_MIPMAP_LINEAR) + NONE, + GENERATE }; - BitSize bitSize; -/*#ifdef SIMPLIFIED - MipmapType mipmap = NONE; // �� ��������� �������� -#else*/ - MipmapType mipmap = GENERATE; // �� ��������� �������� -//#endif + Format format = RGBA; + MipmapType mipmap = GENERATE; }; class Texture { diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index e652ad6..3e7d863 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -1,4 +1,4 @@ -#include "utils/Utils.h" +#include "utils/Utils.h" #include #include #include @@ -98,44 +98,45 @@ namespace ZL std::vector readFileFromZIP(const std::string& filename, const std::string& zipfilename) { #ifdef EMSCRIPTEN - const std::string zipPath = zipfilename; - int zipErr; - zip_t* archive = zip_open(zipPath.c_str(), ZIP_RDONLY, &zipErr); - if (!archive) { - throw std::runtime_error("?????? ???????? ZIP: " + zipPath); - } + int zipErr = 0; + zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr); + if (!archive) { + std::cout << "Failed to open ZIP: " << zipfilename << " (error code " << zipErr << ")" << std::endl; + throw std::runtime_error("Failed to open ZIP: " + zipfilename); + } - std::string cleanFilename = filename; - if (cleanFilename.rfind("./", 0) == 0) { - cleanFilename = cleanFilename.substr(2); - } + std::string cleanFilename = filename; + if (cleanFilename.rfind("./", 0) == 0) cleanFilename = cleanFilename.substr(2); - std::cout << "???? ? ZIP: " << cleanFilename << std::endl; + zip_stat_t fileStat; + zip_stat_init(&fileStat); + if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0 || !(fileStat.valid & ZIP_STAT_SIZE)) { + zip_close(archive); + std::cout << "Failed to stat file in ZIP: " << cleanFilename << " in " << zipfilename << std::endl; + throw std::runtime_error("Can't stat file in ZIP: " + cleanFilename); + } - zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0); - if (!zipFile) { - zip_close(archive); - throw std::runtime_error("???? ?? ?????? ? ZIP: " + cleanFilename); - } + zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0); + if (!zipFile) { + zip_close(archive); + std::cout << "Failed to open file in ZIP: " << cleanFilename << std::endl; + throw std::runtime_error("Can't open file in ZIP: " + cleanFilename); + } - zip_stat_t fileStat; - if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0) { - zip_fclose(zipFile); - zip_close(archive); - throw std::runtime_error("?????? ?????? ZIP-??????????."); - } + std::vector fileData(static_cast(fileStat.size)); + zip_int64_t bytesRead = zip_fread(zipFile, fileData.data(), fileStat.size); - std::vector fileData; - fileData.resize(fileStat.size); + // Обязательно закрываем всё перед проверкой результата или throw + zip_fclose(zipFile); + zip_close(archive); - zip_fread(zipFile, fileData.data(), fileData.size()); + if (bytesRead < 0 || static_cast(bytesRead) != fileStat.size) { + std::cout << "Failed to read file from ZIP: " << cleanFilename << " (bytes read: " << bytesRead << ", expected: " << fileStat.size << ")" << std::endl; + throw std::runtime_error("Error reading data from ZIP: " + cleanFilename); + } - zip_fclose(zipFile); - zip_close(archive); - - return fileData; + return fileData; #else - // ??????? if (zipfilename.empty()) { std::cerr << "readFileFromZIP: zipfilename empty" << std::endl;