Working on game states

This commit is contained in:
Vladislav Khorev 2026-06-18 20:51:44 +03:00
parent 0ca00f965d
commit e0968b5522
10 changed files with 340 additions and 251 deletions

View File

@ -119,6 +119,7 @@ set(SOURCES
../src/items/ItemRegistry.cpp ../src/items/ItemRegistry.cpp
../src/items/InteractiveObject.h ../src/items/InteractiveObject.h
../src/items/InteractiveObject.cpp ../src/items/InteractiveObject.cpp
../src/items/InteractiveObjectState.h
../src/dialogue/DialogueTypes.h ../src/dialogue/DialogueTypes.h
../src/dialogue/DialogueDatabase.h ../src/dialogue/DialogueDatabase.h
../src/dialogue/DialogueDatabase.cpp ../src/dialogue/DialogueDatabase.cpp

View File

@ -74,6 +74,7 @@ add_executable(witcher001
../src/items/ItemRegistry.cpp ../src/items/ItemRegistry.cpp
../src/items/InteractiveObject.h ../src/items/InteractiveObject.h
../src/items/InteractiveObject.cpp ../src/items/InteractiveObject.cpp
../src/items/InteractiveObjectState.h
../src/dialogue/DialogueTypes.h ../src/dialogue/DialogueTypes.h
../src/dialogue/DialogueDatabase.h ../src/dialogue/DialogueDatabase.h
../src/dialogue/DialogueDatabase.cpp ../src/dialogue/DialogueDatabase.cpp

View File

@ -456,7 +456,7 @@ 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 || intObj.isAnimating) { if (!intObj.state.isActive || intObj.state.isAnimating) {
//std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl; //std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl;
continue; continue;
} }
@ -470,10 +470,10 @@ namespace ZL
//std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", " //std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
// << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl; // << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
const bool hasBox = !(intObj.boundsMin.isZero() && intObj.boundsMax.isZero()); const bool hasBox = !(intObj.state.boundsMin.isZero() && intObj.state.boundsMax.isZero());
if (hasBox) { if (hasBox) {
const Eigen::Vector3f worldMin = intObj.position + intObj.boundsMin; const Eigen::Vector3f worldMin = intObj.state.position + intObj.state.boundsMin;
const Eigen::Vector3f worldMax = intObj.position + intObj.boundsMax; const Eigen::Vector3f worldMax = intObj.state.position + intObj.state.boundsMax;
float tMin = 0.1f; float tMin = 0.1f;
float tMax = FLT_MAX; float tMax = FLT_MAX;
bool miss = false; bool miss = false;
@ -498,7 +498,7 @@ namespace ZL
closestObject = &intObj; closestObject = &intObj;
} }
} else { } else {
Eigen::Vector3f toObject = intObj.position - rayOrigin; Eigen::Vector3f toObject = intObj.state.position - rayOrigin;
float distanceAlongRay = toObject.dot(rayDir); float distanceAlongRay = toObject.dot(rayDir);
@ -507,9 +507,9 @@ namespace ZL
} }
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay; Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
float distToObject = (closestPointOnRay - intObj.position).norm(); float distToObject = (closestPointOnRay - intObj.state.position).norm();
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) { if (distToObject <= intObj.state.interactionRadius && distanceAlongRay < closestDistance) {
closestDistance = distanceAlongRay; closestDistance = distanceAlongRay;
closestObject = &intObj; closestObject = &intObj;
} }
@ -632,7 +632,7 @@ namespace ZL
} }
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.state.isActive) {
intObj.draw(renderer); intObj.draw(renderer);
} }
} }
@ -724,9 +724,9 @@ namespace ZL
} }
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.castShadow && intObj.loadedObject.texture) { if (intObj.state.castShadow && intObj.loadedObject.texture) {
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix(intObj.position); renderer.TranslateMatrix(intObj.state.position);
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh); renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
renderer.PopMatrix(); renderer.PopMatrix();
} }
@ -818,7 +818,7 @@ namespace ZL
CheckGlError(__FILE__, __LINE__); CheckGlError(__FILE__, __LINE__);
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.state.isActive) {
intObj.draw(renderer); intObj.draw(renderer);
} }
} }
@ -911,7 +911,7 @@ namespace ZL
} }
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.state.isActive) {
intObj.drawDarklands(renderer); intObj.drawDarklands(renderer);
} }
} }
@ -971,9 +971,9 @@ namespace ZL
renderer.DrawVertexRenderStruct(gameObj.mesh); renderer.DrawVertexRenderStruct(gameObj.mesh);
} }
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.castShadowNight && intObj.loadedObject.texture) { if (intObj.state.castShadowNight && intObj.loadedObject.texture) {
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix(intObj.position); renderer.TranslateMatrix(intObj.state.position);
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh); renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
renderer.PopMatrix(); renderer.PopMatrix();
} }
@ -1144,7 +1144,7 @@ namespace ZL
} }
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
if (intObj.isActive) { if (intObj.state.isActive) {
intObj.draw(renderer); intObj.draw(renderer);
} }
} }
@ -1492,22 +1492,22 @@ namespace ZL
for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS; for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS;
// Check if player reached target interactive object // Check if player reached target interactive object
if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->isAnimating) { if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->state.isAnimating) {
const Eigen::Vector3f& approachTarget = targetInteractiveObject->hasInteractionPosition const Eigen::Vector3f& approachTarget = targetInteractiveObject->state.hasInteractionPosition
? targetInteractiveObject->interactionPosition ? targetInteractiveObject->state.interactionPosition
: targetInteractiveObject->position; : targetInteractiveObject->state.position;
float distToObject = (player->state.position - approachTarget).norm(); float distToObject = (player->state.position - approachTarget).norm();
// If player is close enough to pick up the item // If player is close enough to pick up the item
if (distToObject <= targetInteractiveObject->approachRadius) { if (distToObject <= targetInteractiveObject->state.approachRadius) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl; std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << std::endl; std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << std::endl;
// Call custom activate function if specified, otherwise use fallback // Call custom activate function if specified, otherwise use fallback
try { try {
if (!targetInteractiveObject->activateFunctionName.empty()) { if (!targetInteractiveObject->state.activateFunctionName.empty()) {
std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl; std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->state.activateFunctionName << std::endl;
scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName); scriptEngine.callActivateFunction(targetInteractiveObject->state.activateFunctionName);
} }
else { else {
std::cout << "[PICKUP] Using fallback callback" << std::endl; std::cout << "[PICKUP] Using fallback callback" << std::endl;
@ -1632,19 +1632,19 @@ namespace ZL
// First check if we clicked on interactive object // First check if we clicked on interactive object
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir); InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
if (clickedObject && player && clickedObject->isActive) { if (clickedObject && player && clickedObject->state.isActive) {
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl; std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl;
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", " std::cout << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", "
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl; << clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl;
std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", " std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", "
<< player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl; << player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
targetInteractiveObject = clickedObject; targetInteractiveObject = clickedObject;
targetInteractNpcIndex = -1; targetInteractNpcIndex = -1;
targetTeleportZone = nullptr; targetTeleportZone = nullptr;
player->setTarget(clickedObject->hasInteractionPosition player->setTarget(clickedObject->state.hasInteractionPosition
? clickedObject->interactionPosition ? clickedObject->state.interactionPosition
: clickedObject->position); : clickedObject->state.position);
player->state.attackTargetIndex = CharacterState::kNoTarget; player->state.attackTargetIndex = CharacterState::kNoTarget;
std::cout << "[CLICK] Player moving to object..." << std::endl; std::cout << "[CLICK] Player moving to object..." << std::endl;
} }

View File

@ -212,12 +212,12 @@ namespace ZL
const bool isSelected = (idx == selectedInteractiveObjectIndex); const bool isSelected = (idx == selectedInteractiveObjectIndex);
// --- bounds box --- // --- bounds box ---
if (obj.boundsMin != zero || obj.boundsMax != zero) { if (obj.state.boundsMin != zero || obj.state.boundsMax != zero) {
const Eigen::Vector3f& color = isSelected ? colorSelected : colorDefault; const Eigen::Vector3f& color = isSelected ? colorSelected : colorDefault;
const float x0 = obj.boundsMin.x(), x1 = obj.boundsMax.x(); const float x0 = obj.state.boundsMin.x(), x1 = obj.state.boundsMax.x();
const float y0 = obj.boundsMin.y(), y1 = obj.boundsMax.y(); const float y0 = obj.state.boundsMin.y(), y1 = obj.state.boundsMax.y();
const float z0 = obj.boundsMin.z(), z1 = obj.boundsMax.z(); const float z0 = obj.state.boundsMin.z(), z1 = obj.state.boundsMax.z();
Eigen::Vector3f corners[8] = { Eigen::Vector3f corners[8] = {
{ x0, y0, z0 }, { x1, y0, z0 }, { x0, y0, z0 }, { x1, y0, z0 },
@ -227,12 +227,12 @@ namespace ZL
}; };
// Apply the same TRS the draw function applies: scale → rotateY → translate // Apply the same TRS the draw function applies: scale → rotateY → translate
if (obj.scale != 1.f) { if (obj.state.scale != 1.f) {
for (auto& c : corners) c *= obj.scale; for (auto& c : corners) c *= obj.state.scale;
} }
if (obj.rotationY != 0.f) { if (obj.state.rotationY != 0.f) {
const float cosR = std::cos(obj.rotationY); const float cosR = std::cos(obj.state.rotationY);
const float sinR = std::sin(obj.rotationY); const float sinR = std::sin(obj.state.rotationY);
for (auto& c : corners) { for (auto& c : corners) {
const float nx = c.x() * cosR - c.z() * sinR; const float nx = c.x() * cosR - c.z() * sinR;
const float nz = c.x() * sinR + c.z() * cosR; const float nz = c.x() * sinR + c.z() * cosR;
@ -240,7 +240,7 @@ namespace ZL
c.z() = nz; c.z() = nz;
} }
} }
for (auto& c : corners) c += obj.position; for (auto& c : corners) c += obj.state.position;
VertexRenderStruct mesh; VertexRenderStruct mesh;
mesh.data = CreateBoundsBoxMesh(corners, color); mesh.data = CreateBoundsBoxMesh(corners, color);
@ -249,8 +249,8 @@ namespace ZL
} }
// --- interaction position pole --- // --- interaction position pole ---
if (obj.hasInteractionPosition) { if (obj.state.hasInteractionPosition) {
const Eigen::Vector3f& p = obj.interactionPosition; const Eigen::Vector3f& p = obj.state.interactionPosition;
static constexpr float hw = 0.05f; // half width/depth static constexpr float hw = 0.05f; // half width/depth
static constexpr float hh = 1.5f; // half height (pole = 3.0 tall) static constexpr float hh = 1.5f; // half height (pole = 3.0 tall)
@ -312,8 +312,8 @@ namespace ZL
if (ctrlHeld) { if (ctrlHeld) {
// Set the interaction position for the selected object (world XZ, Y=0). // Set the interaction position for the selected object (world XZ, Y=0).
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex]; auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
target.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z()); target.state.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z());
target.hasInteractionPosition = true; target.state.hasInteractionPosition = true;
boundsClickCount = 0; // cancel any in-progress bounds placement boundsClickCount = 0; // cancel any in-progress bounds placement
buildInteractiveObjectBoundsMeshes(); buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex std::cout << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex
@ -326,17 +326,17 @@ namespace ZL
// Convert world-space hit to object local space (inverse of scale → rotateY → translate) // Convert world-space hit to object local space (inverse of scale → rotateY → translate)
auto worldToLocal = [&](const Eigen::Vector3f& w) -> Eigen::Vector3f { auto worldToLocal = [&](const Eigen::Vector3f& w) -> Eigen::Vector3f {
Eigen::Vector3f local = w - obj.position; Eigen::Vector3f local = w - obj.state.position;
if (obj.rotationY != 0.f) { if (obj.state.rotationY != 0.f) {
const float c = std::cos(-obj.rotationY); const float c = std::cos(-obj.state.rotationY);
const float s = std::sin(-obj.rotationY); const float s = std::sin(-obj.state.rotationY);
const float nx = local.x() * c - local.z() * s; const float nx = local.x() * c - local.z() * s;
const float nz = local.x() * s + local.z() * c; const float nz = local.x() * s + local.z() * c;
local.x() = nx; local.x() = nx;
local.z() = nz; local.z() = nz;
} }
if (obj.scale > 1e-6f && obj.scale != 1.f) if (obj.state.scale > 1e-6f && obj.state.scale != 1.f)
local /= obj.scale; local /= obj.state.scale;
return local; return local;
}; };
@ -350,9 +350,9 @@ namespace ZL
const Eigen::Vector3f localB = worldToLocal(worldHit); const Eigen::Vector3f localB = worldToLocal(worldHit);
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex]; auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
target.boundsMin = Eigen::Vector3f( target.state.boundsMin = Eigen::Vector3f(
min(localA.x(), localB.x()), 0.f, min(localA.z(), localB.z())); min(localA.x(), localB.x()), 0.f, min(localA.z(), localB.z()));
target.boundsMax = Eigen::Vector3f( target.state.boundsMax = Eigen::Vector3f(
max(localA.x(), localB.x()), 1.f, max(localA.z(), localB.z())); max(localA.x(), localB.x()), 1.f, max(localA.z(), localB.z()));
boundsClickCount = 0; boundsClickCount = 0;
@ -360,8 +360,8 @@ namespace ZL
std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
<< " (" << target.loadedObject.name << ")" << " (" << target.loadedObject.name << ")"
<< " min=(" << target.boundsMin.x() << ", " << target.boundsMin.z() << ")" << " min=(" << target.state.boundsMin.x() << ", " << target.state.boundsMin.z() << ")"
<< " max=(" << target.boundsMax.x() << ", " << target.boundsMax.z() << ")\n"; << " max=(" << target.state.boundsMax.x() << ", " << target.state.boundsMax.z() << ")\n";
} }
} }
@ -382,34 +382,34 @@ namespace ZL
for (const auto& obj : loc.interactiveObjects) { for (const auto& obj : loc.interactiveObjects) {
json item; json item;
item["name"] = obj.loadedObject.name; item["name"] = obj.loadedObject.name;
item["texturePath"] = obj.loadedObject.texturePath; item["texturePath"] = obj.loadedObject.creationInfo.texturePath;
if (!obj.loadedObject.textureDarkandsPath.empty()) if (!obj.loadedObject.creationInfo.textureDarkandsPath.empty())
item["textureDarkandsPath"] = obj.loadedObject.textureDarkandsPath; item["textureDarkandsPath"] = obj.loadedObject.creationInfo.textureDarkandsPath;
item["meshPath"] = obj.loadedObject.meshPath; item["meshPath"] = obj.loadedObject.creationInfo.meshPath;
item["rotationX"] = obj.loadedObject.meshRotationX; item["rotationX"] = obj.loadedObject.creationInfo.meshRotationX;
item["rotationY"] = obj.loadedObject.meshRotationY; item["rotationY"] = obj.loadedObject.creationInfo.meshRotationY;
item["rotationZ"] = obj.loadedObject.meshRotationZ; item["rotationZ"] = obj.loadedObject.creationInfo.meshRotationZ;
item["positionX"] = obj.jsonPositionX; item["positionX"] = obj.state.jsonPositionX;
item["positionY"] = obj.jsonPositionY; item["positionY"] = obj.state.jsonPositionY;
item["positionZ"] = obj.jsonPositionZ; item["positionZ"] = obj.state.jsonPositionZ;
item["scale"] = obj.loadedObject.meshScale; item["scale"] = obj.loadedObject.creationInfo.meshScale;
item["interactionRadius"] = obj.interactionRadius; item["interactionRadius"] = obj.state.interactionRadius;
item["approachRadius"] = obj.approachRadius; item["approachRadius"] = obj.state.approachRadius;
if (!obj.activateFunctionName.empty()) if (!obj.state.activateFunctionName.empty())
item["activateFunction"] = obj.activateFunctionName; item["activateFunction"] = obj.state.activateFunctionName;
item["pivotX"] = obj.pivot.x(); item["pivotX"] = obj.state.pivot.x();
item["pivotY"] = obj.pivot.y(); item["pivotY"] = obj.state.pivot.y();
item["pivotZ"] = obj.pivot.z(); item["pivotZ"] = obj.state.pivot.z();
item["boundsMinX"] = obj.boundsMin.x(); item["boundsMinX"] = obj.state.boundsMin.x();
item["boundsMinY"] = obj.boundsMin.y(); item["boundsMinY"] = obj.state.boundsMin.y();
item["boundsMinZ"] = obj.boundsMin.z(); item["boundsMinZ"] = obj.state.boundsMin.z();
item["boundsMaxX"] = obj.boundsMax.x(); item["boundsMaxX"] = obj.state.boundsMax.x();
item["boundsMaxY"] = obj.boundsMax.y(); item["boundsMaxY"] = obj.state.boundsMax.y();
item["boundsMaxZ"] = obj.boundsMax.z(); item["boundsMaxZ"] = obj.state.boundsMax.z();
if (obj.hasInteractionPosition) { if (obj.state.hasInteractionPosition) {
item["interactionPositionX"] = obj.interactionPosition.x(); item["interactionPositionX"] = obj.state.interactionPosition.x();
item["interactionPositionY"] = obj.interactionPosition.y(); item["interactionPositionY"] = obj.state.interactionPosition.y();
item["interactionPositionZ"] = obj.interactionPosition.z(); item["interactionPositionZ"] = obj.state.interactionPosition.z();
} }
j["objects"].push_back(item); j["objects"].push_back(item);
} }

