Compare commits

..

7 Commits

Author SHA1 Message Date
Vladislav Khorev
fe01a5d1b2 Working on audio in web part 2 2026-04-17 17:14:44 +03:00
Vladislav Khorev
b6e0422d71 Working on audio in web part 1 2026-04-17 17:13:55 +03:00
Vladislav Khorev
3778a603f2 merge 2026-04-17 16:30:09 +03:00
Vladislav Khorev
e310822462 Working on web version 2026-04-17 16:13:56 +03:00
Vladislav Khorev
3d3c3b2a36 merge 2026-04-17 13:42:45 +03:00
16e7580418 Merge branch 'witcher001-cutscene' of gitea.fishrungames.com:salmon-engine-projects/space-game001 into witcher001-cutscene 2026-04-17 02:33:56 +06:00
be4dcd4cf9 fix: make choice options the only clickable area in dialogue choices 2026-04-17 02:04:36 +06:00
36 changed files with 622 additions and 383 deletions

3
.gitattributes vendored
View File

@ -2,3 +2,6 @@
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text
*.anim filter=lfs diff=lfs merge=lfs -text *.anim filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text

BIN
audio/background.wav (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,111 @@
import bpy
import bmesh
import mathutils
import random
import math
class SolidTreeGenerator:
def __init__(self, levels=5, length=3.0, radius=0.3):
self.levels = levels
self.base_length = length
self.base_radius = radius
# Хранилище для данных: (start_pos, end_pos, radius_start, radius_end)
self.branches_data = []
def calculate_tree(self, start_pos, direction, length, radius, level):
if level <= 0 or length < 0.1:
return
end_pos = start_pos + direction * length
# Сохраняем данные сегмента
self.branches_data.append({
'start': start_pos.copy(),
'end': end_pos.copy(),
'r_start': radius,
'r_end': radius * 0.7
})
# 1. Основной ствол (продолжение)
trunk_dir = (direction + self.get_random_vector(0.1)).normalized()
self.calculate_tree(end_pos, trunk_dir, length * 0.8, radius * 0.7, level - 1)
# 2. Боковые ветки (ветвление)
if level > 1:
num_sides = random.randint(2, 3) # Минимум 2 ветки для видимости
for _ in range(num_sides):
# Создаем вектор, сильно отклоненный от ствола (30-60 градусов)
axis = self.get_random_vector(1.0).normalized()
angle = math.radians(random.uniform(30, 60))
side_dir = direction.copy()
side_dir.rotate(mathutils.Quaternion(axis, angle))
# Боковые ветки короче
self.calculate_tree(end_pos, side_dir, length * 0.6, radius * 0.5, level - 1)
def get_random_vector(self, intensity):
return mathutils.Vector((
random.uniform(-intensity, intensity),
random.uniform(-intensity, intensity),
random.uniform(-intensity, intensity)
))
def build_mesh(self):
mesh = bpy.data.meshes.new("TreeMesh")
obj = bpy.data.objects.new("Tree", mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()
skin_layer = bm.verts.layers.skin.verify()
# Словарь для предотвращения дублирования вершин в одной точке
# Ключ - кортеж координат, Значение - объект вершины BMesh
vert_map = {}
for b in self.branches_data:
# Превращаем координаты в кортежи для словаря
s_key = tuple(round(v, 4) for v in b['start'])
e_key = tuple(round(v, 4) for v in b['end'])
# Получаем или создаем начальную вершину
if s_key not in vert_map:
v_start = bm.verts.new(b['start'])
v_start[skin_layer].radius = (b['r_start'], b['r_start'])
vert_map[s_key] = v_start
else:
v_start = vert_map[s_key]
# Получаем или создаем конечную вершину
if e_key not in vert_map:
v_end = bm.verts.new(b['end'])
v_end[skin_layer].radius = (b['r_end'], b['r_end'])
vert_map[e_key] = v_end
else:
v_end = vert_map[e_key]
# Создаем ребро, если его еще нет
if not bm.edges.get((v_start, v_end)):
bm.edges.new((v_start, v_end))
# Находим корень (самую нижнюю точку) и помечаем его
root_v = min(bm.verts, key=lambda v: v.co.z)
root_v[skin_layer].use_root = True
bm.to_mesh(mesh)
bm.free()
# Модификаторы
obj.modifiers.new(name="Skin", type='SKIN')
sub = obj.modifiers.new(name="Subdiv", type='SUBSURF')
sub.levels = 1 # Для начала 1, чтобы не тормозило
# Очистка сцены
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# Запуск
generator = SolidTreeGenerator(levels=5, length=3.0, radius=0.4)
generator.calculate_tree(mathutils.Vector((0,0,0)), mathutils.Vector((0,0,1)), 3.0, 0.4, 5)
generator.build_mesh()

View File

@ -1,156 +0,0 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"children": [
{
"type": "FrameLayout",
"name": "leftPanel",
"x": 100,
"y": 100,
"width": 320,
"height": 400,
"children": [
{
"type": "LinearLayout",
"name": "mainButtons",
"orientation": "vertical",
"spacing": 10,
"x": 0,
"y": 0,
"width": 300,
"height": 300,
"children": [
{
"type": "Button",
"name": "playButton",
"x": 100,
"y": 300,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/button.png",
"hover": "./resources/sand.png",
"pressed": "./resources/button.png"
}
},
{
"type": "Button",
"name": "settingsButton",
"x": 100,
"y": 200,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 0.5
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand.png",
"hover": "./resources/button.png",
"pressed": "./resources/sand.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": 100,
"y": 100,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 1.0
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
},
"bgScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
1280,
0
],
"duration": 5.0,
"easing": "linear"
}
]
}
},
"textures": {
"normal": "./resources/rock.png",
"hover": "./resources/button.png",
"pressed": "./resources/rock.png"
}
}
]
}
]
},
{
"type": "Slider",
"name": "musicVolumeSlider",
"x": 1140,
"y": 100,
"width": 10,
"height": 500,
"value": 0.5,
"orientation": "vertical",
"textures": {
"track": "./resources/musicVolumeBarTexture.png",
"knob": "./resources/musicVolumeBarButton.png"
}
}
]
}
}

View File

