From e31082246269589738202f890d3210848139d2ec Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 17 Apr 2026 16:13:56 +0300 Subject: [PATCH] Working on web version --- blender scripts/generate_tree001.py | 111 ++++++++++++++++ config/ui.json | 156 ----------------------- proj-web/CMakeLists.txt | 66 +++++----- proj-web/index.html | 82 ++++++++++++ proj-windows/CMakeLists.txt | 2 - resources/dialogue/sample_dialogues.json | 2 +- src/BoneAnimatedModel.cpp | 26 ++++ src/Character.cpp | 26 ++++ src/Game.cpp | 14 +- src/Location.cpp | 11 +- src/Projectile.cpp | 71 ----------- src/Projectile.h | 38 ------ src/dialogue/DialogueDatabase.cpp | 26 +++- src/items/GameObjectLoader.cpp | 68 +++++++++- src/render/OpenGlExtensions.cpp | 10 ++ src/render/OpenGlExtensions.h | 2 + src/render/Renderer.cpp | 2 +- src/render/ShadowMap.cpp | 31 ++++- src/render/TextRenderer.cpp | 6 +- src/render/TextureManager.cpp | 10 +- 20 files changed, 432 insertions(+), 328 deletions(-) create mode 100644 blender scripts/generate_tree001.py delete mode 100644 config/ui.json create mode 100644 proj-web/index.html delete mode 100644 src/Projectile.cpp delete mode 100644 src/Projectile.h diff --git a/blender scripts/generate_tree001.py b/blender scripts/generate_tree001.py new file mode 100644 index 0000000..5f34c80 --- /dev/null +++ b/blender scripts/generate_tree001.py @@ -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() + diff --git a/config/ui.json b/config/ui.json deleted file mode 100644 index 974d34a..0000000 --- a/config/ui.json +++ /dev/null @@ -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" - } - } - ] - } -} \ No newline at end of file diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index e1fd3a7..2d5b599 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT CMAKE_MAKE_PROGRAM AND WIN32) endif() endif() -project(space-game001 LANGUAGES C CXX) +project(bishkek-witcher LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -83,49 +83,49 @@ set(SOURCES ../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/render/ShadowMap.cpp + ../src/render/ShadowMap.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/WebSocketClientBase.h -# ../src/network/WebSocketClientBase.cpp -# ../src/network/WebSocketClientEmscripten.h -# ../src/network/WebSocketClientEmscripten.cpp ../src/render/TextRenderer.h ../src/render/TextRenderer.cpp ../src/MenuManager.h ../src/MenuManager.cpp -# ../src/Space.h -# ../src/Space.cpp + ../src/Location.h + ../src/Location.cpp ../src/GameConstants.h ../src/GameConstants.cpp ../src/ScriptEngine.h ../src/ScriptEngine.cpp ../src/navigation/PathFinder.h ../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 ../thirdparty/eigen-5.0.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) -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 @@ -155,7 +155,7 @@ set(EMSCRIPTEN_FLAGS "-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. # resources.zip is downloaded asynchronously at runtime and served as a separate file. @@ -171,11 +171,11 @@ set(EMSCRIPTEN_LINK_FLAGS ) # Применяем настройки линковки -target_link_options(space-game001 PRIVATE ${EMSCRIPTEN_LINK_FLAGS}) +target_link_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_LINK_FLAGS}) # Для совместимости со старыми версиями CMake, если target_link_options недостаточно 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}" SUFFIX ".html" ) @@ -193,33 +193,33 @@ add_custom_command( ) 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") # 1. Устанавливаем основной HTML файл -install(TARGETS space-game001 +install(TARGETS bishkek-witcher RUNTIME DESTINATION . ) # 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/space-game001.js" - "${CMAKE_CURRENT_BINARY_DIR}/space-game001.wasm" - "${CMAKE_CURRENT_BINARY_DIR}/space-game001.data" + "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.js" + "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.wasm" + "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.data" DESTINATION . ) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/space-game001plain.html" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/index.html" DESTINATION . ) # resources.zip is served separately and downloaded asynchronously at runtime 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 . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Automatically deploying to public directory..." diff --git a/proj-web/index.html b/proj-web/index.html new file mode 100644 index 0000000..2f35ddb --- /dev/null +++ b/proj-web/index.html @@ -0,0 +1,82 @@ + + + + + + Bishkek Witcher + + + + +
Downloading...
+ + + + + + diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0ec0df8..5bcf89b 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -46,8 +46,6 @@ add_executable(space-game001 ../src/render/ShadowMap.h ../src/UiManager.cpp ../src/UiManager.h -# ../src/Projectile.h -# ../src/Projectile.cpp ../src/render/TextRenderer.h ../src/render/TextRenderer.cpp ../src/MenuManager.h diff --git a/resources/dialogue/sample_dialogues.json b/resources/dialogue/sample_dialogues.json index b232fa4..afb586e 100644 --- a/resources/dialogue/sample_dialogues.json +++ b/resources/dialogue/sample_dialogues.json @@ -59,7 +59,7 @@ "type": "Choice", "speaker": "Hero", "portrait": "resources/hero.png", - "text": "Choose your answer.", + "text": "", "choices": [ { "id": "main_1", diff --git a/src/BoneAnimatedModel.cpp b/src/BoneAnimatedModel.cpp index 099e6d3..8d7ccc6 100644 --- a/src/BoneAnimatedModel.cpp +++ b/src/BoneAnimatedModel.cpp @@ -8,6 +8,10 @@ namespace ZL { +#ifdef EMSCRIPTEN + using std::min; + using std::max; +#endif int getIndexByValue(const std::string& name, const std::vector& words) { @@ -909,29 +913,51 @@ namespace ZL void GpuSkinningShaderData::RenderVBO(Renderer& renderer) { + CheckGlError(__FILE__, __LINE__); // Bind position and texcoord VBOs glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer()); renderer.VertexAttribPointer3fv("vPosition", 0, NULL); + CheckGlError(__FILE__, __LINE__); if (bindPoseMutable.texCoordVBO) { glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer()); 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 glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer()); renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL); + CheckGlError(__FILE__, __LINE__); + glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer()); renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL); + CheckGlError(__FILE__, __LINE__); + // Bind bone weight VBOs glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer()); renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL); + CheckGlError(__FILE__, __LINE__); + glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer()); renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL); + CheckGlError(__FILE__, __LINE__); + glDrawArrays(GL_TRIANGLES, 0, static_cast(bindPoseMutable.data.PositionData.size())); } diff --git a/src/Character.cpp b/src/Character.cpp index 3360992..6e3e2dc 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -417,12 +417,18 @@ void Character::drawShadowDepthCpu(Renderer& renderer) { } void Character::drawShadowDepthGpuSkinning(Renderer& renderer) { + + CheckGlError(__FILE__, __LINE__); AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); if (it == animations.end()) return; + CheckGlError(__FILE__, __LINE__); + if (!prepareGpuSkinning()) return; + CheckGlError(__FILE__, __LINE__); + static const std::string shadowSkinningShader = "shadow_depth_skinning"; static const std::string boneMatricesUniform = "uBoneMatrices[0]"; @@ -435,14 +441,19 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) { renderer.ScaleMatrix(modelScale); renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); + CheckGlError(__FILE__, __LINE__); renderer.RenderUniformMatrix4fvArray(boneMatricesUniform, static_cast(it->second.gpuSkinningShaderData.skinningMatrices.size()), false, it->second.gpuSkinningShaderData.skinningMatrices[0].data()); + CheckGlError(__FILE__, __LINE__); it->second.gpuSkinningShaderData.RenderVBO(renderer); + CheckGlError(__FILE__, __LINE__); renderer.PopMatrix(); renderer.shaderManager.PopShader(); + + CheckGlError(__FILE__, __LINE__); } // ==================== Main pass with shadows ==================== @@ -498,8 +509,12 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri auto it = animations.find(drawState); if (it == animations.end() || !texture) return; + CheckGlError(__FILE__, __LINE__); + if (!prepareGpuSkinning()) return; + CheckGlError(__FILE__, __LINE__); + static const std::string skinningShadowShader = "skinning_shadow"; 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.RenderUniform3fv("uLightDir", lightDirCamera.data()); + CheckGlError(__FILE__, __LINE__); + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shadowMapTex); glActiveTexture(GL_TEXTURE0); + CheckGlError(__FILE__, __LINE__); + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); @@ -523,17 +542,24 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri renderer.ScaleMatrix(modelScale); renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); + CheckGlError(__FILE__, __LINE__); renderer.RenderUniformMatrix4fvArray(boneMatricesUniform, static_cast(it->second.gpuSkinningShaderData.skinningMatrices.size()), false, it->second.gpuSkinningShaderData.skinningMatrices[0].data()); + CheckGlError(__FILE__, __LINE__); glBindTexture(GL_TEXTURE_2D, texture->getTexID()); + CheckGlError(__FILE__, __LINE__); it->second.gpuSkinningShaderData.RenderVBO(renderer); + CheckGlError(__FILE__, __LINE__); + renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); + + CheckGlError(__FILE__, __LINE__); } } // namespace ZL diff --git a/src/Game.cpp b/src/Game.cpp index c2cf608..6eeacff 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -72,7 +72,7 @@ namespace ZL Environment::computeProjectionDimensions(); ZL::BindOpenGlFunctions(); - ZL::CheckGlError(); + ZL::CheckGlError(__FILE__, __LINE__); renderer.InitOpenGL(); #ifdef EMSCRIPTEN @@ -236,7 +236,7 @@ namespace ZL glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); } void Game::drawScene() { @@ -249,11 +249,15 @@ namespace ZL if (currentLocation) { if (currentLocation->shadowMap) { + CheckGlError(__FILE__, __LINE__); currentLocation->drawShadowDepthPass(); + CheckGlError(__FILE__, __LINE__); currentLocation->drawGameWithShadows(); + CheckGlError(__FILE__, __LINE__); } else { currentLocation->drawGame(); + CheckGlError(__FILE__, __LINE__); } } else @@ -262,7 +266,7 @@ namespace ZL } drawUI(); } - CheckGlError(); + CheckGlError(__FILE__, __LINE__); } void Game::drawLoading() @@ -289,7 +293,7 @@ namespace ZL renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); } @@ -329,7 +333,7 @@ namespace ZL } void Game::render() { - ZL::CheckGlError(); + ZL::CheckGlError(__FILE__, __LINE__); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/src/Location.cpp b/src/Location.cpp index a873de9..71e6884 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -9,7 +9,7 @@ #include #include #include - +#include #include "GameConstants.h" @@ -449,19 +449,23 @@ namespace ZL renderer.RenderUniform3fv("uLightDir", lightDirCamera.data()); #endif + CheckGlError(__FILE__, __LINE__); glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); renderer.DrawVertexRenderStruct(roomMesh); + CheckGlError(__FILE__, __LINE__); for (auto& [name, gameObj] : gameObjects) { glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); renderer.DrawVertexRenderStruct(gameObj.mesh); } + CheckGlError(__FILE__, __LINE__); for (auto& intObj : interactiveObjects) { if (intObj.isActive) { intObj.draw(renderer); } } + CheckGlError(__FILE__, __LINE__); #ifdef DEBUG_LIGHT // In debug-light mode characters use the plain shaders (draw normally @@ -470,9 +474,14 @@ namespace ZL for (auto& npc : npcs) npc->draw(renderer); #else // Characters use their own shadow-aware shaders + CheckGlError(__FILE__, __LINE__); + if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); + CheckGlError(__FILE__, __LINE__); + for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); #endif + CheckGlError(__FILE__, __LINE__); renderer.PopMatrix(); renderer.PopProjectionMatrix(); diff --git a/src/Projectile.cpp b/src/Projectile.cpp deleted file mode 100644 index ee2eaba..0000000 --- a/src/Projectile.cpp +++ /dev/null @@ -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 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 \ No newline at end of file diff --git a/src/Projectile.h b/src/Projectile.h deleted file mode 100644 index 2f3b4cc..0000000 --- a/src/Projectile.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "render/Renderer.h" -#include "render/TextureManager.h" -#include -#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 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; - - void rebuildMesh(Renderer& renderer); - }; - -} // namespace ZL \ No newline at end of file diff --git a/src/dialogue/DialogueDatabase.cpp b/src/dialogue/DialogueDatabase.cpp index 93ada15..bc5db6d 100644 --- a/src/dialogue/DialogueDatabase.cpp +++ b/src/dialogue/DialogueDatabase.cpp @@ -3,6 +3,11 @@ #include "utils/Utils.h" #include +namespace ZL +{ + extern const char* CONST_ZIP_FILE; +} + namespace ZL::Dialogue { NodeType DialogueDatabase::parseNodeType(const std::string& value) { @@ -202,10 +207,23 @@ bool DialogueDatabase::loadFromFile(const std::string& path) { dialogues.clear(); cutscenes.clear(); - const std::string raw = ZL::readTextFile(path); - if (raw.empty()) { - std::cerr << "[dialogue] Failed to read file: " << path << "\n"; - return false; + std::string raw; + try { + if (strlen(CONST_ZIP_FILE) == 0) { + 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; diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index fb5a9c9..c7cf2a9 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -14,10 +14,40 @@ namespace ZL { std::vector GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector 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; + 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 { - std::ifstream file(jsonPath); + /*std::ifstream file(jsonPath); if (!file.is_open()) { throw std::runtime_error("Could not open file: " + jsonPath); } @@ -26,7 +56,7 @@ namespace ZL { throw std::runtime_error("JSON file is empty: " + jsonPath); } - file >> j; + file >> j;*/ if (!j.contains("objects") || !j["objects"].is_array()) { std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl; @@ -255,10 +285,40 @@ namespace ZL { std::vector GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector 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; + 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 { - std::ifstream file(jsonPath); + /*std::ifstream file(jsonPath); if (!file.is_open()) { throw std::runtime_error("Could not open file: " + jsonPath); } @@ -267,7 +327,7 @@ namespace ZL { throw std::runtime_error("JSON file is empty: " + jsonPath); } - file >> j; + file >> j;*/ if (!j.contains("npcs") || !j["npcs"].is_array()) { std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl; diff --git a/src/render/OpenGlExtensions.cpp b/src/render/OpenGlExtensions.cpp index f57e1c5..294d25c 100644 --- a/src/render/OpenGlExtensions.cpp +++ b/src/render/OpenGlExtensions.cpp @@ -329,6 +329,16 @@ namespace ZL { size_t error = glGetError(); 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"); } } diff --git a/src/render/OpenGlExtensions.h b/src/render/OpenGlExtensions.h index a80ddcb..af73844 100644 --- a/src/render/OpenGlExtensions.h +++ b/src/render/OpenGlExtensions.h @@ -161,4 +161,6 @@ namespace ZL { bool BindOpenGlFunctions(); void CheckGlError(); + + void CheckGlError(const char* file, int line); } \ No newline at end of file diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 83e2ab1..aebce47 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -536,7 +536,7 @@ namespace ZL { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthFunc(GL_LEQUAL); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); } void Renderer::PushProjectionMatrix(float width, float height, float zNear, float zFar) diff --git a/src/render/ShadowMap.cpp b/src/render/ShadowMap.cpp index f689ed5..55e3503 100644 --- a/src/render/ShadowMap.cpp +++ b/src/render/ShadowMap.cpp @@ -56,6 +56,22 @@ namespace ZL { { 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); glBindTexture(GL_TEXTURE_2D, depthTexture); 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_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - +#endif glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0); // No color buffer for this FBO — depth only. +#ifdef EMSCRIPTEN + GLenum drawBuffers[] = { GL_NONE }; + glDrawBuffers(1, drawBuffers); +#else glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - std::cerr << "Error: Shadow map framebuffer is not complete!" << std::endl; +#endif + + 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); diff --git a/src/render/TextRenderer.cpp b/src/render/TextRenderer.cpp index 5cbb122..ad1e915 100644 --- a/src/render/TextRenderer.cpp +++ b/src/render/TextRenderer.cpp @@ -54,13 +54,13 @@ bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixe zipfilename ); #endif - ZL::CheckGlError(); + ZL::CheckGlError(__FILE__, __LINE__); if (!loadGlyphs(ttfPath, pixelSize, zipfilename)) return false; - ZL::CheckGlError(); + ZL::CheckGlError(__FILE__, __LINE__); textMesh.data.PositionData.resize(6, Eigen::Vector3f(0, 0, 0)); textMesh.RefreshVBO(); - ZL::CheckGlError(); + ZL::CheckGlError(__FILE__, __LINE__); return true; } diff --git a/src/render/TextureManager.cpp b/src/render/TextureManager.cpp index e5373bf..e348e7b 100644 --- a/src/render/TextureManager.cpp +++ b/src/render/TextureManager.cpp @@ -54,7 +54,7 @@ namespace ZL glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, static_cast(width), static_cast(height), 0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data()); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); // 3. Фильтрация и Мип-мапы // ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет! @@ -67,7 +67,7 @@ namespace ZL } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } @@ -91,7 +91,7 @@ namespace ZL glBindTexture(GL_TEXTURE_CUBE_MAP, texID); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -106,7 +106,7 @@ namespace ZL #endif #endif - CheckGlError(); + CheckGlError(__FILE__, __LINE__); for (int i = 0; i < 6; ++i) { @@ -136,7 +136,7 @@ namespace ZL GL_UNSIGNED_BYTE, texDataArray[i].data.data() ); - CheckGlError(); + CheckGlError(__FILE__, __LINE__); } glBindTexture(GL_TEXTURE_CUBE_MAP, 0);