View File

@ -141,7 +141,7 @@ namespace ZL {
api.set_function("deactivate_interactive_object", [loc](const std::string& objectName) { api.set_function("deactivate_interactive_object", [loc](const std::string& objectName) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.isActive = false; intObj.state.isActive = false;
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl; std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
return; return;
} }
@ -153,7 +153,7 @@ namespace ZL {
api.set_function("activate_interactive_object", [loc](const std::string& objectName) { api.set_function("activate_interactive_object", [loc](const std::string& objectName) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.isActive = true; intObj.state.isActive = true;
std::cout << "[script] activate_interactive_object: " << objectName << std::endl; std::cout << "[script] activate_interactive_object: " << objectName << std::endl;
return; return;
} }
@ -164,7 +164,7 @@ namespace ZL {
api.set_function("set_object_rotation", [loc](const std::string& objectName, float value) { api.set_function("set_object_rotation", [loc](const std::string& objectName, float value) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.rotationY = value * static_cast<float>(M_PI) / 180.f; intObj.state.rotationY = value * static_cast<float>(M_PI) / 180.f;
std::cout << "[script] set_object_rotation: " << objectName << " " << value << std::endl; std::cout << "[script] set_object_rotation: " << objectName << " " << value << std::endl;
return; return;
} }
@ -175,7 +175,7 @@ namespace ZL {
api.set_function("set_object_alpha", [loc](const std::string& objectName, float value) { api.set_function("set_object_alpha", [loc](const std::string& objectName, float value) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.alpha = value; intObj.state.alpha = value;
std::cout << "[script] set_object_alpha: " <<objectName << " " << value << std::endl; std::cout << "[script] set_object_alpha: " <<objectName << " " << value << std::endl;
return; return;
} }
@ -538,7 +538,7 @@ namespace ZL {
float durationSec, sol::object onComplete) { float durationSec, sol::object onComplete) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name != name) continue; if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) { if (intObj.state.isAnimating) {
std::cerr << "[script] move_object: '" << name << "' is already animating\n"; std::cerr << "[script] move_object: '" << name << "' is already animating\n";
return; return;
} }
@ -565,7 +565,7 @@ namespace ZL {
float durationSec, sol::object onComplete) { float durationSec, sol::object onComplete) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name != name) continue; if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) { if (intObj.state.isAnimating) {
std::cerr << "[script] rotate_object: '" << name << "' is already animating\n"; std::cerr << "[script] rotate_object: '" << name << "' is already animating\n";
return; return;
} }
@ -581,7 +581,7 @@ namespace ZL {
}; };
} }
const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f; const float angleRad = angleDeg * static_cast<float>(M_PI) / 180.f;
intObj.rotateTo(intObj.rotationY + angleRad, durationSec, std::move(cb)); intObj.rotateTo(intObj.state.rotationY + angleRad, durationSec, std::move(cb));
return; return;
} }
std::cerr << "[script] rotate_object: object '" << name << "' not found\n"; std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
@ -593,7 +593,7 @@ namespace ZL {
float durationSec, sol::object onComplete) { float durationSec, sol::object onComplete) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name != name) continue; if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) { if (intObj.state.isAnimating) {
std::cerr << "[script] fade_object: '" << name << "' is already animating\n"; std::cerr << "[script] fade_object: '" << name << "' is already animating\n";
return; return;
} }
@ -620,7 +620,7 @@ namespace ZL {
float durationSec, sol::object onComplete) { float durationSec, sol::object onComplete) {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name != name) continue; if (intObj.loadedObject.name != name) continue;
if (intObj.isAnimating) { if (intObj.state.isAnimating) {
std::cerr << "[script] scale_object: '" << name << "' is already animating\n"; std::cerr << "[script] scale_object: '" << name << "' is already animating\n";
return; return;
} }