@ -8,7 +8,7 @@ if(NOT CMAKE_MAKE_PROGRAM AND WIN32)
endif() endif()
endif() endif()
project(space-game001 LANGUAGES C CXX) project(bishkek-witcher LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -83,49 +83,49 @@ set(SOURCES
../src/utils/Utils.h ../src/utils/Utils.h
../src/SparkEmitter.cpp ../src/SparkEmitter.cpp
../src/SparkEmitter.h ../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.cpp
../src/utils/Perlin.h ../src/utils/Perlin.h
../src/utils/TaskManager.cpp ../src/utils/TaskManager.cpp
../src/utils/TaskManager.h ../src/utils/TaskManager.h
# ../src/planet/StoneObject.cpp
# ../src/planet/StoneObject.h
../src/render/FrameBuffer.cpp ../src/render/FrameBuffer.cpp
../src/render/FrameBuffer.h ../src/render/FrameBuffer.h
../src/render/ShadowMap.cpp
../src/render/ShadowMap.h
../src/UiManager.cpp ../src/UiManager.cpp
../src/UiManager.h ../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/WebSocketClientBase.h
# ../src/network/WebSocketClientBase.cpp
# ../src/network/WebSocketClientEmscripten.h
# ../src/network/WebSocketClientEmscripten.cpp
../src/render/TextRenderer.h ../src/render/TextRenderer.h
../src/render/TextRenderer.cpp ../src/render/TextRenderer.cpp
../src/MenuManager.h ../src/MenuManager.h
../src/MenuManager.cpp ../src/MenuManager.cpp
# ../src/Space.h ../src/Location.h
# ../src/Space.cpp ../src/Location.cpp
../src/GameConstants.h ../src/GameConstants.h
../src/GameConstants.cpp ../src/GameConstants.cpp
../src/ScriptEngine.h ../src/ScriptEngine.h
../src/ScriptEngine.cpp ../src/ScriptEngine.cpp
../src/navigation/PathFinder.h ../src/navigation/PathFinder.h
../src/navigation/PathFinder.cpp ../src/navigation/PathFinder.cpp
../src/items/GameObjectLoader.h
../src/items/GameObjectLoader.cpp
../src/items/Item.h
../src/items/Item.cpp
../src/items/InteractiveObject.h
../src/items/InteractiveObject.cpp
../src/dialogue/DialogueTypes.h
../src/dialogue/DialogueDatabase.h
../src/dialogue/DialogueDatabase.cpp
../src/dialogue/DialogueRuntime.h
../src/dialogue/DialogueRuntime.cpp
../src/dialogue/DialogueOverlay.h
../src/dialogue/DialogueOverlay.cpp
../src/dialogue/DialogueSystem.h
../src/dialogue/DialogueSystem.cpp
) )
add_executable(space-game001 ${SOURCES}) add_executable(bishkek-witcher ${SOURCES})
# Настройка путей к инклудам (используем скачанные исходники) # Настройка путей к инклудам (используем скачанные исходники)
target_include_directories(space-game001 PRIVATE target_include_directories(bishkek-witcher PRIVATE
../src ../src
../thirdparty/eigen-5.0.0 ../thirdparty/eigen-5.0.0
../thirdparty/boost_1_90_0 ../thirdparty/boost_1_90_0
@ -140,7 +140,7 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build) add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
target_link_libraries(space-game001 PRIVATE zip z lua_static websocket.js) target_link_libraries(bishkek-witcher PRIVATE zip z lua_static websocket.js)
# Эмскриптен-флаги # Эмскриптен-флаги
set(EMSCRIPTEN_FLAGS set(EMSCRIPTEN_FLAGS
@ -149,13 +149,15 @@ set(EMSCRIPTEN_FLAGS
"-sUSE_LIBPNG=1" "-sUSE_LIBPNG=1"
"-sUSE_ZLIB=1" "-sUSE_ZLIB=1"
"-sUSE_SDL_TTF=2" "-sUSE_SDL_TTF=2"
"-sUSE_SDL_MIXER=2"
"-sSDL2_MIXER_FORMATS=[ogg,mp3]"
#"-pthread" #"-pthread"
#"-sUSE_PTHREADS=1" #"-sUSE_PTHREADS=1"
"-fexceptions" "-fexceptions"
"-DNETWORK" "-DNETWORK"
) )
target_compile_options(space-game001 PRIVATE ${EMSCRIPTEN_FLAGS} "-O2") target_compile_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
# Only loading.png and the shaders used before resources.zip is ready are preloaded. # Only loading.png and the shaders used before resources.zip is ready are preloaded.
# resources.zip is downloaded asynchronously at runtime and served as a separate file. # resources.zip is downloaded asynchronously at runtime and served as a separate file.
@ -168,14 +170,15 @@ set(EMSCRIPTEN_LINK_FLAGS
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start.lua@resources/start.lua" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start.lua@resources/start.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../audio@audio"
) )
# Применяем настройки линковки # Применяем настройки линковки
target_link_options(space-game001 PRIVATE ${EMSCRIPTEN_LINK_FLAGS}) target_link_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_LINK_FLAGS})
# Для совместимости со старыми версиями CMake, если target_link_options недостаточно # Для совместимости со старыми версиями CMake, если target_link_options недостаточно
string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}") string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}")
set_target_properties(space-game001 PROPERTIES set_target_properties(bishkek-witcher PROPERTIES
LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}" LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}"
SUFFIX ".html" SUFFIX ".html"
) )
@ -193,33 +196,33 @@ add_custom_command(
) )
add_custom_target(pack_resources DEPENDS "${RESOURCES_ZIP}") add_custom_target(pack_resources DEPENDS "${RESOURCES_ZIP}")
add_dependencies(space-game001 pack_resources) add_dependencies(bishkek-witcher pack_resources)
# Определяем путь к директории установки (относительно папки билда) # Определяем путь к директории установки (относительно папки билда)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/public") set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/public")
# 1. Устанавливаем основной HTML файл # 1. Устанавливаем основной HTML файл
install(TARGETS space-game001 install(TARGETS bishkek-witcher
RUNTIME DESTINATION . RUNTIME DESTINATION .
) )
# 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных) # 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных)
install(FILES install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.js" "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.js"
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.wasm" "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.wasm"
"${CMAKE_CURRENT_BINARY_DIR}/space-game001.data" "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.data"
DESTINATION . DESTINATION .
) )
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/space-game001plain.html" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/index.html"
DESTINATION . DESTINATION .
) )
# resources.zip is served separately and downloaded asynchronously at runtime # resources.zip is served separately and downloaded asynchronously at runtime
install(FILES "${RESOURCES_ZIP}" DESTINATION .) install(FILES "${RESOURCES_ZIP}" DESTINATION .)
add_custom_command(TARGET space-game001 POST_BUILD add_custom_command(TARGET bishkek-witcher POST_BUILD
COMMAND ${CMAKE_COMMAND} --install . COMMAND ${CMAKE_COMMAND} --install .
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Automatically deploying to public directory..." COMMENT "Automatically deploying to public directory..."

82
proj-web/index.html Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Bishkek Witcher</title>
<style>
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed;
}
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<script>
function prepareModuleEnvironment() {
window.Module = window.Module || {};
var canvasEl = document.getElementById('canvas');
window.Module.canvas = canvasEl;
window.Module.setStatus = window.Module.setStatus || function (text) {
var statusElement = document.getElementById("status");
statusElement.innerHTML = text;
statusElement.style.display = text ? 'block' : 'none';
};
}
function loadGameScript() {
var s = document.createElement('script');
s.src = 'bishkek-witcher.js';
s.async = true;
document.body.appendChild(s);
}
document.getElementById('fs-button').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(function(e) {
console.error('Fullscreen error: ' + e.message);
});
} else {
document.exitFullscreen();
}
});
document.addEventListener('DOMContentLoaded', function() {
prepareModuleEnvironment();
loadGameScript();
});
window.addEventListener("orientationchange", function() {
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 200);
});
</script>
</body>
</html>

