#include "InteractiveObject.h" #include "render/Renderer.h" #include "render/TextureManager.h" #include "../TextModel.h" #include #include #include #include #include #include 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(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( 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( std::chrono::duration_cast( 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