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