View File

@ -46,8 +46,6 @@ add_executable(space-game001
../src/render/ShadowMap.h ../src/render/ShadowMap.h
../src/UiManager.cpp ../src/UiManager.cpp
../src/UiManager.h ../src/UiManager.h
# ../src/Projectile.h
# ../src/Projectile.cpp
../src/render/TextRenderer.h ../src/render/TextRenderer.h
../src/render/TextRenderer.cpp ../src/render/TextRenderer.cpp
../src/MenuManager.h ../src/MenuManager.h
@ -175,6 +173,7 @@ endif()
# Какие папки с ресурсами нужно копировать # Какие папки с ресурсами нужно копировать
set(RUNTIME_RESOURCE_DIRS set(RUNTIME_RESOURCE_DIRS
"resources" "resources"
"audio"
) )
# Копируем ресурсы и шейдеры в папку exe и в корень build/ # Копируем ресурсы и шейдеры в папку exe и в корень build/

View File

@ -59,7 +59,7 @@
"type": "Choice", "type": "Choice",
"speaker": "Hero", "speaker": "Hero",
"portrait": "resources/hero.png", "portrait": "resources/hero.png",
"text": "Choose your answer.", "text": "",
"choices": [ "choices": [
{ {
"id": "main_1", "id": "main_1",

Binary file not shown.

View File

@ -1,9 +1,14 @@
#include "AudioPlayerAsync.h" #include "AudioPlayerAsync.h"
#include <iostream> #include <iostream>
#ifdef __EMSCRIPTEN__
AudioPlayerAsync::AudioPlayerAsync() {}
#else
AudioPlayerAsync::AudioPlayerAsync() : worker(&AudioPlayerAsync::workerThread, this) {} AudioPlayerAsync::AudioPlayerAsync() : worker(&AudioPlayerAsync::workerThread, this) {}
#endif
AudioPlayerAsync::~AudioPlayerAsync() { AudioPlayerAsync::~AudioPlayerAsync() {
#ifndef __EMSCRIPTEN__
{ {
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
stop = true; stop = true;
@ -11,6 +16,7 @@ AudioPlayerAsync::~AudioPlayerAsync() {
} }
if (worker.joinable()) if (worker.joinable())
worker.join(); worker.join();
#endif
shutdown(); shutdown();
} }
@ -57,8 +63,7 @@ void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, in
return; return;
} }
std::unique_lock<std::mutex> lock(mtx); auto task = [this, filePath, loops, channel]() {
taskQueue.push([this, filePath, loops, channel]() {
Mix_Chunk* sound = nullptr; Mix_Chunk* sound = nullptr;
{ {
std::lock_guard<std::mutex> cacheLock(soundCacheMutex); std::lock_guard<std::mutex> cacheLock(soundCacheMutex);
@ -82,15 +87,21 @@ void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, in
if (result == -1) { if (result == -1) {
std::cerr << "Mix_PlayChannel failed: " << Mix_GetError() << std::endl; std::cerr << "Mix_PlayChannel failed: " << Mix_GetError() << std::endl;
} }
}); };
#ifdef __EMSCRIPTEN__
task();
#else
std::unique_lock<std::mutex> lock(mtx);
taskQueue.push(std::move(task));
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) { void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) {
if (!initialized) return; if (!initialized) return;
std::unique_lock<std::mutex> lock(mtx); auto task = [filePath, loops]() {
taskQueue.push([this, filePath, loops]() {
Mix_Music* music = Mix_LoadMUS(filePath.c_str()); Mix_Music* music = Mix_LoadMUS(filePath.c_str());
if (!music) { if (!music) {
std::cerr << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; std::cerr << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl;
@ -100,57 +111,85 @@ void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) {
std::cerr << "Mix_PlayMusic failed: " << Mix_GetError() << std::endl; std::cerr << "Mix_PlayMusic failed: " << Mix_GetError() << std::endl;
Mix_FreeMusic(music); Mix_FreeMusic(music);
} }
}); };
#ifdef __EMSCRIPTEN__
task();
#else
std::unique_lock<std::mutex> lock(mtx);
taskQueue.push(std::move(task));
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::stopMusicAsync() { void AudioPlayerAsync::stopMusicAsync() {
if (!initialized) return; if (!initialized) return;
#ifdef __EMSCRIPTEN__
Mix_HaltMusic();
#else
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
taskQueue.push([]() { taskQueue.push([]() {
Mix_HaltMusic(); Mix_HaltMusic();
}); });
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::pauseMusicAsync() { void AudioPlayerAsync::pauseMusicAsync() {
if (!initialized) return; if (!initialized) return;
#ifdef __EMSCRIPTEN__
Mix_PauseMusic();
#else
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
taskQueue.push([]() { taskQueue.push([]() {
Mix_PauseMusic(); Mix_PauseMusic();
}); });
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::resumeMusicAsync() { void AudioPlayerAsync::resumeMusicAsync() {
if (!initialized) return; if (!initialized) return;
#ifdef __EMSCRIPTEN__
Mix_ResumeMusic();
#else
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
taskQueue.push([]() { taskQueue.push([]() {
Mix_ResumeMusic(); Mix_ResumeMusic();
}); });
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::setMusicVolume(int volume) { void AudioPlayerAsync::setMusicVolume(int volume) {
if (!initialized) return; if (!initialized) return;
volume = std::max(0, std::min(128, volume)); volume = std::max(0, std::min(128, volume));
#ifdef __EMSCRIPTEN__
Mix_VolumeMusic(volume);
#else
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
taskQueue.push([volume]() { taskQueue.push([volume]() {
Mix_VolumeMusic(volume); Mix_VolumeMusic(volume);
}); });
cv.notify_one(); cv.notify_one();
#endif
} }
void AudioPlayerAsync::setSoundVolume(int volume) { void AudioPlayerAsync::setSoundVolume(int volume) {
if (!initialized) return; if (!initialized) return;
volume = std::max(0, std::min(128, volume)); volume = std::max(0, std::min(128, volume));
#ifdef __EMSCRIPTEN__
Mix_Volume(-1, volume);
#else
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
taskQueue.push([volume]() { taskQueue.push([volume]() {
Mix_Volume(-1, volume); // все каналы Mix_Volume(-1, volume); // все каналы
}); });
cv.notify_one(); cv.notify_one();
#endif
} }
#ifndef __EMSCRIPTEN__
void AudioPlayerAsync::workerThread() { void AudioPlayerAsync::workerThread() {
while (true) { while (true) {
std::function<void()> task; std::function<void()> task;
@ -164,4 +203,5 @@ void AudioPlayerAsync::workerThread() {
} }
task(); task();
} }
} }
#endif

View File

@ -5,11 +5,14 @@
#include <unordered_map> #include <unordered_map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <SDL2/SDL.h>
#ifndef __EMSCRIPTEN__
#include <queue> #include <queue>
#include <thread> #include <thread>
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <SDL2/SDL.h> #endif
class AudioPlayerAsync { class AudioPlayerAsync {
public: public:
@ -30,12 +33,14 @@ public:
void exit() { stop = true; } void exit() { stop = true; }
private: private:
#ifndef __EMSCRIPTEN__
void workerThread(); void workerThread();
std::thread worker; std::thread worker;
std::mutex mtx; std::mutex mtx;
std::condition_variable cv; std::condition_variable cv;
std::queue<std::function<void()>> taskQueue; std::queue<std::function<void()>> taskQueue;
#endif
bool stop = false; bool stop = false;
std::unordered_map<std::string, Mix_Chunk*> soundCache; std::unordered_map<std::string, Mix_Chunk*> soundCache;

View File

@ -8,6 +8,10 @@
namespace ZL namespace ZL
{ {
#ifdef EMSCRIPTEN
using std::min;
using std::max;
#endif
int getIndexByValue(const std::string& name, const std::vector<std::string>& words) int getIndexByValue(const std::string& name, const std::vector<std::string>& words)
{ {
@ -909,29 +913,51 @@ namespace ZL
void GpuSkinningShaderData::RenderVBO(Renderer& renderer) void GpuSkinningShaderData::RenderVBO(Renderer& renderer)
{ {
CheckGlError(__FILE__, __LINE__);
// Bind position and texcoord VBOs // Bind position and texcoord VBOs
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL); renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
CheckGlError(__FILE__, __LINE__);
if (bindPoseMutable.texCoordVBO) { if (bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL); renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
CheckGlError(__FILE__, __LINE__);
} }
CheckGlError(__FILE__, __LINE__);
if (bindPoseMutable.normalVBO) {
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.normalVBO->getBuffer());
renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
CheckGlError(__FILE__, __LINE__);
} else {
renderer.DisableVertexAttribArray("vNormal");
CheckGlError(__FILE__, __LINE__);
}
CheckGlError(__FILE__, __LINE__);
// Bind bone index VBOs // Bind bone index VBOs
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL); renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL); renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
CheckGlError(__FILE__, __LINE__);
// Bind bone weight VBOs // Bind bone weight VBOs
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL); renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL); renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
CheckGlError(__FILE__, __LINE__);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size())); glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
} }

View File

@ -417,12 +417,18 @@ void Character::drawShadowDepthCpu(Renderer& renderer) {
} }
void Character::drawShadowDepthGpuSkinning(Renderer& renderer) { void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
CheckGlError(__FILE__, __LINE__);
AnimationState drawState = resolveActiveState(); AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState); auto it = animations.find(drawState);
if (it == animations.end()) return; if (it == animations.end()) return;
CheckGlError(__FILE__, __LINE__);
if (!prepareGpuSkinning()) return; if (!prepareGpuSkinning()) return;
CheckGlError(__FILE__, __LINE__);
static const std::string shadowSkinningShader = "shadow_depth_skinning"; static const std::string shadowSkinningShader = "shadow_depth_skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]"; static const std::string boneMatricesUniform = "uBoneMatrices[0]";
@ -435,14 +441,19 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
renderer.ScaleMatrix(modelScale); renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
CheckGlError(__FILE__, __LINE__);
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform, renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false, static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data()); it->second.gpuSkinningShaderData.skinningMatrices[0].data());
CheckGlError(__FILE__, __LINE__);
it->second.gpuSkinningShaderData.RenderVBO(renderer); it->second.gpuSkinningShaderData.RenderVBO(renderer);
CheckGlError(__FILE__, __LINE__);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(__FILE__, __LINE__);
} }
// ==================== Main pass with shadows ==================== // ==================== Main pass with shadows ====================
@ -498,8 +509,12 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
auto it = animations.find(drawState); auto it = animations.find(drawState);
if (it == animations.end() || !texture) return; if (it == animations.end() || !texture) return;
CheckGlError(__FILE__, __LINE__);
if (!prepareGpuSkinning()) return; if (!prepareGpuSkinning()) return;
CheckGlError(__FILE__, __LINE__);
static const std::string skinningShadowShader = "skinning_shadow"; static const std::string skinningShadowShader = "skinning_shadow";
static const std::string boneMatricesUniform = "uBoneMatrices[0]"; static const std::string boneMatricesUniform = "uBoneMatrices[0]";
@ -509,10 +524,14 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data()); renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data()); renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
CheckGlError(__FILE__, __LINE__);
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shadowMapTex); glBindTexture(GL_TEXTURE_2D, shadowMapTex);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
CheckGlError(__FILE__, __LINE__);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -523,17 +542,24 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
renderer.ScaleMatrix(modelScale); renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
CheckGlError(__FILE__, __LINE__);
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform, renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false, static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data()); it->second.gpuSkinningShaderData.skinningMatrices[0].data());
CheckGlError(__FILE__, __LINE__);
glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glBindTexture(GL_TEXTURE_2D, texture->getTexID());
CheckGlError(__FILE__, __LINE__);
it->second.gpuSkinningShaderData.RenderVBO(renderer); it->second.gpuSkinningShaderData.RenderVBO(renderer);
CheckGlError(__FILE__, __LINE__);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(__FILE__, __LINE__);
} }
} // namespace ZL } // namespace ZL

