Working on game states
This commit is contained in:
1 parent
0ca00f965d
commit
e0968b5522
10 files changed
+340
-251
No files matched your search
@@ -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 {
|
||||
|
||||
+125
-70
@@ -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 = {});
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user