Adapting for web

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

View File

@ -21,23 +21,52 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake")
set(SOURCES set(SOURCES
../src/main.cpp ../src/main.cpp
../src/Game.cpp ../src/Game.cpp
../src/Game.h
../src/Environment.cpp ../src/Environment.cpp
../src/BoneAnimatedModel.cpp ../src/Environment.h
../src/TextModel.cpp
../src/Projectile.cpp
../src/SparkEmitter.cpp
../src/UiManager.cpp
../src/render/Renderer.cpp ../src/render/Renderer.cpp
../src/render/Renderer.h
../src/render/ShaderManager.cpp ../src/render/ShaderManager.cpp
../src/render/ShaderManager.h
../src/render/TextureManager.cpp ../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.cpp
../src/render/OpenGlExtensions.h
../src/utils/Utils.cpp ../src/utils/Utils.cpp
../src/utils/TaskManager.cpp ../src/utils/Utils.h
../src/utils/Perlin.cpp ../src/SparkEmitter.cpp
../src/planet/PlanetData.cpp ../src/SparkEmitter.h
../src/planet/PlanetObject.cpp ../src/planet/PlanetObject.cpp
../src/planet/StoneObject.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}) add_executable(space-game001 ${SOURCES})
@ -68,7 +97,8 @@ set(EMSCRIPTEN_FLAGS
"-sUSE_SDL=2" "-sUSE_SDL=2"
"-sUSE_SDL_IMAGE=2" "-sUSE_SDL_IMAGE=2"
"-sUSE_LIBPNG=1" "-sUSE_LIBPNG=1"
"-sUSE_ZLIB=1" # Добавили zlib порт "-sUSE_ZLIB=1"
"-sUSE_SDL_TTF=2"
"-pthread" "-pthread"
"-sUSE_PTHREADS=1" "-sUSE_PTHREADS=1"
"-fexceptions" "-fexceptions"

View File

@ -16,7 +16,5 @@
"scaleRange": [0.8, 1.2], "scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0], "lifeTimeRange": [600.0, 1400.0],
"texture": "resources/spark.png", "texture": "resources/spark.png",
"shaderProgramName": "default", "shaderProgramName": "spark"
"vertexShader": "resources/shaders/spark.vertex",
"fragmentShader": "resources/shaders/spark.fragment"
} }

BIN
resources/main_menu/exit.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/lang.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/line.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/multi.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/single.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/subtitle.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/title.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/version.png (Stored with Git LFS)

Binary file not shown.

View File

@ -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));
}

View File

@ -1,4 +1,4 @@
//precisionhighp float; precision highp float;
// Фрагментный шейдер: // Фрагментный шейдер:
uniform vec3 uColor; uniform vec3 uColor;

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
varying vec3 color; varying vec3 color;
void main() void main()

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
//precisionmediump float;
uniform samplerCube Texture;
varying vec3 dir;
void main(){
gl_FragColor = textureCube(Texture, normalize(dir));
}

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
uniform samplerCube Texture; uniform samplerCube Texture;
varying vec3 dir; varying vec3 dir;

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
uniform sampler2D Texture; uniform sampler2D Texture;
varying vec2 texCoord; varying vec2 texCoord;

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
uniform sampler2D Texture; uniform sampler2D Texture;
varying vec2 texCoord; varying vec2 texCoord;

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
uniform samplerCube Texture; uniform samplerCube Texture;
uniform float skyPercent; uniform float skyPercent;
uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
//precisionmediump float; precision mediump float;
varying vec2 TexCoord; varying vec2 TexCoord;
varying float vHeight; varying float vHeight;

View File

@ -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);
}
}

View File

@ -1,4 +1,4 @@
//precisionhighp float; precision highp float;
varying vec2 TexCoord; varying vec2 TexCoord;
varying vec3 vViewDirTangent; varying vec3 vViewDirTangent;
varying vec3 Color; varying vec3 Color;
@ -11,7 +11,7 @@ uniform float uHeightScale;
uniform float uDistanceToPlanetSurface; uniform float uDistanceToPlanetSurface;
uniform float uCurrentZFar; uniform float uCurrentZFar;
uniform vec3 uViewPos; uniform highp vec3 uViewPos;
const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман

View File

@ -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);
}
}

View File

@ -1,4 +1,4 @@
//precisionhighp float; precision highp float;
// planetStone фрагментный шейдер // planetStone фрагментный шейдер
varying vec2 TexCoord; varying vec2 TexCoord;

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;
}

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;
}

View File

@ -1,12 +1,11 @@
#version 330 core attribute vec2 vPosition;
in vec2 vPosition; attribute vec2 vTexCoord;
in vec2 vTexCoord;
out vec2 TexCoord; varying vec2 TexCoord;
uniform mat4 uProjection; uniform mat4 uProjection;
void main() { void main() {
TexCoord = vTexCoord; TexCoord = vTexCoord;
gl_Position = uProjection * vec4(vPosition.xy, 0.0, 1.0); gl_Position = uProjection * vec4(vPosition, 0.0, 1.0);
} }

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);
}

View File

@ -26,7 +26,8 @@ namespace ZL
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
const char* CONST_ZIP_FILE = "resources.zip"; const char* CONST_ZIP_FILE = "resources.zip";
#else #else
const char* CONST_ZIP_FILE = ""; const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip";
//const char* CONST_ZIP_FILE = "";
#endif #endif
static bool g_exitBgAnimating = false; static bool g_exitBgAnimating = false;
@ -245,8 +246,10 @@ namespace ZL
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
ZL::CheckGlError(); ZL::CheckGlError();
//#ifndef SIMPLIFIED //#ifndef SIMPLIFIED
#ifdef EMSCRIPTEN
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); 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("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); 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("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("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("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 /*#else
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_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/default_env.vertex", "resources/shaders/default_env_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); boxAlive.resize(boxCoordsArr.size(), true);
ZL::CheckGlError();
textRenderer = std::make_unique<ZL::TextRenderer>(); 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.clear();
boxLabels.reserve(boxCoordsArr.size()); boxLabels.reserve(boxCoordsArr.size());
for (size_t i = 0; i < boxCoordsArr.size(); ++i) { for (size_t i = 0; i < boxCoordsArr.size(); ++i) {

View File

@ -127,7 +127,7 @@ namespace ZL {
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) { void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) {
if (!configured) { 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) { if (getActiveParticleCount() == 0) {
@ -135,7 +135,7 @@ namespace ZL {
} }
if (!texture) { 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(); prepareDrawData();
@ -183,7 +183,7 @@ namespace ZL {
void SparkEmitter::update(float deltaTimeMs) { void SparkEmitter::update(float deltaTimeMs) {
if (!configured) { 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(); auto currentTime = std::chrono::steady_clock::now();
@ -233,7 +233,7 @@ namespace ZL {
void SparkEmitter::emit() { void SparkEmitter::emit() {
if (!configured) { 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; if (emissionPoints.empty()) return;
@ -287,7 +287,7 @@ namespace ZL {
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) { void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
if (!configured) { 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); particle.velocity = getRandomVelocity(emitterIndex);
@ -303,7 +303,7 @@ namespace ZL {
Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) { Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) {
if (!configured) { 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; static std::random_device rd;
@ -359,14 +359,14 @@ namespace ZL {
auto buf = readFileFromZIP(path, zipFile); auto buf = readFileFromZIP(path, zipFile);
if (buf.empty()) { if (buf.empty()) {
std::cerr << "Failed to read JSON from zip: " << path << " in " << zipFile << std::endl; 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()); content.assign(buf.begin(), buf.end());
} }
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "Failed to open JSON file: " << path << " : " << e.what() << std::endl; 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; json j;
@ -376,14 +376,14 @@ namespace ZL {
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl; 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; std::cout << "JSON content: " << j.dump(2) << std::endl;
auto requireKey = [&](const std::string& key) { auto requireKey = [&](const std::string& key) {
if (!j.contains(key)) { if (!j.contains(key)) {
std::cerr << "Missing required key in spark JSON: " << key << std::endl; 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 { else {
std::cerr << "Emission points parsed but empty" << std::endl; 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 { else {
std::cerr << "emissionPoints is not array" << std::endl; 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 // Ranges
@ -466,7 +466,7 @@ namespace ZL {
} }
else { else {
std::cerr << "speedRange missing or invalid" << std::endl; 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()) { if (j.contains("zSpeedRange") && j["zSpeedRange"].is_array()) {
@ -477,7 +477,7 @@ namespace ZL {
} }
else { else {
std::cerr << "zSpeedRange missing or invalid" << std::endl; 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()) { if (j.contains("scaleRange") && j["scaleRange"].is_array()) {
@ -488,7 +488,7 @@ namespace ZL {
} }
else { else {
std::cerr << "scaleRange missing or invalid" << std::endl; 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()) { if (j.contains("lifeTimeRange") && j["lifeTimeRange"].is_array()) {
@ -499,29 +499,33 @@ namespace ZL {
} }
else { else {
std::cerr << "lifeTimeRange missing or invalid" << std::endl; 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 // texture
if (j.contains("texture") && j["texture"].is_string()) { if (j.contains("texture") && j["texture"].is_string()) {
std::string texPath = j["texture"].get<std::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 { try {
std::cout << "Loading texture step 1" << std::endl;
auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str()); auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
std::cout << "Loading texture step 2" << std::endl;
texture = std::make_shared<Texture>(texData); texture = std::make_shared<Texture>(texData);
std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl; std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cout << "Texture load error: " << e.what() << std::endl;
std::cerr << "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 { else {
std::cerr << "No texture specified or invalid type in JSON" << std::endl; 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 // shaders
if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) { if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
shaderProgramName = j["shaderProgramName"].get<std::string>(); shaderProgramName = j["shaderProgramName"].get<std::string>();
@ -529,9 +533,12 @@ namespace ZL {
} }
else { else {
std::cerr << "shaderProgramName missing or invalid" << std::endl; 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") if (j.contains("vertexShader") && j.contains("fragmentShader")
&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) { && j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
std::string v = j["vertexShader"].get<std::string>(); std::string v = j["vertexShader"].get<std::string>();
@ -544,9 +551,9 @@ namespace ZL {
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "Shader load error: " << e.what() << std::endl; 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; drawDataDirty = true;
configured = true; configured = true;

View File

@ -247,6 +247,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr; if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>(); std::string path = t[key].get<std::string>();
try { 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()); auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(data); return std::make_shared<Texture>(data);
} }
@ -276,6 +277,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr; if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>(); std::string path = t[key].get<std::string>();
try { try {
std::cout << "UiManager: --loading texture for button '" << "' : " << path << " Zip file: " << zipFile << std::endl;
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str()); auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(data); return std::make_shared<Texture>(data);
} }
@ -345,7 +347,7 @@ namespace ZL {
if (j.contains("centered")) tv->centered = j["centered"].get<bool>(); if (j.contains("centered")) tv->centered = j["centered"].get<bool>();
tv->textRenderer = std::make_unique<TextRenderer>(); 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; std::cerr << "Failed to init TextRenderer for TextView: " << tv->name << std::endl;
} }

View File

@ -33,5 +33,10 @@ namespace ZL {
std::vector<int> getPendingRespawns() override { std::vector<int> getPendingRespawns() override {
return {}; return {};
} }
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override
{
return {};
}
}; };
} }

View File

@ -1,4 +1,4 @@
#include "render/TextRenderer.h" #include "render/TextRenderer.h"
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
@ -11,53 +11,87 @@ namespace ZL {
TextRenderer::~TextRenderer() TextRenderer::~TextRenderer()
{ {
for (auto& kv : glyphs) { /*for (auto& kv : glyphs) {
if (kv.second.texID) glDeleteTextures(1, &kv.second.texID); if (kv.second.texID) glDeleteTextures(1, &kv.second.texID);
} }*/
glyphs.clear(); glyphs.clear();
textMesh.positionVBO.reset();
/*
if (vbo) glDeleteBuffers(1, &vbo); if (vbo) glDeleteBuffers(1, &vbo);
// if (vao) glDeleteVertexArrays(1, &vao); // if (vao) glDeleteVertexArrays(1, &vao);
vao = 0; 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; r = &renderer;
#ifdef EMSCRIPTEN
r->shaderManager.AddShaderFromFiles(shaderName, r->shaderManager.AddShaderFromFiles(shaderName,
"resources/shaders/text2d.vertex", "resources/shaders/text2d.vertex",
"resources/shaders/text2d.fragment", "resources/shaders/text2d_web.fragment",
"" // ZIP пустой 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 // VAO/VBO для 6 вершин (2 треугольника) * (pos.xy + uv.xy) = 4 float
// glGenVertexArrays(1, &vao); // glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo); /* glGenBuffers(1, &vbo);
//glBindVertexArray(vao); //glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
*/
ZL::CheckGlError();
return true; 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; FT_Library ft;
if (FT_Init_FreeType(&ft)) { if (FT_Init_FreeType(&ft)) {
std::cerr << "FreeType: FT_Init_FreeType failed\n"; std::cerr << "FreeType: FT_Init_FreeType failed\n";
return false; return false;
} }
// 2. Инициализируем Face из памяти
FT_Face 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); FT_Done_FreeType(ft);
return false; return false;
} }
@ -67,52 +101,50 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glyphs.clear(); glyphs.clear();
// Проходим по стандартным ASCII символам
for (unsigned char c = 32; c < 128; ++c) { 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; TextureDataStruct glyphData;
glGenTextures(1, &tex); glyphData.width = face->glyph->bitmap.width;
glBindTexture(GL_TEXTURE_2D, tex); glyphData.height = face->glyph->bitmap.rows;
glTexImage2D( glyphData.format = TextureDataStruct::R8;
GL_TEXTURE_2D, glyphData.mipmap = TextureDataStruct::NONE;
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Копируем буфер FreeType в вектор данных
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); size_t dataSize = glyphData.width * glyphData.height;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glyphData.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
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.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); 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; g.advance = (unsigned int)face->glyph->advance.x;
glyphs.emplace((char)c, g); glyphs.emplace((char)c, g);
} }
// Очистка
FT_Done_Face(face); FT_Done_Face(face);
FT_Done_FreeType(ft); FT_Done_FreeType(ft);
// После FT_Done_Face данные из fontData больше не нужны,
// вектор сам очистится при выходе из функции.
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
return true; return true;
} }
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color) 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; float totalW = 0.0f;
if (centered) { if (centered) {
for (char ch : text) { 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; x -= totalW * 0.5f;
} }
r->shaderManager.PushShader(shaderName); // 2. Подготовка данных (аналог CreateRect2D, но для всей строки)
VertexDataStruct textData;
// Орто-проекция в пикселях: (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)));
float penX = x; float penX = x;
float penY = y; 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 xpos = penX + g.bearing.x() * scale;
float ypos = penY - (g.size.y() - g.bearing.y()) * scale; float ypos = penY - (g.size.y() - g.bearing.y()) * scale;
float w = g.size.x() * scale; float w = g.size.x() * scale;
float h = g.size.y() * scale; float h = g.size.y() * scale;
// 2 треугольника // Добавляем 2 треугольника (6 вершин) для текущего символа
float verts[6][4] = { // Координаты Z ставим 0.0f, так как это 2D
{ xpos, ypos + h, 0.0f, 0.0f }, textData.PositionData.push_back({ xpos, ypos + h, 0.0f });
{ xpos, ypos, 0.0f, 1.0f }, textData.PositionData.push_back({ xpos, ypos, 0.0f });
{ xpos + w, ypos, 1.0f, 1.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 }, // UV-координаты (здесь есть нюанс с атласом, ниже поясню)
{ xpos + w, ypos, 1.0f, 1.0f }, textData.TexCoordData.push_back({ 0.0f, 0.0f });
{ xpos + w, ypos + h, 1.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 });
glBindTexture(GL_TEXTURE_2D, g.texID); textData.TexCoordData.push_back({ 1.0f, 1.0f });
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts); textData.TexCoordData.push_back({ 1.0f, 0.0f });
glDrawArrays(GL_TRIANGLES, 0, 6);
penX += (g.advance >> 6) * scale; penX += (g.advance >> 6) * scale;
} }
// cleanup // 3. Обновляем VBO через наш стандартный механизм
glBindTexture(GL_TEXTURE_2D, 0); // Примечание: для текста лучше использовать GL_DYNAMIC_DRAW,
glBindBuffer(GL_ARRAY_BUFFER, 0); // но 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("vPosition");
r->DisableVertexAttribArray("vTexCoord"); r->DisableVertexAttribArray("vTexCoord");
r->shaderManager.PopShader(); r->shaderManager.PopShader();
} }
} // namespace ZL } // namespace ZL