View File

@ -73,7 +73,7 @@ namespace ZL
Environment::computeProjectionDimensions(); Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
ZL::CheckGlError(); ZL::CheckGlError(__FILE__, __LINE__);
renderer.InitOpenGL(); renderer.InitOpenGL();
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@ -246,7 +246,7 @@ namespace ZL
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
CheckGlError(); CheckGlError(__FILE__, __LINE__);
} }
void Game::drawScene() { void Game::drawScene() {
@ -259,11 +259,15 @@ namespace ZL
if (currentLocation) if (currentLocation)
{ {
if (currentLocation->shadowMap) { if (currentLocation->shadowMap) {
CheckGlError(__FILE__, __LINE__);
currentLocation->drawShadowDepthPass(); currentLocation->drawShadowDepthPass();
CheckGlError(__FILE__, __LINE__);
currentLocation->drawGameWithShadows(); currentLocation->drawGameWithShadows();
CheckGlError(__FILE__, __LINE__);
} }
else { else {
currentLocation->drawGame(); currentLocation->drawGame();
CheckGlError(__FILE__, __LINE__);
} }
} }
else else
@ -272,7 +276,7 @@ namespace ZL
} }
drawUI(); drawUI();
} }
CheckGlError(); CheckGlError(__FILE__, __LINE__);
} }
void Game::drawLoading() void Game::drawLoading()
@ -299,7 +303,7 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError(__FILE__, __LINE__);
} }
@ -339,7 +343,7 @@ namespace ZL
} }
void Game::render() { void Game::render() {
ZL::CheckGlError(); ZL::CheckGlError(__FILE__, __LINE__);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -461,12 +465,10 @@ namespace ZL
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
if (currentLocation) if (currentLocation)
{ {
currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y); currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y, mx, my);
} }
} }
if (event.type == SDL_MOUSEWHEEL) { if (event.type == SDL_MOUSEWHEEL) {
@ -482,23 +484,19 @@ namespace ZL
} }
} }
/*if (event.type == SDL_KEYDOWN/* && dialogueSystem.handleKeyDown(event.key.keysym.sym)) {
continue;
}*/
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_1: case SDLK_1:
if (audioPlayer) audioPlayer->playSoundAsync("resources/sounds/background.wav"); if (audioPlayer) audioPlayer->playSoundAsync("audio/background.wav");
break; break;
case SDLK_2: case SDLK_2:
if (audioPlayer) audioPlayer->playMusicAsync("resources/sounds/lullaby-music-vol20-186394--online-audio-convert.com.ogg"); if (audioPlayer) audioPlayer->playMusicAsync("audio/lullaby-music-vol20-186394--online-audio-convert.com.ogg");
break; break;
case SDLK_3: case SDLK_3:
if (audioPlayer) audioPlayer->stopMusicAsync(); if (audioPlayer) audioPlayer->stopMusicAsync();
break; break;
case SDLK_f: case SDLK_f:
currentLocation->dialogueSystem.startDialogue("test_cutscene_pan_dialogue_silent"); currentLocation->dialogueSystem.startDialogue("test_choice_dialogue");
break; break;
case SDLK_e: case SDLK_e:

