Working on doors and scripting
This commit is contained in:
parent
774397f690
commit
0f2b2e55af
@ -52,7 +52,7 @@
|
|||||||
"positionY": 0.95746,
|
"positionY": 0.95746,
|
||||||
"positionZ": 0.786023,
|
"positionZ": 0.786023,
|
||||||
"scale": 0.5,
|
"scale": 0.5,
|
||||||
"interactionRadius": 1.5,
|
"interactionRadius": 0.3,
|
||||||
"activateFunction": "on_book_pickup"
|
"activateFunction": "on_book_pickup"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -107,6 +107,9 @@
|
|||||||
"positionX": 1.5,
|
"positionX": 1.5,
|
||||||
"positionY": 0.975,
|
"positionY": 0.975,
|
||||||
"positionZ": 6.965,
|
"positionZ": 6.965,
|
||||||
|
"pivotX": 0.0,
|
||||||
|
"pivotY": 0.0,
|
||||||
|
"pivotZ": 0.565,
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
"interactionRadius": 0.5,
|
"interactionRadius": 0.5,
|
||||||
"activateFunction": "on_library_door_click"
|
"activateFunction": "on_library_door_click"
|
||||||
@ -121,6 +124,9 @@
|
|||||||
"positionX": -1.5,
|
"positionX": -1.5,
|
||||||
"positionY": 0.975,
|
"positionY": 0.975,
|
||||||
"positionZ": 6.965,
|
"positionZ": 6.965,
|
||||||
|
"pivotX": 0.0,
|
||||||
|
"pivotY": 0.0,
|
||||||
|
"pivotZ": 0.565,
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
"interactionRadius": 0.5,
|
"interactionRadius": 0.5,
|
||||||
"activateFunction": "on_teachers_door_click"
|
"activateFunction": "on_teachers_door_click"
|
||||||
@ -136,6 +142,9 @@
|
|||||||
"positionY": 0.975,
|
"positionY": 0.975,
|
||||||
"positionZ": 8,
|
"positionZ": 8,
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
|
"pivotX": 0.565,
|
||||||
|
"pivotY": 0.0,
|
||||||
|
"pivotZ": 0.0,
|
||||||
"interactionRadius": 0.5,
|
"interactionRadius": 0.5,
|
||||||
"activateFunction": "on_hall_door_click"
|
"activateFunction": "on_hall_door_click"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
"positionX": -0.141875,
|
"positionX": -0.141875,
|
||||||
"positionY": 0.0,
|
"positionY": 0.0,
|
||||||
"positionZ": 9.75898,
|
"positionZ": 9.75898,
|
||||||
"radius": 2.0,
|
"radius": 1.7,
|
||||||
"hysteresis": 0.1,
|
"hysteresis": 0.1,
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
|||||||
@ -69,10 +69,70 @@
|
|||||||
"type": "End"
|
"type": "End"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "door_night_dialog001",
|
||||||
|
"start": "line_1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "line_1",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Бекзат",
|
||||||
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
|
"text": "Дверь закрыта на ключ.",
|
||||||
|
"next": "line_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_2",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Бекзат",
|
||||||
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
|
"text": "Видимо когда я спал, охранник запер дверь.",
|
||||||
|
"next": "line_3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_3",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Бекзат",
|
||||||
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
|
"text": "Это уже не смешно, как я отсюда выберусь?",
|
||||||
|
"next": "end_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_1",
|
||||||
|
"type": "End"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "teacher_dialog001",
|
"id": "teacher_dialog001",
|
||||||
"start": "line_1",
|
"start": "line_1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "line_1",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Аида Дженибековна",
|
||||||
|
"portrait": "resources/w/avatar_teacher.png",
|
||||||
|
"text": "Бекзат, не мешай, я занята.",
|
||||||
|
"next": "line_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_2",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Бекзат",
|
||||||
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
|
"text": "Ладно...",
|
||||||
|
"next": "end_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_1",
|
||||||
|
"type": "End"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "teacher_dialog002",
|
||||||
|
"start": "line_1",
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "line_1",
|
"id": "line_1",
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
hall_door_opened = false
|
||||||
|
teacher_door_opened = false
|
||||||
lection_is_over = false
|
lection_is_over = false
|
||||||
|
|
||||||
player_hold_book = false
|
player_hold_book = false
|
||||||
player_hold_knife = false
|
player_hold_knife = false
|
||||||
teacher_arrived = false
|
teacher_arrived = false
|
||||||
|
teacher_told_about_book = false
|
||||||
|
|
||||||
night_time = false
|
night_time = false
|
||||||
|
|
||||||
@ -55,6 +58,7 @@ game_api.npc_walk_to(0, -4.57412, 0, 6.78495, on_teacher_arrived2)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function on_book_pickup()
|
function on_book_pickup()
|
||||||
|
if (teacher_told_about_book) then
|
||||||
if not player_hold_book then
|
if not player_hold_book then
|
||||||
game_api.pickup_item("book")
|
game_api.pickup_item("book")
|
||||||
game_api.deactivate_interactive_object("Book001")
|
game_api.deactivate_interactive_object("Book001")
|
||||||
@ -65,6 +69,7 @@ function on_book_pickup()
|
|||||||
player_hold_book = false
|
player_hold_book = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function on_bookshelf_clicked()
|
function on_bookshelf_clicked()
|
||||||
if not player_hold_book then
|
if not player_hold_book then
|
||||||
@ -88,34 +93,52 @@ function on_npc_interact(npc_index)
|
|||||||
game_api.start_dialogue("knife_dialog001")
|
game_api.start_dialogue("knife_dialog001")
|
||||||
end
|
end
|
||||||
if npc_index == 0 then
|
if npc_index == 0 then
|
||||||
--game_api.start_dialogue("teacher_dialog001")
|
game_api.start_dialogue("teacher_dialog001")
|
||||||
--game_api.set_trigger_zone_enabled(2, false)
|
--game_api.set_trigger_zone_enabled(2, false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function teacher_zone001_enter_callback()
|
function teacher_zone001_enter_callback()
|
||||||
game_api.start_dialogue("teacher_dialog001")
|
game_api.start_dialogue("teacher_dialog002")
|
||||||
game_api.set_trigger_zone_enabled(2, false)
|
game_api.set_trigger_zone_enabled(2, false)
|
||||||
|
teacher_told_about_book = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function on_locked_door_click()
|
function on_locked_door_click()
|
||||||
game_api.start_dialogue("door_dialog001")
|
game_api.start_dialogue("door_dialog001")
|
||||||
|
-- Example: uncomment and replace "DoorName" to animate a door open over 1.5 seconds
|
||||||
|
-- game_api.rotate_object("DoorName", 90, 1.5, function()
|
||||||
|
-- print("Door finished opening!")
|
||||||
|
-- end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_library_door_click()
|
function on_library_door_click()
|
||||||
|
if (night_time) then
|
||||||
|
game_api.start_dialogue("door_night_dialog001")
|
||||||
|
else
|
||||||
if (not lection_is_over) then
|
if (not lection_is_over) then
|
||||||
game_api.start_dialogue("door_dialog001")
|
game_api.start_dialogue("door_dialog001")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function on_teachers_door_click()
|
function on_teachers_door_click()
|
||||||
if (not lection_is_over) then
|
if (not lection_is_over) then
|
||||||
game_api.start_dialogue("door_dialog001")
|
game_api.start_dialogue("door_dialog001")
|
||||||
|
else
|
||||||
|
if (not teacher_door_opened) then
|
||||||
|
teacher_door_opened = true
|
||||||
|
game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_hall_door_click()
|
function on_hall_door_click()
|
||||||
|
if (not hall_door_opened) then
|
||||||
|
hall_door_opened = true
|
||||||
|
game_api.rotate_object("Hall_Leaf001", 90, 0.5, nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -151,13 +174,19 @@ function on_computer_clicked()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function on_teacher_arrived_intermediate()
|
||||||
|
game_api.rotate_object("Room_N_2_Leaf001", -90, 0.5, nil)
|
||||||
|
game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
game_api.set_cutscene_callback("test_cutscene_01", function()
|
game_api.set_cutscene_callback("test_cutscene_01", function()
|
||||||
print("Cutscene done!")
|
print("Cutscene done!")
|
||||||
lection_is_over = true
|
lection_is_over = true
|
||||||
if (player_hold_knife == false) then
|
if (player_hold_knife == false) then
|
||||||
game_api.set_npc_enabled(1, true)
|
game_api.set_npc_enabled(1, true)
|
||||||
end
|
end
|
||||||
game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived)
|
game_api.npc_walk_to(0, 0.916388, 0, 6.32083, on_teacher_arrived_intermediate)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
game_api.set_cutscene_callback("test_cutscene_02", function()
|
game_api.set_cutscene_callback("test_cutscene_02", function()
|
||||||
@ -165,6 +194,7 @@ game_api.set_cutscene_callback("test_cutscene_02", function()
|
|||||||
night_time = true
|
night_time = true
|
||||||
game_api.set_npc_enabled(0, false)
|
game_api.set_npc_enabled(0, false)
|
||||||
game_api.switch_navigation(0)
|
game_api.switch_navigation(0)
|
||||||
|
game_api.rotate_object("Room_N_2_Leaf001", 90, 0.01, nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
game_api.set_trigger_zone_callbacks("teacher_dialog_zone001",
|
game_api.set_trigger_zone_callbacks("teacher_dialog_zone001",
|
||||||
|
|||||||
@ -511,6 +511,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_1:
|
case SDLK_1:
|
||||||
|
|||||||
@ -515,8 +515,8 @@ namespace ZL
|
|||||||
for (auto& intObj : interactiveObjects) {
|
for (auto& intObj : interactiveObjects) {
|
||||||
std::cout << "[RAYCAST] Checking object: " << intObj.loadedObject.name << " (active: " << intObj.isActive << ")" << std::endl;
|
std::cout << "[RAYCAST] Checking object: " << intObj.loadedObject.name << " (active: " << intObj.isActive << ")" << std::endl;
|
||||||
|
|
||||||
if (!intObj.isActive) {
|
if (!intObj.isActive || intObj.isAnimating) {
|
||||||
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
|
std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1054,6 +1054,10 @@ namespace ZL
|
|||||||
|
|
||||||
void Location::update(int64_t delta)
|
void Location::update(int64_t delta)
|
||||||
{
|
{
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
intObj.update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
if (player) {
|
if (player) {
|
||||||
player->update(delta);
|
player->update(delta);
|
||||||
dialogueSystem.update(static_cast<int>(delta));
|
dialogueSystem.update(static_cast<int>(delta));
|
||||||
@ -1068,7 +1072,7 @@ namespace ZL
|
|||||||
updateDynamicReplans(delta);
|
updateDynamicReplans(delta);
|
||||||
|
|
||||||
// Check if player reached target interactive object
|
// Check if player reached target interactive object
|
||||||
if (targetInteractiveObject && player) {
|
if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) {
|
||||||
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
||||||
|
|
||||||
// If player is close enough to pick up the item
|
// If player is close enough to pick up the item
|
||||||
|
|||||||
@ -185,6 +185,88 @@ namespace ZL {
|
|||||||
npcs[index]->enabled = value;
|
npcs[index]->enabled = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// move_object(name, x, y, z, duration_sec [, on_complete])
|
||||||
|
api.set_function("move_object",
|
||||||
|
[game](const std::string& name, float x, float y, float z,
|
||||||
|
float durationSec, sol::object onComplete) {
|
||||||
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
|
if (intObj.loadedObject.name != name) continue;
|
||||||
|
if (intObj.isAnimating) {
|
||||||
|
std::cerr << "[script] move_object: '" << name << "' is already animating\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::function<void()> cb;
|
||||||
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
|
cb = [fn]() mutable {
|
||||||
|
auto res = fn();
|
||||||
|
if (!res.valid()) {
|
||||||
|
sol::error err = res;
|
||||||
|
std::cerr << "[script] move_object on_complete error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
intObj.moveTo(Eigen::Vector3f(x, y, z), durationSec, std::move(cb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << "[script] move_object: object '" << name << "' not found\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
// rotate_object(name, angle_deg, duration_sec [, on_complete])
|
||||||
|
api.set_function("rotate_object",
|
||||||
|
[game](const std::string& name, float angleDeg,
|
||||||
|
float durationSec, sol::object onComplete) {
|
||||||
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
|
if (intObj.loadedObject.name != name) continue;
|
||||||
|
if (intObj.isAnimating) {
|
||||||
|
std::cerr << "[script] rotate_object: '" << name << "' is already animating\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::function<void()> cb;
|
||||||
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
|
cb = [fn]() mutable {
|
||||||
|
auto res = fn();
|
||||||
|
if (!res.valid()) {
|
||||||
|
sol::error err = res;
|
||||||
|
std::cerr << "[script] rotate_object on_complete error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f;
|
||||||
|
intObj.rotateTo(intObj.rotationY + angleRad, durationSec, std::move(cb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
// scale_object(name, target_scale, duration_sec [, on_complete])
|
||||||
|
api.set_function("scale_object",
|
||||||
|
[game](const std::string& name, float targetScale,
|
||||||
|
float durationSec, sol::object onComplete) {
|
||||||
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
|
if (intObj.loadedObject.name != name) continue;
|
||||||
|
if (intObj.isAnimating) {
|
||||||
|
std::cerr << "[script] scale_object: '" << name << "' is already animating\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::function<void()> cb;
|
||||||
|
if (onComplete.is<sol::protected_function>()) {
|
||||||
|
sol::protected_function fn = onComplete.as<sol::protected_function>();
|
||||||
|
cb = [fn]() mutable {
|
||||||
|
auto res = fn();
|
||||||
|
if (!res.valid()) {
|
||||||
|
sol::error err = res;
|
||||||
|
std::cerr << "[script] scale_object on_complete error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
intObj.scaleTo(targetScale, durationSec, std::move(cb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
|
||||||
|
});
|
||||||
|
|
||||||
lua.script_file(scriptPath);
|
lua.script_file(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,9 @@ namespace ZL {
|
|||||||
data.base = parseGameObjectData(item);
|
data.base = parseGameObjectData(item);
|
||||||
data.interactionRadius = item.value("interactionRadius", 2.0f);
|
data.interactionRadius = item.value("interactionRadius", 2.0f);
|
||||||
data.activateFunctionName = item.value("activateFunction", "");
|
data.activateFunctionName = item.value("activateFunction", "");
|
||||||
|
data.pivotX = item.value("pivotX", 0.0f);
|
||||||
|
data.pivotY = item.value("pivotY", 0.0f);
|
||||||
|
data.pivotZ = item.value("pivotZ", 0.0f);
|
||||||
if (!data.base.meshPath.empty())
|
if (!data.base.meshPath.empty())
|
||||||
objects.push_back(std::move(data));
|
objects.push_back(std::move(data));
|
||||||
}
|
}
|
||||||
@ -176,6 +179,7 @@ namespace ZL {
|
|||||||
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
|
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
|
||||||
intObj.interactionRadius = data.interactionRadius;
|
intObj.interactionRadius = data.interactionRadius;
|
||||||
intObj.activateFunctionName = data.activateFunctionName;
|
intObj.activateFunctionName = data.activateFunctionName;
|
||||||
|
intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ);
|
||||||
|
|
||||||
intObj.loadedObject.mesh.RefreshVBO();
|
intObj.loadedObject.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,9 @@ namespace ZL {
|
|||||||
GameObjectData base;
|
GameObjectData base;
|
||||||
float interactionRadius = 2.0f;
|
float interactionRadius = 2.0f;
|
||||||
std::string activateFunctionName;
|
std::string activateFunctionName;
|
||||||
|
float pivotX = 0.0f;
|
||||||
|
float pivotY = 0.0f;
|
||||||
|
float pivotZ = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NpcData {
|
struct NpcData {
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include "render/TextureManager.h"
|
#include "render/TextureManager.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
extern const std::string textureUniformName;
|
extern const std::string textureUniformName;
|
||||||
@ -10,10 +12,102 @@ namespace ZL {
|
|||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
|
void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete) {
|
||||||
|
if (isAnimating) return;
|
||||||
|
AnimTask task;
|
||||||
|
task.type = AnimTask::Type::Move;
|
||||||
|
task.startPos = position;
|
||||||
|
task.startRotY = rotationY;
|
||||||
|
task.startScale = scale;
|
||||||
|
task.targetPos = target;
|
||||||
|
task.targetRotY = rotationY;
|
||||||
|
task.targetScale = scale;
|
||||||
|
task.durationMs = durationSec * 1000.f;
|
||||||
|
task.elapsedMs = 0.f;
|
||||||
|
task.onComplete = std::move(onComplete);
|
||||||
|
animTask = std::move(task);
|
||||||
|
isAnimating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
|
||||||
|
if (isAnimating) return;
|
||||||
|
AnimTask task;
|
||||||
|
task.type = AnimTask::Type::Rotate;
|
||||||
|
task.startPos = position;
|
||||||
|
task.startRotY = rotationY;
|
||||||
|
task.startScale = scale;
|
||||||
|
task.targetPos = position;
|
||||||
|
task.targetRotY = targetRotY;
|
||||||
|
task.targetScale = scale;
|
||||||
|
task.durationMs = durationSec * 1000.f;
|
||||||
|
task.elapsedMs = 0.f;
|
||||||
|
task.onComplete = std::move(onComplete);
|
||||||
|
animTask = std::move(task);
|
||||||
|
isAnimating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
|
||||||
|
if (isAnimating) return;
|
||||||
|
AnimTask task;
|
||||||
|
task.type = AnimTask::Type::Scale;
|
||||||
|
task.startPos = position;
|
||||||
|
task.startRotY = rotationY;
|
||||||
|
task.startScale = scale;
|
||||||
|
task.targetPos = position;
|
||||||
|
task.targetRotY = rotationY;
|
||||||
|
task.targetScale = targetScale;
|
||||||
|
task.durationMs = durationSec * 1000.f;
|
||||||
|
task.elapsedMs = 0.f;
|
||||||
|
task.onComplete = std::move(onComplete);
|
||||||
|
animTask = std::move(task);
|
||||||
|
isAnimating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InteractiveObject::update(int64_t deltaMs) {
|
||||||
|
if (!isAnimating || !animTask) return;
|
||||||
|
|
||||||
|
AnimTask& task = *animTask;
|
||||||
|
task.elapsedMs += static_cast<float>(deltaMs);
|
||||||
|
|
||||||
|
const float t = min(task.elapsedMs / task.durationMs, 1.0f);
|
||||||
|
|
||||||
|
switch (task.type) {
|
||||||
|
case AnimTask::Type::Move:
|
||||||
|
position = task.startPos + (task.targetPos - task.startPos) * t;
|
||||||
|
break;
|
||||||
|
case AnimTask::Type::Rotate:
|
||||||
|
rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
|
||||||
|
break;
|
||||||
|
case AnimTask::Type::Scale:
|
||||||
|
scale = task.startScale + (task.targetScale - task.startScale) * t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t >= 1.0f) {
|
||||||
|
// Snap to exact target
|
||||||
|
position = task.targetPos;
|
||||||
|
rotationY = task.targetRotY;
|
||||||
|
scale = task.targetScale;
|
||||||
|
|
||||||
|
std::function<void()> cb = std::move(task.onComplete);
|
||||||
|
animTask.reset();
|
||||||
|
isAnimating = false;
|
||||||
|
|
||||||
|
if (cb) cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InteractiveObject::draw(Renderer& renderer) const {
|
void InteractiveObject::draw(Renderer& renderer) const {
|
||||||
if (!isActive || !loadedObject.texture) return;
|
if (!isActive || !loadedObject.texture) return;
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix(position);
|
renderer.TranslateMatrix(position);
|
||||||
|
if (rotationY != 0.f) {
|
||||||
|
renderer.TranslateMatrix(pivot);
|
||||||
|
renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||||
|
renderer.TranslateMatrix(-pivot);
|
||||||
|
}
|
||||||
|
if (scale != 1.f)
|
||||||
|
renderer.ScaleMatrix(scale);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
||||||
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <functional>
|
||||||
|
#include <cstdint>
|
||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
#include "render/Renderer.h"
|
#include "render/Renderer.h"
|
||||||
|
|
||||||
@ -17,12 +20,35 @@ namespace ZL {
|
|||||||
struct InteractiveObject {
|
struct InteractiveObject {
|
||||||
LoadedGameObject loadedObject; // name, texture, mesh
|
LoadedGameObject loadedObject; // name, texture, mesh
|
||||||
Eigen::Vector3f position;
|
Eigen::Vector3f position;
|
||||||
|
Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); // local-space offset to rotation pivot
|
||||||
|
float rotationY = 0.0f; // Y-axis rotation in radians
|
||||||
|
float scale = 1.0f; // uniform scale
|
||||||
float interactionRadius;
|
float interactionRadius;
|
||||||
bool isActive = true;
|
bool isActive = true;
|
||||||
|
bool isAnimating = false; // true while a timed animation is running
|
||||||
std::string activateFunctionName;
|
std::string activateFunctionName;
|
||||||
|
|
||||||
|
struct AnimTask {
|
||||||
|
enum class Type { Move, Rotate, Scale } type;
|
||||||
|
Eigen::Vector3f startPos;
|
||||||
|
float startRotY;
|
||||||
|
float startScale;
|
||||||
|
Eigen::Vector3f targetPos;
|
||||||
|
float targetRotY;
|
||||||
|
float targetScale;
|
||||||
|
float durationMs;
|
||||||
|
float elapsedMs = 0.f;
|
||||||
|
std::function<void()> onComplete;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<AnimTask> animTask;
|
||||||
|
|
||||||
InteractiveObject() : interactionRadius(2.0f) {}
|
InteractiveObject() : interactionRadius(2.0f) {}
|
||||||
|
|
||||||
|
void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {});
|
||||||
|
void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {});
|
||||||
|
void scaleTo(float targetScale, float durationSec, std::function<void()> onComplete = {});
|
||||||
|
void update(int64_t deltaMs);
|
||||||
void draw(Renderer& renderer) const;
|
void draw(Renderer& renderer) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user