261 lines
11 KiB
C++
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
|