Working on game states
This commit is contained in:
parent
0ca00f965d
commit
e0968b5522
@ -119,6 +119,7 @@ set(SOURCES
|
||||
../src/items/ItemRegistry.cpp
|
||||
../src/items/InteractiveObject.h
|
||||
../src/items/InteractiveObject.cpp
|
||||
../src/items/InteractiveObjectState.h
|
||||
../src/dialogue/DialogueTypes.h
|
||||
../src/dialogue/DialogueDatabase.h
|
||||
../src/dialogue/DialogueDatabase.cpp
|
||||
|
||||
@ -74,6 +74,7 @@ add_executable(witcher001
|
||||
../src/items/ItemRegistry.cpp
|
||||
../src/items/InteractiveObject.h
|
||||
../src/items/InteractiveObject.cpp
|
||||
../src/items/InteractiveObjectState.h
|
||||
../src/dialogue/DialogueTypes.h
|
||||
../src/dialogue/DialogueDatabase.h
|
||||
../src/dialogue/DialogueDatabase.cpp
|
||||
|
||||
@ -456,7 +456,7 @@ namespace ZL
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
//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;
|
||||
continue;
|
||||
}
|
||||
@ -470,10 +470,10 @@ namespace ZL
|
||||
//std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
||||
// << 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) {
|
||||
const Eigen::Vector3f worldMin = intObj.position + intObj.boundsMin;
|
||||
const Eigen::Vector3f worldMax = intObj.position + intObj.boundsMax;
|
||||
const Eigen::Vector3f worldMin = intObj.state.position + intObj.state.boundsMin;
|
||||
const Eigen::Vector3f worldMax = intObj.state.position + intObj.state.boundsMax;
|
||||
float tMin = 0.1f;
|
||||
float tMax = FLT_MAX;
|
||||
bool miss = false;
|
||||
@ -498,7 +498,7 @@ namespace ZL
|
||||
closestObject = &intObj;
|
||||
}
|
||||
} else {
|
||||
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
||||
Eigen::Vector3f toObject = intObj.state.position - rayOrigin;
|
||||
|
||||
float distanceAlongRay = toObject.dot(rayDir);
|
||||
|
||||
@ -507,9 +507,9 @@ namespace ZL
|
||||
}
|
||||
|
||||
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;
|
||||
closestObject = &intObj;
|
||||
}
|
||||
@ -632,7 +632,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.isActive) {
|
||||
if (intObj.state.isActive) {
|
||||
intObj.draw(renderer);
|
||||
}
|
||||
}
|
||||
@ -724,9 +724,9 @@ namespace ZL
|
||||
}
|
||||
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.castShadow && intObj.loadedObject.texture) {
|
||||
if (intObj.state.castShadow && intObj.loadedObject.texture) {
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix(intObj.position);
|
||||
renderer.TranslateMatrix(intObj.state.position);
|
||||
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
|
||||
renderer.PopMatrix();
|
||||
}
|
||||
@ -818,7 +818,7 @@ namespace ZL
|
||||
|
||||
CheckGlError(__FILE__, __LINE__);
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.isActive) {
|
||||
if (intObj.state.isActive) {
|
||||
intObj.draw(renderer);
|
||||
}
|
||||
}
|
||||
@ -911,7 +911,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.isActive) {
|
||||
if (intObj.state.isActive) {
|
||||
intObj.drawDarklands(renderer);
|
||||
}
|
||||
}
|
||||
@ -971,9 +971,9 @@ namespace ZL
|
||||
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||
}
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.castShadowNight && intObj.loadedObject.texture) {
|
||||
if (intObj.state.castShadowNight && intObj.loadedObject.texture) {
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix(intObj.position);
|
||||
renderer.TranslateMatrix(intObj.state.position);
|
||||
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
|
||||
renderer.PopMatrix();
|
||||
}
|
||||
@ -1144,7 +1144,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.isActive) {
|
||||
if (intObj.state.isActive) {
|
||||
intObj.draw(renderer);
|
||||
}
|
||||
}
|
||||
@ -1492,22 +1492,22 @@ namespace ZL
|
||||
for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS;
|
||||
|
||||
// Check if player reached target interactive object
|
||||
if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->isAnimating) {
|
||||
const Eigen::Vector3f& approachTarget = targetInteractiveObject->hasInteractionPosition
|
||||
? targetInteractiveObject->interactionPosition
|
||||
: targetInteractiveObject->position;
|
||||
if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->state.isAnimating) {
|
||||
const Eigen::Vector3f& approachTarget = targetInteractiveObject->state.hasInteractionPosition
|
||||
? targetInteractiveObject->state.interactionPosition
|
||||
: targetInteractiveObject->state.position;
|
||||
float distToObject = (player->state.position - approachTarget).norm();
|
||||
|
||||
// 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] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << 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);
|
||||
if (!targetInteractiveObject->state.activateFunctionName.empty()) {
|
||||
std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->state.activateFunctionName << std::endl;
|
||||
scriptEngine.callActivateFunction(targetInteractiveObject->state.activateFunctionName);
|
||||
}
|
||||
else {
|
||||
std::cout << "[PICKUP] Using fallback callback" << std::endl;
|
||||
@ -1632,19 +1632,19 @@ namespace ZL
|
||||
|
||||
// First check if we clicked on interactive object
|
||||
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] Object position: (" << clickedObject->position.x() << ", "
|
||||
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
|
||||
std::cout << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", "
|
||||
<< clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl;
|
||||
std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", "
|
||||
<< player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
|
||||
|
||||
targetInteractiveObject = clickedObject;
|
||||
targetInteractNpcIndex = -1;
|
||||
targetTeleportZone = nullptr;
|
||||
player->setTarget(clickedObject->hasInteractionPosition
|
||||
? clickedObject->interactionPosition
|
||||
: clickedObject->position);
|
||||
player->setTarget(clickedObject->state.hasInteractionPosition
|
||||
? clickedObject->state.interactionPosition
|
||||
: clickedObject->state.position);
|
||||
player->state.attackTargetIndex = CharacterState::kNoTarget;
|
||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||
}
|
||||
|
||||
@ -212,12 +212,12 @@ namespace ZL
|
||||
const bool isSelected = (idx == selectedInteractiveObjectIndex);
|
||||
|
||||
// --- 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 float x0 = obj.boundsMin.x(), x1 = obj.boundsMax.x();
|
||||
const float y0 = obj.boundsMin.y(), y1 = obj.boundsMax.y();
|
||||
const float z0 = obj.boundsMin.z(), z1 = obj.boundsMax.z();
|
||||
const float x0 = obj.state.boundsMin.x(), x1 = obj.state.boundsMax.x();
|
||||
const float y0 = obj.state.boundsMin.y(), y1 = obj.state.boundsMax.y();
|
||||
const float z0 = obj.state.boundsMin.z(), z1 = obj.state.boundsMax.z();
|
||||
|
||||
Eigen::Vector3f corners[8] = {
|
||||
{ x0, y0, z0 }, { x1, y0, z0 },
|
||||
@ -227,12 +227,12 @@ namespace ZL
|
||||
};
|
||||
|
||||
// Apply the same TRS the draw function applies: scale → rotateY → translate
|
||||
if (obj.scale != 1.f) {
|
||||
for (auto& c : corners) c *= obj.scale;
|
||||
if (obj.state.scale != 1.f) {
|
||||
for (auto& c : corners) c *= obj.state.scale;
|
||||
}
|
||||
if (obj.rotationY != 0.f) {
|
||||
const float cosR = std::cos(obj.rotationY);
|
||||
const float sinR = std::sin(obj.rotationY);
|
||||
if (obj.state.rotationY != 0.f) {
|
||||
const float cosR = std::cos(obj.state.rotationY);
|
||||
const float sinR = std::sin(obj.state.rotationY);
|
||||
for (auto& c : corners) {
|
||||
const float nx = c.x() * cosR - c.z() * sinR;
|
||||
const float nz = c.x() * sinR + c.z() * cosR;
|
||||
@ -240,7 +240,7 @@ namespace ZL
|
||||
c.z() = nz;
|
||||
}
|
||||
}
|
||||
for (auto& c : corners) c += obj.position;
|
||||
for (auto& c : corners) c += obj.state.position;
|
||||
|
||||
VertexRenderStruct mesh;
|
||||
mesh.data = CreateBoundsBoxMesh(corners, color);
|
||||
@ -249,8 +249,8 @@ namespace ZL
|
||||
}
|
||||
|
||||
// --- interaction position pole ---
|
||||
if (obj.hasInteractionPosition) {
|
||||
const Eigen::Vector3f& p = obj.interactionPosition;
|
||||
if (obj.state.hasInteractionPosition) {
|
||||
const Eigen::Vector3f& p = obj.state.interactionPosition;
|
||||
static constexpr float hw = 0.05f; // half width/depth
|
||||
static constexpr float hh = 1.5f; // half height (pole = 3.0 tall)
|
||||
|
||||
@ -312,8 +312,8 @@ namespace ZL
|
||||
if (ctrlHeld) {
|
||||
// Set the interaction position for the selected object (world XZ, Y=0).
|
||||
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
||||
target.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z());
|
||||
target.hasInteractionPosition = true;
|
||||
target.state.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z());
|
||||
target.state.hasInteractionPosition = true;
|
||||
boundsClickCount = 0; // cancel any in-progress bounds placement
|
||||
buildInteractiveObjectBoundsMeshes();
|
||||
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)
|
||||
auto worldToLocal = [&](const Eigen::Vector3f& w) -> Eigen::Vector3f {
|
||||
Eigen::Vector3f local = w - obj.position;
|
||||
if (obj.rotationY != 0.f) {
|
||||
const float c = std::cos(-obj.rotationY);
|
||||
const float s = std::sin(-obj.rotationY);
|
||||
Eigen::Vector3f local = w - obj.state.position;
|
||||
if (obj.state.rotationY != 0.f) {
|
||||
const float c = std::cos(-obj.state.rotationY);
|
||||
const float s = std::sin(-obj.state.rotationY);
|
||||
const float nx = local.x() * c - local.z() * s;
|
||||
const float nz = local.x() * s + local.z() * c;
|
||||
local.x() = nx;
|
||||
local.z() = nz;
|
||||
}
|
||||
if (obj.scale > 1e-6f && obj.scale != 1.f)
|
||||
local /= obj.scale;
|
||||
if (obj.state.scale > 1e-6f && obj.state.scale != 1.f)
|
||||
local /= obj.state.scale;
|
||||
return local;
|
||||
};
|
||||
|
||||
@ -350,9 +350,9 @@ namespace ZL
|
||||
const Eigen::Vector3f localB = worldToLocal(worldHit);
|
||||
|
||||
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()));
|
||||
target.boundsMax = Eigen::Vector3f(
|
||||
target.state.boundsMax = Eigen::Vector3f(
|
||||
max(localA.x(), localB.x()), 1.f, max(localA.z(), localB.z()));
|
||||
|
||||
boundsClickCount = 0;
|
||||
@ -360,8 +360,8 @@ namespace ZL
|
||||
|
||||
std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
|
||||
<< " (" << target.loadedObject.name << ")"
|
||||
<< " min=(" << target.boundsMin.x() << ", " << target.boundsMin.z() << ")"
|
||||
<< " max=(" << target.boundsMax.x() << ", " << target.boundsMax.z() << ")\n";
|
||||
<< " min=(" << target.state.boundsMin.x() << ", " << target.state.boundsMin.z() << ")"
|
||||
<< " max=(" << target.state.boundsMax.x() << ", " << target.state.boundsMax.z() << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,34 +382,34 @@ namespace ZL
|
||||
for (const auto& obj : loc.interactiveObjects) {
|
||||
json item;
|
||||
item["name"] = obj.loadedObject.name;
|
||||
item["texturePath"] = obj.loadedObject.texturePath;
|
||||
if (!obj.loadedObject.textureDarkandsPath.empty())
|
||||
item["textureDarkandsPath"] = obj.loadedObject.textureDarkandsPath;
|
||||
item["meshPath"] = obj.loadedObject.meshPath;
|
||||
item["rotationX"] = obj.loadedObject.meshRotationX;
|
||||
item["rotationY"] = obj.loadedObject.meshRotationY;
|
||||
item["rotationZ"] = obj.loadedObject.meshRotationZ;
|
||||
item["positionX"] = obj.jsonPositionX;
|
||||
item["positionY"] = obj.jsonPositionY;
|
||||
item["positionZ"] = obj.jsonPositionZ;
|
||||
item["scale"] = obj.loadedObject.meshScale;
|
||||
item["interactionRadius"] = obj.interactionRadius;
|
||||
item["approachRadius"] = obj.approachRadius;
|
||||
if (!obj.activateFunctionName.empty())
|
||||
item["activateFunction"] = obj.activateFunctionName;
|
||||
item["pivotX"] = obj.pivot.x();
|
||||
item["pivotY"] = obj.pivot.y();
|
||||
item["pivotZ"] = obj.pivot.z();
|
||||
item["boundsMinX"] = obj.boundsMin.x();
|
||||
item["boundsMinY"] = obj.boundsMin.y();
|
||||
item["boundsMinZ"] = obj.boundsMin.z();
|
||||
item["boundsMaxX"] = obj.boundsMax.x();
|
||||
item["boundsMaxY"] = obj.boundsMax.y();
|
||||
item["boundsMaxZ"] = obj.boundsMax.z();
|
||||
if (obj.hasInteractionPosition) {
|
||||
item["interactionPositionX"] = obj.interactionPosition.x();
|
||||
item["interactionPositionY"] = obj.interactionPosition.y();
|
||||
item["interactionPositionZ"] = obj.interactionPosition.z();
|
||||
item["texturePath"] = obj.loadedObject.creationInfo.texturePath;
|
||||
if (!obj.loadedObject.creationInfo.textureDarkandsPath.empty())
|
||||
item["textureDarkandsPath"] = obj.loadedObject.creationInfo.textureDarkandsPath;
|
||||
item["meshPath"] = obj.loadedObject.creationInfo.meshPath;
|
||||
item["rotationX"] = obj.loadedObject.creationInfo.meshRotationX;
|
||||
item["rotationY"] = obj.loadedObject.creationInfo.meshRotationY;
|
||||
item["rotationZ"] = obj.loadedObject.creationInfo.meshRotationZ;
|
||||
item["positionX"] = obj.state.jsonPositionX;
|
||||
item["positionY"] = obj.state.jsonPositionY;
|
||||
item["positionZ"] = obj.state.jsonPositionZ;
|
||||
item["scale"] = obj.loadedObject.creationInfo.meshScale;
|
||||
item["interactionRadius"] = obj.state.interactionRadius;
|
||||
item["approachRadius"] = obj.state.approachRadius;
|
||||
if (!obj.state.activateFunctionName.empty())
|
||||
item["activateFunction"] = obj.state.activateFunctionName;
|
||||
item["pivotX"] = obj.state.pivot.x();
|
||||
item["pivotY"] = obj.state.pivot.y();
|
||||
item["pivotZ"] = obj.state.pivot.z();
|
||||
item["boundsMinX"] = obj.state.boundsMin.x();
|
||||
item["boundsMinY"] = obj.state.boundsMin.y();
|
||||
item["boundsMinZ"] = obj.state.boundsMin.z();
|
||||
item["boundsMaxX"] = obj.state.boundsMax.x();
|
||||
item["boundsMaxY"] = obj.state.boundsMax.y();
|
||||
item["boundsMaxZ"] = obj.state.boundsMax.z();
|
||||
if (obj.state.hasInteractionPosition) {
|
||||
item["interactionPositionX"] = obj.state.interactionPosition.x();
|
||||
item["interactionPositionY"] = obj.state.interactionPosition.y();
|
||||
item["interactionPositionZ"] = obj.state.interactionPosition.z();
|
||||
}
|
||||
j["objects"].push_back(item);
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ namespace ZL {
|
||||
api.set_function("deactivate_interactive_object", [loc](const std::string& objectName) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name == objectName) {
|
||||
intObj.isActive = false;
|
||||
intObj.state.isActive = false;
|
||||
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
|
||||
return;
|
||||
}
|
||||
@ -153,7 +153,7 @@ namespace ZL {
|
||||
api.set_function("activate_interactive_object", [loc](const std::string& objectName) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name == objectName) {
|
||||
intObj.isActive = true;
|
||||
intObj.state.isActive = true;
|
||||
std::cout << "[script] activate_interactive_object: " << objectName << std::endl;
|
||||
return;
|
||||
}
|
||||
@ -164,7 +164,7 @@ namespace ZL {
|
||||
api.set_function("set_object_rotation", [loc](const std::string& objectName, float value) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@ -175,7 +175,7 @@ namespace ZL {
|
||||
api.set_function("set_object_alpha", [loc](const std::string& objectName, float value) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name == objectName) {
|
||||
intObj.alpha = value;
|
||||
intObj.state.alpha = value;
|
||||
std::cout << "[script] set_object_alpha: " <<objectName << " " << value << std::endl;
|
||||
return;
|
||||
}
|
||||
@ -538,7 +538,7 @@ namespace ZL {
|
||||
float durationSec, sol::object onComplete) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name != name) continue;
|
||||
if (intObj.isAnimating) {
|
||||
if (intObj.state.isAnimating) {
|
||||
std::cerr << "[script] move_object: '" << name << "' is already animating\n";
|
||||
return;
|
||||
}
|
||||
@ -565,7 +565,7 @@ namespace ZL {
|
||||
float durationSec, sol::object onComplete) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name != name) continue;
|
||||
if (intObj.isAnimating) {
|
||||
if (intObj.state.isAnimating) {
|
||||
std::cerr << "[script] rotate_object: '" << name << "' is already animating\n";
|
||||
return;
|
||||
}
|
||||
@ -581,7 +581,7 @@ namespace ZL {
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
std::cerr << "[script] rotate_object: object '" << name << "' not found\n";
|
||||
@ -593,7 +593,7 @@ namespace ZL {
|
||||
float durationSec, sol::object onComplete) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name != name) continue;
|
||||
if (intObj.isAnimating) {
|
||||
if (intObj.state.isAnimating) {
|
||||
std::cerr << "[script] fade_object: '" << name << "' is already animating\n";
|
||||
return;
|
||||
}
|
||||
@ -620,7 +620,7 @@ namespace ZL {
|
||||
float durationSec, sol::object onComplete) {
|
||||
for (auto& intObj : loc->interactiveObjects) {
|
||||
if (intObj.loadedObject.name != name) continue;
|
||||
if (intObj.isAnimating) {
|
||||
if (intObj.state.isAnimating) {
|
||||
std::cerr << "[script] scale_object: '" << name << "' is already animating\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -118,10 +118,45 @@ namespace ZL {
|
||||
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 obj;
|
||||
obj.name = data.name;
|
||||
obj.creationInfo = data.toLoadedGameObjectState();
|
||||
|
||||
obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath);
|
||||
if (!data.textureDarkandsPath.empty())
|
||||
@ -192,54 +227,9 @@ namespace ZL {
|
||||
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
|
||||
std::cout << "Loading interactive object: " << data.base.name << std::endl;
|
||||
try {
|
||||
InteractiveObject intObj;
|
||||
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
|
||||
intObj.interactionRadius = data.interactionRadius;
|
||||
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));
|
||||
InteractiveObject intObj = InteractiveObject::createFromState(
|
||||
data.toInteractiveObjectState(), renderer, zipPath);
|
||||
intObj.loadedObject.name = data.base.name;
|
||||
|
||||
if (!data.activateFunctionName.empty())
|
||||
std::cout << "Successfully loaded interactive: " << data.base.name
|
||||
@ -247,6 +237,7 @@ namespace ZL {
|
||||
else
|
||||
std::cout << "Successfully loaded interactive: " << data.base.name << std::endl;
|
||||
|
||||
interactiveObjects.push_back(std::move(intObj));
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include "TextModel.h"
|
||||
#include "InteractiveObject.h"
|
||||
#include "InteractiveObjectState.h"
|
||||
#include "../CharacterState.h"
|
||||
|
||||
namespace ZL {
|
||||
@ -25,6 +26,8 @@ namespace ZL {
|
||||
float positionY = 0.0f;
|
||||
float positionZ = 0.0f;
|
||||
float scale = 1.0f;
|
||||
|
||||
LoadedGameObjectState toLoadedGameObjectState() const;
|
||||
};
|
||||
|
||||
struct InteractiveObjectData {
|
||||
@ -42,6 +45,8 @@ namespace ZL {
|
||||
bool castShadow = true;
|
||||
bool castShadowNight = true;
|
||||
bool isActive = true;
|
||||
|
||||
InteractiveObjectState toInteractiveObjectState() const;
|
||||
};
|
||||
|
||||
struct NpcData {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "InteractiveObject.h"
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
#include "../TextModel.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
@ -14,133 +15,188 @@ namespace ZL {
|
||||
using std::max;
|
||||
#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) {
|
||||
if (isAnimating) return;
|
||||
if (state.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.type = AnimTask::Type::Move;
|
||||
task.startPos = state.position;
|
||||
task.startRotY = state.rotationY;
|
||||
task.startScale = state.scale;
|
||||
task.targetPos = target;
|
||||
task.targetRotY = state.rotationY;
|
||||
task.targetScale = state.scale;
|
||||
task.durationMs = durationSec * 1000.f;
|
||||
task.elapsedMs = 0.f;
|
||||
task.onComplete = std::move(onComplete);
|
||||
animTask = std::move(task);
|
||||
isAnimating = true;
|
||||
state.animTask = std::move(task);
|
||||
state.isAnimating = true;
|
||||
}
|
||||
|
||||
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
|
||||
if (isAnimating) return;
|
||||
if (state.isAnimating) return;
|
||||
AnimTask task;
|
||||
task.type = AnimTask::Type::Rotate;
|
||||
task.startPos = position;
|
||||
task.startRotY = rotationY;
|
||||
task.startScale = scale;
|
||||
task.targetPos = position;
|
||||
task.startPos = state.position;
|
||||
task.startRotY = state.rotationY;
|
||||
task.startScale = state.scale;
|
||||
task.targetPos = state.position;
|
||||
task.targetRotY = targetRotY;
|
||||
task.targetScale = scale;
|
||||
task.targetScale = state.scale;
|
||||
task.durationMs = durationSec * 1000.f;
|
||||
task.elapsedMs = 0.f;
|
||||
task.onComplete = std::move(onComplete);
|
||||
animTask = std::move(task);
|
||||
isAnimating = true;
|
||||
state.animTask = std::move(task);
|
||||
state.isAnimating = true;
|
||||
}
|
||||
|
||||
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
|
||||
if (isAnimating) return;
|
||||
if (state.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.startPos = state.position;
|
||||
task.startRotY = state.rotationY;
|
||||
task.startScale = state.scale;
|
||||
task.targetPos = state.position;
|
||||
task.targetRotY = state.rotationY;
|
||||
task.targetScale = targetScale;
|
||||
task.durationMs = durationSec * 1000.f;
|
||||
task.elapsedMs = 0.f;
|
||||
task.onComplete = std::move(onComplete);
|
||||
animTask = std::move(task);
|
||||
isAnimating = true;
|
||||
state.animTask = std::move(task);
|
||||
state.isAnimating = true;
|
||||
}
|
||||
|
||||
void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) {
|
||||
if (isAnimating) return;
|
||||
if (state.isAnimating) return;
|
||||
AnimTask task;
|
||||
task.type = AnimTask::Type::Fade;
|
||||
task.startPos = position;
|
||||
task.startRotY = rotationY;
|
||||
task.startScale = alpha; // reuse startScale to hold start alpha
|
||||
task.targetPos = position;
|
||||
task.targetRotY = rotationY;
|
||||
task.targetScale = targetAlpha; // reuse targetScale to hold target alpha
|
||||
task.startPos = state.position;
|
||||
task.startRotY = state.rotationY;
|
||||
task.startScale = state.alpha; // reuse startScale to hold start alpha
|
||||
task.targetPos = state.position;
|
||||
task.targetRotY = state.rotationY;
|
||||
task.targetScale = targetAlpha; // reuse targetScale to hold target alpha
|
||||
task.durationMs = durationSec * 1000.f;
|
||||
task.elapsedMs = 0.f;
|
||||
task.onComplete = std::move(onComplete);
|
||||
animTask = std::move(task);
|
||||
isAnimating = true;
|
||||
state.animTask = std::move(task);
|
||||
state.isAnimating = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
state.position = task.startPos + (task.targetPos - task.startPos) * t;
|
||||
break;
|
||||
case AnimTask::Type::Rotate:
|
||||
rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
|
||||
state.rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
|
||||
break;
|
||||
case AnimTask::Type::Scale:
|
||||
scale = task.startScale + (task.targetScale - task.startScale) * t;
|
||||
state.scale = task.startScale + (task.targetScale - task.startScale) * t;
|
||||
break;
|
||||
case AnimTask::Type::Fade:
|
||||
alpha = task.startScale + (task.targetScale - task.startScale) * t;
|
||||
state.alpha = task.startScale + (task.targetScale - task.startScale) * t;
|
||||
break;
|
||||
}
|
||||
|
||||
if (t >= 1.0f) {
|
||||
// Snap to exact target
|
||||
position = task.targetPos;
|
||||
rotationY = task.targetRotY;
|
||||
state.position = task.targetPos;
|
||||
state.rotationY = task.targetRotY;
|
||||
if (task.type == AnimTask::Type::Fade)
|
||||
alpha = task.targetScale; // targetScale reused to hold targetAlpha for Fade
|
||||
state.alpha = task.targetScale;
|
||||
else
|
||||
scale = task.targetScale;
|
||||
state.scale = task.targetScale;
|
||||
|
||||
std::function<void()> cb = std::move(task.onComplete);
|
||||
animTask.reset();
|
||||
isAnimating = false;
|
||||
state.animTask.reset();
|
||||
state.isAnimating = false;
|
||||
|
||||
if (cb) cb();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix(position);
|
||||
if (rotationY != 0.f) {
|
||||
renderer.TranslateMatrix(pivot);
|
||||
renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||
renderer.TranslateMatrix(-pivot);
|
||||
renderer.TranslateMatrix(state.position);
|
||||
if (state.rotationY != 0.f) {
|
||||
renderer.TranslateMatrix(state.pivot);
|
||||
renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||
renderer.TranslateMatrix(-state.pivot);
|
||||
}
|
||||
if (scale != 1.f)
|
||||
renderer.ScaleMatrix(scale);
|
||||
if (state.scale != 1.f)
|
||||
renderer.ScaleMatrix(state.scale);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.RenderUniform1f("uAlpha", alpha);
|
||||
renderer.RenderUniform1f("uAlpha", state.alpha);
|
||||
glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
|
||||
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||
renderer.PopMatrix();
|
||||
@ -149,30 +205,29 @@ namespace ZL {
|
||||
}
|
||||
|
||||
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);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix(position);
|
||||
if (rotationY != 0.f) {
|
||||
renderer.TranslateMatrix(pivot);
|
||||
renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||
renderer.TranslateMatrix(-pivot);
|
||||
renderer.TranslateMatrix(state.position);
|
||||
if (state.rotationY != 0.f) {
|
||||
renderer.TranslateMatrix(state.pivot);
|
||||
renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||
renderer.TranslateMatrix(-state.pivot);
|
||||
}
|
||||
if (scale != 1.f)
|
||||
renderer.ScaleMatrix(scale);
|
||||
if (state.scale != 1.f)
|
||||
renderer.ScaleMatrix(state.scale);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.RenderUniform1f("uAlpha", alpha);
|
||||
renderer.RenderUniform1f("uAlpha", state.alpha);
|
||||
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||
renderer.PopMatrix();
|
||||
|
||||
// Restore uAlpha so subsequent fog-shader users (character weapons, etc.) are not affected.
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f);
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
} // namespace ZL
|
||||
|
||||
@ -6,62 +6,30 @@
|
||||
#include <cstdint>
|
||||
#include <Eigen/Core>
|
||||
#include "render/Renderer.h"
|
||||
#include "InteractiveObjectState.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
class Renderer;
|
||||
|
||||
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> textureDarklands;
|
||||
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 {
|
||||
LoadedGameObject loadedObject; // name, texture, mesh
|
||||
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 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;
|
||||
LoadedGameObject loadedObject; // rendering assets
|
||||
InteractiveObjectState state; // all mutable + creation state
|
||||
|
||||
bool hasInteractionPosition = false;
|
||||
bool isActive = true;
|
||||
bool isAnimating = false; // true while a timed animation is running
|
||||
bool castShadow = true; // included in day shadow depth pass regardless of isActive
|
||||
bool castShadowNight = true; // included in night shadow depth pass regardless of isActive
|
||||
std::string activateFunctionName;
|
||||
|
||||
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) {}
|
||||
// Factory: creates a fully-loaded InteractiveObject from a state descriptor.
|
||||
// Loads textures and mesh from state.objectInfo, computes mesh centering,
|
||||
// and sets state.position = jsonPosition + meshCenter.
|
||||
static InteractiveObject createFromState(InteractiveObjectState st,
|
||||
Renderer& renderer,
|
||||
const std::string& zipPath = "");
|
||||
|
||||
void moveTo(const Eigen::Vector3f& target, float durationSec, std::function<void()> onComplete = {});
|
||||
void rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete = {});
|
||||
|
||||
68
src/items/InteractiveObjectState.h
Normal file
68
src/items/InteractiveObjectState.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user