Compare commits
2 Commits
f2f56125f0
...
a1dda3fd50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1dda3fd50 | ||
|
|
92ba3f2b60 |
@ -2,6 +2,7 @@
|
|||||||
-- NPC PATROL WAYPOINTS
|
-- NPC PATROL WAYPOINTS
|
||||||
-- ============================================
|
-- ============================================
|
||||||
|
|
||||||
|
|
||||||
local function step3()
|
local function step3()
|
||||||
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
||||||
end
|
end
|
||||||
@ -32,7 +33,6 @@ end
|
|||||||
|
|
||||||
function on_npc_interact(npc_index)
|
function on_npc_interact(npc_index)
|
||||||
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
||||||
|
|
||||||
if npc_index == 1 then
|
if npc_index == 1 then
|
||||||
game_api.start_dialogue("test_line_dialogue")
|
game_api.start_dialogue("test_line_dialogue")
|
||||||
else
|
else
|
||||||
|
|||||||
@ -758,64 +758,7 @@ namespace ZL
|
|||||||
|
|
||||||
prepared = true;
|
prepared = true;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
void BoneSystem::ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const
|
|
||||||
{
|
|
||||||
int startingKeyFrame = -1;
|
|
||||||
for (size_t i = 0; i < animations[0].keyFrames.size() - 1; i++)
|
|
||||||
{
|
|
||||||
int oldFrame = animations[0].keyFrames[i].frame;
|
|
||||||
int nextFrame = animations[0].keyFrames[i + 1].frame;
|
|
||||||
if (frame >= oldFrame && frame < nextFrame)
|
|
||||||
{
|
|
||||||
startingKeyFrame = static_cast<int>(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startingKeyFrame == -1)
|
|
||||||
{
|
|
||||||
outMatrices.resize(startBones.size());
|
|
||||||
for (auto& m : outMatrices) m = Matrix4f::Identity();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
|
|
||||||
int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
|
|
||||||
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
|
||||||
|
|
||||||
const std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
|
|
||||||
const std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
|
|
||||||
|
|
||||||
outMatrices.resize(startBones.size());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < startBones.size(); i++)
|
|
||||||
{
|
|
||||||
Vector3f interpPos;
|
|
||||||
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
|
|
||||||
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
|
|
||||||
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
|
|
||||||
|
|
||||||
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
|
||||||
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
|
||||||
|
|
||||||
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
|
|
||||||
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
|
|
||||||
Eigen::Quaternionf result = q1.slerp(t, q2);
|
|
||||||
|
|
||||||
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
|
|
||||||
|
|
||||||
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
|
|
||||||
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
|
|
||||||
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
|
|
||||||
|
|
||||||
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
|
||||||
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
|
||||||
|
|
||||||
outMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
void BoneSystem::Interpolate(int frame)
|
void BoneSystem::Interpolate(int frame)
|
||||||
{
|
{
|
||||||
int startingFrame = -1;
|
int startingFrame = -1;
|
||||||
|
|||||||
739
src/Game.cpp
739
src/Game.cpp
@ -143,111 +143,12 @@ namespace ZL
|
|||||||
|
|
||||||
std::cout << "Load resurces step 4" << std::endl;
|
std::cout << "Load resurces step 4" << std::endl;
|
||||||
|
|
||||||
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE));
|
currentLocation = std::make_unique<Location>(renderer, inventory);
|
||||||
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());
|
currentLocation->setup();
|
||||||
roomMesh.RefreshVBO();
|
|
||||||
std::cout << "Load resurces step 5" << std::endl;
|
std::cout << "Load resurces step 5" << std::endl;
|
||||||
|
|
||||||
// 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 violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
// Player (Viola)
|
|
||||||
player = std::make_unique<Character>();
|
|
||||||
/*
|
|
||||||
player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE);
|
|
||||||
*/
|
|
||||||
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(violaTexture);
|
|
||||||
player->walkSpeed = 3.0f;
|
|
||||||
player->rotationSpeed = 8.0f;
|
|
||||||
|
|
||||||
player->modelScale = 1.f;
|
|
||||||
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
|
||||||
//Eigen::Quaternionf::Identity();
|
|
||||||
/*Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5f, Eigen::Vector3f::UnitX())) * */
|
|
||||||
/*Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitZ()));*/
|
|
||||||
|
|
||||||
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 defaultTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/default_skin001.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
auto npc01 = std::make_unique<Character>();
|
|
||||||
npc01->loadAnimation(AnimationState::STAND, "resources/w/default_idle002.txt", CONST_ZIP_FILE);
|
|
||||||
npc01->loadAnimation(AnimationState::WALK, "resources/w/default_walk001.txt", CONST_ZIP_FILE);
|
|
||||||
//npc01->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
|
|
||||||
//npc01->loadAnimation(AnimationState::WALK, "resources/walkviola_uv010.txt", CONST_ZIP_FILE);
|
|
||||||
npc01->setTexture(defaultTexture);
|
|
||||||
npc01->walkSpeed = 1.5f;
|
|
||||||
npc01->rotationSpeed = 8.0f;
|
|
||||||
npc01->modelScale = 0.01f;
|
|
||||||
//npc01->modelScale = 0.1f;
|
|
||||||
npc01->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 10" << std::endl;
|
|
||||||
npc01->position = Eigen::Vector3f(0.f, 0.f, -10.f);
|
|
||||||
npc01->setTarget(npc01->position);
|
|
||||||
npcs.push_back(std::move(npc01));
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 11" << std::endl;
|
|
||||||
auto npc02 = std::make_unique<Character>();
|
|
||||||
/*
|
|
||||||
npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
|
||||||
*/
|
|
||||||
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->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
|
||||||
//npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", 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));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 12" << std::endl;
|
std::cout << "Load resurces step 12" << std::endl;
|
||||||
|
|
||||||
// Shadow mapping shaders
|
// Shadow mapping shaders
|
||||||
@ -263,27 +164,6 @@ namespace ZL
|
|||||||
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
|
|
||||||
shadowMap = std::make_unique<ShadowMap>(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();
|
|
||||||
|
|
||||||
loadingCompleted = true;
|
|
||||||
|
|
||||||
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
|
|
||||||
});*/
|
|
||||||
|
|
||||||
scriptEngine.init(this);
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 13" << std::endl;
|
std::cout << "Load resurces step 13" << std::endl;
|
||||||
|
|
||||||
@ -297,10 +177,25 @@ namespace ZL
|
|||||||
|
|
||||||
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
||||||
std::cout << "[UI] Inventory button clicked" << std::endl;
|
std::cout << "[UI] Inventory button clicked" << std::endl;
|
||||||
//scriptEngine.callItemPickupCallback("toggle_inventory");
|
this->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
||||||
scriptEngine.showInventory(this);
|
this->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
||||||
//scriptEngine
|
this->inventoryOpen = true;
|
||||||
//scriptEngine
|
|
||||||
|
// Update UI with current items
|
||||||
|
const auto& items = this->inventory.getItems();
|
||||||
|
std::string itemText;
|
||||||
|
|
||||||
|
if (items.empty()) {
|
||||||
|
itemText = "Inventory (Empty)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||||
|
for (size_t i = 0; i < items.size(); ++i) {
|
||||||
|
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||||
});
|
});
|
||||||
|
|
||||||
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
||||||
@ -313,152 +208,9 @@ namespace ZL
|
|||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Game::setupNavigation()
|
loadingCompleted = true;
|
||||||
{
|
|
||||||
std::vector<PathFinder::ObstacleMesh> 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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Game::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 Game::drawDebugNavigation()
|
|
||||||
{
|
|
||||||
renderer.shaderManager.PushShader("defaultColor");
|
|
||||||
renderer.SetMatrix();
|
|
||||||
for (const auto& mesh : debugNavMeshes) {
|
|
||||||
renderer.DrawVertexRenderStruct(mesh);
|
|
||||||
}
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
renderer.SetMatrix();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
InteractiveObject* Game::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* Game::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 Game::drawUI()
|
void Game::drawUI()
|
||||||
@ -473,7 +225,10 @@ namespace ZL
|
|||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
|
||||||
menuManager.uiManager.draw(renderer);
|
menuManager.uiManager.draw(renderer);
|
||||||
dialogueSystem.draw(renderer);
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->dialogueSystem.draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
@ -484,221 +239,6 @@ namespace ZL
|
|||||||
CheckGlError();
|
CheckGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::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<float>(Environment::width) / static_cast<float>(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());
|
|
||||||
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
|
||||||
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(fireboxMesh);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(inaiMesh);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(benchMesh);
|
|
||||||
*/
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
|
||||||
if (intObj.isActive) {
|
|
||||||
intObj.draw(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player) player->draw(renderer);
|
|
||||||
for (auto& npc : npcs) npc->draw(renderer);
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
drawDebugNavigation();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::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 Game::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<float>(Environment::width) / static_cast<float>(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
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#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
|
|
||||||
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
|
||||||
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::drawScene() {
|
void Game::drawScene() {
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
if (!loadingCompleted) {
|
if (!loadingCompleted) {
|
||||||
@ -706,11 +246,19 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (shadowMap) {
|
if (currentLocation)
|
||||||
drawShadowDepthPass();
|
{
|
||||||
drawGameWithShadows();
|
if (currentLocation->shadowMap) {
|
||||||
} else {
|
currentLocation->drawShadowDepthPass();
|
||||||
drawGame();
|
currentLocation->drawGameWithShadows();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentLocation->drawGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ??? Main menu???
|
||||||
}
|
}
|
||||||
drawUI();
|
drawUI();
|
||||||
}
|
}
|
||||||
@ -772,41 +320,11 @@ namespace ZL
|
|||||||
|
|
||||||
lastTickCount = newTickCount;
|
lastTickCount = newTickCount;
|
||||||
|
|
||||||
if (player) {
|
if (currentLocation)
|
||||||
player->update(delta);
|
{
|
||||||
dialogueSystem.update(static_cast<int>(delta), player->position);
|
currentLocation->update(delta);
|
||||||
}
|
|
||||||
for (auto& npc : npcs) npc->update(delta);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,97 +412,16 @@ namespace ZL
|
|||||||
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
||||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
|
|
||||||
player->attackTarget = nullptr;
|
|
||||||
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
||||||
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate ray for picking
|
if (currentLocation)
|
||||||
if (dialogueSystem.blocksGameplayInput()) {
|
|
||||||
dialogueSystem.handlePointerReleased(static_cast<float>(mx), Environment::projectionHeight - static_cast<float>(my));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unproject click to ground plane (y=0) for Viola's walk target
|
|
||||||
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
|
|
||||||
float ndcY = 1.0f - 2.0f * event.button.y / 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<int>(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;
|
currentLocation->handleDown(ZL::UiManager::MOUSE_FINGER_ID, event.button.x, event.button.y, mx, my);
|
||||||
}
|
}
|
||||||
}
|
///.....
|
||||||
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
}
|
}
|
||||||
@ -994,9 +431,19 @@ namespace ZL
|
|||||||
rightMouseDown = true;
|
rightMouseDown = true;
|
||||||
lastMouseX = event.button.x;
|
lastMouseX = event.button.x;
|
||||||
lastMouseY = event.button.y;
|
lastMouseY = event.button.y;
|
||||||
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->rightMouseDown = true;
|
||||||
|
currentLocation->lastMouseX = event.button.x;
|
||||||
|
currentLocation->lastMouseY = event.button.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rightMouseDown = false;
|
rightMouseDown = false;
|
||||||
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->rightMouseDown = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1005,20 +452,11 @@ namespace ZL
|
|||||||
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 (rightMouseDown) {
|
if (currentLocation)
|
||||||
int dx = event.motion.x - lastMouseX;
|
{
|
||||||
int dy = event.motion.y - lastMouseY;
|
currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y);
|
||||||
lastMouseX = event.motion.x;
|
|
||||||
lastMouseY = event.motion.y;
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_MOUSEWHEEL) {
|
if (event.type == SDL_MOUSEWHEEL) {
|
||||||
@ -1034,36 +472,24 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYDOWN && dialogueSystem.handleKeyDown(event.key.keysym.sym)) {
|
if (event.type == SDL_KEYDOWN/* && dialogueSystem.handleKeyDown(event.key.keysym.sym)*/) {
|
||||||
continue;
|
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_f:
|
case SDLK_f:
|
||||||
dialogueSystem.startDialogue("test_cutscene_pan_dialogue_silent");
|
currentLocation->dialogueSystem.startDialogue("test_cutscene_pan_dialogue_silent");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_e:
|
case SDLK_e:
|
||||||
dialogueSystem.startDialogue("test_cutscene_pan_dialogue");
|
currentLocation->dialogueSystem.startDialogue("test_cutscene_pan_dialogue");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (player->battle_state == 0)
|
|
||||||
{
|
|
||||||
player->battle_state = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
player->battle_state = 0;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_l:
|
case SDLK_l:
|
||||||
if (player->attack == 0)
|
|
||||||
{
|
|
||||||
player->attack = 1;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDLK_RETURN:
|
case SDLK_RETURN:
|
||||||
|
|
||||||
@ -1093,21 +519,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYUP) {
|
if (event.type == SDL_KEYUP) {
|
||||||
/*benchMesh.data.Move({-x, -y, 0});
|
|
||||||
if (event.key.keysym.sym == SDLK_a) {
|
|
||||||
x = x - 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_d) {
|
|
||||||
x = x + 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_w) {
|
|
||||||
y = y - 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_s) {
|
|
||||||
y = y + 0.1;
|
|
||||||
}
|
|
||||||
benchMesh.data.Move({ x, y, 0 });
|
|
||||||
benchMesh.RefreshVBO();*/
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -1152,25 +564,4 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::requestDialogueStart(const std::string& dialogueId)
|
|
||||||
{
|
|
||||||
return dialogueSystem.startDialogue(dialogueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::setDialogueFlag(const std::string& flag, int value)
|
|
||||||
{
|
|
||||||
dialogueSystem.setFlag(flag, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Game::getDialogueFlag(const std::string& flag) const
|
|
||||||
{
|
|
||||||
return dialogueSystem.getFlag(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available)
|
|
||||||
{
|
|
||||||
return navigation.setAreaAvailable(areaName, available);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
67
src/Game.h
67
src/Game.h
@ -17,12 +17,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <render/TextRenderer.h>
|
#include <render/TextRenderer.h>
|
||||||
#include "MenuManager.h"
|
#include "MenuManager.h"
|
||||||
#include "ScriptEngine.h"
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "dialogue/DialogueSystem.h"
|
|
||||||
#include "render/ShadowMap.h"
|
|
||||||
#include "navigation/PathFinder.h"
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include "Location.h"
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -37,86 +34,39 @@ namespace ZL {
|
|||||||
void render();
|
void render();
|
||||||
|
|
||||||
bool shouldExit() const { return Environment::exitGameLoop; }
|
bool shouldExit() const { return Environment::exitGameLoop; }
|
||||||
bool requestDialogueStart(const std::string& dialogueId);
|
|
||||||
void setDialogueFlag(const std::string& flag, int value);
|
|
||||||
int getDialogueFlag(const std::string& flag) const;
|
|
||||||
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
|
|
||||||
|
|
||||||
Renderer renderer;
|
Renderer renderer;
|
||||||
TaskManager taskManager;
|
TaskManager taskManager;
|
||||||
MainThreadHandler mainThreadHandler;
|
MainThreadHandler mainThreadHandler;
|
||||||
//std::unique_ptr<INetworkClient> networkClient;
|
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> loadingTexture;
|
std::shared_ptr<Texture> loadingTexture;
|
||||||
VertexRenderStruct loadingMesh;
|
VertexRenderStruct loadingMesh;
|
||||||
bool loadingCompleted = false;
|
bool loadingCompleted = false;
|
||||||
|
|
||||||
|
std::shared_ptr<Location> currentLocation;
|
||||||
|
|
||||||
std::shared_ptr<Texture> roomTexture;
|
|
||||||
VertexRenderStruct roomMesh;
|
|
||||||
|
|
||||||
std::unordered_map<std::string, LoadedGameObject> gameObjects;
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::shared_ptr<Texture> fireboxTexture;
|
|
||||||
VertexRenderStruct fireboxMesh;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> inaiTexture;
|
|
||||||
VertexRenderStruct inaiMesh;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> benchTexture;
|
|
||||||
VertexRenderStruct benchMesh;
|
|
||||||
*/
|
|
||||||
std::vector<InteractiveObject> interactiveObjects;
|
|
||||||
Inventory inventory;
|
Inventory inventory;
|
||||||
InteractiveObject* pickedUpObject = nullptr;
|
InteractiveObject* pickedUpObject = nullptr;
|
||||||
|
|
||||||
// Interactive object targeting
|
|
||||||
InteractiveObject* targetInteractiveObject = nullptr;
|
|
||||||
bool inventoryOpen = false;
|
bool inventoryOpen = false;
|
||||||
|
|
||||||
std::unique_ptr<Character> player;
|
|
||||||
std::vector<std::unique_ptr<Character>> npcs;
|
|
||||||
|
|
||||||
float cameraAzimuth = 0.0f;
|
|
||||||
float cameraInclination = M_PI * 30.f / 180.f;
|
|
||||||
|
|
||||||
// Public access for ScriptEngine
|
|
||||||
MenuManager menuManager;
|
MenuManager menuManager;
|
||||||
ScriptEngine scriptEngine;
|
|
||||||
PathFinder navigation;
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
std::vector<VertexRenderStruct> debugNavMeshes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool rightMouseDown = false;
|
bool rightMouseDown = false;
|
||||||
int lastMouseX = 0;
|
int lastMouseX = 0;
|
||||||
int lastMouseY = 0;
|
int lastMouseY = 0;
|
||||||
|
|
||||||
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
|
|
||||||
|
|
||||||
int64_t getSyncTimeMs();
|
int64_t getSyncTimeMs();
|
||||||
void processTickCount();
|
void processTickCount();
|
||||||
void drawScene();
|
void drawScene();
|
||||||
void drawUI();
|
void drawUI();
|
||||||
void drawGame();
|
|
||||||
void drawLoading();
|
void drawLoading();
|
||||||
void drawShadowDepthPass();
|
|
||||||
void drawGameWithShadows();
|
|
||||||
void setupNavigation();
|
|
||||||
void handleDown(int64_t fingerId, int mx, int my);
|
void handleDown(int64_t fingerId, 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 mx, int my);
|
||||||
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
|
||||||
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
void buildDebugNavMeshes();
|
|
||||||
void drawDebugNavigation();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
static Game* s_instance;
|
static Game* s_instance;
|
||||||
@ -126,18 +76,9 @@ namespace ZL {
|
|||||||
|
|
||||||
int64_t newTickCount;
|
int64_t newTickCount;
|
||||||
int64_t lastTickCount;
|
int64_t lastTickCount;
|
||||||
uint32_t connectingStartTicks = 0;
|
|
||||||
static constexpr uint32_t CONNECTING_TIMEOUT_MS = 10000;
|
|
||||||
|
|
||||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||||
|
|
||||||
//MenuManager menuManager;
|
|
||||||
Dialogue::DialogueSystem dialogueSystem;
|
|
||||||
//ScriptEngine scriptEngine;
|
|
||||||
|
|
||||||
std::unique_ptr<ShadowMap> shadowMap;
|
|
||||||
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
652
src/Location.cpp
652
src/Location.cpp
@ -1,2 +1,654 @@
|
|||||||
#include "Location.h"
|
#include "Location.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
#include "render/OpenGlExtensions.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
#include "TextModel.h"
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "GameConstants.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
extern const char* CONST_ZIP_FILE;
|
||||||
|
|
||||||
|
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
|
||||||
|
|
||||||
|
Location::Location(Renderer& iRenderer, Inventory& iInventory)
|
||||||
|
: renderer(iRenderer)
|
||||||
|
, inventory(iInventory)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::setup()
|
||||||
|
{
|
||||||
|
roomTexture = std::make_unique<Texture>(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();
|
||||||
|
|
||||||
|
// 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<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
player = std::make_unique<Character>();
|
||||||
|
|
||||||
|
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<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
std::cout << "Load resurces step 11" << std::endl;
|
||||||
|
auto npc02 = std::make_unique<Character>();
|
||||||
|
|
||||||
|
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<ShadowMap>(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::setupNavigation()
|
||||||
|
{
|
||||||
|
std::vector<PathFinder::ObstacleMesh> 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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
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<float>(Environment::width) / static_cast<float>(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());
|
||||||
|
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(fireboxMesh);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(inaiMesh);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(benchMesh);
|
||||||
|
*/
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive) {
|
||||||
|
intObj.draw(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player) 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<float>(Environment::width) / static_cast<float>(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
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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<int>(delta), player->position);
|
||||||
|
}
|
||||||
|
for (auto& npc : npcs) npc->update(delta);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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<float>(mx), Environment::projectionHeight - static_cast<float>(my));
|
||||||
|
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<int>(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 mx, int my)
|
||||||
|
{
|
||||||
|
if (rightMouseDown) {
|
||||||
|
int dx = mx - lastMouseX;
|
||||||
|
int dy = my - lastMouseY;
|
||||||
|
lastMouseX = mx;
|
||||||
|
lastMouseY = my;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
@ -1 +1,81 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
#include "items/GameObjectLoader.h"
|
||||||
|
#include "items/InteractiveObject.h"
|
||||||
|
#include "navigation/PathFinder.h"
|
||||||
|
#include "render/ShadowMap.h"
|
||||||
|
|
||||||
|
#include "ScriptEngine.h"
|
||||||
|
#include "dialogue/DialogueSystem.h"
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
class Location
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Location(Renderer& iRenderer, Inventory& iInventory);
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> roomTexture;
|
||||||
|
VertexRenderStruct roomMesh;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, LoadedGameObject> gameObjects;
|
||||||
|
|
||||||
|
std::vector<InteractiveObject> interactiveObjects;
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<Character> player;
|
||||||
|
std::vector<std::unique_ptr<Character>> npcs;
|
||||||
|
|
||||||
|
float cameraAzimuth = 0.0f;
|
||||||
|
float cameraInclination = M_PI * 30.f / 180.f;
|
||||||
|
|
||||||
|
PathFinder navigation;
|
||||||
|
|
||||||
|
std::unique_ptr<ShadowMap> shadowMap;
|
||||||
|
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
||||||
|
InteractiveObject* targetInteractiveObject = nullptr;
|
||||||
|
|
||||||
|
ScriptEngine scriptEngine;
|
||||||
|
Dialogue::DialogueSystem dialogueSystem;
|
||||||
|
|
||||||
|
#ifdef SHOW_PATH
|
||||||
|
std::vector<VertexRenderStruct> debugNavMeshes;
|
||||||
|
void buildDebugNavMeshes();
|
||||||
|
void drawDebugNavigation();
|
||||||
|
#endif
|
||||||
|
bool rightMouseDown = false;
|
||||||
|
int lastMouseX = 0;
|
||||||
|
int lastMouseY = 0;
|
||||||
|
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void setupNavigation();
|
||||||
|
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
||||||
|
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
|
||||||
|
|
||||||
|
void drawGame();
|
||||||
|
void drawShadowDepthPass();
|
||||||
|
void drawGameWithShadows();
|
||||||
|
|
||||||
|
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
|
||||||
|
|
||||||
|
void update(int64_t deltaMs);
|
||||||
|
|
||||||
|
void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||||
|
void handleUp(int64_t fingerId, int mx, int my);
|
||||||
|
void handleMotion(int64_t fingerId, int mx, int my);
|
||||||
|
|
||||||
|
bool requestDialogueStart(const std::string& dialogueId);
|
||||||
|
void setDialogueFlag(const std::string& flag, int value);
|
||||||
|
int getDialogueFlag(const std::string& flag) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Renderer& renderer;
|
||||||
|
Inventory& inventory;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include "Location.h"
|
||||||
|
|
||||||
#define SOL_ALL_SAFETIES_ON 1
|
#define SOL_ALL_SAFETIES_ON 1
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
@ -15,7 +16,7 @@ namespace ZL {
|
|||||||
ScriptEngine::ScriptEngine() = default;
|
ScriptEngine::ScriptEngine() = default;
|
||||||
ScriptEngine::~ScriptEngine() = default;
|
ScriptEngine::~ScriptEngine() = default;
|
||||||
|
|
||||||
void ScriptEngine::init(Game* game) {
|
void ScriptEngine::init(Location* game, Inventory* inventory) {
|
||||||
impl = std::make_unique<Impl>();
|
impl = std::make_unique<Impl>();
|
||||||
sol::state& lua = impl->lua;
|
sol::state& lua = impl->lua;
|
||||||
|
|
||||||
@ -49,13 +50,15 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// pickup_item(object_name)
|
// pickup_item(object_name)
|
||||||
api.set_function("pickup_item", [game](const std::string& objectName) {
|
api.set_function("pickup_item", [game, inventory](const std::string& objectName) {
|
||||||
|
|
||||||
|
|
||||||
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
||||||
|
|
||||||
for (auto& intObj : game->interactiveObjects) {
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
if (intObj.id == objectName && intObj.isActive) {
|
if (intObj.id == objectName && intObj.isActive) {
|
||||||
// Add item to inventory
|
// Add item to inventory
|
||||||
game->inventory.addItem(intObj.dropItem);
|
inventory->addItem(intObj.dropItem);
|
||||||
|
|
||||||
// Deactivate object
|
// Deactivate object
|
||||||
intObj.isActive = false;
|
intObj.isActive = false;
|
||||||
@ -68,51 +71,18 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// show_inventory()
|
|
||||||
api.set_function("show_inventory", [game]() {
|
|
||||||
std::cout << "[script] show_inventory called" << std::endl;
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
|
||||||
game->inventoryOpen = true;
|
|
||||||
|
|
||||||
// Update UI with current items
|
|
||||||
const auto& items = game->inventory.getItems();
|
|
||||||
std::string itemText;
|
|
||||||
|
|
||||||
if (items.empty()) {
|
|
||||||
itemText = "Inventory (Empty)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
|
||||||
for (size_t i = 0; i < items.size(); ++i) {
|
|
||||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
|
||||||
});
|
|
||||||
|
|
||||||
// hide_inventory()
|
|
||||||
api.set_function("hide_inventory", [game]() {
|
|
||||||
std::cout << "[script] hide_inventory called" << std::endl;
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
|
||||||
game->inventoryOpen = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// add_item(item_id, name, description, icon)
|
// add_item(item_id, name, description, icon)
|
||||||
api.set_function("add_item", [game](const std::string& id, const std::string& name,
|
api.set_function("add_item", [game, inventory](const std::string& id, const std::string& name,
|
||||||
const std::string& description, const std::string& icon) {
|
const std::string& description, const std::string& icon) {
|
||||||
std::cout << "[script] add_item: " << name << std::endl;
|
std::cout << "[script] add_item: " << name << std::endl;
|
||||||
Item newItem(id, name, description, icon);
|
Item newItem(id, name, description, icon);
|
||||||
game->inventory.addItem(newItem);
|
inventory->addItem(newItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove_item(item_id)
|
// remove_item(item_id)
|
||||||
api.set_function("remove_item", [game](const std::string& id) {
|
api.set_function("remove_item", [game, inventory](const std::string& id) {
|
||||||
std::cout << "[script] remove_item: " << id << std::endl;
|
std::cout << "[script] remove_item: " << id << std::endl;
|
||||||
game->inventory.removeItem(id);
|
inventory->removeItem(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// deactivate_interactive_object(object_name)
|
// deactivate_interactive_object(object_name)
|
||||||
@ -129,13 +99,13 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get_inventory_count()
|
// get_inventory_count()
|
||||||
api.set_function("get_inventory_count", [game]() {
|
api.set_function("get_inventory_count", [inventory]() {
|
||||||
return game->inventory.getCount();
|
return inventory->getCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
// has_item(item_id)
|
// has_item(item_id)
|
||||||
api.set_function("has_item", [game](const std::string& id) {
|
api.set_function("has_item", [inventory](const std::string& id) {
|
||||||
return game->inventory.hasItem(id);
|
return inventory->hasItem(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
api.set_function("start_dialogue",
|
api.set_function("start_dialogue",
|
||||||
@ -164,7 +134,7 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// receive_npc_gift(npc_index)
|
// receive_npc_gift(npc_index)
|
||||||
api.set_function("receive_npc_gift", [game](int npcIndex) {
|
api.set_function("receive_npc_gift", [game, inventory](int npcIndex) {
|
||||||
std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
|
std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
|
||||||
|
|
||||||
auto& npcs = game->npcs;
|
auto& npcs = game->npcs;
|
||||||
@ -188,7 +158,7 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game->inventory.addItem(npc->giftItem);
|
inventory->addItem(npc->giftItem);
|
||||||
npc->giftReceived = true;
|
npc->giftReceived = true;
|
||||||
|
|
||||||
std::cout << "[script] Received gift from " << npc->npcName << ": "
|
std::cout << "[script] Received gift from " << npc->npcName << ": "
|
||||||
@ -284,31 +254,4 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::showInventory(Game* game) {
|
|
||||||
std::cout << "[script] toggle_inventory called" << std::endl;
|
|
||||||
|
|
||||||
bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);
|
|
||||||
|
|
||||||
game->inventoryOpen = !isVisible;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
const auto& items = game->inventory.getItems();
|
|
||||||
std::string itemText;
|
|
||||||
|
|
||||||
if (items.empty()) {
|
|
||||||
itemText = "Inventory (Empty)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
|
||||||
for (size_t i = 0; i < items.size(); ++i) {
|
|
||||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
class Game;
|
class Location;
|
||||||
|
class Inventory;
|
||||||
|
|
||||||
class ScriptEngine {
|
class ScriptEngine {
|
||||||
public:
|
public:
|
||||||
@ -12,7 +13,7 @@ public:
|
|||||||
~ScriptEngine();
|
~ScriptEngine();
|
||||||
|
|
||||||
// Must be called once, after the Game's NPCs are ready.
|
// Must be called once, after the Game's NPCs are ready.
|
||||||
void init(Game* game);
|
void init(Location* game, Inventory* inventory);
|
||||||
|
|
||||||
// Execute a Lua script file. Logs errors to stderr.
|
// Execute a Lua script file. Logs errors to stderr.
|
||||||
void runScript(const std::string& path);
|
void runScript(const std::string& path);
|
||||||
@ -21,7 +22,6 @@ public:
|
|||||||
|
|
||||||
void callActivateFunction(const std::string& functionName);
|
void callActivateFunction(const std::string& functionName);
|
||||||
|
|
||||||
void showInventory(Game* game);
|
|
||||||
void callNpcInteractCallback(int npcIndex);
|
void callNpcInteractCallback(int npcIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user