View File

@ -9,7 +9,7 @@
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <cfloat>
#include "GameConstants.h" #include "GameConstants.h"
@ -449,19 +449,23 @@ namespace ZL
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data()); renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
#endif #endif
CheckGlError(__FILE__, __LINE__);
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
renderer.DrawVertexRenderStruct(roomMesh); renderer.DrawVertexRenderStruct(roomMesh);
CheckGlError(__FILE__, __LINE__);
for (auto& [name, gameObj] : gameObjects) { for (auto& [name, gameObj] : gameObjects) {
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh); renderer.DrawVertexRenderStruct(gameObj.mesh);
} }
CheckGlError(__FILE__, __LINE__);
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.isActive) {
intObj.draw(renderer); intObj.draw(renderer);
} }
} }
CheckGlError(__FILE__, __LINE__);
#ifdef DEBUG_LIGHT #ifdef DEBUG_LIGHT
// In debug-light mode characters use the plain shaders (draw normally // In debug-light mode characters use the plain shaders (draw normally
@ -470,9 +474,14 @@ namespace ZL
for (auto& npc : npcs) npc->draw(renderer); for (auto& npc : npcs) npc->draw(renderer);
#else #else
// Characters use their own shadow-aware shaders // Characters use their own shadow-aware shaders
CheckGlError(__FILE__, __LINE__);
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
CheckGlError(__FILE__, __LINE__);
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
#endif #endif
CheckGlError(__FILE__, __LINE__);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
@ -617,13 +626,20 @@ namespace ZL
{ {
} }
void Location::handleMotion(int64_t fingerId, int mx, int my) void Location::handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my)
{ {
if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerMoved(
static_cast<float>(mx),
Environment::projectionHeight - static_cast<float>(my)
);
}
if (rightMouseDown) { if (rightMouseDown) {
int dx = mx - lastMouseX; int dx = eventX - lastMouseX;
int dy = my - lastMouseY; int dy = eventY - lastMouseY;
lastMouseX = mx; lastMouseX = eventX;
lastMouseY = my; lastMouseY = eventY;
const float sensitivity = 0.005f; const float sensitivity = 0.005f;
cameraAzimuth += dx * sensitivity; cameraAzimuth += dx * sensitivity;

View File

@ -67,7 +67,7 @@ namespace ZL
void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my); void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
void handleUp(int64_t fingerId, int mx, int my); void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int64_t fingerId, int mx, int my); void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my);
bool requestDialogueStart(const std::string& dialogueId); bool requestDialogueStart(const std::string& dialogueId);
void setDialogueFlag(const std::string& flag, int value); void setDialogueFlag(const std::string& flag, int value);

View File

@ -1,71 +0,0 @@
#include "Projectile.h"
namespace ZL {
Projectile::Projectile()
: pos({ 0,0,0 })
, vel({ 0,0,0 })
, life(0.0f)
, maxLife(0.0f)
, active(false)
, size(0.5f)
, texture(nullptr)
{
}
void Projectile::init(const Vector3f& startPos, const Vector3f& startVel, float lifeMs, float s, std::shared_ptr<Texture> tex, Renderer& renderer) {
pos = startPos;
vel = startVel;
life = 0.0f;
maxLife = lifeMs;
size = s;
texture = tex;
active = true;
rebuildMesh(renderer);
mesh.RefreshVBO();
}
void Projectile::update(float deltaMs, Renderer& renderer) {
if (!active) return;
pos = pos + vel * (deltaMs / 1000.0f);
life += deltaMs;
if (life >= maxLife) {
active = false;
return;
}
rebuildMesh(renderer);
mesh.RefreshVBO();
}
void Projectile::rebuildMesh(Renderer&) {
float half = 10 * size * 0.5f;
mesh.data.PositionData.clear();
mesh.data.TexCoordData.clear();
mesh.data.PositionData.push_back({ pos(0) - half, pos(1) - half, pos(2) });
mesh.data.PositionData.push_back({ pos(0) - half, pos(1) + half, pos(2) });
mesh.data.PositionData.push_back({ pos(0) + half, pos(1) + half, pos(2) });
mesh.data.PositionData.push_back({ pos(0) - half, pos(1) - half, pos(2) });
mesh.data.PositionData.push_back({ pos(0) + half, pos(1) + half, pos(2) });
mesh.data.PositionData.push_back({ pos(0) + half, pos(1) - half, pos(2) });
mesh.data.TexCoordData.push_back({ 0.0f, 0.0f });
mesh.data.TexCoordData.push_back({ 0.0f, 1.0f });
mesh.data.TexCoordData.push_back({ 1.0f, 1.0f });
mesh.data.TexCoordData.push_back({ 0.0f, 0.0f });
mesh.data.TexCoordData.push_back({ 1.0f, 1.0f });
mesh.data.TexCoordData.push_back({ 1.0f, 0.0f });
}
void Projectile::draw(Renderer& renderer) const {
if (!active || !texture) return;
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
renderer.DrawVertexRenderStruct(mesh);
}
} // namespace ZL

