shadow-over-bishkek001/src/items/InteractiveObject.cpp
2026-06-18 21:48:27 +03:00

261 lines
11 KiB
C++

#include "InteractiveObject.h"
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "../TextModel.h"
#include <iostream>
#include <string>
#include <algorithm>
#include <Eigen/Geometry>
namespace ZL {
extern const std::string textureUniformName;
#ifdef EMSCRIPTEN
using std::min;
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 (state.isAnimating) return;
AnimTask task;
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);
state.animTask = std::move(task);
state.isAnimating = true;
}
void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function<void()> onComplete) {
if (state.isAnimating) return;
AnimTask task;
task.type = AnimTask::Type::Rotate;
task.startPos = state.position;
task.startRotY = state.rotationY;
task.startScale = state.scale;
task.targetPos = state.position;
task.targetRotY = targetRotY;
task.targetScale = state.scale;
task.durationMs = durationSec * 1000.f;
task.elapsedMs = 0.f;
task.onComplete = std::move(onComplete);
state.animTask = std::move(task);
state.isAnimating = true;
}
void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function<void()> onComplete) {
if (state.isAnimating) return;
AnimTask task;
task.type = AnimTask::Type::Scale;
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);
state.animTask = std::move(task);
state.isAnimating = true;
}
void InteractiveObject::fadeTo(float targetAlpha, float durationSec, std::function<void()> onComplete) {
if (state.isAnimating) return;
AnimTask task;
task.type = AnimTask::Type::Fade;
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);
state.animTask = std::move(task);
state.isAnimating = true;
}
void InteractiveObject::update(int64_t deltaMs) {
if (!state.isAnimating || !state.animTask) return;
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:
state.position = task.startPos + (task.targetPos - task.startPos) * t;
break;
case AnimTask::Type::Rotate:
state.rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t;
break;
case AnimTask::Type::Scale:
state.scale = task.startScale + (task.targetScale - task.startScale) * t;
break;
case AnimTask::Type::Fade:
state.alpha = task.startScale + (task.targetScale - task.startScale) * t;
break;
}
if (t >= 1.0f) {
state.position = task.targetPos;
state.rotationY = task.targetRotY;
if (task.type == AnimTask::Type::Fade)
state.alpha = task.targetScale;
else
state.scale = task.targetScale;
std::function<void()> cb = std::move(task.onComplete);
state.animTask.reset();
state.isAnimating = false;
if (cb) cb();
}
}
void InteractiveObject::drawDarklands(Renderer& renderer) const {
if (!state.isActive || !loadedObject.textureDarklands) return;
if (state.alpha < 0.999f) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
renderer.PushMatrix();
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 (state.scale != 1.f)
renderer.ScaleMatrix(state.scale);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f("uAlpha", state.alpha);
glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
renderer.DrawVertexRenderStruct(loadedObject.mesh);
renderer.PopMatrix();
renderer.RenderUniform1f("uAlpha", 1.0f);
}
void InteractiveObject::draw(Renderer& renderer) const {
if (!state.isActive || !loadedObject.texture) return;
if (state.alpha < 0.999f) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
renderer.PushMatrix();
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 (state.scale != 1.f)
renderer.ScaleMatrix(state.scale);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f("uAlpha", state.alpha);
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
renderer.DrawVertexRenderStruct(loadedObject.mesh);
renderer.PopMatrix();
renderer.RenderUniform1f("uAlpha", 1.0f);
}
void InteractiveObjectState::save(nlohmann::json& out) const
{
out["positionX"] = position.x();
out["positionY"] = position.y();
out["positionZ"] = position.z();
out["rotationY"] = rotationY;
out["scale"] = scale;
out["alpha"] = alpha;
out["isActive"] = isActive;
// isAnimating and animTask are not saved: animations snap to their end
// state on load (the final position/scale/alpha is already in the fields above).
}
void InteractiveObjectState::load(const nlohmann::json& in)
{
position.x() = in.value("positionX", position.x());
position.y() = in.value("positionY", position.y());
position.z() = in.value("positionZ", position.z());
rotationY = in.value("rotationY", rotationY);
scale = in.value("scale", scale);
alpha = in.value("alpha", alpha);
isActive = in.value("isActive", isActive);
// Cancel any in-flight animation; state was restored to final values above.
isAnimating = false;
animTask.reset();
}
} // namespace ZL