View File

@ -118,10 +118,45 @@ namespace ZL {
return objects; return objects;
} }
LoadedGameObjectState GameObjectData::toLoadedGameObjectState() const {
LoadedGameObjectState st;
st.texturePath = texturePath;
st.textureDarkandsPath = textureDarkandsPath;
st.meshPath = meshPath;
st.meshRotationX = rotationX;
st.meshRotationY = rotationY;
st.meshRotationZ = rotationZ;
st.meshScale = scale;
return st;
}
InteractiveObjectState InteractiveObjectData::toInteractiveObjectState() const {
InteractiveObjectState st;
st.objectInfo = base.toLoadedGameObjectState();
st.interactionRadius = interactionRadius;
st.approachRadius = approachRadius;
st.castShadow = castShadow;
st.castShadowNight = castShadowNight;
st.isActive = isActive;
st.activateFunctionName = activateFunctionName;
st.pivot = Eigen::Vector3f(pivotX, pivotY, pivotZ);
st.boundsMin = Eigen::Vector3f(boundsMinX, boundsMinY, boundsMinZ);
st.boundsMax = Eigen::Vector3f(boundsMaxX, boundsMaxY, boundsMaxZ);
st.hasInteractionPosition = hasInteractionPosition;
if (hasInteractionPosition)
st.interactionPosition = Eigen::Vector3f(interactionPositionX, interactionPositionY, interactionPositionZ);
st.jsonPositionX = base.positionX;
st.jsonPositionY = base.positionY;
st.jsonPositionZ = base.positionZ;
// state.position is set by InteractiveObject::createFromState() after mesh-center computation
return st;
}
LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath) LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath)
{ {
LoadedGameObject obj; LoadedGameObject obj;
obj.name = data.name; obj.name = data.name;
obj.creationInfo = data.toLoadedGameObjectState();
obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath); obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath);
if (!data.textureDarkandsPath.empty()) if (!data.textureDarkandsPath.empty())
@ -192,54 +227,9 @@ namespace ZL {
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) { for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
std::cout << "Loading interactive object: " << data.base.name << std::endl; std::cout << "Loading interactive object: " << data.base.name << std::endl;
try { try {
InteractiveObject intObj; InteractiveObject intObj = InteractiveObject::createFromState(
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath); data.toInteractiveObjectState(), renderer, zipPath);
intObj.interactionRadius = data.interactionRadius; intObj.loadedObject.name = data.base.name;
intObj.approachRadius = data.approachRadius;
intObj.castShadow = data.castShadow;
intObj.castShadowNight = data.castShadowNight;
intObj.isActive = data.isActive;
intObj.activateFunctionName = data.activateFunctionName;
intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ);
intObj.boundsMin = Eigen::Vector3f(data.boundsMinX, data.boundsMinY, data.boundsMinZ);
intObj.boundsMax = Eigen::Vector3f(data.boundsMaxX, data.boundsMaxY, data.boundsMaxZ);
intObj.hasInteractionPosition = data.hasInteractionPosition;
if (data.hasInteractionPosition)
intObj.interactionPosition = Eigen::Vector3f(data.interactionPositionX, data.interactionPositionY, data.interactionPositionZ);
// Store source data needed for serialization.
intObj.loadedObject.texturePath = data.base.texturePath;
intObj.loadedObject.textureDarkandsPath = data.base.textureDarkandsPath;
intObj.loadedObject.meshPath = data.base.meshPath;
intObj.loadedObject.meshRotationX = data.base.rotationX;
intObj.loadedObject.meshRotationY = data.base.rotationY;
intObj.loadedObject.meshRotationZ = data.base.rotationZ;
intObj.loadedObject.meshScale = data.base.scale;
intObj.jsonPositionX = data.base.positionX;
intObj.jsonPositionY = data.base.positionY;
intObj.jsonPositionZ = data.base.positionZ;
intObj.loadedObject.mesh.RefreshVBO();
// Center mesh at origin; store corrected world position separately.
if (!intObj.loadedObject.mesh.data.PositionData.empty()) {
Eigen::Vector3f meshMin = intObj.loadedObject.mesh.data.PositionData[0];
Eigen::Vector3f meshMax = intObj.loadedObject.mesh.data.PositionData[0];
for (const auto& v : intObj.loadedObject.mesh.data.PositionData) {
meshMin = meshMin.cwiseMin(v);
meshMax = meshMax.cwiseMax(v);
}
Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
intObj.loadedObject.mesh.data.Move(-meshCenter);
intObj.loadedObject.mesh.RefreshVBO();
intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ) + meshCenter;
} else {
intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ);
}
interactiveObjects.push_back(std::move(intObj));
if (!data.activateFunctionName.empty()) if (!data.activateFunctionName.empty())
std::cout << "Successfully loaded interactive: " << data.base.name std::cout << "Successfully loaded interactive: " << data.base.name
@ -247,6 +237,7 @@ namespace ZL {
else else
std::cout << "Successfully loaded interactive: " << data.base.name << std::endl; std::cout << "Successfully loaded interactive: " << data.base.name << std::endl;
interactiveObjects.push_back(std::move(intObj));
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl; std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl;
} }