View File

@ -1,38 +0,0 @@
#pragma once
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include <memory>
#include "SparkEmitter.h"
namespace ZL {
class Projectile {
public:
Projectile();
~Projectile() = default;
void init(const Vector3f& startPos, const Vector3f& startVel, float lifeMs, float size, std::shared_ptr<Texture> tex, Renderer& renderer);
void update(float deltaMs, Renderer& renderer);
void draw(Renderer& renderer) const;
bool isActive() const { return active; }
Vector3f getPosition() const { return pos; }
void deactivate() { active = false; }
SparkEmitter projectileEmitter;
private:
Vector3f pos;
Vector3f vel;
float life;
float maxLife;
bool active;
float size;
VertexRenderStruct mesh;
std::shared_ptr<Texture> texture;
void rebuildMesh(Renderer& renderer);
};
} // namespace ZL

View File

@ -3,6 +3,11 @@
#include "utils/Utils.h" #include "utils/Utils.h"
#include <iostream> #include <iostream>
namespace ZL
{
extern const char* CONST_ZIP_FILE;
}
namespace ZL::Dialogue { namespace ZL::Dialogue {
NodeType DialogueDatabase::parseNodeType(const std::string& value) { NodeType DialogueDatabase::parseNodeType(const std::string& value) {
@ -202,10 +207,23 @@ bool DialogueDatabase::loadFromFile(const std::string& path) {
dialogues.clear(); dialogues.clear();
cutscenes.clear(); cutscenes.clear();
const std::string raw = ZL::readTextFile(path); std::string raw;
if (raw.empty()) { try {
std::cerr << "[dialogue] Failed to read file: " << path << "\n"; if (strlen(CONST_ZIP_FILE) == 0) {
return false; raw = readTextFile(path);
}
else {
auto buf = readFileFromZIP(path, CONST_ZIP_FILE);
if (buf.empty()) {
std::cerr << "UiManager: failed to read " << path << " from zip " << CONST_ZIP_FILE << std::endl;
throw std::runtime_error("Failed to load UI file: " + path);
}
raw.assign(buf.begin(), buf.end());
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: failed to open " << path << " : " << e.what() << std::endl;
throw std::runtime_error("Failed to load UI file: " + path);
} }
json root; json root;

View File

@ -84,6 +84,12 @@ bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
return ok; return ok;
} }
void DialogueOverlay::update(const PresentationModel& model, int deltaMs) {
if (model.mode != PresentationMode::Choice) {
hoveredChoiceIndex = -1;
}
}
void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
if (model.mode == PresentationMode::Hidden) { if (model.mode == PresentationMode::Hidden) {
lastChoiceRects.clear(); lastChoiceRects.clear();
@ -170,12 +176,9 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
lastChoiceRects.push_back(rect); lastChoiceRects.push_back(rect);
choiceQuads[i].rebuild(rect); choiceQuads[i].rebuild(rect);
const bool isSelected = static_cast<int>(i) == model.selectedChoice; const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
std::shared_ptr<Texture> choiceTexture = choiceMainTexture; std::shared_ptr<Texture> choiceTexture = (model.choices[i].kind == ChoiceKind::Optional) ? choiceOptionalTexture : choiceMainTexture;
if (model.choices[i].kind == ChoiceKind::Optional) { if (isHighlighted) {
choiceTexture = choiceOptionalTexture;
}
if (isSelected) {
choiceTexture = choiceSelectedTexture; choiceTexture = choiceSelectedTexture;
} }
drawQuad(renderer, choiceQuads[i], choiceTexture); drawQuad(renderer, choiceQuads[i], choiceTexture);
@ -187,10 +190,10 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
for (size_t i = 0; i < model.choices.size(); ++i) { for (size_t i = 0; i < model.choices.size(); ++i) {
const UiRect& rect = lastChoiceRects[i]; const UiRect& rect = lastChoiceRects[i];
const bool isSelected = static_cast<int>(i) == model.selectedChoice; const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional) const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional)
? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f} ? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f }; : std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };
choiceRenderer->drawText( choiceRenderer->drawText(
wrapText(model.choices[i].text, 52), wrapText(model.choices[i].text, 52),
@ -198,7 +201,7 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
rect.y + 9.0f, rect.y + 9.0f,
1.0f, 1.0f,
false, false,
isSelected ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
); );
} }
} }
@ -417,21 +420,48 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const { void DialogueOverlay::handlePointerDown(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
handlePointerMoved(x, y, model);
return;
}
}
void DialogueOverlay::handlePointerMoved(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
hoveredChoiceIndex = -1;
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (rectContains(lastChoiceRects[i], x, y)) {
hoveredChoiceIndex = static_cast<int>(i);
break;
}
}
return;
}
hoveredChoiceIndex = -1;
}
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue) {
outChoiceIndex = -1; outChoiceIndex = -1;
outAdvanceDialogue = false;
if (model.mode == PresentationMode::Choice) { if (model.mode == PresentationMode::Choice) {
for (size_t i = 0; i < lastChoiceRects.size(); ++i) { for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (lastChoiceRects[i].contains(x, y)) { if (rectContains(lastChoiceRects[i], x, y)) {
outChoiceIndex = static_cast<int>(i); outChoiceIndex = static_cast<int>(i);
return true; return true;
} }
} }
return lastDialogueAdvanceRect.contains(x, y); return false;
} }
if (model.mode == PresentationMode::Dialogue) { if (model.mode == PresentationMode::Dialogue) {
return lastDialogueAdvanceRect.contains(x, y); if (lastDialogueAdvanceRect.contains(x, y)) {
outAdvanceDialogue = true;
return true;
}
return false;
} }
if (model.mode == PresentationMode::Cutscene) { if (model.mode == PresentationMode::Cutscene) {
@ -494,4 +524,8 @@ std::string DialogueOverlay::wrapText(const std::string& input, size_t maxLineLe
return output; return output;
} }
bool DialogueOverlay::rectContains(const UiRect& rect, float x, float y) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
} // namespace ZL::Dialogue } // namespace ZL::Dialogue

View File