View File

@ -1,17 +1,18 @@
#pragma once #pragma once
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <Eigen/Dense> #include <Eigen/Dense>
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h"
#include <array> #include <array>
namespace ZL { namespace ZL {
struct GlyphInfo { 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 size; // glyph size in pixels
Eigen::Vector2f bearing; // offset from baseline Eigen::Vector2f bearing; // offset from baseline
unsigned int advance = 0; // advance.x in 1/64 pixels unsigned int advance = 0; // advance.x in 1/64 pixels
@ -22,19 +23,21 @@ public:
TextRenderer() = default; TextRenderer() = default;
~TextRenderer(); ~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 }); 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: private:
bool loadGlyphs(const std::string& ttfPath, int pixelSize); bool loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
Renderer* r = nullptr; Renderer* r = nullptr;
std::unordered_map<char, GlyphInfo> glyphs; std::unordered_map<char, GlyphInfo> glyphs;
// OpenGL objects for a dynamic quad // OpenGL objects for a dynamic quad
unsigned int vao = 0; //unsigned int vao = 0;
unsigned int vbo = 0; //unsigned int vbo = 0;
VertexRenderStruct textMesh;
std::string shaderName = "text2d"; std::string shaderName = "text2d";
}; };

View File

@ -1,4 +1,4 @@
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#ifdef PNG_ENABLED #ifdef PNG_ENABLED
#include "png.h" #include "png.h"
@ -11,48 +11,68 @@ namespace ZL
Texture::Texture(const TextureDataStruct& texData) { Texture::Texture(const TextureDataStruct& texData) {
width = texData.width; width = texData.width;
height = texData.height; height = texData.height;
glGenTextures(1, &texID); glGenTextures(1, &texID);
if (texID == 0) { glBindTexture(GL_TEXTURE_2D, texID);
throw std::runtime_error("glGenTextures did not work");
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(); CheckGlError();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> // 3. Фильтрация и Мип-мапы
if (texData.mipmap == TextureDataStruct::GENERATE) { // ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет!
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> if (texData.mipmap == TextureDataStruct::GENERATE && texData.format != TextureDataStruct::R8) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
} }
else { else {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} }
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
CheckGlError();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (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(); CheckGlError();
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (texData.mipmap == TextureDataStruct::GENERATE) {
glGenerateMipmap(GL_TEXTURE_2D);
CheckGlError();
}
} }
Texture::Texture(const std::array<TextureDataStruct, 6>& texDataArray) Texture::Texture(const std::array<TextureDataStruct, 6>& texDataArray)
{ {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
width = texDataArray[0].width; width = texDataArray[0].width;
height = texDataArray[0].height; height = texDataArray[0].height;
@ -73,36 +93,28 @@ namespace ZL
CheckGlError(); CheckGlError();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> Cubemap
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> GL_LINEAR <20><><EFBFBD> MIN_FILTER, <20><><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (e.g., GL_LINEAR_MIPMAP_LINEAR), <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> glGenerateMipmap.
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> Cubemap
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 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); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// GL_TEXTURE_WRAP_R <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> WebGL 1.0/OpenGL ES 2.0 <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
#ifndef __ANDROID__ #ifndef __ANDROID__
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
#endif #endif
#endif #endif
CheckGlError(); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> CheckGlError();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> 6 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// GL_TEXTURE_CUBE_MAP_POSITIVE_X + i <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>: +X (0), -X (1), +Y (2), -Y (3), +Z (4), -Z (5)
for (int i = 0; i < 6; ++i) for (int i = 0; i < 6; ++i)
{ {
GLint internalFormat; GLint internalFormat;
GLenum format; GLenum format;
// <20> WebGL 1.0/OpenGL ES 2.0 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (internalFormat)
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (format). if (texDataArray[i].format == TextureDataStruct::RGB)
if (texDataArray[i].bitSize == TextureDataStruct::BS_24BIT)
{ {
internalFormat = GL_RGB; // internalFormat internalFormat = GL_RGB; // internalFormat
format = GL_RGB; // format format = GL_RGB; // format
@ -114,20 +126,19 @@ namespace ZL
} }
glTexImage2D( glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> MIP-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0,
internalFormat, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) internalFormat,
static_cast<GLsizei>(width), static_cast<GLsizei>(width),
static_cast<GLsizei>(height), static_cast<GLsizei>(height),
0, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0) 0,
format, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> format,
GL_UNSIGNED_BYTE, // <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> GL_UNSIGNED_BYTE,
texDataArray[i].data.data() // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> texDataArray[i].data.data()
); );
CheckGlError(); CheckGlError();
} }
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
} }
@ -173,7 +184,7 @@ namespace ZL
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]); texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]); 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; size_t dataSize = texData.width * texData.height * 3;
@ -222,7 +233,7 @@ namespace ZL
texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]); texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]); 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; size_t dataSize = texData.width * texData.height * 4;
@ -255,48 +266,33 @@ namespace ZL
#ifdef PNG_ENABLED #ifdef PNG_ENABLED
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
struct png_data_t { struct png_data_t {
const char* data; const char* data;
size_t size; size_t size;
size_t offset; size_t offset;
}; };
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> libpng
// 'png_ptr' - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> png
// 'out_ptr' - <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 'bytes_to_read' - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) { void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> png_data_t, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> png_set_read_fn
png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr); png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr);
if (data->offset + bytes_to_read > data->size) { if (data->offset + bytes_to_read > data->size) {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
// <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> libpng.
png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer."); png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer.");
bytes_to_read = data->size - data->offset; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> bytes_to_read = data->size - data->offset;
} }
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD> libpng
std::memcpy(out_ptr, data->data + data->offset, bytes_to_read); std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
data->offset += bytes_to_read; data->offset += bytes_to_read;
} }
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> nullptr)
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//throw std::runtime_error();
std::cout << "PNG Warning: " << warning_msg << std::endl; std::cout << "PNG Warning: " << warning_msg << std::endl;
} }
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> setjmp)
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
std::cout << "PNG Error: " << error_msg << std::endl; std::cout << "PNG Error: " << error_msg << std::endl;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> longjmp <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> PNG
longjmp(png_jmpbuf(png_ptr), 1); longjmp(png_jmpbuf(png_ptr), 1);
} }
@ -304,7 +300,6 @@ namespace ZL
{ {
TextureDataStruct texData; TextureDataStruct texData;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
png_data_t png_data = { fileArr.data(), fileArr.size(), 0 }; png_data_t png_data = { fileArr.data(), fileArr.size(), 0 };
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); 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"); throw std::runtime_error("Could not create PNG info structure");
} }
// === <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
// 1. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> longjmp
if (setjmp(png_jmpbuf(png))) { if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, nullptr); png_destroy_read_struct(&png, &info, nullptr);
throw std::runtime_error("Error during PNG read (longjmp was executed)"); throw std::runtime_error("Error during PNG read (longjmp was executed)");
} }
// 2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> nullptr <20> error_ptr <20> warning_ptr <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn); png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn);
// 3. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> png_data
png_set_read_fn(png, &png_data, user_read_data); png_set_read_fn(png, &png_data, user_read_data);
// ===================================================================
png_read_info(png, info); png_read_info(png, info);
texData.width = png_get_image_width(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 color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info); png_byte bit_depth = png_get_bit_depth(png, info);
// === <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) ===
if (bit_depth == 16) if (bit_depth == 16)
png_set_strip_16(png); png_set_strip_16(png);
@ -363,9 +351,7 @@ namespace ZL
png_set_gray_to_rgb(png); png_set_gray_to_rgb(png);
png_read_update_info(png, info); png_read_update_info(png, info);
// ====================================================
// === <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) ===
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height); png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height);
for (int y = 0; y < texData.height; y++) { for (int y = 0; y < texData.height; y++) {
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
@ -379,11 +365,11 @@ namespace ZL
if (has_alpha) if (has_alpha)
{ {
texData.bitSize = TextureDataStruct::BS_32BIT; texData.format = TextureDataStruct::RGBA;
} }
else else
{ {
texData.bitSize = TextureDataStruct::BS_24BIT; texData.format = TextureDataStruct::RGB;
} }
int channels = has_alpha ? 4 : 3; int channels = has_alpha ? 4 : 3;
@ -424,7 +410,6 @@ namespace ZL
throw std::runtime_error("Could not read file data into memory"); throw std::runtime_error("Could not read file data into memory");
} }
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
return CreateTextureDataFromPng(fileArr); return CreateTextureDataFromPng(fileArr);
} }

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include "utils/Utils.h" #include "utils/Utils.h"
@ -15,23 +15,20 @@ namespace ZL
size_t height; size_t height;
std::vector<char> data; std::vector<char> data;
enum BitSize { enum Format {
BS_24BIT, R8, // Для шрифтов (GL_RED)
BS_32BIT RGB, // BS_24BIT
RGBA // BS_32BIT
}; };
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
enum MipmapType { enum MipmapType {
NONE, // <20><><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD> (GL_LINEAR) NONE,
GENERATE // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (GL_LINEAR_MIPMAP_LINEAR) GENERATE
}; };
BitSize bitSize; Format format = RGBA;
/*#ifdef SIMPLIFIED MipmapType mipmap = GENERATE;
MipmapType mipmap = NONE; // <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
#else*/
MipmapType mipmap = GENERATE; // <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//#endif
}; };
class Texture class Texture
{ {

View File

@ -1,4 +1,4 @@
#include "utils/Utils.h" #include "utils/Utils.h"
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <vector> #include <vector>
@ -98,44 +98,45 @@ namespace ZL
std::vector<char> readFileFromZIP(const std::string& filename, const std::string& zipfilename) { std::vector<char> readFileFromZIP(const std::string& filename, const std::string& zipfilename) {
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
const std::string zipPath = zipfilename; int zipErr = 0;
int zipErr; zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr);
zip_t* archive = zip_open(zipPath.c_str(), ZIP_RDONLY, &zipErr); if (!archive) {
if (!archive) { std::cout << "Failed to open ZIP: " << zipfilename << " (error code " << zipErr << ")" << std::endl;
throw std::runtime_error("?????? ???????? ZIP: " + zipPath); throw std::runtime_error("Failed to open ZIP: " + zipfilename);
} }
std::string cleanFilename = filename; std::string cleanFilename = filename;
if (cleanFilename.rfind("./", 0) == 0) { if (cleanFilename.rfind("./", 0) == 0) cleanFilename = cleanFilename.substr(2);
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); zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0);
if (!zipFile) { if (!zipFile) {
zip_close(archive); zip_close(archive);
throw std::runtime_error("???? ?? ?????? ? ZIP: " + cleanFilename); 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; std::vector<char> fileData(static_cast<size_t>(fileStat.size));
if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0) { zip_int64_t bytesRead = zip_fread(zipFile, fileData.data(), fileStat.size);
zip_fclose(zipFile);
zip_close(archive);
throw std::runtime_error("?????? ?????? ZIP-??????????.");
}
std::vector<char> fileData; // Обязательно закрываем всё перед проверкой результата или throw
fileData.resize(fileStat.size); 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); return fileData;
zip_close(archive);
return fileData;
#else #else
// ???????
if (zipfilename.empty()) { if (zipfilename.empty()) {
std::cerr << "readFileFromZIP: zipfilename empty" << std::endl; std::cerr << "readFileFromZIP: zipfilename empty" << std::endl;