View File

@ -7,6 +7,7 @@
#include "external/nlohmann/json.hpp" #include "external/nlohmann/json.hpp"
#include "TextModel.h" #include "TextModel.h"
#include "InteractiveObject.h" #include "InteractiveObject.h"
#include "InteractiveObjectState.h"
#include "../CharacterState.h" #include "../CharacterState.h"
namespace ZL { namespace ZL {
@ -25,6 +26,8 @@ namespace ZL {
float positionY = 0.0f; float positionY = 0.0f;
float positionZ = 0.0f; float positionZ = 0.0f;
float scale = 1.0f; float scale = 1.0f;
LoadedGameObjectState toLoadedGameObjectState() const;
}; };
struct InteractiveObjectData { struct InteractiveObjectData {
@ -42,6 +45,8 @@ namespace ZL {
bool castShadow = true; bool castShadow = true;
bool castShadowNight = true; bool castShadowNight = true;
bool isActive = true; bool isActive = true;
InteractiveObjectState toInteractiveObjectState() const;
}; };
struct NpcData { struct NpcData {

View File

@ -1,6 +1,7 @@
#include "InteractiveObject.h" #include "InteractiveObject.h"
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "../TextModel.h"
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
@ -14,133 +15,188 @@ namespace ZL {
using std::max; using std::max;
#endif #endif
InteractiveObject InteractiveObject::createFromState(InteractiveObjectState st,
Renderer& renderer,
const std::string& zipPath)
{
InteractiveObject obj;
obj.state = std::move(st);
const LoadedGameObjectState& info = obj.state.objectInfo;
obj.loadedObject.texture = renderer.textureManager.LoadFromPng(info.texturePath, zipPath);
if (!info.textureDarkandsPath.empty())
obj.loadedObject.textureDarklands = renderer.textureManager.LoadFromPng(info.textureDarkandsPath, zipPath);
if (info.meshPath.size() > 4 && info.meshPath.compare(info.meshPath.size() - 4, 4, ".bin") == 0)
obj.loadedObject.mesh.data = LoadModelFromBinFile(info.meshPath, zipPath);
else
obj.loadedObject.mesh.data = LoadFromTextFile02(info.meshPath, zipPath);
constexpr float kDeg2Rad = static_cast<float>(M_PI) / 180.0f;
Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
if (info.meshRotationX != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(info.meshRotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot;
if (info.meshRotationY != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(info.meshRotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot;
if (info.meshRotationZ != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(info.meshRotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot;
obj.loadedObject.mesh.data.RotateByMatrix(rot.toRotationMatrix());
if (info.meshScale != 1.0f)
obj.loadedObject.mesh.data.Scale(info.meshScale);
obj.loadedObject.mesh.RefreshVBO();
// Center mesh at origin; adjust world position by the centering offset.
if (!obj.loadedObject.mesh.data.PositionData.empty()) {
Eigen::Vector3f meshMin = obj.loadedObject.mesh.data.PositionData[0];
Eigen::Vector3f meshMax = obj.loadedObject.mesh.data.PositionData[0];
for (const auto& v : obj.loadedObject.mesh.data.PositionData) {
meshMin = meshMin.cwiseMin(v);
meshMax = meshMax.cwiseMax(v);
}
const Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
obj.loadedObject.mesh.data.Move(-meshCenter);
obj.loadedObject.mesh.RefreshVBO();
obj.state.position = Eigen::Vector3f(obj.state.jsonPositionX,
obj.state.jsonPositionY,
obj.state.jsonPositionZ) + meshCenter;
} else {
obj.state.position = Eigen::Vector3f(obj.state.jsonPositionX,
obj.state.jsonPositionY,
obj.state.jsonPositionZ);
}
return obj;
}
void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete) { void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete) {
if (isAnimating) return; if (state.isAnimating) return;
AnimTask task; AnimTask task;
task.type = AnimTask::Type::Move; task.type = AnimTask::Type::Move;
task.startPos = position; task.startPos = state.position;
task.startRotY = rotationY; task.startRotY = state.rotationY;
task.startScale = scale; task.startScale = state.scale;
task.targetPos = target; task.targetPos = target;
task.targetRotY = rotationY; task.targetRotY = state.rotationY;
task.targetScale = scale; task.targetScale = state.scale;
task.durationMs = durationSec * 1000.f; task.durationMs = durationSec * 1000.f;
task.elapsedMs = 0.f; task.elapsedMs = 0.f;
task.onComplete = std::move(onComplete); task.onComplete = std::move(onComplete);
animTask = std::move(task); state.animTask = std::move(task);
isAnimating = true; state.isAnimating = true;
} }
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) { void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
if (isAnimating) return; if (state.isAnimating) return;
AnimTask task; AnimTask task;
task.type = AnimTask::Type::Rotate; task.type = AnimTask::Type::Rotate;
task.startPos = position; task.startPos = state.position;
task.startRotY = rotationY; task.startRotY = state.rotationY;
task.startScale = scale; task.startScale = state.scale;
task.targetPos = position; task.targetPos = state.position;
task.targetRotY = targetRotY; task.targetRotY = targetRotY;
task.targetScale = scale; task.targetScale = state.scale;
task.durationMs = durationSec * 1000.f; task.durationMs = durationSec * 1000.f;
task.elapsedMs = 0.f; task.elapsedMs = 0.f;
task.onComplete = std::move(onComplete); task.onComplete = std::move(onComplete);
animTask = std::move(task); state.animTask = std::move(task);
isAnimating = true; state.isAnimating = true;
} }
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) { void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
if (isAnimating) return; if (state.isAnimating) return;
AnimTask task; AnimTask task;
task.type = AnimTask::Type::Scale; task.type = AnimTask::Type::Scale;
task.startPos = position; task.startPos = state.position;
task.startRotY = rotationY; task.startRotY = state.rotationY;
task.startScale = scale; task.startScale = state.scale;
task.targetPos = position; task.targetPos = state.position;
task.targetRotY = rotationY; task.targetRotY = state.rotationY;
task.targetScale = targetScale; task.targetScale = targetScale;
task.durationMs = durationSec * 1000.f; task.durationMs = durationSec * 1000.f;
task.elapsedMs = 0.f; task.elapsedMs = 0.f;
task.onComplete = std::move(onComplete); task.onComplete = std::move(onComplete);
animTask = std::move(task); state.animTask = std::move(task);
isAnimating = true; state.isAnimating = true;
} }
void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) { void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) {
if (isAnimating) return; if (state.isAnimating) return;
AnimTask task; AnimTask task;
task.type = AnimTask::Type::Fade; task.type = AnimTask::Type::Fade;
task.startPos = position; task.startPos = state.position;
task.startRotY = rotationY; task.startRotY = state.rotationY;
task.startScale = alpha; // reuse startScale to hold start alpha task.startScale = state.alpha; // reuse startScale to hold start alpha
task.targetPos = position; task.targetPos = state.position;
task.targetRotY = rotationY; task.targetRotY = state.rotationY;
task.targetScale = targetAlpha; // reuse targetScale to hold target alpha task.targetScale = targetAlpha; // reuse targetScale to hold target alpha
task.durationMs = durationSec * 1000.f; task.durationMs = durationSec * 1000.f;
task.elapsedMs = 0.f; task.elapsedMs = 0.f;
task.onComplete = std::move(onComplete); task.onComplete = std::move(onComplete);
animTask = std::move(task); state.animTask = std::move(task);
isAnimating = true; state.isAnimating = true;
} }
void InteractiveObject::update(int64_t deltaMs) { void InteractiveObject::update(int64_t deltaMs) {
if (!isAnimating || !animTask) return; if (!state.isAnimating || !state.animTask) return;
AnimTask& task = *animTask; AnimTask& task = *state.animTask;
task.elapsedMs += static_cast<float>(deltaMs); task.elapsedMs += static_cast<float>(deltaMs);
const float t = min(task.elapsedMs / task.durationMs, 1.0f); const float t = min(task.elapsedMs / task.durationMs, 1.0f);
switch (task.type) { switch (task.type) {
case AnimTask::Type::Move: case AnimTask::Type::Move:
position = task.startPos + (task.targetPos - task.startPos) * t; state.position = task.startPos + (task.targetPos - task.startPos) * t;
break; break;
case AnimTask::Type::Rotate: case AnimTask::Type::Rotate:
rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t; state.rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
break; break;
case AnimTask::Type::Scale: case AnimTask::Type::Scale:
scale = task.startScale + (task.targetScale - task.startScale) * t; state.scale = task.startScale + (task.targetScale - task.startScale) * t;
break; break;
case AnimTask::Type::Fade: case AnimTask::Type::Fade:
alpha = task.startScale + (task.targetScale - task.startScale) * t; state.alpha = task.startScale + (task.targetScale - task.startScale) * t;
break; break;
} }
if (t >= 1.0f) { if (t >= 1.0f) {
// Snap to exact target state.position = task.targetPos;
position = task.targetPos; state.rotationY = task.targetRotY;
rotationY = task.targetRotY;
if (task.type == AnimTask::Type::Fade) if (task.type == AnimTask::Type::Fade)
alpha = task.targetScale; // targetScale reused to hold targetAlpha for Fade state.alpha = task.targetScale;
else else
scale = task.targetScale; state.scale = task.targetScale;
std::function<void()> cb = std::move(task.onComplete); std::function<void()> cb = std::move(task.onComplete);
animTask.reset(); state.animTask.reset();
isAnimating = false; state.isAnimating = false;
if (cb) cb(); if (cb) cb();
} }
} }
void InteractiveObject::drawDarklands(Renderer& renderer) const { void InteractiveObject::drawDarklands(Renderer& renderer) const {
if (!isActive || !loadedObject.textureDarklands) return; if (!state.isActive || !loadedObject.textureDarklands) return;
if (alpha < 0.999f) { if (state.alpha < 0.999f) {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} }
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix(position); renderer.TranslateMatrix(state.position);
if (rotationY != 0.f) { if (state.rotationY != 0.f) {
renderer.TranslateMatrix(pivot); renderer.TranslateMatrix(state.pivot);
renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix()); renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
renderer.TranslateMatrix(-pivot); renderer.TranslateMatrix(-state.pivot);
} }
if (scale != 1.f) if (state.scale != 1.f)
renderer.ScaleMatrix(scale); renderer.ScaleMatrix(state.scale);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f("uAlpha", alpha); renderer.RenderUniform1f("uAlpha", state.alpha);
glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID()); glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
renderer.DrawVertexRenderStruct(loadedObject.mesh); renderer.DrawVertexRenderStruct(loadedObject.mesh);
renderer.PopMatrix(); renderer.PopMatrix();
@ -149,29 +205,28 @@ namespace ZL {
} }
void InteractiveObject::draw(Renderer& renderer) const { void InteractiveObject::draw(Renderer& renderer) const {
if (!isActive || !loadedObject.texture) return; if (!state.isActive || !loadedObject.texture) return;
if (alpha < 0.999f) { if (state.alpha < 0.999f) {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} }
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix(position); renderer.TranslateMatrix(state.position);
if (rotationY != 0.f) { if (state.rotationY != 0.f) {
renderer.TranslateMatrix(pivot); renderer.TranslateMatrix(state.pivot);
renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix()); renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
renderer.TranslateMatrix(-pivot); renderer.TranslateMatrix(-state.pivot);
} }
if (scale != 1.f) if (state.scale != 1.f)
renderer.ScaleMatrix(scale); renderer.ScaleMatrix(state.scale);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f("uAlpha", alpha); renderer.RenderUniform1f("uAlpha", state.alpha);
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID()); glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
renderer.DrawVertexRenderStruct(loadedObject.mesh); renderer.DrawVertexRenderStruct(loadedObject.mesh);
renderer.PopMatrix(); renderer.PopMatrix();
// Restore uAlpha so subsequent fog-shader users (character weapons, etc.) are not affected.
renderer.RenderUniform1f("uAlpha", 1.0f); renderer.RenderUniform1f("uAlpha", 1.0f);
} }