@ -15,11 +15,12 @@ namespace ZL::Dialogue {
class DialogueOverlay { class DialogueOverlay {
public: public:
bool init(Renderer& renderer, const std::string& zipFile = ""); bool init(Renderer& renderer, const std::string& zipFile = "");
void update(const PresentationModel& model, int deltaMs);
void draw(Renderer& renderer, const PresentationModel& model); void draw(Renderer& renderer, const PresentationModel& model);
// Coordinates are expected in the game's UI projection space void handlePointerDown(float x, float y, const PresentationModel& model);
// Returns true only when the click should advance/select. void handlePointerMoved(float x, float y, const PresentationModel& model);
bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const; bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue);
private: private:
struct TexturedQuad { struct TexturedQuad {
@ -60,6 +61,8 @@ private:
mutable UiRect lastCutsceneAdvanceRect{}; mutable UiRect lastCutsceneAdvanceRect{};
mutable bool cutsceneAdvanceEnabled = false; mutable bool cutsceneAdvanceEnabled = false;
int hoveredChoiceIndex = -1;
std::unique_ptr<TextRenderer> nameRenderer; std::unique_ptr<TextRenderer> nameRenderer;
std::unique_ptr<TextRenderer> bodyRenderer; std::unique_ptr<TextRenderer> bodyRenderer;
std::unique_ptr<TextRenderer> choiceRenderer; std::unique_ptr<TextRenderer> choiceRenderer;
@ -80,6 +83,7 @@ private:
void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const; void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const;
static std::string wrapText(const std::string& input, size_t maxLineLength); static std::string wrapText(const std::string& input, size_t maxLineLength);
static bool rectContains(const UiRect& rect, float x, float y);
static float lerpFloat(float a, float b, float t); static float lerpFloat(float a, float b, float t);
static ResolvedViewport resolveViewportPose( static ResolvedViewport resolveViewportPose(

View File

@ -185,10 +185,26 @@ void DialogueRuntime::moveSelection(int delta) {
} }
const int count = static_cast<int>(visibleChoices.size()); const int count = static_cast<int>(visibleChoices.size());
selectedChoice = (selectedChoice + delta) % count; if (selectedChoice < 0 || selectedChoice >= count) {
if (selectedChoice < 0) { selectedChoice = (delta >= 0) ? 0 : (count - 1);
selectedChoice += count;
} }
else {
selectedChoice = (selectedChoice + delta) % count;
if (selectedChoice < 0) {
selectedChoice += count;
}
}
presentation.selectedChoice = selectedChoice;
}
void DialogueRuntime::selectChoice(int index) {
if (mode != Mode::WaitingForChoice || visibleChoices.empty()) {
return;
}
if (index < 0 || index >= static_cast<int>(visibleChoices.size())) {
return;
}
selectedChoice = index;
presentation.selectedChoice = selectedChoice; presentation.selectedChoice = selectedChoice;
} }
@ -346,14 +362,14 @@ void DialogueRuntime::presentChoices(const Node& node) {
} }
mode = Mode::WaitingForChoice; mode = Mode::WaitingForChoice;
selectedChoice = 0; selectedChoice = -1;
presentation.mode = PresentationMode::Choice; presentation.mode = PresentationMode::Choice;
presentation.speaker = node.speaker; presentation.speaker = node.speaker;
presentation.fullText = node.text; presentation.fullText = node.text;
presentation.visibleText = node.text; presentation.visibleText = node.text;
presentation.portraitPath = node.portrait; presentation.portraitPath = node.portrait;
presentation.backgroundPath.clear(); presentation.backgroundPath.clear();
presentation.selectedChoice = 0; presentation.selectedChoice = -1;
presentation.revealCompleted = true; presentation.revealCompleted = true;
presentation.showCutsceneSubtitle = false; presentation.showCutsceneSubtitle = false;
presentation.cutsceneCamera = {}; presentation.cutsceneCamera = {};

View File

@ -26,6 +26,7 @@ public:
void confirmAdvance(); void confirmAdvance();
void moveSelection(int delta); void moveSelection(int delta);
void selectChoice(int index);
const PresentationModel& getPresentation() const { return presentation; } const PresentationModel& getPresentation() const { return presentation; }

View File

@ -66,24 +66,43 @@ bool DialogueSystem::handleKeyDown(SDL_Keycode key) {
} }
} }
void DialogueSystem::handlePointerDown(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerDown(x, y, runtime.getPresentation());
}
void DialogueSystem::handlePointerMoved(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerMoved(x, y, runtime.getPresentation());
}
bool DialogueSystem::handlePointerReleased(float x, float y) { bool DialogueSystem::handlePointerReleased(float x, float y) {
if (!runtime.isActive()) { if (!runtime.isActive()) {
return false; return false;
} }
int choiceIndex = -1; int choiceIndex = -1;
bool advanceDialogue = false;
const PresentationModel& model = runtime.getPresentation(); const PresentationModel& model = runtime.getPresentation();
if (!overlay.handlePointerReleased(x, y, model, choiceIndex)) { if (!overlay.handlePointerReleased(x, y, model, choiceIndex, advanceDialogue)) {
return false; return false;
} }
if (choiceIndex >= 0) { if (choiceIndex >= 0) {
while (runtime.getPresentation().selectedChoice != choiceIndex) { runtime.selectChoice(choiceIndex);
runtime.moveSelection(1); runtime.confirmAdvance();
} return true;
}
if (advanceDialogue) {
runtime.confirmAdvance();
return true;
} }
runtime.confirmAdvance();
return true; return true;
} }

View File

@ -27,6 +27,8 @@ public:
void draw(Renderer& renderer); void draw(Renderer& renderer);
bool handleKeyDown(SDL_Keycode key); bool handleKeyDown(SDL_Keycode key);
void handlePointerDown(float x, float y);
void handlePointerMoved(float x, float y);
bool handlePointerReleased(float x, float y); bool handlePointerReleased(float x, float y);
bool startDialogue(const std::string& dialogueId); bool startDialogue(const std::string& dialogueId);

View File

