210 lines
8.6 KiB
C++
210 lines
8.6 KiB
C++
#include "InteractiveObject.h"
|
|
#include "render/Renderer.h"
|
|
#include "render/TextureManager.h"
|
|
#include "../TextModel.h"
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <Eigen/Geometry>
|
|
|
|
namespace FRG {
|
|
extern const std::string textureUniformName;
|
|
|
|
#ifdef EMSCRIPTEN
|
|
using std::min;
|
|
using std::max;
|
|
#endif
|
|
#ifdef __linux__
|
|
using std::min;
|
|
using std::max;
|
|
#endif
|
|
|
|
InteractiveObject InteractiveObject::createFromState(InteractiveObjectState& st,
|
|
Renderer& renderer,
|
|
const std::string& zipPath)
|
|
{
|
|
InteractiveObject obj(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();
|
|
|
|
// Interactive-object definitions store the centered world position.
|
|
// Center the presentation mesh at the origin without changing state.
|
|
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();
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
|
|
void InteractiveObject::capturePresentationState()
|
|
{
|
|
PresentationSample sample;
|
|
sample.position = state.position;
|
|
sample.rotationY = state.rotationY;
|
|
sample.scale = state.scale;
|
|
sample.alpha = state.alpha;
|
|
sample.isActive = state.isActive;
|
|
|
|
const auto now = std::chrono::steady_clock::now();
|
|
const bool largeGap = hasPresentationSample_ &&
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
now - lastPresentationUpdate_).count() > 250;
|
|
if (!hasPresentationSample_ || largeGap ||
|
|
currentPresentationSample_.isActive != sample.isActive) {
|
|
previousPresentationSample_ = sample;
|
|
} else {
|
|
previousPresentationSample_ = currentPresentationSample_;
|
|
}
|
|
currentPresentationSample_ = sample;
|
|
lastPresentationUpdate_ = now;
|
|
hasPresentationSample_ = true;
|
|
}
|
|
|
|
InteractiveObject::PresentationSample
|
|
InteractiveObject::interpolatedPresentationState() const
|
|
{
|
|
PresentationSample result;
|
|
result.position = state.position;
|
|
result.rotationY = state.rotationY;
|
|
result.scale = state.scale;
|
|
result.alpha = state.alpha;
|
|
result.isActive = state.isActive;
|
|
if (!hasPresentationSample_) return result;
|
|
|
|
constexpr float kSimulationTickMs = 100.0f;
|
|
const float elapsedMs = static_cast<float>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - lastPresentationUpdate_).count());
|
|
const float alpha = (std::max)(0.0f,
|
|
(std::min)(1.0f, elapsedMs / kSimulationTickMs));
|
|
|
|
result.position = previousPresentationSample_.position +
|
|
alpha * (currentPresentationSample_.position -
|
|
previousPresentationSample_.position);
|
|
result.scale = previousPresentationSample_.scale +
|
|
alpha * (currentPresentationSample_.scale -
|
|
previousPresentationSample_.scale);
|
|
result.alpha = previousPresentationSample_.alpha +
|
|
alpha * (currentPresentationSample_.alpha -
|
|
previousPresentationSample_.alpha);
|
|
|
|
constexpr float kPi = 3.14159265358979323846f;
|
|
constexpr float kTwoPi = 2.0f * kPi;
|
|
float rotationDelta = currentPresentationSample_.rotationY -
|
|
previousPresentationSample_.rotationY;
|
|
while (rotationDelta > kPi) rotationDelta -= kTwoPi;
|
|
while (rotationDelta < -kPi) rotationDelta += kTwoPi;
|
|
result.rotationY = previousPresentationSample_.rotationY +
|
|
alpha * rotationDelta;
|
|
return result;
|
|
}
|
|
|
|
void InteractiveObject::drawDarklands(Renderer& renderer) const {
|
|
if (!state.isActive || !loadedObject.textureDarklands) return;
|
|
const PresentationSample presentation = interpolatedPresentationState();
|
|
|
|
if (state.alpha < 0.999f) {
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix(presentation.position);
|
|
if (presentation.rotationY != 0.f) {
|
|
renderer.TranslateMatrix(state.pivot);
|
|
renderer.RotateMatrix(Eigen::AngleAxisf(presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
|
renderer.TranslateMatrix(-state.pivot);
|
|
}
|
|
if (presentation.scale != 1.f)
|
|
renderer.ScaleMatrix(presentation.scale);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1f("uAlpha", presentation.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;
|
|
const PresentationSample presentation = interpolatedPresentationState();
|
|
|
|
if (state.alpha < 0.999f) {
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix(presentation.position);
|
|
if (presentation.rotationY != 0.f) {
|
|
renderer.TranslateMatrix(state.pivot);
|
|
renderer.RotateMatrix(Eigen::AngleAxisf(presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
|
renderer.TranslateMatrix(-state.pivot);
|
|
}
|
|
if (presentation.scale != 1.f)
|
|
renderer.ScaleMatrix(presentation.scale);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1f("uAlpha", presentation.alpha);
|
|
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
|
renderer.PopMatrix();
|
|
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
}
|
|
|
|
void InteractiveObject::drawShadowDepth(Renderer& renderer) const
|
|
{
|
|
if (!state.isActive) return;
|
|
const PresentationSample presentation = interpolatedPresentationState();
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix(presentation.position);
|
|
if (presentation.rotationY != 0.f) {
|
|
renderer.TranslateMatrix(state.pivot);
|
|
renderer.RotateMatrix(Eigen::AngleAxisf(
|
|
presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
|
renderer.TranslateMatrix(-state.pivot);
|
|
}
|
|
if (presentation.scale != 1.f)
|
|
renderer.ScaleMatrix(presentation.scale);
|
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
|
renderer.PopMatrix();
|
|
}
|
|
|
|
} // namespace FRG
|