Adapting for web
This commit is contained in:
1 parent
cf1ec1e76f
commit
a59bcc0c4b
42 files changed
+843
-294
No files matched your search
+42
-12
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
}
|
||||||
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;
|
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,4 +1,4 @@
|
|||||||
//precisionmediump float;
|
precision mediump float;
|
||||||
varying vec3 color;
|
varying vec3 color;
|
||||||
|
|
||||||
void main()
|
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,4 +1,4 @@
|
|||||||
//precisionmediump float;
|
precision mediump float;
|
||||||
uniform samplerCube Texture;
|
uniform samplerCube Texture;
|
||||||
|
|
||||||
varying vec3 dir;
|
varying vec3 dir;
|
||||||
|
|||||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
|||||||
//precisionmediump float;
|
precision mediump float;
|
||||||
uniform sampler2D Texture;
|
uniform sampler2D Texture;
|
||||||
varying vec2 texCoord;
|
varying vec2 texCoord;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//precisionmediump float;
|
precision mediump float;
|
||||||
uniform sampler2D Texture;
|
uniform sampler2D Texture;
|
||||||
varying vec2 texCoord;
|
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,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; // Глобальный фактор дня/ночи для позиции игрока
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
//precisionmediump float;
|
precision mediump float;
|
||||||
varying vec2 TexCoord;
|
varying vec2 TexCoord;
|
||||||
varying float vHeight;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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); // Синий туман
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
//precisionhighp float;
|
precision highp float;
|
||||||
// planetStone фрагментный шейдер
|
// planetStone фрагментный шейдер
|
||||||
|
|
||||||
varying vec2 TexCoord;
|
varying vec2 TexCoord;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//precisionmediump float;
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
varying vec2 texCoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 color = texture2D(Texture,texCoord).rgba;
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
precision mediump float;
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
varying vec2 texCoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 color = texture2D(Texture,texCoord).rgba;
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -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
@@ -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) {
|
||||||
|
|||||||
+29
-22
@@ -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;
|
||||||
|
|||||||
+3
-1
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,5 +33,10 @@ namespace ZL {
|
|||||||
std::vector<int> getPendingRespawns() override {
|
std::vector<int> getPendingRespawns() override {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+136
-88
@@ -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
|
||||||
@@ -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";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
// ��������� ���������� � ����������� �� ���������� ����
|
// 3. Фильтрация и Мип-мапы
|
||||||
if (texData.mipmap == TextureDataStruct::GENERATE) {
|
// ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет!
|
||||||
// ���������� ����������� ���������� ��� ���������� ��������
|
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 {
|
||||||
// ������� �������� ���������� (����� ������� �� ��������)
|
|
||||||
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();
|
|
||||||
|
|
||||||
// �������� ������ �������� ������ (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);
|
||||||
// ��������� ���-�����, ���� ��� ����������
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
// ��������, ��� ��� ����� ����� ���������� �������
|
|
||||||
width = texDataArray[0].width;
|
width = texDataArray[0].width;
|
||||||
height = texDataArray[0].height;
|
height = texDataArray[0].height;
|
||||||
|
|
||||||
@@ -73,36 +93,28 @@ namespace ZL
|
|||||||
|
|
||||||
CheckGlError();
|
CheckGlError();
|
||||||
|
|
||||||
// ��������� ���������� ��� Cubemap
|
|
||||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
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);
|
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_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 �� �������������� � WebGL 1.0/OpenGL ES 2.0 � �������� ������.
|
|
||||||
// ������������ ��� ����� ������ ��� ���������� ��������.
|
|
||||||
#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(); // �������� ����� ��������� ����������
|
CheckGlError();
|
||||||
|
|
||||||
// �������� ������ ��� ������ �� 6 ������
|
|
||||||
// GL_TEXTURE_CUBE_MAP_POSITIVE_X + i ���� �����: +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;
|
||||||
|
|
||||||
// � WebGL 1.0/OpenGL ES 2.0 ���������� ������ (internalFormat)
|
|
||||||
// ������ ������ ��������������� ������� ������ (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, // ������� �����
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
|
||||||
0, // ������� MIP-��������
|
0,
|
||||||
internalFormat, // ���������� ������ (������ ��������� � ��������)
|
internalFormat,
|
||||||
static_cast<GLsizei>(width),
|
static_cast<GLsizei>(width),
|
||||||
static_cast<GLsizei>(height),
|
static_cast<GLsizei>(height),
|
||||||
0, // ������� (������ 0)
|
0,
|
||||||
format, // ������ �������� ������
|
format,
|
||||||
GL_UNSIGNED_BYTE, // ��� ������
|
GL_UNSIGNED_BYTE,
|
||||||
texDataArray[i].data.data() // ��������� �� ������
|
texDataArray[i].data.data()
|
||||||
);
|
);
|
||||||
CheckGlError();
|
CheckGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ������� �������� ��� �������
|
|
||||||
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
|
||||||
|
|
||||||
// ��������� ��� �������� ������ � �����/������� � ������� ������� ������
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ���������������� ������� ������ ��� 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) {
|
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);
|
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) {
|
||||||
// ������� ��������� ������, ��� ���� � �������.
|
|
||||||
// ������ ������ ����������� ������, �� ����� ������ ��������� ������� ��� ������� ������.
|
|
||||||
// � ���� ������ �� ������� ������ 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; // �������������, ����� ��������� ����������
|
bytes_to_read = data->size - data->offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// �������� ������ �� ������ ������� � ����� libpng
|
|
||||||
std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);
|
std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);
|
||||||
|
|
||||||
// ��������� ��������
|
|
||||||
data->offset += bytes_to_read;
|
data->offset += bytes_to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ���������������� ������� �������������� (�� �������, ����� ������������ 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) {
|
||||||
// ����� ����� ����������� ����������� ��������������
|
|
||||||
//throw std::runtime_error();
|
|
||||||
std::cout << "PNG Warning: " << warning_msg << std::endl;
|
std::cout << "PNG Warning: " << warning_msg << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ���������������� ������� ������ (����������� ��� 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) {
|
||||||
// ����� ����� ����������� ����������� ������
|
|
||||||
std::cout << "PNG Error: " << error_msg << std::endl;
|
std::cout << "PNG Error: " << error_msg << std::endl;
|
||||||
// ����������� �������� longjmp ��� ������ �� �������� ������/������ PNG
|
|
||||||
longjmp(png_jmpbuf(png_ptr), 1);
|
longjmp(png_jmpbuf(png_ptr), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +300,6 @@ namespace ZL
|
|||||||
{
|
{
|
||||||
TextureDataStruct texData;
|
TextureDataStruct texData;
|
||||||
|
|
||||||
// ��������� ��� ���������� ������� �� �������
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ��������� ���������������� ������� ������ � ��������� ������ ===
|
|
||||||
// 1. ��������� ����������� ������ � 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. ��������� ���������������� ������� ��� ��������� ������ � ��������������
|
|
||||||
// ������ nullptr � error_ptr � warning_ptr ����� �������� ��������� �� ���� ��������� ������, ���� ����������
|
|
||||||
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. ��������� ���������������� ������� ������ � �������� �� ����� ��������� 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);
|
||||||
|
|
||||||
// === ���� �������������� (�������� ��� ���������) ===
|
|
||||||
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);
|
||||||
// ====================================================
|
|
||||||
|
|
||||||
// === ������ �������� (�������� ��� ���������) ===
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// �������� ����� �������, ������� �������� � �������� ����
|
|
||||||
return CreateTextureDataFromPng(fileArr);
|
return CreateTextureDataFromPng(fileArr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-13
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ����� ������������ ��� ���������� ���-���������
|
|
||||||
enum MipmapType {
|
enum MipmapType {
|
||||||
NONE, // ��� ���-����� (GL_LINEAR)
|
NONE,
|
||||||
GENERATE // �������������� ��������� (GL_LINEAR_MIPMAP_LINEAR)
|
GENERATE
|
||||||
};
|
};
|
||||||
|
|
||||||
BitSize bitSize;
|
Format format = RGBA;
|
||||||
/*#ifdef SIMPLIFIED
|
MipmapType mipmap = GENERATE;
|
||||||
MipmapType mipmap = NONE; // �� ��������� ��������
|
|
||||||
#else*/
|
|
||||||
MipmapType mipmap = GENERATE; // �� ��������� ��������
|
|
||||||
//#endif
|
|
||||||
};
|
};
|
||||||
class Texture
|
class Texture
|
||||||
{
|
{
|
||||||
|
|||||||
+32
-31
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user