@ -174,7 +174,7 @@ struct PresentationModel {
std::string portraitPath; std::string portraitPath;
std::string backgroundPath; std::string backgroundPath;
std::vector<PresentedChoice> choices; std::vector<PresentedChoice> choices;
int selectedChoice = 0; int selectedChoice = -1;
bool revealCompleted = true; bool revealCompleted = true;
bool showCutsceneSubtitle = false; bool showCutsceneSubtitle = false;

View File

@ -14,10 +14,40 @@ namespace ZL {
std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath) std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
{ {
std::vector<GameObjectData> objects; std::vector<GameObjectData> objects;
std::string content;
try {
if (zipPath.empty()) {
content = readTextFile(jsonPath);
}
else {
auto buf = readFileFromZIP(jsonPath, zipPath);
if (buf.empty()) {
std::cerr << "UiManager: failed to read " << jsonPath << " from zip " << zipPath << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
content.assign(buf.begin(), buf.end());
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: failed to open " << jsonPath << " : " << e.what() << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
json j; json j;
try {
j = json::parse(content);
}
catch (const std::exception& e) {
std::cerr << "UiManager: json parse error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
//json j;
try { try {
std::ifstream file(jsonPath); /*std::ifstream file(jsonPath);
if (!file.is_open()) { if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + jsonPath); throw std::runtime_error("Could not open file: " + jsonPath);
} }
@ -26,7 +56,7 @@ namespace ZL {
throw std::runtime_error("JSON file is empty: " + jsonPath); throw std::runtime_error("JSON file is empty: " + jsonPath);
} }
file >> j; file >> j;*/
if (!j.contains("objects") || !j["objects"].is_array()) { if (!j.contains("objects") || !j["objects"].is_array()) {
std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl; std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl;
@ -255,10 +285,40 @@ namespace ZL {
std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath) std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
{ {
std::vector<NpcData> npcs; std::vector<NpcData> npcs;
std::string content;
try {
if (zipPath.empty()) {
content = readTextFile(jsonPath);
}
else {
auto buf = readFileFromZIP(jsonPath, zipPath);
if (buf.empty()) {
std::cerr << "UiManager: failed to read " << jsonPath << " from zip " << zipPath << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
content.assign(buf.begin(), buf.end());
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: failed to open " << jsonPath << " : " << e.what() << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
json j; json j;
try {
j = json::parse(content);
}
catch (const std::exception& e) {
std::cerr << "UiManager: json parse error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load UI file: " + jsonPath);
}
//json j;
try { try {
std::ifstream file(jsonPath); /*std::ifstream file(jsonPath);
if (!file.is_open()) { if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + jsonPath); throw std::runtime_error("Could not open file: " + jsonPath);
} }
@ -267,7 +327,7 @@ namespace ZL {
throw std::runtime_error("JSON file is empty: " + jsonPath); throw std::runtime_error("JSON file is empty: " + jsonPath);
} }
file >> j; file >> j;*/
if (!j.contains("npcs") || !j["npcs"].is_array()) { if (!j.contains("npcs") || !j["npcs"].is_array()) {
std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl; std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl;

View File

@ -12,7 +12,7 @@ namespace ZL {
void InteractiveObject::draw(Renderer& renderer) const { void InteractiveObject::draw(Renderer& renderer) const {
if (!isActive || !texture) return; if (!isActive || !texture) return;
/*
std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl; std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl;
std::cout << "[DRAW] Object: " << name << std::endl; std::cout << "[DRAW] Object: " << name << std::endl;
std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl; std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl;
@ -22,7 +22,7 @@ namespace ZL {
std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", " std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", "
<< mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl; << mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl;
} }
*/
// Apply position transformation // Apply position transformation
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix(position); renderer.TranslateMatrix(position);

View File

@ -329,6 +329,16 @@ namespace ZL {
size_t error = glGetError(); size_t error = glGetError();
if (error != GL_NO_ERROR) if (error != GL_NO_ERROR)
{ {
std::cout << "OpenGL error: " << error << std::endl;
throw std::runtime_error("Gl error");
}
}
void CheckGlError(const char* file, int line)
{
size_t error = glGetError();
if (error != GL_NO_ERROR)
{
std::cout << "OpenGL error: " << error << " happened in file: " << file << " at line: " << line << std::endl;
throw std::runtime_error("Gl error"); throw std::runtime_error("Gl error");
} }
} }

View File

@ -161,4 +161,6 @@ namespace ZL {
bool BindOpenGlFunctions(); bool BindOpenGlFunctions();
void CheckGlError(); void CheckGlError();
void CheckGlError(const char* file, int line);
} }

View File

@ -536,7 +536,7 @@ namespace ZL {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LEQUAL);
CheckGlError(); CheckGlError(__FILE__, __LINE__);
} }
void Renderer::PushProjectionMatrix(float width, float height, float zNear, float zFar) void Renderer::PushProjectionMatrix(float width, float height, float zNear, float zFar)

View File

@ -56,6 +56,22 @@ namespace ZL {
{ {
glGenFramebuffers(1, &fbo); glGenFramebuffers(1, &fbo);
#ifdef EMSCRIPTEN
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
// Используем GL_DEPTH_COMPONENT24 для WebGL 2
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24,
resolution, resolution, 0,
GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
// ВАЖНО: Используем GL_NEAREST, так как GL_LINEAR часто ломает FBO в WebGL
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#else
glGenTextures(1, &depthTexture); glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
@ -65,17 +81,24 @@ namespace ZL {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 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_WRAP_T, GL_CLAMP_TO_EDGE);
#endif
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D, depthTexture, 0); GL_TEXTURE_2D, depthTexture, 0);
// No color buffer for this FBO — depth only. // No color buffer for this FBO — depth only.
#ifdef EMSCRIPTEN
GLenum drawBuffers[] = { GL_NONE };
glDrawBuffers(1, drawBuffers);
#else
glDrawBuffer(GL_NONE); glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE); glReadBuffer(GL_NONE);
#endif
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Error: Shadow map framebuffer is not complete!" << std::endl; GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Error: Shadow map framebuffer is not complete! Status: 0x"
<< std::hex << status << std::endl;
} }
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);

View File

@ -54,13 +54,13 @@ bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixe
zipfilename zipfilename
); );
#endif #endif
ZL::CheckGlError(); ZL::CheckGlError(__FILE__, __LINE__);
if (!loadGlyphs(ttfPath, pixelSize, zipfilename)) return false; if (!loadGlyphs(ttfPath, pixelSize, zipfilename)) return false;
ZL::CheckGlError(); ZL::CheckGlError(__FILE__, __LINE__);
textMesh.data.PositionData.resize(6, Eigen::Vector3f(0, 0, 0)); textMesh.data.PositionData.resize(6, Eigen::Vector3f(0, 0, 0));
textMesh.RefreshVBO(); textMesh.RefreshVBO();
ZL::CheckGlError(); ZL::CheckGlError(__FILE__, __LINE__);
return true; return true;
} }

View File

@ -54,7 +54,7 @@ namespace ZL
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
static_cast<GLsizei>(width), static_cast<GLsizei>(height), static_cast<GLsizei>(width), static_cast<GLsizei>(height),
0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data()); 0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data());
CheckGlError(); CheckGlError(__FILE__, __LINE__);
// 3. Фильтрация и Мип-мапы // 3. Фильтрация и Мип-мапы
// ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет! // ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет!
@ -67,7 +67,7 @@ namespace ZL
} }
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
CheckGlError(); CheckGlError(__FILE__, __LINE__);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
} }
@ -91,7 +91,7 @@ namespace ZL
glBindTexture(GL_TEXTURE_CUBE_MAP, texID); glBindTexture(GL_TEXTURE_CUBE_MAP, texID);
CheckGlError(); CheckGlError(__FILE__, __LINE__);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@ -106,7 +106,7 @@ namespace ZL
#endif #endif
#endif #endif
CheckGlError(); CheckGlError(__FILE__, __LINE__);
for (int i = 0; i < 6; ++i) for (int i = 0; i < 6; ++i)
{ {
@ -136,7 +136,7 @@ namespace ZL
GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
texDataArray[i].data.data() texDataArray[i].data.data()
); );
CheckGlError(); CheckGlError(__FILE__, __LINE__);
} }
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0);