View File

@ -6,62 +6,30 @@
#include <cstdint> #include <cstdint>
#include <Eigen/Core> #include <Eigen/Core>
#include "render/Renderer.h" #include "render/Renderer.h"
#include "InteractiveObjectState.h"
namespace ZL { namespace ZL {
class Renderer; class Renderer;
struct LoadedGameObject { struct LoadedGameObject {
std::string name; // identity key — stays at top level
LoadedGameObjectState creationInfo; // paths + transforms for save/recreate
std::shared_ptr<Texture> texture; std::shared_ptr<Texture> texture;
std::shared_ptr<Texture> textureDarklands; std::shared_ptr<Texture> textureDarklands;
VertexRenderStruct mesh; VertexRenderStruct mesh;
std::string name;
// Source paths and baked transform — populated by GameObjectLoader, used for serialization.
std::string texturePath;
std::string textureDarkandsPath;
std::string meshPath;
float meshRotationX = 0.f, meshRotationY = 0.f, meshRotationZ = 0.f;
float meshScale = 1.f;
}; };
struct InteractiveObject { struct InteractiveObject {
LoadedGameObject loadedObject; // name, texture, mesh LoadedGameObject loadedObject; // rendering assets
Eigen::Vector3f position; InteractiveObjectState state; // all mutable + creation state
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 alpha = 1.0f; // opacity: 1=opaque, 0=fully transparent
float interactionRadius;
float approachRadius = 2.0f;
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero();
// Original JSON position before mesh-centering offset applied at load time — needed for serialization.
float jsonPositionX = 0.f, jsonPositionY = 0.f, jsonPositionZ = 0.f;
bool hasInteractionPosition = false; // Factory: creates a fully-loaded InteractiveObject from a state descriptor.
bool isActive = true; // Loads textures and mesh from state.objectInfo, computes mesh centering,
bool isAnimating = false; // true while a timed animation is running // and sets state.position = jsonPosition + meshCenter.
bool castShadow = true; // included in day shadow depth pass regardless of isActive static InteractiveObject createFromState(InteractiveObjectState st,
bool castShadowNight = true; // included in night shadow depth pass regardless of isActive Renderer& renderer,
std::string activateFunctionName; const std::string& zipPath = "");
struct AnimTask {
enum class Type { Move, Rotate, Scale, Fade } 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) {}
void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {}); void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {});
void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {}); void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {});

