Working on game states

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

No files matched your search

+39 -48
View File
@@ -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;
}
+5
View File
@@ -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
View File
@@ -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
+11 -43
View File
@@ -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
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