Working on web version

This commit is contained in:
Vladislav Khorev committed 2026-04-17 16:13:56 +03:00
1 parent 3d3c3b2a36
commit e310822462
20 files changed
+432 -328

No files matched your search

+26
View File
@@ -8,6 +8,10 @@
namespace ZL
{
#ifdef EMSCRIPTEN
using std::min;
using std::max;
#endif
int getIndexByValue(const std::string& name, const std::vector<std::string>& 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<GLsizei>(bindPoseMutable.data.PositionData.size()));
}
+26
View File
@@ -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<int>(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<float>(Environment::width) / static_cast<float>(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<int>(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
+9 -5
View File
@@ -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);
+10 -1
View File
@@ -9,7 +9,7 @@
#include <algorithm>
#include <functional>
#include <memory>
#include <cfloat>
#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();
-71
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
-38
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
+22 -4
View File
@@ -3,6 +3,11 @@
#include "utils/Utils.h"
#include <iostream>
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;
+64 -4
View File
@@ -14,10 +14,40 @@ namespace ZL {
std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
{
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;
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<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
{
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;
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;
+10
View File
@@ -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");
}
}
+2
View File
@@ -161,4 +161,6 @@ namespace ZL {
bool BindOpenGlFunctions();
void CheckGlError();
void CheckGlError(const char* file, int line);
}
+1 -1
View File
@@ -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)
+27 -4
View File
@@ -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);
+3 -3
View File
@@ -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;
}
+5 -5
View File
@@ -54,7 +54,7 @@ namespace ZL
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
static_cast<GLsizei>(width), static_cast<GLsizei>(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);