View File

@ -0,0 +1,68 @@
#pragma once
#include <string>
#include <optional>
#include <functional>
#include <Eigen/Core>
#include <Eigen/Geometry>
namespace ZL {
// Animation task for timed position/rotation/scale/alpha transitions.
// Moved here from InteractiveObject so InteractiveObjectState can own it.
struct AnimTask {
enum class Type { Move, Rotate, Scale, Fade } type;
Eigen::Vector3f startPos;
float startRotY = 0.f;
float startScale = 1.f;
Eigen::Vector3f targetPos;
float targetRotY = 0.f;
float targetScale = 1.f;
float durationMs = 1000.f;
float elapsedMs = 0.f;
std::function<void()> onComplete; // non-serializable
};
// Paths and baked mesh transforms needed to recreate a LoadedGameObject from disk.
struct LoadedGameObjectState {
std::string texturePath;
std::string textureDarkandsPath;
std::string meshPath;
float meshRotationX = 0.f;
float meshRotationY = 0.f;
float meshRotationZ = 0.f;
float meshScale = 1.f;
};
// All serializable state for an InteractiveObject: creation info + runtime mutable state.
// Mirrors CharacterState / CharacterCreationInfo but kept as a single flat class
// since interactive objects don't need the two-level separation.
class InteractiveObjectState {
public:
// --- Creation info (fixed at load time) ---
LoadedGameObjectState objectInfo;
float interactionRadius = 2.f;
float approachRadius = 2.f;
Eigen::Vector3f pivot = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
bool hasInteractionPosition = false;
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero();
bool castShadow = true;
bool castShadowNight = true;
std::string activateFunctionName;
// Original JSON position before mesh-centering offset — needed for re-serialization.
float jsonPositionX = 0.f;
float jsonPositionY = 0.f;
float jsonPositionZ = 0.f;
// --- Runtime mutable state ---
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float rotationY = 0.f;
float scale = 1.f;
float alpha = 1.f;
bool isActive = true;
bool isAnimating = false;
std::optional<AnimTask> animTask;
};
} // namespace ZL