Adapting for web

This commit is contained in:
Vladislav Khorev committed 2026-02-08 22:38:24 +03:00
1 parent cf1ec1e76f
commit a59bcc0c4b
42 files changed
+843 -294

No files matched your search

+42 -12
View File
@@ -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"
+1 -3
View File
@@ -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"
}
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.
@@ -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));
}
@@ -1,4 +1,4 @@
//precisionhighp float;
precision highp float;
// Фрагментный шейдер:
uniform vec3 uColor;
@@ -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);
}
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
varying vec3 color;
void main()
@@ -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;
}
@@ -0,0 +1,8 @@
//precisionmediump float;
uniform samplerCube Texture;
varying vec3 dir;
void main(){
gl_FragColor = textureCube(Texture, normalize(dir));
}
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
uniform samplerCube Texture;
varying vec3 dir;
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
@@ -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);
}
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
uniform samplerCube Texture;
uniform float skyPercent;
uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока
@@ -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);
}
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionmediump float;
precision mediump float;
varying vec2 TexCoord;
varying float vHeight;
@@ -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);
}
}
+2 -2
View File
@@ -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); // Синий туман
@@ -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);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//precisionhighp float;
precision highp float;
// planetStone фрагментный шейдер
varying vec2 TexCoord;
+9
View File
@@ -0,0 +1,9 @@
//precisionmediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
gl_FragColor = color;
}
+9
View File
@@ -0,0 +1,9 @@
precision mediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
gl_FragColor = color;
}
+4 -5
View File
@@ -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);
}
File renamed without changes.
+19
View File
@@ -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);
}
+19 -5
View File
@@ -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<ZL::TextRenderer>();
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) {
+29 -22
View File
@@ -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::string>();
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<Texture>(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<std::string>();
@@ -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<std::string>();
@@ -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;
+3 -1
View File
@@ -247,6 +247,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
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<Texture>(data);
}
@@ -276,6 +277,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
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<Texture>(data);
}
@@ -345,7 +347,7 @@ namespace ZL {
if (j.contains("centered")) tv->centered = j["centered"].get<bool>();
tv->textRenderer = std::make_unique<TextRenderer>();
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;
}
+5
View File
@@ -33,5 +33,10 @@ namespace ZL {
std::vector<int> getPendingRespawns() override {
return {};
}
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override
{
return {};
}
};
}
+136 -88
View File
@@ -1,4 +1,4 @@
#include "render/TextRenderer.h"
#include "render/TextRenderer.h"
#include <ft2build.h>
#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<char> 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<const FT_Byte*>(fontData.data()),
static_cast<FT_Long>(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<Texture>(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<float, 4> 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
+9 -6
View File
@@ -1,17 +1,18 @@
#pragma once
#pragma once
#include <string>
#include <memory>
#include <vector>
#include <unordered_map>
#include <Eigen/Dense>
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include <array>
namespace ZL {
struct GlyphInfo {
unsigned int texID = 0; // GL texture for glyph
std::shared_ptr<Texture> 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<float, 4> 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<char, GlyphInfo> 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";
};
+67 -82
View File
@@ -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<GLsizei>(width), static_cast<GLsizei>(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<GLsizei>(width),
static_cast<GLsizei>(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<TextureDataStruct, 6>& 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<GLsizei>(width),
static_cast<GLsizei>(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<uint32_t*>(&fileArr[18]);
texData.height = *reinterpret_cast<uint32_t*>(&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<uint32_t*>(&fileArr[18]);
texData.height = *reinterpret_cast<uint32_t*>(&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);
}
+10 -13
View File
@@ -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<char> 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
{
+32 -31
View File
@@ -1,4 +1,4 @@
#include "utils/Utils.h"
#include "utils/Utils.h"
#include <cstring>
#include <iterator>
#include <vector>
@@ -98,44 +98,45 @@ namespace ZL
std::vector<char> 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<char> fileData(static_cast<size_t>(fileStat.size));
zip_int64_t bytesRead = zip_fread(zipFile, fileData.data(), fileStat.size);
std::vector<char> fileData;
fileData.resize(fileStat.size);
// Обязательно закрываем всё перед проверкой результата или throw
zip_fclose(zipFile);
zip_close(archive);
zip_fread(zipFile, fileData.data(), fileData.size());
if (bytesRead < 0 || static_cast<zip_uint64_t>(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;