#include "Location.h" #include "utils/Utils.h" #include "render/OpenGlExtensions.h" #include #include "render/TextureManager.h" #include "TextModel.h" #include #include #include #include #include #include #include "GameConstants.h" namespace ZL { extern float x; extern const char* CONST_ZIP_FILE; static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f; Location::Location(Renderer& iRenderer, Inventory& iInventory, const std::string& locId) : renderer(iRenderer) , inventory(iInventory) , locationId(locId) { } /*void Location::setup() { roomTexture = std::make_unique(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE); roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); roomMesh.RefreshVBO(); carTexture = std::make_unique(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE)); carMesh.data = LoadFromTextFile02("resources/e/car001.txt", CONST_ZIP_FILE); //carMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carMesh.RefreshVBO(); // Load static game objects gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); // Load interactive objects interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); player = std::make_unique(); player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); player->setTexture(playerTexture); player->walkSpeed = 3.0f; player->rotationSpeed = 8.0f; player->modelScale = 1.f; player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); player->canAttack = true; player->isPlayer = true; std::cout << "Load resurces step 9" << std::endl; // Load NPCs from JSON npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE); auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); std::cout << "Load resurces step 11" << std::endl; auto npc02 = std::make_unique(); npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->setTexture(ghostTexture); npc02->walkSpeed = 1.5f; npc02->rotationSpeed = 8.0f; npc02->modelScale = 0.01f; //npc02->modelScale = 0.1f; npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f); npc02->setTarget(npc02->position); npc02->canAttack = true; npc02->attackTarget = player.get(); npcs.push_back(std::move(npc02)); // Create shadow map (2048x2048, ortho size 40, near 0.1, far 100) shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); std::cout << "Shadow map initialized" << std::endl; setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); /*dialogueSystem.addTriggerZone({ "ghost_room_trigger", "test_line_dialogue", Eigen::Vector3f(0.0f, 0.0f, -8.5f), 2.0f, true, false }); }*/ /*void Location::setup() { std::string roomTexPath; std::string roomMeshPath; std::string gameObjectsPath; std::string interactiveObjectsPath; std::string npcsPath; if (locationId == "forest") { std::cout << "[LOCATION] Setting up FOREST location (using placeholder resources)" << std::endl; roomTexPath = "resources/w/room005.png"; roomMeshPath = "resources/w/room001.txt"; gameObjectsPath = "resources/config2/gameobjects2.json"; interactiveObjectsPath = "resources/config2/gameobjects2.json"; npcsPath = "resources/config2/npcs2.json"; } else // default { std::cout << "[LOCATION] Setting up DEFAULT location" << std::endl; roomTexPath = "resources/w/room005.png"; roomMeshPath = "resources/w/room001.txt"; gameObjectsPath = "resources/config2/gameobjects.json"; interactiveObjectsPath = "resources/config2/gameobjects.json"; npcsPath = "resources/config2/npcs.json"; } roomTexture = std::make_unique(CreateTextureDataFromPng(roomTexPath.c_str(), CONST_ZIP_FILE)); roomMesh.data = LoadFromTextFile02(roomMeshPath.c_str(), CONST_ZIP_FILE); roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); roomMesh.RefreshVBO(); carTexture = std::make_unique(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE)); carMesh.data = LoadFromTextFile02("resources/e/car001.txt", CONST_ZIP_FILE); carMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carMesh.RefreshVBO(); carWheelTexture = std::make_unique(CreateTextureDataFromPng("resources/e/Wheel_base002.png", CONST_ZIP_FILE)); carWheelMesh.data = LoadFromTextFile02("resources/e/car_wheel001.txt", CONST_ZIP_FILE); carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carWheelMesh.RefreshVBO(); gameObjects = GameObjectLoader::loadAndCreateGameObjects(gameObjectsPath, renderer, CONST_ZIP_FILE); interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects(interactiveObjectsPath, renderer, CONST_ZIP_FILE); auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); player = std::make_unique(); player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); player->setTexture(playerTexture); player->walkSpeed = 3.0f; player->rotationSpeed = 8.0f; player->modelScale = 1.f; player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); player->canAttack = true; player->isPlayer = true; std::cout << "Load resources step 9" << std::endl; npcs = GameObjectLoader::loadAndCreateNpcs(npcsPath, CONST_ZIP_FILE); auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); std::cout << "Load resources step 11" << std::endl; auto npc02 = std::make_unique(); npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->setTexture(ghostTexture); npc02->walkSpeed = 1.5f; npc02->rotationSpeed = 8.0f; npc02->modelScale = 0.01f; npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f); npc02->setTarget(npc02->position); npc02->canAttack = true; npc02->attackTarget = player.get(); npcs.push_back(std::move(npc02)); shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); std::cout << "Shadow map initialized" << std::endl; setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); }*/ void Location::setup() { if (locationId == "forest") { std::cout << "[LOCATION] Setting up FOREST location (custom models only)" << std::endl; gameObjects.clear(); interactiveObjects.clear(); npcs.clear(); debugNavMeshes.clear(); roomMesh.data = VertexDataStruct(); //roomMesh.data = LoadFromTextFile02("resources/w/road001.txt", CONST_ZIP_FILE); //roomMesh.RefreshVBO(); roomTexture = std::make_unique(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); struct ModelAsset { const char* meshPath; const char* texPath; Eigen::Vector3f position; float scale; }; std::vector models = { {"resources/out/AzsBase001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/AzsRoof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/floor001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/roof001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/Walls001.txt", "resources/ghost_avatar.png", {0.0f, 2.0f, 0.0f}, 2.0f}, {"resources/out/Price001.txt", "resources/w/blue.png", {0.0f, 1.0f, 5.0f}, 2.0f} }; for (size_t i = 0; i < models.size(); ++i) { const auto& m = models[i]; LoadedGameObject obj; obj.mesh.data = LoadFromTextFile02(m.meshPath, CONST_ZIP_FILE); obj.mesh.data.Scale(m.scale); obj.mesh.data.Move(m.position); obj.mesh.RefreshVBO(); obj.texture = std::make_shared(CreateTextureDataFromPng(m.texPath, CONST_ZIP_FILE)); gameObjects["forest_model_" + std::to_string(i)] = std::move(obj); } auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); player = std::make_unique(); player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); player->setTexture(playerTexture); player->walkSpeed = 3.0f; player->rotationSpeed = 8.0f; player->modelScale = 1.f; player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); player->canAttack = true; player->isPlayer = true; shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); std::cout << "[FOREST] Setup complete, loaded " << gameObjects.size() << " custom models" << std::endl; } else // default { std::cout << "[LOCATION] Setting up DEFAULT location" << std::endl; std::string roomTexPath = "resources/w/room005.png"; std::string roomMeshPath = "resources/w/room001.txt"; std::string gameObjectsPath = "resources/config2/gameobjects.json"; std::string interactiveObjectsPath = "resources/config2/gameobjects.json"; std::string npcsPath = "resources/config2/npcs.json"; roomTexture = std::make_unique(CreateTextureDataFromPng(roomTexPath.c_str(), CONST_ZIP_FILE)); roomMesh.data = LoadFromTextFile02(roomMeshPath.c_str(), CONST_ZIP_FILE); roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); roomMesh.RefreshVBO(); gameObjects = GameObjectLoader::loadAndCreateGameObjects(gameObjectsPath, renderer, CONST_ZIP_FILE); interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects(interactiveObjectsPath, renderer, CONST_ZIP_FILE); npcs = GameObjectLoader::loadAndCreateNpcs(npcsPath, CONST_ZIP_FILE); auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); player = std::make_unique(); player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); player->setTexture(playerTexture); player->walkSpeed = 3.0f; player->rotationSpeed = 8.0f; player->modelScale = 1.f; player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); player->canAttack = true; player->isPlayer = true; auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); auto npc02 = std::make_unique(); npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); npc02->setTexture(ghostTexture); npc02->walkSpeed = 1.5f; npc02->rotationSpeed = 8.0f; npc02->modelScale = 0.01f; npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f); npc02->setTarget(npc02->position); npc02->canAttack = true; npc02->attackTarget = player.get(); npcs.push_back(std::move(npc02)); shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); setupNavigation(); scriptEngine.init(this, &inventory); dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); } carTexture = std::make_unique(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE)); carMesh.data = LoadFromTextFile02("resources/e/car001.txt", CONST_ZIP_FILE); carMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carMesh.RefreshVBO(); carWheelTexture = std::make_unique(CreateTextureDataFromPng("resources/e/Wheel_base002.png", CONST_ZIP_FILE)); carWheelMesh.data = LoadFromTextFile02("resources/e/car_wheel001.txt", CONST_ZIP_FILE); carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carWheelMesh.RefreshVBO(); } void Location::setupNavigation() { std::vector obstacles; /*obstacles.reserve(gameObjects.size() + interactiveObjects.size()); for (const auto& item : gameObjects) { const LoadedGameObject& gameObj = item.second; obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() }); } for (const InteractiveObject& intObj : interactiveObjects) { if (!intObj.isActive) { continue; } obstacles.push_back({ &intObj.mesh.data, intObj.position }); }*/ if (locationId == "forest") { navigation.build(obstacles, "resources/config2/navigation2.json", CONST_ZIP_FILE); } else { navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE); } //#ifdef SHOW_PATH buildDebugNavMeshes(); //#endif auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) { return navigation.findPath(start, end); }; if (player) { player->setPathPlanner(planner); } for (auto& npc : npcs) { if (npc) { npc->setPathPlanner(planner); } } } //#ifdef SHOW_PATH void Location::buildDebugNavMeshes() { debugNavMeshes.clear(); const auto& areas = navigation.getAreas(); float y = navigation.getFloorY() + 0.02f; Eigen::Vector3f red(1.0f, 0.0f, 0.0f); for (const auto& area : areas) { if (area.polygon.size() < 3) continue; VertexRenderStruct mesh; mesh.data = CreatePolygonFloor(area.polygon, y, red); mesh.RefreshVBO(); debugNavMeshes.push_back(std::move(mesh)); } } void Location::drawDebugNavigation() { renderer.shaderManager.PushShader("defaultColor"); renderer.SetMatrix(); for (const auto& mesh : debugNavMeshes) { renderer.DrawVertexRenderStruct(mesh); } renderer.shaderManager.PopShader(); renderer.SetMatrix(); } //#endif InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) { if (interactiveObjects.empty()) { std::cout << "[RAYCAST] No interactive objects to check" << std::endl; return nullptr; } std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl; std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl; std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl; float closestDistance = FLT_MAX; InteractiveObject* closestObject = nullptr; for (auto& intObj : interactiveObjects) { std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl; if (!intObj.isActive) { std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl; continue; } std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", " << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl; Eigen::Vector3f toObject = intObj.position - rayOrigin; std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl; float distanceAlongRay = toObject.dot(rayDir); std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl; if (distanceAlongRay < 0.1f) { std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl; continue; } Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay; float distToObject = (closestPointOnRay - intObj.position).norm(); std::cout << "[RAYCAST] Distance to object: " << distToObject << " (interaction radius: " << intObj.interactionRadius << ")" << std::endl; if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) { std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl; closestDistance = distanceAlongRay; closestObject = &intObj; } } if (closestObject) { std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl; } else { std::cout << "[RAYCAST] No objects hit" << std::endl; } return closestObject; } Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) { Character* closestNpc = nullptr; float closestDist = maxDistance; for (auto& npc : npcs) { Eigen::Vector3f toNpc = npc->position - rayOrigin; float distAlongRay = toNpc.dot(rayDir); if (distAlongRay < 0.1f) continue; Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay; float distToNpc = (closestPoint - npc->position).norm(); float radius = npc->modelScale * 50.0f; if (distToNpc <= radius && distAlongRay < closestDist) { closestDist = distAlongRay; closestNpc = npc.get(); } } return closestNpc; } void Location::drawGame() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); //renderer.TranslateMatrix({ 0, -6.f, 0 }); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); if (roomMesh.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); renderer.DrawVertexRenderStruct(roomMesh); } for (auto& [name, gameObj] : gameObjects) { glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); renderer.DrawVertexRenderStruct(gameObj.mesh); } for (auto& intObj : interactiveObjects) { if (intObj.isActive) { intObj.draw(renderer); } } renderer.PushMatrix(); //renderer.LoadIdentity(); renderer.TranslateMatrix(carPosition); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carRotation, Eigen::Vector3f::UnitY())).toRotationMatrix()); glBindTexture(GL_TEXTURE_2D, carTexture->getTexID()); renderer.DrawVertexRenderStruct(carMesh); if (carWheelTexture) { constexpr float track_width = 1.28f; constexpr float wheel_base = 2.25f; constexpr float shift = 0.6f; const Eigen::Vector3f wheelPositions[4] = { Eigen::Vector3f( track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front right Eigen::Vector3f(-track_width, 0.f - 0.21, -(wheel_base + shift)+1.25), // front left Eigen::Vector3f( track_width, 0.f - 0.21, (wheel_base - shift)+1.1), // rear right Eigen::Vector3f(-track_width, 0.f - 0.21, (wheel_base - shift)+1.1) // rear left }; const bool isFront[4] = { true, true, false, false }; glBindTexture(GL_TEXTURE_2D, carWheelTexture->getTexID()); for (int i = 0; i < 4; ++i) { renderer.PushMatrix(); renderer.TranslateMatrix(wheelPositions[i]); if (isFront[i]) { renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(carSteeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); } renderer.DrawVertexRenderStruct(carWheelMesh); renderer.PopMatrix(); } } renderer.PopMatrix(); if (player && !inCar) player->draw(renderer); for (auto& npc : npcs) npc->draw(renderer); //#ifdef SHOW_PATH drawDebugNavigation(); //#endif renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); } void Location::drawShadowDepthPass() { if (!shadowMap) return; const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero(); shadowMap->updateLightSpaceMatrix(sceneCenter); shadowMap->bind(); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); // Use front-face culling during depth pass to reduce shadow acne on lit faces //glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); renderer.shaderManager.PushShader("shadow_depth"); // Set up light's orthographic projection const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix(); const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix(); // Push the light's projection matrix via the 6-param ortho overload won't // match our pre-computed matrix. Instead, use the raw stack approach: // push a dummy projection then overwrite via PushSpecialMatrix-style. // Simpler: push ortho then push the light view as modelview. renderer.PushProjectionMatrix( -40.0f, 40.0f, -40.0f, 40.0f, 0.1f, 100.0f); const Eigen::Vector3f& lightDir = shadowMap->getLightDirection(); Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f; Eigen::Vector3f up(0.0f, 1.0f, 0.0f); if (std::abs(lightDir.dot(up)) > 0.99f) { up = Eigen::Vector3f(0.0f, 0.0f, 1.0f); } // Build the light view matrix and push it renderer.PushSpecialMatrix(lightView); // Draw static geometry renderer.DrawVertexRenderStruct(roomMesh); for (auto& [name, gameObj] : gameObjects) { renderer.DrawVertexRenderStruct(gameObj.mesh); } for (auto& intObj : interactiveObjects) { if (intObj.isActive && intObj.texture) { renderer.PushMatrix(); renderer.TranslateMatrix(intObj.position); renderer.DrawVertexRenderStruct(intObj.mesh); renderer.PopMatrix(); } } // Draw characters (they handle their own skinning shader switch internally) if (player) player->drawShadowDepth(renderer); for (auto& npc : npcs) npc->drawShadowDepth(renderer); renderer.PopMatrix(); // light view renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); //glCullFace(GL_BACK); glDisable(GL_CULL_FACE); shadowMap->unbind(); } void Location::drawGameWithShadows() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); #ifdef DEBUG_LIGHT // Debug mode: render from the light's point of view using the plain // textured shader so we can see what the shadow map "sees". renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.PushProjectionMatrix( -40.0f, 40.0f, -40.0f, 40.0f, 0.1f, 1000.0f); renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix()); #else static const std::string shadowShaderName = "default_shadow"; renderer.shaderManager.PushShader(shadowShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i("uShadowMap", 1); // Bind shadow map texture to unit 1 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture()); glActiveTexture(GL_TEXTURE0); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero(); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); // Capture the camera view matrix and compute uLightFromCamera cameraViewMatrix = renderer.GetCurrentModelViewMatrix(); Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse(); Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse; renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data()); // Light direction in camera space for diffuse lighting Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3, 3>(0, 0) * shadowMap->getLightDirection(); 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 // but from the light's viewpoint — projection/view already on stack). if (player) player->draw(renderer); 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__); drawDebugNavigation(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); } bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available) { return navigation.setAreaAvailable(areaName, available); } void Location::update(int64_t delta) { if (player) { player->update(delta); dialogueSystem.update(static_cast(delta), player->position); } for (auto& npc : npcs) npc->update(delta); if (inCar) { float dt = static_cast(delta) / 1000.0f; if (keyForward) { carVelocity += carAcceleration * dt; } if (keyBackward) { carVelocity -= carAcceleration * dt; } if (!keyForward && !keyBackward) { float resistance = carFriction * dt; if (carVelocity > 0.f) { carVelocity = max(0.f, carVelocity - resistance); } else if (carVelocity < 0.f) { carVelocity = min(0.f, carVelocity + resistance); } } carVelocity = max(-carMaxReverseSpeed, min(carMaxSpeed, carVelocity)); if (std::abs(carVelocity) > 0.01f) { float speedFactor = carVelocity / carMaxSpeed; if (keyLeft) carRotation += carTurnRate * dt * speedFactor; if (keyRight) carRotation -= carTurnRate * dt * speedFactor; } float targetSteer = (keyLeft ? 1.f : 0.f) - (keyRight ? 1.f : 0.f); targetSteer *= carMaxSteerAngle; float steerLerp = min(1.f, dt * 8.f); carSteeringAngle += (targetSteer - carSteeringAngle) * steerLerp; Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation)); carPosition += forward * carVelocity * dt; if (player) { player->position = carPosition; } } // Check if player reached target interactive object if (targetInteractiveObject && player) { float distToObject = (player->position - targetInteractiveObject->position).norm(); // If player is close enough to pick up the item if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) { std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl; std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl; // Call custom activate function if specified, otherwise use fallback try { if (!targetInteractiveObject->activateFunctionName.empty()) { std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl; scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName); } else { std::cout << "[PICKUP] Using fallback callback" << std::endl; scriptEngine.callItemPickupCallback(targetInteractiveObject->id); } } catch (const std::exception& e) { std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl; } targetInteractiveObject = nullptr; } } } void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my) { // Calculate ray for picking if (dialogueSystem.blocksGameplayInput()) { dialogueSystem.handlePointerReleased(static_cast(mx), Environment::projectionHeight - static_cast(my)); return; } if (inCar) { return; } player->attackTarget = nullptr; // Unproject click to ground plane (y=0) for Viola's walk target float ndcX = 2.0f * eventX / Environment::width - 1.0f; float ndcY = 1.0f - 2.0f * eventY / Environment::height; float aspect = (float)Environment::width / (float)Environment::height; float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f); float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth); float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination); Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim); Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl); Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl); const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero(); Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom; Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized(); std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl; std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl; // First check if we clicked on interactive object InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir); if (clickedObject && player && clickedObject->isActive) { std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl; std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", " << clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl; std::cout << "[CLICK] Player position: (" << player->position.x() << ", " << player->position.y() << ", " << player->position.z() << ")" << std::endl; targetInteractiveObject = clickedObject; player->setTarget(clickedObject->position); std::cout << "[CLICK] Player moving to object..." << std::endl; } else { // Check if we clicked on an NPC Character* clickedNpc = raycastNpcs(camPos, rayDir); if (clickedNpc && player) { float distance = (player->position - clickedNpc->position).norm(); int npcIndex = -1; for (size_t i = 0; i < npcs.size(); ++i) { if (npcs[i].get() == clickedNpc) { npcIndex = static_cast(i); break; } } if (npcIndex != -1) { if (distance <= clickedNpc->interactionRadius) { std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl; scriptEngine.callNpcInteractCallback(npcIndex); } else { std::cout << "[CLICK] Too far from NPC (distance " << distance << " > " << clickedNpc->interactionRadius << ")" << std::endl; } if (clickedNpc->canAttack) { player->attackTarget = clickedNpc; } } } else if (rayDir.y() < -0.001f && player) { // Otherwise, unproject click to ground plane for Viola's walk target float t = -camPos.y() / rayDir.y(); Eigen::Vector3f hit = camPos + rayDir * t; std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl; if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK) { player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); } } else { std::cout << "[CLICK] No valid target found" << std::endl; } } std::cout << "========================================\n" << std::endl; } void Location::handleUp(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(mx), Environment::projectionHeight - static_cast(my) ); } if (rightMouseDown) { int dx = eventX - lastMouseX; int dy = eventY - lastMouseY; lastMouseX = eventX; lastMouseY = eventY; const float sensitivity = 0.005f; cameraAzimuth += dx * sensitivity; cameraInclination += dy * sensitivity; const float minInclination = M_PI * 30.f / 180.f; const float maxInclination = M_PI * 0.5f; cameraInclination = max(minInclination, min(maxInclination, cameraInclination)); } } void Location::handleKeyDown(int sdlKey) { switch (sdlKey) { case SDLK_w: keyForward = true; break; case SDLK_s: keyBackward = true; break; case SDLK_a: keyLeft = true; break; case SDLK_d: keyRight = true; break; default: break; } } void Location::handleKeyUp(int sdlKey) { switch (sdlKey) { case SDLK_w: keyForward = false; break; case SDLK_s: keyBackward = false; break; case SDLK_a: keyLeft = false; break; case SDLK_d: keyRight = false; break; default: break; } } bool Location::requestDialogueStart(const std::string& dialogueId) { return dialogueSystem.startDialogue(dialogueId); } void Location::setDialogueFlag(const std::string& flag, int value) { dialogueSystem.setFlag(flag, value); } int Location::getDialogueFlag(const std::string& flag) const { return dialogueSystem.getFlag(flag); } void Location::cleanup() { std::cout << "[LOCATION] Starting cleanup..." << std::endl; npcs.clear(); interactiveObjects.clear(); gameObjects.clear(); player.reset(); roomTexture.reset(); //dialogueSystem.dialogueDatabase.clear(); navigation = PathFinder(); cameraAzimuth = 0.0f; cameraInclination = M_PI * 30.f / 180.f; targetInteractiveObject = nullptr; rightMouseDown = false; lastMouseX = 0; lastMouseY = 0; std::cout << "[LOCATION] Cleanup complete" << std::endl; } void Location::unload() { std::cout << "[LOCATION] Starting unload..." << std::endl; cleanup(); std::cout << "[LOCATION] Unload complete" << std::endl; } } // namespace ZL