From f4e10bccccde74f9e738dfdae8b8d9c4ae2ecc24 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sat, 15 Nov 2025 16:24:52 +0800 Subject: [PATCH] feat(animation): implement animation clip and layer systems - Added AnimationClip class with curve and keyframe management - Implemented AnimationCurve with multiple interpolation types - Created AnimationEventMarker for timeline events - Added AnimationLayer with track and clip playback support - Implemented blending modes for animation mixing - Added event system with listeners and triggers - Included utility functions for time/frame conversion - Added copy and merge functionality for clips - Implemented loop and playback control mechanisms - Added metadata support including author and description - Included UUID generation for unique identification - Added frame rate and duration management - Implemented parameter override system - Added physics system integration support --- .idea/Vivid2DRenderer.iml | 9 + Vivid2DRenderer/.idea/Vivid2DRenderer.iml | 9 + Vivid2DRenderer/model/AnimationParameter.cpp | 341 ++++++++++ Vivid2DRenderer/model/AnimationParameter.h | 302 +++++++++ Vivid2DRenderer/model/FrameInterpolator.cpp | 264 ++++++++ Vivid2DRenderer/model/FrameInterpolator.h | 41 ++ Vivid2DRenderer/model/Mesh2D.cpp | 470 +++++++++++++ Vivid2DRenderer/model/Mesh2D.h | 413 ++++++++++++ Vivid2DRenderer/model/Model2D.cpp | 459 +++++++++++++ Vivid2DRenderer/model/Model2D.h | 527 +++++++++++++++ Vivid2DRenderer/model/ModelPart.cpp | 312 +++++++++ Vivid2DRenderer/model/ModelPart.h | 337 ++++++++++ Vivid2DRenderer/model/util/AnimationClip.cpp | 550 +++++++++++++++ Vivid2DRenderer/model/util/AnimationClip.h | 186 +++++ Vivid2DRenderer/model/util/AnimationLayer.cpp | 513 ++++++++++++++ Vivid2DRenderer/model/util/AnimationLayer.h | 237 +++++++ Vivid2DRenderer/model/util/BoundingBox.cpp | 358 ++++++++++ Vivid2DRenderer/model/util/BoundingBox.h | 277 ++++++++ Vivid2DRenderer/model/util/LightSource.cpp | 66 ++ Vivid2DRenderer/model/util/LightSource.h | 63 ++ Vivid2DRenderer/model/util/ModelPose.cpp | 334 +++++++++ Vivid2DRenderer/model/util/ModelPose.h | 193 ++++++ Vivid2DRenderer/model/util/PhysicsSystem.cpp | 636 ++++++++++++++++++ Vivid2DRenderer/model/util/PhysicsSystem.h | 363 ++++++++++ Vivid2DRenderer/model/util/Vertex.cpp | 128 ++++ Vivid2DRenderer/model/util/Vertex.h | 167 +++++ Vivid2DRenderer/model/util/VertexList.cpp | 216 ++++++ Vivid2DRenderer/model/util/VertexList.h | 172 +++++ .../systems/MultiSelectionBoxRenderer.cpp | 244 +++++++ .../systems/MultiSelectionBoxRenderer.h | 91 +++ Vivid2DRenderer/systems/Texture.cpp | 608 +++++++++++++++++ Vivid2DRenderer/systems/Texture.h | 250 +++++++ 32 files changed, 9136 insertions(+) create mode 100644 .idea/Vivid2DRenderer.iml create mode 100644 Vivid2DRenderer/.idea/Vivid2DRenderer.iml create mode 100644 Vivid2DRenderer/model/AnimationParameter.cpp create mode 100644 Vivid2DRenderer/model/AnimationParameter.h create mode 100644 Vivid2DRenderer/model/FrameInterpolator.cpp create mode 100644 Vivid2DRenderer/model/FrameInterpolator.h create mode 100644 Vivid2DRenderer/model/Mesh2D.cpp create mode 100644 Vivid2DRenderer/model/Mesh2D.h create mode 100644 Vivid2DRenderer/model/Model2D.cpp create mode 100644 Vivid2DRenderer/model/Model2D.h create mode 100644 Vivid2DRenderer/model/ModelPart.cpp create mode 100644 Vivid2DRenderer/model/ModelPart.h create mode 100644 Vivid2DRenderer/model/util/AnimationClip.cpp create mode 100644 Vivid2DRenderer/model/util/AnimationClip.h create mode 100644 Vivid2DRenderer/model/util/AnimationLayer.cpp create mode 100644 Vivid2DRenderer/model/util/AnimationLayer.h create mode 100644 Vivid2DRenderer/model/util/BoundingBox.cpp create mode 100644 Vivid2DRenderer/model/util/BoundingBox.h create mode 100644 Vivid2DRenderer/model/util/LightSource.cpp create mode 100644 Vivid2DRenderer/model/util/LightSource.h create mode 100644 Vivid2DRenderer/model/util/ModelPose.cpp create mode 100644 Vivid2DRenderer/model/util/ModelPose.h create mode 100644 Vivid2DRenderer/model/util/PhysicsSystem.cpp create mode 100644 Vivid2DRenderer/model/util/PhysicsSystem.h create mode 100644 Vivid2DRenderer/model/util/Vertex.cpp create mode 100644 Vivid2DRenderer/model/util/Vertex.h create mode 100644 Vivid2DRenderer/model/util/VertexList.cpp create mode 100644 Vivid2DRenderer/model/util/VertexList.h create mode 100644 Vivid2DRenderer/systems/MultiSelectionBoxRenderer.cpp create mode 100644 Vivid2DRenderer/systems/MultiSelectionBoxRenderer.h create mode 100644 Vivid2DRenderer/systems/Texture.cpp create mode 100644 Vivid2DRenderer/systems/Texture.h diff --git a/.idea/Vivid2DRenderer.iml b/.idea/Vivid2DRenderer.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Vivid2DRenderer.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Vivid2DRenderer/.idea/Vivid2DRenderer.iml b/Vivid2DRenderer/.idea/Vivid2DRenderer.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/Vivid2DRenderer/.idea/Vivid2DRenderer.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Vivid2DRenderer/model/AnimationParameter.cpp b/Vivid2DRenderer/model/AnimationParameter.cpp new file mode 100644 index 0000000..4df87ba --- /dev/null +++ b/Vivid2DRenderer/model/AnimationParameter.cpp @@ -0,0 +1,341 @@ +#include "pch.h" + +#include "AnimationParameter.h" +#include "Mesh2D.h" +#include "ModelPart.h" + +#include +#include +#include +#include +#include + +namespace Vivid2D { + + // --- AnimationParameter 实现 (与你提供的代码相同) --- + AnimationParameter::AnimationParameter(std::string id, float min, float max, float defaultValue) + : m_Id(std::move(id)), + m_DefaultValue(defaultValue), + m_MinValue(min), + m_MaxValue(max) { + m_Value = defaultValue; + } + + void AnimationParameter::setValue(float value) { + float clamped = std::clamp(value, m_MinValue, m_MaxValue); + if (m_Value != clamped) { + m_Value = clamped; + m_Changed = true; + } + } + + AnimationParameter AnimationParameter::copy() const { + AnimationParameter cpy(m_Id, m_MinValue, m_MaxValue, m_DefaultValue); + cpy.m_Value = m_Value; + cpy.m_Changed = m_Changed; + cpy.m_Keyframes = m_Keyframes; + return cpy; + } + + bool AnimationParameter::hasChanged() const { return m_Changed; } + void AnimationParameter::markClean() { m_Changed = false; } + float AnimationParameter::getValue() const { return m_Value; } + const std::string& AnimationParameter::getId() const { return m_Id; } + float AnimationParameter::getMinValue() const { return m_MinValue; } + float AnimationParameter::getMaxValue() const { return m_MaxValue; } + float AnimationParameter::getDefaultValue() const { return m_DefaultValue; } + void AnimationParameter::reset() { setValue(m_DefaultValue); } + + float AnimationParameter::getNormalizedValue() const { + float range = m_MaxValue - m_MinValue; + if (range == 0.0f) return 0.0f; + return (m_Value - m_MinValue) / range; + } + + void AnimationParameter::setNormalizedValue(float normalized) { + float newValue = m_MinValue + normalized * (m_MaxValue - m_MinValue); + setValue(newValue); + } + + bool AnimationParameter::addKeyframe(float frameValue) { + float clamped = std::clamp(frameValue, m_MinValue, m_MaxValue); + return m_Keyframes.insert(clamped).second; + } + + bool AnimationParameter::removeKeyframe(float frameValue) { + return m_Keyframes.erase(frameValue) > 0; + } + + bool AnimationParameter::isKeyframe(float frameValue) const { + return m_Keyframes.count(frameValue) > 0; + } + + const std::set& AnimationParameter::getKeyframes() const { + return m_Keyframes; + } + + void AnimationParameter::clearKeyframes() { + m_Keyframes.clear(); + } + + std::optional AnimationParameter::getNearestKeyframe(float value, float snapThreshold) const { + if (snapThreshold <= 0 || m_Keyframes.empty()) { + return std::nullopt; + } + auto it_next = m_Keyframes.lower_bound(value); + std::optional prev_key; + float distToPrev = std::numeric_limits::max(); + if (it_next != m_Keyframes.begin()) { + auto it_prev = std::prev(it_next); + prev_key = *it_prev; + distToPrev = std::abs(*it_prev - value); + } + std::optional next_key; + float distToNext = std::numeric_limits::max(); + if (it_next != m_Keyframes.end()) { + next_key = *it_next; + distToNext = std::abs(*it_next - value); + } + if (distToPrev < snapThreshold && distToPrev <= distToNext) { + return prev_key; + } + if (distToNext < snapThreshold && distToNext < distToPrev) { + return next_key; + } + return std::nullopt; + } + + bool AnimationParameter::operator==(const AnimationParameter& other) const { + return m_Id == other.m_Id && + m_DefaultValue == other.m_DefaultValue && + m_MinValue == other.m_MinValue && + m_MaxValue == other.m_MaxValue && + m_Keyframes == other.m_Keyframes; + } + + std::string AnimationParameter::toString() const { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "AnimationParameter[ID=" << m_Id + << ", Value=" << m_Value + << (m_Changed ? " (Changed)" : "") + << ", Range=[" << m_MinValue << ", " << m_MaxValue << "]" + << ", Default=" << m_DefaultValue + << ", Keyframes=["; + for (auto it = m_Keyframes.begin(); it != m_Keyframes.end(); ++it) { + ss << (it == m_Keyframes.begin() ? "" : ", ") << *it; + } + ss << "]]"; + return ss.str(); + } + + // --- 新增:ParametersManagement 和相关实现 --- + + void ParametersManagement::broadcast(ModelPart* modelPart, const AnimationParameter& currentAnimParam, float currentKeyframe, const std::string& paramId, const ParameterValue& value) { + if (!modelPart) return; + + bool isKeyframe = currentAnimParam.isKeyframe(currentKeyframe); + + // 查找或创建 modelPart 对应的记录 + auto& record = m_records[modelPart]; + record.modelPart = modelPart; + + // 检查是否存在完全匹配的记录 (keyframe, paramId, animParam, and special id for mesh) + long existingIndex = -1; + for (size_t i = 0; i < record.keyframes.size(); ++i) { + bool keyframeMatches = record.keyframes[i] == currentKeyframe; + bool paramIdMatches = record.paramIds[i] == paramId; + bool animParamMatches = record.animationParameters[i] == currentAnimParam; + bool idMatches = true; + + // 对 meshVertices 进行特殊处理 + if (paramIdMatches && paramId == "meshVertices") { + const auto* currentMap = std::get_if>(&value); + const auto* recordMap = std::get_if>(&record.values[i]); + if (currentMap && recordMap) { + auto currentIdIt = currentMap->find("id"); + auto recordIdIt = recordMap->find("id"); + if (currentIdIt != currentMap->end() && recordIdIt != recordMap->end()) { + const std::string* currentId = std::any_cast(¤tIdIt->second); + const std::string* recordId = std::any_cast(&recordIdIt->second); + idMatches = (currentId && recordId && *currentId == *recordId); + } + else { + idMatches = false; // 如果任一map中没有id,则认为不匹配 + } + } + else { + idMatches = false; + } + } + + if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) { + existingIndex = i; + break; + } + } + + if (existingIndex != -1) { + // 更新现有值 + record.values[existingIndex] = value; + record.isKeyframes[existingIndex] = isKeyframe; // 状态也可能变化 + } + else { + // 添加新记录 + record.animationParameters.push_back(currentAnimParam); + record.paramIds.push_back(paramId); + record.values.push_back(value); + record.keyframes.push_back(currentKeyframe); + record.isKeyframes.push_back(isKeyframe); + } + } + + void ParametersManagement::removeParameterAt(ModelPart* targetModelPart, size_t indexToRemove) { + auto it = m_records.find(targetModelPart); + if (it != m_records.end()) { + ParameterRecord& record = it->second; + if (indexToRemove < record.paramIds.size()) { + record.animationParameters.erase(record.animationParameters.begin() + indexToRemove); + record.paramIds.erase(record.paramIds.begin() + indexToRemove); + record.values.erase(record.values.begin() + indexToRemove); + record.keyframes.erase(record.keyframes.begin() + indexToRemove); + record.isKeyframes.erase(record.isKeyframes.begin() + indexToRemove); + } + // 如果记录变空,则从 map 中移除 + if (record.paramIds.empty()) { + m_records.erase(it); + } + } + } + + void ParametersManagement::removeParameter(ModelPart* modelPart, const std::string& paramId) { + auto it = m_records.find(modelPart); + if (it == m_records.end()) return; + + if (paramId == "all") { + m_records.erase(it); + return; + } + + ParameterRecord& record = it->second; + // 从后往前遍历以安全删除 + for (int i = record.paramIds.size() - 1; i >= 0; --i) { + if (record.paramIds[i] == paramId) { + record.animationParameters.erase(record.animationParameters.begin() + i); + record.paramIds.erase(record.paramIds.begin() + i); + record.values.erase(record.values.begin() + i); + record.keyframes.erase(record.keyframes.begin() + i); + record.isKeyframes.erase(record.isKeyframes.begin() + i); + } + } + + if (record.paramIds.empty()) { + m_records.erase(it); + } + } + + std::optional ParametersManagement::getValue(ModelPart* modelPart, const std::string& paramId) const { + auto it = m_records.find(modelPart); + if (it == m_records.end()) { + return std::nullopt; + } + + const ParameterRecord& sourceRecord = it->second; + ParameterRecord resultRecord; + resultRecord.modelPart = modelPart; + + for (size_t i = 0; i < sourceRecord.paramIds.size(); ++i) { + if (sourceRecord.paramIds[i] == paramId) { + resultRecord.animationParameters.push_back(sourceRecord.animationParameters[i]); + resultRecord.paramIds.push_back(sourceRecord.paramIds[i]); + resultRecord.values.push_back(sourceRecord.values[i]); + resultRecord.keyframes.push_back(sourceRecord.keyframes[i]); + resultRecord.isKeyframes.push_back(sourceRecord.isKeyframes[i]); + } + } + + if (resultRecord.paramIds.empty()) { + return std::nullopt; + } + + return resultRecord; + } + + const ParameterRecord* ParametersManagement::getModelPartParameters(ModelPart* modelPart) const { + auto it = m_records.find(modelPart); + if (it != m_records.end()) { + return &it->second; + } + return nullptr; + } + + const std::map& ParametersManagement::getAllRecords() const { + return m_records; + } + + // --- toString 实现 --- + + // 辅助函数,用于将 ParameterValue 转换为字符串 + std::string parameterValueToString(const ParameterValue& value) { + std::stringstream ss; + std::visit([&ss](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + ss << "[Null]"; + } + else if constexpr (std::is_same_v) { + ss << arg; + } + else if constexpr (std::is_same_v>) { + ss << "(" << arg.first << ", " << arg.second << ")"; + } + else if constexpr (std::is_same_v>) { + ss << "{"; + for (auto it = arg.begin(); it != arg.end(); ++it) { + ss << (it == arg.begin() ? "" : ", ") << it->first << ":"; + if (it->second.type() == typeid(std::string)) { + ss << "\"" << std::any_cast(it->second) << "\""; + } + else { + ss << "[... complex data ...]"; + } + } + ss << "}"; + } + }, value); + return ss.str(); + } + + std::string ParameterRecord::toString() const { + std::stringstream ss; + std::string partName = (modelPart != nullptr) ? modelPart->getName() : "[NULL ModelPart]"; + ss << "ParameterRecord[Part=" << partName << ", Details=["; + for (size_t i = 0; i < paramIds.size(); ++i) { + if (i > 0) ss << "; "; + ss << "{ID=" << paramIds[i] + << ", V=" << parameterValueToString(values[i]) + << ", KF=" << keyframes[i] + << ", IsKF=" << (isKeyframes[i] ? "true" : "false") + << "}"; + } + ss << "]]"; + return ss.str(); + } + + std::string ParametersManagement::toString() const { + std::stringstream ss; + ss << "ParametersManagement State:\n"; + if (m_records.empty()) { + ss << " No recorded parameters.\n"; + } + else { + int recordIndex = 0; + for (const auto& pair : m_records) { + ss << " --- Record " << recordIndex++ << " ---\n"; + ss << " " << pair.second.toString() << "\n"; + } + } + return ss.str(); + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/AnimationParameter.h b/Vivid2DRenderer/model/AnimationParameter.h new file mode 100644 index 0000000..943bff3 --- /dev/null +++ b/Vivid2DRenderer/model/AnimationParameter.h @@ -0,0 +1,302 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include // For toString implementations + +namespace Vivid2D { + + // 向前声明 ModelPart 类,避免循环引用 + class ModelPart; + + /** + * @class AnimationParameter + * @brief 表示一个可动画的参数,封装了其当前值、范围、默认值以及关联的关键帧信息。 + * @details + * 该类用于驱动模型部件的各种属性(如位置、旋转、网格变形等)的动画。 + * 它跟踪参数值的变化,并提供了一套管理关键帧的接口。 + */ + class VIVID_2D_MYDLL_API AnimationParameter { + public: + /** + * @brief 默认构造函数。 + */ + AnimationParameter() + : m_Id(""), m_Value(0.0f), m_DefaultValue(0.0f), + m_MinValue(0.0f), m_MaxValue(0.0f), m_Changed(false) {} + + /** + * @brief 构造一个具有完整定义的动画参数。 + * @param id 参数的唯一标识符。 + * @param min 参数允许的最小值。 + * @param max 参数允许的最大值。 + * @param defaultValue 参数的默认值。 + */ + AnimationParameter(std::string id, float min, float max, float defaultValue); + + /** + * @brief 设置参数的当前值,并将其限制在[min, max]范围内。 + * @details 如果新值与旧值不同,则将参数标记为已更改(dirty)。 + * @param value 要设置的新值。 + */ + void setValue(float value); + + /** + * @brief 创建并返回当前参数对象的一个副本。 + * @return 当前对象的深拷贝。 + */ + AnimationParameter copy() const; + + /** + * @brief 检查自上次调用 markClean() 以来,参数值是否已更改。 + * @return 如果值已更改,则返回 true,否则返回 false。 + */ + bool hasChanged() const; + + /** + * @brief 将参数标记为未更改(clean)。 + */ + void markClean(); + + /** + * @brief 获取参数的当前值。 + * @return 当前的浮点数值。 + */ + float getValue() const; + + /** + * @brief 获取参数的唯一标识符。 + * @return 参数的 ID 字符串。 + */ + const std::string& getId() const; + + /** + * @brief 获取参数的最小值。 + * @return 允许的最小值。 + */ + float getMinValue() const; + + /** + * @brief 获取参数的最大值。 + * @return 允许的最大值。 + */ + float getMaxValue() const; + + /** + * @brief 获取参数的默认值。 + * @return 默认值。 + */ + float getDefaultValue() const; + + /** + * @brief 将参数的当前值重置为其默认值。 + */ + void reset(); + + /** + * @brief 获取归一化后的参数值,范围在 [0.0, 1.0] 之间。 + * @return 归一化后的值。 + */ + float getNormalizedValue() const; + + /** + * @brief 根据一个归一化的值(0.0 到 1.0)来设置参数的实际值。 + * @param normalized 介于 0.0 和 1.0 之间的归一化值。 + */ + void setNormalizedValue(float normalized); + + // --- 关键帧管理 --- + /** + * @brief 在参数的时间轴上添加一个关键帧。 + * @param frameValue 要添加为关键帧的值。 + * @return 如果成功添加(即该值之前不是关键帧),则返回 true。 + */ + bool addKeyframe(float frameValue); + + /** + * @brief 从参数的时间轴上移除一个关键帧。 + * @param frameValue 要移除的关键帧的值。 + * @return 如果成功移除(即该值之前是关键帧),则返回 true。 + */ + bool removeKeyframe(float frameValue); + + /** + * @brief 检查给定的值是否是一个关键帧。 + * @param frameValue 要检查的值。 + * @return 如果是关键帧,则返回 true。 + */ + bool isKeyframe(float frameValue) const; + + /** + * @brief 获取所有关键帧的集合。 + * @return 包含所有关键帧值的常量引用集合。 + */ + const std::set& getKeyframes() const; + + /** + * @brief 清除所有已设置的关键帧。 + */ + void clearKeyframes(); + + /** + * @brief 查找距离给定值最近的关键帧。 + * @param value 要在其附近查找的值。 + * @param snapThreshold 查找范围的阈值。 + * @return 如果在阈值范围内找到关键帧,则返回该关键帧的值;否则返回 std::nullopt。 + */ + std::optional getNearestKeyframe(float value, float snapThreshold) const; + + // --- C++ 特性 --- + /** + * @brief 比较两个 AnimationParameter 对象是否相等。 + * @param other 要比较的另一个对象。 + * @return 如果所有成员(除 m_Changed 标志外)都相等,则返回 true。 + */ + bool operator==(const AnimationParameter& other) const; + + /** + * @brief 将参数的状态转换为字符串表示形式,便于调试。 + * @return 描述参数状态的字符串。 + */ + std::string toString() const; + + private: + //! 参数的唯一标识符 + std::string m_Id; + //! 参数的当前值 + float m_Value; + //! 参数的默认值 + float m_DefaultValue; + //! 参数的最小值 + float m_MinValue; + //! 参数的最大值 + float m_MaxValue; + //! 标记参数值是否已更改 + bool m_Changed = false; + //! 存储此参数所有关键帧值的集合 + std::set m_Keyframes; + }; + + + /** + * @using ParameterValue + * @brief 定义一个可以存储不同类型参数值的变体类型。 + * @details + * 这是一个灵活的数据容器,可以持有不同类型的动画数据: + * - `std::monostate`: 表示空或无效值。 + * - `float`: 用于简单的单值参数,如旋转、不透明度。 + * - `std::pair`: 用于二维向量参数,如位置、缩放。 + * - `std::map`: 用于复杂的结构化数据,如网格顶点变形。 + */ + using ParameterValue = std::variant< + std::monostate, + float, + std::pair, + std::map + >; + + /** + * @struct ParameterRecord + * @brief 存储单个 ModelPart 在动画时间轴上的所有参数数据记录。 + * @details + * 这个结构体聚合了与一个模型部件相关的所有动画信息, + * 包括它所关联的部件、控制参数、关键帧位置以及在这些关键帧上的具体参数值。 + */ + struct VIVID_2D_MYDLL_API ParameterRecord { + //! 指向此记录所属的 ModelPart + ModelPart* modelPart = nullptr; + //! 描述动画状态的参数列表(例如,时间轴本身) + std::vector animationParameters; + //! 与每个`values`条目对应的参数ID列表 (e.g., "rotate", "translateX") + std::vector paramIds; + //! 在每个关键帧上,每个参数的具体值 + std::vector values; + //! 所有关键帧在时间轴上的位置 + std::vector keyframes; + //! 标记每个时间点是否为关键帧 + std::vector isKeyframes; + + /** + * @brief 将记录内容转换为字符串,用于调试。 + * @return 描述记录内容的字符串。 + */ + std::string toString() const; + }; + + /** + * @class ParametersManagement + * @brief 管理场景中所有 ModelPart 的动画参数记录。 + * @details + * 这是一个中央管理器,负责存储、更新和查询每个模型部件在动画时间轴上的所有关键帧数据。 + * 它通过一个映射表将 ModelPart 指针与它们的 ParameterRecord 关联起来,以实现高效访问。 + */ + class VIVID_2D_MYDLL_API ParametersManagement { + public: + ParametersManagement() = default; + + /** + * @brief 更新或创建一个参数记录,通常由UI或动画系统调用以记录一次参数变化。 + * @param modelPart 发生变化的模型部件。 + * @param currentAnimParam UI上当前选中的主控制参数(通常是时间轴)。 + * @param currentKeyframe 当前在时间轴上的位置/帧。 + * @param paramId 被修改的参数的唯一标识符 (例如, "rotate", "meshVertices")。 + * @param value 该参数在当前关键帧上的新值。 + */ + void broadcast(ModelPart* modelPart, const AnimationParameter& currentAnimParam, float currentKeyframe, const std::string& paramId, const ParameterValue& value); + + /** + * @brief 精确地从指定 ModelPart 的记录中,删除特定索引位置的参数条目。 + * @param targetModelPart 目标 ModelPart。 + * @param indexToRemove 要删除的条目在其 `paramIds`, `values` 等向量中的索引。 + */ + void removeParameterAt(ModelPart* targetModelPart, size_t indexToRemove); + + /** + * @brief 从指定 ModelPart 的记录中移除与特定 paramId 相关的所有关键帧数据。 + * @param modelPart 目标 ModelPart。 + * @param paramId 要移除的参数ID。如果提供 "all",则会移除该部件的所有参数记录。 + */ + void removeParameter(ModelPart* modelPart, const std::string& paramId); + + /** + * @brief 获取与给定 ModelPart 和 paramId 匹配的参数记录。 + * @details + * 此函数会筛选出 `modelPart` 的记录中所有 `paramId` 匹配的条目, + * 并返回一个只包含这些匹配项的新 ParameterRecord。 + * @param modelPart 目标 ModelPart。 + * @param paramId 要查询的参数 ID。 + * @return 如果找到任何匹配项,则返回一个包含这些匹配项的 ParameterRecord optional;否则返回 std::nullopt。 + */ + std::optional getValue(ModelPart* modelPart, const std::string& paramId) const; + + /** + * @brief 获取一个 ModelPart 的完整参数记录。 + * @param modelPart 要查询的 ModelPart。 + * @return 如果该部件有记录,则返回指向该记录的常量指针;否则返回 nullptr。 + */ + const ParameterRecord* getModelPartParameters(ModelPart* modelPart) const; + + /** + * @brief 获取所有参数记录的只读引用。 + * @return 一个从 ModelPart 指针到其 ParameterRecord 的常量引用映射。 + */ + const std::map& getAllRecords() const; + + /** + * @brief 将所有记录转换为字符串形式,用于调试。 + * @return 包含所有参数管理信息的字符串。 + */ + std::string toString() const; + + private: + //! 使用 map 存储每个 ModelPart 的参数记录,以实现高效查找。 + //! 键是 ModelPart 的裸指针,值是其完整的动画数据记录。 + std::map m_records; + }; + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/FrameInterpolator.cpp b/Vivid2DRenderer/model/FrameInterpolator.cpp new file mode 100644 index 0000000..fcdc0bc --- /dev/null +++ b/Vivid2DRenderer/model/FrameInterpolator.cpp @@ -0,0 +1,264 @@ +#include "pch.h" + +#include "FrameInterpolator.h" +#include "ModelPart.h" +#include "Mesh2D.h" + +#include "util/Vertex.h" +#include "spdlog/spdlog.h" +#include "glm/glm.hpp" +#include "glm/gtc/constants.hpp" + +#include +#include +#include +#include +#include +#include + +namespace Vivid2D { + namespace { // 使用匿名命名空间来隐藏这些辅助函数,等同于 private static + + // --- 辅助转换方法 --- + + float toFloat(const ParameterValue& pv) { + if (const float* val = std::get_if(&pv)) { + return *val; + } + return 0.0f; + } + + glm::vec2 readVec2(const ParameterValue& pv) { + if (const auto* val = std::get_if>(&pv)) { + return { val->first, val->second }; + } + if (const float* val = std::get_if(&pv)) { + return { *val, *val }; + } + return { 0.0f, 0.0f }; + } + + // --- 角度归一化 --- + + float normalizeAngle(float a) { + while (a <= -glm::pi()) a += 2 * glm::pi(); + while (a > glm::pi()) a -= 2 * glm::pi(); + return a; + } + + float normalizeAnimAngleUnits(float a) { + if (std::abs(a) > glm::pi() * 2.1f) { + return glm::radians(a); + } + return a; + } + + // --- 查找索引 --- + + std::vector findIndicesForParam(const ParameterRecord& fullParam, const std::string& paramId, const AnimationParameter& animParam) { + std::vector indices; + for (size_t i = 0; i < fullParam.paramIds.size(); ++i) { + if (fullParam.paramIds[i] == paramId && fullParam.animationParameters[i] == animParam) { + indices.push_back(i); + } + } + return indices; + } + + std::vector findIndicesForDeformationVertex(const ParameterRecord& fullParam, const std::string& vertexId, const AnimationParameter& animParam) { + std::vector indices; + for (size_t i = 0; i < fullParam.paramIds.size(); ++i) { + if (fullParam.paramIds[i] == "meshVertices" && fullParam.animationParameters[i] == animParam) { + if (const auto* mapVal = std::get_if>(&fullParam.values[i])) { + auto it = mapVal->find("id"); + if (it != mapVal->end()) { + if (const auto* id = std::any_cast(&it->second)) { + if (*id == vertexId) { + indices.push_back(i); + } + } + } + } + } + } + return indices; + } + + // --- 查找关键帧并计算插值系数 T --- + + std::pair findSurroundingKeyframes(const ParameterRecord& param, const std::vector& indices, float current) { + ptrdiff_t prevIndex = -1, nextIndex = -1; + float prevVal = -std::numeric_limits::infinity(); + float nextVal = std::numeric_limits::infinity(); + + for (size_t idx : indices) { + float val = param.keyframes[idx]; + if (val <= current && val >= prevVal) { + prevIndex = idx; + prevVal = val; + } + if (val >= current && val <= nextVal) { + nextIndex = idx; + nextVal = val; + } + } + return { prevIndex, nextIndex }; + } + + float computeT(float prevVal, float nextVal, float current) { + if (std::abs(nextVal - prevVal) < 1e-6) return 0.0f; + return std::clamp((current - prevVal) / (nextVal - prevVal), 0.0f, 1.0f); + } + + // --- 计算目标值 --- + + std::optional computeVec2Target(const ParameterRecord& fullParam, const std::string& paramId, float current, const AnimationParameter& animParam) { + auto idxs = findIndicesForParam(fullParam, paramId, animParam); + if (idxs.empty()) return std::nullopt; + + auto [prevIndex, nextIndex] = findSurroundingKeyframes(fullParam, idxs, current); + + if (prevIndex != -1 && nextIndex != -1) { + if (prevIndex == nextIndex) return readVec2(fullParam.values[prevIndex]); + + glm::vec2 prev = readVec2(fullParam.values[prevIndex]); + glm::vec2 next = readVec2(fullParam.values[nextIndex]); + float t = computeT(fullParam.keyframes[prevIndex], fullParam.keyframes[nextIndex], current); + return glm::mix(prev, next, t); + } + if (prevIndex != -1) return readVec2(fullParam.values[prevIndex]); + if (nextIndex != -1) return readVec2(fullParam.values[nextIndex]); + + return std::nullopt; + } + + std::optional computeRotationTarget(const ParameterRecord& fullParam, const std::string& paramId, float current, const AnimationParameter& animParam) { + auto idxs = findIndicesForParam(fullParam, paramId, animParam); + if (idxs.empty()) return std::nullopt; + + auto [prevIndex, nextIndex] = findSurroundingKeyframes(fullParam, idxs, current); + + if (prevIndex != -1 && nextIndex != -1) { + float p = normalizeAnimAngleUnits(toFloat(fullParam.values[prevIndex])); + if (prevIndex == nextIndex) return p; + + float q = normalizeAnimAngleUnits(toFloat(fullParam.values[nextIndex])); + float t = computeT(fullParam.keyframes[prevIndex], fullParam.keyframes[nextIndex], current); + return p + t * normalizeAngle(q - p); + } + if (prevIndex != -1) return normalizeAnimAngleUnits(toFloat(fullParam.values[prevIndex])); + if (nextIndex != -1) return normalizeAnimAngleUnits(toFloat(fullParam.values[nextIndex])); + + return std::nullopt; + } + + std::map computeMeshVerticesTarget(const ParameterRecord& fullParam, float current, const AnimationParameter& animParam) { + std::map targetDeformations; + std::set uniqueVertexIds; + + // 1. 收集所有唯一的顶点ID + for (size_t i = 0; i < fullParam.paramIds.size(); ++i) { + if (fullParam.paramIds[i] == "meshVertices" && fullParam.animationParameters[i] == animParam) { + if (const auto* mapVal = std::get_if>(&fullParam.values[i])) { + if (auto it = mapVal->find("id"); it != mapVal->end()) { + if (const auto* id = std::any_cast(&it->second)) { + uniqueVertexIds.insert(*id); + } + } + } + } + } + if (uniqueVertexIds.empty()) return targetDeformations; + + // 2. 为每个顶点ID计算插值后的位置 + for (const auto& vertexId : uniqueVertexIds) { + auto idxs = findIndicesForDeformationVertex(fullParam, vertexId, animParam); + if (idxs.empty()) continue; + + auto [prevIndex, nextIndex] = findSurroundingKeyframes(fullParam, idxs, current); + std::optional finalPos; + + auto getVertexPos = [&](ptrdiff_t index) -> glm::vec2 { + const auto& mapVal = std::get>(fullParam.values[index]); + const auto& vertexVal = mapVal.at("Vertex"); // .at() will throw if not found + return std::any_cast(vertexVal); + }; + + if (prevIndex != -1 && nextIndex != -1) { + glm::vec2 prevPos = getVertexPos(prevIndex); + if (prevIndex == nextIndex) { + finalPos = prevPos; + } + else { + glm::vec2 nextPos = getVertexPos(nextIndex); + float t = computeT(fullParam.keyframes[prevIndex], fullParam.keyframes[nextIndex], current); + finalPos = glm::mix(prevPos, nextPos, t); + } + } + else if (prevIndex != -1) { + finalPos = getVertexPos(prevIndex); + } + else if (nextIndex != -1) { + finalPos = getVertexPos(nextIndex); + } + + if (finalPos) { + targetDeformations[vertexId] = *finalPos; + } + } + return targetDeformations; + } + } // 匿名 namespace 结束 + + void FrameInterpolator::applyFrameInterpolations( + const ParametersManagement& pm, + const std::vector& parts, + const AnimationParameter& currentAnimationParameter, + std::shared_ptr logger) + { + if (parts.empty()) return; + + float current = currentAnimationParameter.getValue(); + + for (ModelPart* part : parts) { + if (!part) continue; + + const ParameterRecord* fullParam = pm.getModelPartParameters(part); + if (!fullParam) continue; + + try { + auto targetPivot = computeVec2Target(*fullParam, "pivot", current, currentAnimationParameter); + auto targetScale = computeVec2Target(*fullParam, "scale", current, currentAnimationParameter); + auto targetPosition = computeVec2Target(*fullParam, "position", current, currentAnimationParameter); + auto targetRotation = computeRotationTarget(*fullParam, "rotate", current, currentAnimationParameter); + + if (targetPivot) part->setPivot(*targetPivot); + if (targetScale) part->setScale(*targetScale); + if (targetPosition) part->setPosition(*targetPosition); + if (targetRotation) part->setRotation(*targetRotation); + + auto targetDeformations = computeMeshVerticesTarget(*fullParam, current, currentAnimationParameter); + + if (targetDeformations.empty() || part->getMeshes().empty()) { + part->updateMeshVertices(); + } + else { + if (Mesh2D* targetMesh = part->getMeshes().front().get()) { + for (auto& [vertexId, worldPos] : targetDeformations) { + Vertex* vertex = targetMesh->findVertexByName(vertexId); + if (vertex) { + vertex->position = worldPos; + } + } + targetMesh->saveAsOriginal(); + part->updateMeshVertices(); + } + } + } + catch (const std::exception& e) { + if (logger) logger->error("Exception while applying interpolation for part '{}': {}", part->getName(), e.what()); + } + } + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/FrameInterpolator.h b/Vivid2DRenderer/model/FrameInterpolator.h new file mode 100644 index 0000000..c9919ad --- /dev/null +++ b/Vivid2DRenderer/model/FrameInterpolator.h @@ -0,0 +1,41 @@ +#pragma once + +#include "AnimationParameter.h" +#include +#include + +// 向前声明以减少头文件依赖 +namespace spdlog { + class logger; +} + +namespace Vivid2D { + + class ModelPart; + + /** + * @class FrameInterpolator + * @brief 一个静态工具类,用于根据关键帧数据计算和应用插值变换。 + * 对应于 Java 中的 FrameInterpolator 类。 + */ + class VIVID_2D_MYDLL_API FrameInterpolator { + public: + // 删除构造函数,这是一个纯静态类 + FrameInterpolator() = delete; + + /** + * @brief 将变换操作按当前关键帧插值并应用到 ModelPart 列表。 + * @param pm 参数管理器实例。 + * @param parts 要更新的 ModelPart 列表。 + * @param currentAnimationParameter 当前活动的动画参数(包含了时间轴上的当前值)。 + * @param logger 用于记录错误的 spdlog 日志记录器。 + */ + static void applyFrameInterpolations( + const ParametersManagement& pm, + const std::vector& parts, + const AnimationParameter& currentAnimationParameter, + std::shared_ptr logger + ); + }; + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/Mesh2D.cpp b/Vivid2DRenderer/model/Mesh2D.cpp new file mode 100644 index 0000000..473a06e --- /dev/null +++ b/Vivid2DRenderer/model/Mesh2D.cpp @@ -0,0 +1,470 @@ +#include "pch.h" + +#include "Mesh2D.h" +#include +#include +#include +#include + +namespace Vivid2D { + + namespace { + glm::vec2 barycentric(const glm::vec2& p, const glm::vec2& a, const glm::vec2& b, const glm::vec2& c) { + glm::vec2 v0 = b - a, v1 = c - a, v2 = p - a; + float d00 = glm::dot(v0, v0); + float d01 = glm::dot(v0, v1); + float d11 = glm::dot(v1, v1); + float d20 = glm::dot(v2, v0); + float d21 = glm::dot(v2, v1); + float denom = d00 * d11 - d01 * d01; + if (std::abs(denom) < 1e-9f) return glm::vec2(-1.0f); // 退化三角形 + float w = (d11 * d20 - d01 * d21) / denom; + float v = (d00 * d21 - d01 * d20) / denom; + return glm::vec2(w, v); + } + + bool isPointInTriangle(const glm::vec2& p, const glm::vec2& a, const glm::vec2& b, const glm::vec2& c) { + glm::vec2 coords = barycentric(p, a, b, c); + const float epsilon = 1e-6f; + return coords.x >= -epsilon && coords.y >= -epsilon && (coords.x + coords.y) <= 1.0f + epsilon; + } + struct TriangleInfo { + int i1, i2, i3; + Vertex* v1, * v2, * v3; + }; + } + + // ==================== 构造/析构/拷贝/移动 ==================== + + Mesh2D::Mesh2D(std::string name) + : m_Name(std::move(name)), m_ActiveVertexList(std::make_unique("DefaultList")) {} + + Mesh2D::Mesh2D(std::string name, const std::vector& vertices, const std::vector& uvs, const std::vector& indices) + : Mesh2D(std::move(name)) { + setMeshData(vertices, uvs, indices); + } + + Mesh2D::~Mesh2D() { + deleteGPU(); + } + + void Mesh2D::copyFrom(const Mesh2D& other) { + m_Name = other.m_Name; + m_ActiveVertexList = std::make_unique(*other.m_ActiveVertexList); + m_ModelPart = other.m_ModelPart; + m_Texture = other.m_Texture; + m_IsVisible = other.m_IsVisible; + m_DrawMode = other.m_DrawMode; + m_Pivot = other.m_Pivot; + m_OriginalPivot = other.m_OriginalPivot; + m_IsDirty = true; + m_IsUploaded = false; + m_BoundsDirty = true; + m_VaoID = m_VboID = m_EboID = 0; + m_IndexCount = 0; + m_IsSelected = other.m_IsSelected.load(); + m_DeformationControlVertices.clear(); + } + + Mesh2D::Mesh2D(const Mesh2D& other) { copyFrom(other); } + + Mesh2D& Mesh2D::operator=(const Mesh2D& other) { + if (this != &other) { + deleteGPU(); + copyFrom(other); + } + return *this; + } + + Mesh2D::Mesh2D(Mesh2D&& other) noexcept + : m_Name(std::move(other.m_Name)), m_ActiveVertexList(std::move(other.m_ActiveVertexList)), + m_ModelPart(other.m_ModelPart), m_Texture(std::move(other.m_Texture)), + m_IsVisible(other.m_IsVisible), m_DrawMode(other.m_DrawMode), m_VaoID(other.m_VaoID), + m_VboID(other.m_VboID), m_EboID(other.m_EboID), m_IndexCount(other.m_IndexCount), + m_IsUploaded(other.m_IsUploaded), m_IsDirty(other.m_IsDirty), m_Bounds(std::move(other.m_Bounds)), + m_BoundsDirty(other.m_BoundsDirty), m_IsSelected(other.m_IsSelected.load()), m_Pivot(other.m_Pivot), + m_OriginalPivot(other.m_OriginalPivot), m_DeformationControlVertices(std::move(other.m_DeformationControlVertices)) { + other.m_VaoID = other.m_VboID = other.m_EboID = 0; + other.m_IsUploaded = false; + } + + Mesh2D& Mesh2D::operator=(Mesh2D&& other) noexcept { + if (this != &other) { + deleteGPU(); + m_Name = std::move(other.m_Name); m_ActiveVertexList = std::move(other.m_ActiveVertexList); + m_ModelPart = other.m_ModelPart; m_Texture = std::move(other.m_Texture); + m_IsVisible = other.m_IsVisible; m_DrawMode = other.m_DrawMode; + m_VaoID = other.m_VaoID; m_VboID = other.m_VboID; m_EboID = other.m_EboID; + m_IndexCount = other.m_IndexCount; m_IsUploaded = other.m_IsUploaded; + m_IsDirty = other.m_IsDirty; m_Bounds = std::move(other.m_Bounds); + m_BoundsDirty = other.m_BoundsDirty; m_IsSelected = other.m_IsSelected.load(); + m_Pivot = other.m_Pivot; m_OriginalPivot = other.m_OriginalPivot; + m_DeformationControlVertices = std::move(other.m_DeformationControlVertices); + other.m_VaoID = other.m_VboID = other.m_EboID = 0; other.m_IsUploaded = false; + } + return *this; + } + + // ==================== 变形引擎 ==================== + Vertex* Mesh2D::addControlPointAt(float x, float y) { + if (m_ActiveVertexList->getIndices().empty() || !m_ModelPart) return nullptr; + glm::vec2 worldPoint(x, y); + std::optional containingTriangle; + const auto& indices = m_ActiveVertexList->getIndices(); + for (size_t i = 0; i < indices.size(); i += 3) { + const auto& indices = m_ActiveVertexList->getIndices(); + for (size_t i = 0; i < indices.size(); i += 3) { + int i1 = indices[i], i2 = indices[i + 1], i3 = indices[i + 2]; + Vertex* v1 = &m_ActiveVertexList->vertices[i1]; + Vertex* v2 = &m_ActiveVertexList->vertices[i2]; + Vertex* v3 = &m_ActiveVertexList->vertices[i3]; + if (isPointInTriangle(worldPoint, v1->position, v2->position, v3->position)) { + containingTriangle = TriangleInfo{ i1, i2, i3, v1, v2, v3 }; + break; + } + } + } + if (!containingTriangle) return nullptr; + glm::vec2 baryCoords = barycentric(worldPoint, containingTriangle->v1->position, containingTriangle->v2->position, containingTriangle->v3->position); + float w = baryCoords.x, v = baryCoords.y, u = 1.0f - w - v; + glm::vec2 newUv = containingTriangle->v1->uv * u + containingTriangle->v2->uv * w + containingTriangle->v3->uv * v; + glm::vec2 newOriginalPos = containingTriangle->v1->originalPosition * u + containingTriangle->v2->originalPosition * w + containingTriangle->v3->originalPosition * v; + Vertex newVertex(worldPoint, newUv, util::VertexTag::DEFORMATION); + newVertex.originalPosition = newOriginalPos; + addVertex(newVertex); + const int newVertexIndex = getVertexCount() - 1; + std::vector newIndices; newIndices.reserve(indices.size() + 6); + for (size_t i = 0; i < indices.size(); i += 3) { + if (indices[i] == containingTriangle->i1 && indices[i + 1] == containingTriangle->i2 && indices[i + 2] == containingTriangle->i3) { + newIndices.insert(newIndices.end(), { containingTriangle->i1, containingTriangle->i2, newVertexIndex }); + newIndices.insert(newIndices.end(), { containingTriangle->i2, containingTriangle->i3, newVertexIndex }); + newIndices.insert(newIndices.end(), { containingTriangle->i3, containingTriangle->i1, newVertexIndex }); + } + else { + newIndices.insert(newIndices.end(), { indices[i], indices[i + 1], indices[i + 2] }); + } + } + m_ActiveVertexList->setIndices(newIndices); + Vertex* newVertexPtr = getVertex(newVertexIndex); + m_DeformationControlVertices.push_back(newVertexPtr); + markDirty(); + return newVertexPtr; + } + + void Mesh2D::setDeformationControlVertices(const std::vector& controlVertices) { m_DeformationControlVertices = controlVertices; } + + void Mesh2D::applyLocalizedPush(const std::vector& baseVertexState, const glm::vec2& pushCenterWorld, const glm::vec2& delta, float radius) { + if (!m_ActiveVertexList || m_ActiveVertexList->isEmpty() || baseVertexState.size() != m_ActiveVertexList->size()) return; + float radiusSq = radius * radius; + for (size_t i = 0; i < m_ActiveVertexList->size(); ++i) { + Vertex* currentVertex = &m_ActiveVertexList->vertices[i]; + const Vertex& baseStateVertex = baseVertexState[i]; + float distSq = glm::distance2(currentVertex->originalPosition, pushCenterWorld); // 修正:使用 glm::distance2 + if (distSq < radiusSq) { + float influence = std::exp(-(distSq / radiusSq) * 4.0f); + currentVertex->position = baseStateVertex.position + delta * influence; + } + else { + currentVertex->position = baseStateVertex.position; + } + } + markDirty(); + } + + // ==================== 网格数据设置 ==================== + void Mesh2D::setMeshData(const std::vector& vertices, const std::vector& uvs, const std::vector& indices) { + if (vertices.size() % 2 != 0 || uvs.size() % 2 != 0 || vertices.size() != uvs.size()) + throw std::invalid_argument("Invalid vertex or uv data."); + m_ActiveVertexList->clear(); + for (size_t i = 0; i < vertices.size() / 2; ++i) { + m_ActiveVertexList->add(Vertex(vertices[i * 2], vertices[i * 2 + 1], uvs[i * 2], uvs[i * 2 + 1])); + } + m_ActiveVertexList->setIndices(indices); + m_OriginalPivot = m_Pivot; + markDirty(); + } + + void Mesh2D::setVertices(const std::vector& vertices, bool updateOriginal) { + if (vertices.size() % 2 != 0) throw std::invalid_argument("Vertices array must have even length."); + size_t newVertexCount = vertices.size() / 2; + size_t oldVertexCount = getVertexCount(); + if (newVertexCount != oldVertexCount) { + std::vector newVertexCollection; newVertexCollection.reserve(newVertexCount); + for (size_t i = 0; i < newVertexCount; ++i) { + if (i < oldVertexCount) { + newVertexCollection.emplace_back(vertices[i * 2], vertices[i * 2 + 1], getVertex(i)->uv.x, getVertex(i)->uv.y); + } + else { + newVertexCollection.emplace_back(vertices[i * 2], vertices[i * 2 + 1], 0.5f, 0.5f); + } + } + m_ActiveVertexList->vertices = newVertexCollection; // 修正:直接赋值 + regenerateIndicesForNewVertexCount(newVertexCount); + } + else { + for (size_t i = 0; i < newVertexCount; ++i) { + getVertex(i)->position = { vertices[i * 2], vertices[i * 2 + 1] }; + } + } + markDirty(); + if (updateOriginal) saveAsOriginal(); + } + + void Mesh2D::regenerateIndicesForNewVertexCount(int newVertexCount) { + if (newVertexCount < 3) { m_ActiveVertexList->setIndices({}); return; } + std::vector newIndices; + for (int i = 1; i < newVertexCount - 1; ++i) { newIndices.insert(newIndices.end(), { 0, i, i + 1 }); } + m_ActiveVertexList->setIndices(newIndices); + } + + void Mesh2D::setTexture(std::shared_ptr texture) { m_Texture = std::move(texture); } + void Mesh2D::setModelPart(ModelPart* modelPart) { m_ModelPart = modelPart; } + void Mesh2D::setPivot(const glm::vec2& pivot) { m_Pivot = pivot; markDirty(); } + void Mesh2D::movePivot(float dx, float dy) { m_Pivot += glm::vec2(dx, dy); markDirty(); } + + // ==================== 顶点操作 ==================== + void Mesh2D::addVertex(const Vertex& vertex) { m_ActiveVertexList->add(vertex); markDirty(); } + + void Mesh2D::insertVertex(int index, const Vertex& vertex) { + m_ActiveVertexList->vertices.insert(m_ActiveVertexList->vertices.begin() + index, vertex); + updateIndicesAfterVertexInsertion(index); + markDirty(); + } + + void Mesh2D::removeVertex(int index) { + m_ActiveVertexList->remove(index); // VertexList 应该有这个方法 + updateIndicesAfterVertexRemoval(index); + markDirty(); + } + + void Mesh2D::updateIndicesAfterVertexInsertion(int insertedIndex) { + auto indices = m_ActiveVertexList->getIndices(); + for (int& idx : indices) { + if (idx >= insertedIndex) { + idx++; + } + } + m_ActiveVertexList->setIndices(indices); + } + + void Mesh2D::updateIndicesAfterVertexRemoval(int removedIndex) { + auto indices = m_ActiveVertexList->getIndices(); // 获取副本 + std::vector newIndices; + for (size_t i = 0; i < indices.size(); i += 3) { + int i1 = indices[i], i2 = indices[i + 1], i3 = indices[i + 2]; + if (i1 == removedIndex || i2 == removedIndex || i3 == removedIndex) continue; + if (i1 > removedIndex) i1--; if (i2 > removedIndex) i2--; if (i3 > removedIndex) i3--; + newIndices.insert(newIndices.end(), { i1, i2, i3 }); + } + m_ActiveVertexList->setIndices(newIndices); + } + + void Mesh2D::removeVertexAndRemapIndices(Vertex* vertexToRemove) { + if (!m_ActiveVertexList) return; + int removedIndex = -1; + for (int i = 0; i < getVertexCount(); ++i) { + if (getVertex(i) == vertexToRemove) { removedIndex = i; break; } + } + if (removedIndex != -1) removeVertex(removedIndex); + } + + Vertex* Mesh2D::findVertexByName(const std::string& name) { + if (!m_ActiveVertexList) { + return nullptr; + } + for (Vertex& vertex : m_ActiveVertexList->getVertices()) { + if (vertex.getName() == name) { + return &vertex; + } + } + return nullptr; + } + + const Vertex* Mesh2D::findVertexByName(const std::string& name) const { + if (!m_ActiveVertexList) { + return nullptr; + } + for (const Vertex& vertex : m_ActiveVertexList->getVertices()) { + if (vertex.getName() == name) { + return &vertex; + } + } + return nullptr; + } + + // ==================== 变换与状态 ==================== + void Mesh2D::resetToOriginal() { + if (m_ActiveVertexList) { + for (auto& v : m_ActiveVertexList->vertices) { v.resetToOriginal(); } + markDirty(); + } + } + + void Mesh2D::saveAsOriginal() { + if (!m_ActiveVertexList || !m_ModelPart) return; + glm::mat3 inverseWorld = glm::inverse(m_ModelPart->getWorldTransform()); + for (auto& v : m_ActiveVertexList->vertices) { + glm::vec3 transformed = inverseWorld * glm::vec3(v.position, 1.0f); + v.originalPosition = glm::vec2(transformed); + } + for (Vertex* vPtr : m_DeformationControlVertices) { + glm::vec3 transformed = inverseWorld * glm::vec3(vPtr->position, 1.0f); + vPtr->originalPosition = glm::vec2(transformed); + } + } + + void Mesh2D::transformVertices(const std::function& transformer) { + if (m_ActiveVertexList) { + for (int i = 0; i < getVertexCount(); ++i) { transformer(getVertex(i)->position, i); } + markDirty(); + } + } + + void Mesh2D::markDirty() { m_IsDirty = true; m_BoundsDirty = true; } + bool Mesh2D::isDirty() const { return m_IsDirty; } + + // ==================== 边界框与碰撞检测 ==================== + void Mesh2D::updateBounds() const { + m_Bounds.reset(); + if (m_ActiveVertexList) { + for (const auto& v : m_ActiveVertexList->vertices) { m_Bounds.expand(v.position); } + } + m_BoundsDirty = false; + } + + const util::BoundingBox& Mesh2D::getBounds() const { + if (m_BoundsDirty) updateBounds(); + return m_Bounds; + } + + bool Mesh2D::containsPoint(float x, float y, bool precise) const { + if (!getBounds().contains(x, y)) return false; + if (precise) return isPointInMeshGeometry(x, y); + return true; + } + + bool Mesh2D::containsPoint(const glm::vec2& point, bool precise) const { return containsPoint(point.x, point.y, precise); } + + bool Mesh2D::isPointInMeshGeometry(float x, float y) const { + if (!m_ActiveVertexList || getVertexCount() < 3) return false; + const auto& indices = m_ActiveVertexList->getIndices(); + for (size_t i = 0; i < indices.size(); i += 3) { + const auto& v1 = m_ActiveVertexList->get(indices[i]).position; + const auto& v2 = m_ActiveVertexList->get(indices[i + 1]).position; + const auto& v3 = m_ActiveVertexList->get(indices[i + 2]).position; + if (isPointInTriangle({ x,y }, v1, v2, v3)) return true; + } + return false; + } + + // ==================== 渲染 ==================== + void Mesh2D::uploadToGPU() { + if (m_IsUploaded) return; + RenderSystem::assertOnRenderThreadOrInit(); + int vertCount = getVertexCount(); + if (vertCount == 0 || m_ActiveVertexList->getIndices().empty()) return; + std::vector interleavedData; interleavedData.reserve(vertCount * 4); + for (int i = 0; i < vertCount; ++i) { + const auto* v = getVertex(i); + interleavedData.insert(interleavedData.end(), { v->position.x, v->position.y, v->uv.x, v->uv.y }); + } + m_VaoID = RenderSystem::GenVertexArrays(); RenderSystem::BindVertexArray(m_VaoID); + m_VboID = RenderSystem::GenBuffers(); RenderSystem::BindBuffer(GL_ARRAY_BUFFER, m_VboID); + RenderSystem::BufferData(GL_ARRAY_BUFFER, interleavedData.size() * sizeof(float), interleavedData.data(), GL_DYNAMIC_DRAW); + const auto& indices = m_ActiveVertexList->getIndices(); + m_EboID = RenderSystem::GenBuffers(); RenderSystem::BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EboID); + RenderSystem::BufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(int), indices.data(), GL_DYNAMIC_DRAW); + GLsizei stride = 4 * sizeof(float); + RenderSystem::enableVertexAttribArray(0); RenderSystem::vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0); + RenderSystem::enableVertexAttribArray(1); RenderSystem::vertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (void*)(2 * sizeof(float))); + RenderSystem::BindVertexArray(0); + m_IndexCount = indices.size(); m_IsUploaded = true; m_IsDirty = false; + } + + void Mesh2D::deleteGPU() { + if (!m_IsUploaded) return; + RenderSystem::DeleteBuffers(m_VboID); RenderSystem::DeleteBuffers(m_EboID); RenderSystem::DeleteVertexArrays(m_VaoID); + m_VboID = m_EboID = m_VaoID = 0; m_IsUploaded = false; + } + + void Mesh2D::draw(int shaderProgram, const glm::mat3& modelMatrix) { + if (!m_IsVisible || getVertexCount() == 0) return; + if (m_IsDirty) { deleteGPU(); uploadToGPU(); } + else if (!m_IsUploaded) { uploadToGPU(); } + if (!m_IsUploaded) return; + GLint prevProgram = RenderSystem::getCurrentProgram(); + RenderSystem::useProgram(shaderProgram); + RenderSystem::activeTexture(GL_TEXTURE0); + if (m_Texture) m_Texture->bind(); else RenderSystem::bindTexture(0); + RenderSystem::uniform1i(RenderSystem::getUniformLocation(shaderProgram, "uTexture"), 0); + RenderSystem::uniformMatrix3(RenderSystem::getUniformLocation(shaderProgram, "uModelMatrix"), modelMatrix); + RenderSystem::BindVertexArray(m_VaoID); + RenderSystem::drawElements(GL_TRIANGLES, m_IndexCount, GL_UNSIGNED_INT, nullptr); + RenderSystem::BindVertexArray(0); + if (m_Texture) m_Texture->unbind(); + RenderSystem::useProgram(prevProgram); + if (m_IsSelected) { /* 绘制选中框逻辑 */ } + } + + // ==================== Getters & Setters ==================== + const std::string& Mesh2D::getName() const { return m_Name; } + void Mesh2D::setName(const std::string& name) { m_Name = name; } + std::vector Mesh2D::getVertices() const { + std::vector verts; if (!m_ActiveVertexList) return verts; + verts.reserve(getVertexCount() * 2); + for (const auto& v : m_ActiveVertexList->vertices) { verts.push_back(v.position.x); verts.push_back(v.position.y); } + return verts; + } + std::vector Mesh2D::getUVs() const { + std::vector uvs; if (!m_ActiveVertexList) return uvs; + uvs.reserve(getVertexCount() * 2); + for (const auto& v : m_ActiveVertexList->vertices) { uvs.push_back(v.uv.x); uvs.push_back(v.uv.y); } + return uvs; + } + std::vector Mesh2D::getOriginalVertices() const { + std::vector verts; if (!m_ActiveVertexList) return verts; + verts.reserve(getVertexCount() * 2); + for (const auto& v : m_ActiveVertexList->vertices) { verts.push_back(v.originalPosition.x); verts.push_back(v.originalPosition.y); } + return verts; + } + std::vector Mesh2D::getIndices() const { return m_ActiveVertexList ? m_ActiveVertexList->getIndices() : std::vector{}; } + int Mesh2D::getVertexCount() const { return m_ActiveVertexList ? m_ActiveVertexList->size() : 0; } + Vertex* Mesh2D::getVertex(int index) { return m_ActiveVertexList ? &m_ActiveVertexList->vertices[index] : nullptr; } + const Vertex* Mesh2D::getVertex(int index) const { return m_ActiveVertexList ? &m_ActiveVertexList->vertices[index] : nullptr; } + const glm::vec2& Mesh2D::getPivot() const { return m_Pivot; } + const glm::vec2& Mesh2D::getOriginalPivot() const { return m_OriginalPivot; } + bool Mesh2D::isVisible() const { return m_IsVisible; } + void Mesh2D::setVisible(bool visible) { m_IsVisible = visible; } + Mesh2D::DrawMode Mesh2D::getDrawMode() const { return m_DrawMode; } + void Mesh2D::setDrawMode(DrawMode mode) { m_DrawMode = mode; } + bool Mesh2D::isSelected() const { return m_IsSelected.load(); } + void Mesh2D::setSelected(bool selected) { m_IsSelected.store(selected); } + ModelPart* Mesh2D::getModelPart() const { return m_ModelPart; } + util::VertexList* Mesh2D::getActiveVertexList() const { return m_ActiveVertexList.get(); } + const std::vector& Mesh2D::getDeformationControlVertices() const { return m_DeformationControlVertices; } + + // ==================== 静态工厂方法 ==================== + std::unique_ptr Mesh2D::createQuad(const std::string& name, float width, float height) { + float hw = width / 2.0f, hh = height / 2.0f; + std::vector vertices = { -hw,-hh, hw,-hh, hw,hh, -hw,hh, 0,-hh, hw,0, 0,hh, -hw,0, 0,0 }; + std::vector uvs = { 0,1, 1,1, 1,0, 0,0, 0.5f,1, 1,0.5f, 0.5f,0, 0,0.5f, 0.5f,0.5f }; + std::vector indices = { 8,0,4, 8,4,1, 8,1,5, 8,5,2, 8,2,6, 8,6,3, 8,3,7, 8,7,0 }; + return std::make_unique(name, vertices, uvs, indices); + } + + std::unique_ptr Mesh2D::createCircle(const std::string& name, float radius, int segments) { + if (segments < 3) segments = 3; + std::vector vertices, uvs; std::vector indices; + vertices.insert(vertices.end(), { 0.0f, 0.0f }); uvs.insert(uvs.end(), { 0.5f, 0.5f }); + for (int i = 0; i < segments; ++i) { + float angle = i * 2.0f * 3.14159265f / segments; + vertices.push_back(std::cos(angle) * radius); vertices.push_back(std::sin(angle) * radius); + uvs.push_back(std::cos(angle) * 0.5f + 0.5f); uvs.push_back(std::sin(angle) * 0.5f + 0.5f); + } + for (int i = 1; i <= segments; ++i) { + indices.insert(indices.end(), { 0, i, (i % segments) + 1 }); + } + return std::make_unique(name, vertices, uvs, indices); + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/Mesh2D.h b/Vivid2DRenderer/model/Mesh2D.h new file mode 100644 index 0000000..4957b69 --- /dev/null +++ b/Vivid2DRenderer/model/Mesh2D.h @@ -0,0 +1,413 @@ +#pragma once + +#include "ModelPart.h" +#include "util/BoundingBox.h" +#include "util/Vertex.h" +#include "util/VertexList.h" +#include "../systems/Texture.h" +#include "../systems/RenderSystem.h" +#include "../systems/MultiSelectionBoxRenderer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @namespace Vivid2D + * @brief Vivid2D 引擎的核心命名空间,包含了所有与2D渲染和动画相关的类与功能。 + */ +namespace Vivid2D { + // 类型别名,用于简化代码 + using util::VertexList; + using util::BoundingBox; + using util::Vertex; + using Vivid2D::ModelPart; + + /** + * @class Mesh2D + * @brief 表示一个二维网格,是场景中可渲染对象的基本构建块。 + * + * Mesh2D 类管理着顶点数据、纹理坐标、索引、纹理以及渲染状态。 + * 它支持GPU数据上传、绘制、变换、边界框计算和顶点级的精细操作。 + * 同时,它也为变形和动画提供了基础支持。 + */ + class VIVID_2D_MYDLL_API Mesh2D { + public: + /** + * @enum DrawMode + * @brief 定义了渲染网格时顶点数据的解释方式。 + */ + enum class DrawMode { + POINTS, ///< 将每个顶点渲染成一个点。 + LINES, ///< 每两个顶点构成一条独立的线段。 + LINE_STRIP, ///< 顶点按顺序连接成一条连续的线。 + TRIANGLES, ///< 每三个顶点构成一个独立的三角形。 + TRIANGLE_STRIP,///< 顶点按顺序构成一串相连的三角形。 + TRIANGLE_FAN ///< 顶点以扇形方式构成一串共享中心的三角形。 + }; + + // ==================== 构造/析构/拷贝/移动 ==================== + + /** + * @brief 默认构造函数。 + * @param name 网格的名称,默认为 "unnamed"。 + */ + explicit Mesh2D(std::string name = "unnamed"); + + /** + * @brief 使用顶点、UV和索引数据构造一个新的 Mesh2D 对象。 + * @param name 网格的名称。 + * @param vertices 顶点位置数据 (x, y, z, ...)。 + * @param uvs 纹理坐标数据 (u, v, ...)。 + * @param indices 定义顶点连接关系的索引数据。 + */ + Mesh2D(std::string name, const std::vector& vertices, const std::vector& uvs, const std::vector& indices); + + /** + * @brief 析构函数,负责清理GPU资源。 + */ + ~Mesh2D(); + + /** + * @brief 拷贝构造函数。 + * @param other 要拷贝的另一个 Mesh2D 对象。 + */ + Mesh2D(const Mesh2D& other); + + /** + * @brief 拷贝赋值运算符。 + * @param other 要赋值的另一个 Mesh2D 对象。 + * @return 当前对象的引用。 + */ + Mesh2D& operator=(const Mesh2D& other); + + /** + * @brief 移动构造函数。 + * @param other 用于移动的另一个 Mesh2D 对象。 + */ + Mesh2D(Mesh2D&& other) noexcept; + + /** + * @brief 移动赋值运算符。 + * @param other 用于移动赋值的另一个 Mesh2D 对象。 + * @return 当前对象的引用。 + */ + Mesh2D& operator=(Mesh2D&& other) noexcept; + + // ==================== 变形引擎 ==================== + + /** + * @brief 在指定的世界坐标位置添加一个控制点(顶点)。 + * @param x 新控制点的 x 坐标。 + * @param y 新控制点的 y 坐标。 + * @return 指向新创建的顶点的指针。 + */ + Vertex* addControlPointAt(float x, float y); + + /** + * @brief 设置用于网格变形的控制顶点列表。 + * @param controlVertices 一组指向用作控制点的顶点的指针。 + */ + void setDeformationControlVertices(const std::vector& controlVertices); + + /** + * @brief 应用一个局部的推力效果来变形网格。 + * @param baseVertexState 变形前的顶点状态快照。 + * @param pushCenterWorld 推力中心的世界坐标。 + * @param delta 推力的位移向量。 + * @param radius 推力影响的半径范围。 + */ + void applyLocalizedPush(const std::vector& baseVertexState, const glm::vec2& pushCenterWorld, const glm::vec2& delta, float radius); + + // ==================== 网格数据设置 ==================== + + /** + * @brief 一次性设置网格的所有核心数据。 + * @param vertices 顶点位置数据。 + * @param uvs 纹理坐标数据。 + * @param indices 索引数据。 + */ + void setMeshData(const std::vector& vertices, const std::vector& uvs, const std::vector& indices); + + /** + * @brief 设置网格的顶点数据。 + * @param vertices 新的顶点位置数据。 + * @param updateOriginal 如果为 true,则同时更新原始顶点状态(用于 `resetToOriginal`)。 + */ + void setVertices(const std::vector& vertices, bool updateOriginal = false); + + /** + * @brief 设置网格要使用的纹理。 + * @param texture 指向纹理对象的共享指针。 + */ + void setTexture(std::shared_ptr texture); + + /** + * @brief 将此网格关联到一个 ModelPart。 + * @param modelPart 指向所属 ModelPart 的指针。 + */ + void setModelPart(ModelPart* modelPart); + + /** + * @brief 设置网格的轴心点(变换中心)。 + * @param pivot 新的轴心点坐标。 + */ + void setPivot(const glm::vec2& pivot); + + /** + * @brief 移动网格的轴心点。 + * @param dx 在 x 轴上的移动量。 + * @param dy 在 y 轴上的移动量。 + */ + void movePivot(float dx, float dy); + + // ==================== 顶点操作 ==================== + + /** + * @brief 在顶点列表末尾添加一个新顶点。 + * @param vertex 要添加的顶点。 + */ + void addVertex(const Vertex& vertex); + + /** + * @brief 在指定索引位置插入一个顶点。 + * @param index 插入位置的索引。 + * @param vertex 要插入的顶点。 + */ + void insertVertex(int index, const Vertex& vertex); + + /** + * @brief 移除指定索引位置的顶点。 + * @param index 要移除的顶点的索引。 + */ + void removeVertex(int index); + + /** + * @brief 移除一个指定的顶点并重新映射索引以维持网格的完整性。 + * @param vertexToRemove 指向要移除的顶点的指针。 + */ + void removeVertexAndRemapIndices(Vertex* vertexToRemove); + + /** + * @brief 通过名称查找顶点。 + * @param name 要查找的顶点的名称。 + * @return 如果找到,返回指向该顶点的指针;否则返回 nullptr。 + */ + Vertex* findVertexByName(const std::string& name); + + /** + * @brief 通过名称查找顶点 (const 版本)。 + * @param name 要查找的顶点的名称。 + * @return 如果找到,返回指向该顶点的常量指针;否则返回 nullptr。 + */ + const Vertex* findVertexByName(const std::string& name) const; + + // ==================== 变换与状态 ==================== + + /** + * @brief 将所有顶点的位置重置到其原始保存的状态。 + */ + void resetToOriginal(); + + /** + * @brief 将当前的顶点状态保存为原始状态。 + */ + void saveAsOriginal(); + + /** + * @brief 对所有顶点应用一个变换函数。 + * @param transformer 一个函数,接收顶点引用和索引作为参数,并直接修改顶点位置。 + */ + void transformVertices(const std::function& transformer); + + /** + * @brief 标记网格数据已更改,需要在下一帧重新上传到GPU。 + */ + void markDirty(); + + /** + * @brief 检查网格数据是否已被标记为“脏”。 + * @return 如果网格数据已更改且需要更新GPU,则为 true。 + */ + bool isDirty() const; + + // ==================== 边界框与碰撞检测 ==================== + + /** + * @brief 更新网格的包围盒。如果边界框数据是旧的,则会重新计算。 + */ + void updateBounds() const; + + /** + * @brief 获取网格的轴对齐包围盒 (AABB)。 + * @return 对包围盒的常量引用。 + */ + const util::BoundingBox& getBounds() const; + + /** + * @brief 检查一个点是否包含在网格内。 + * @param x 点的 x 坐标。 + * @param y 点的 y 坐标。 + * @param precise 如果为 true,则执行精确的基于几何的检测;否则使用快速的边界框检测。 + * @return 如果点在网格内,则为 true。 + */ + bool containsPoint(float x, float y, bool precise = false) const; + + /** + * @brief 检查一个点是否包含在网格内。 + * @param point 要检查的点。 + * @param precise 如果为 true,则执行精确的基于几何的检测;否则使用快速的边界框检测。 + * @return 如果点在网格内,则为 true。 + */ + bool containsPoint(const glm::vec2& point, bool precise = false) const; + + // ==================== 渲染 ==================== + + /** + * @brief 将网格数据(顶点、UV、索引)上传到GPU的VBO和EBO。 + */ + void uploadToGPU(); + + /** + * @brief 从GPU删除与此网格相关的缓冲对象 (VAO, VBO, EBO)。 + */ + void deleteGPU(); + + /** + * @brief 使用指定的着色器和模型矩阵来渲染网格。 + * @param shaderProgram 要使用的着色器程序的ID。 + * @param modelMatrix 应用于网格的模型变换矩阵。 + */ + void draw(int shaderProgram, const glm::mat3& modelMatrix); + + // ==================== Getters & Setters ==================== + + /** @brief 获取网格的名称。 */ + const std::string& getName() const; + /** @brief 设置网格的名称。 */ + void setName(const std::string& name); + /** @brief 获取当前顶点位置数据。 */ + std::vector getVertices() const; + /** @brief 获取当前纹理坐标数据。 */ + std::vector getUVs() const; + /** @brief 获取原始(未变形)的顶点位置数据。 */ + std::vector getOriginalVertices() const; + /** @brief 获取索引数据。 */ + std::vector getIndices() const; + /** @brief 获取顶点总数。 */ + int getVertexCount() const; + /** @brief 通过索引获取一个可修改的顶点。 */ + Vertex* getVertex(int index); + /** @brief 通过索引获取一个不可修改的顶点。 */ + const Vertex* getVertex(int index) const; + /** @brief 获取当前轴心点。 */ + const glm::vec2& getPivot() const; + /** @brief 获取原始轴心点。 */ + const glm::vec2& getOriginalPivot() const; + /** @brief 检查网格是否可见。 */ + bool isVisible() const; + /** @brief 设置网格的可见性。 */ + void setVisible(bool visible); + /** @brief 获取当前的绘制模式。 */ + DrawMode getDrawMode() const; + /** @brief 设置绘制模式。 */ + void setDrawMode(DrawMode mode); + /** @brief 检查网格是否被选中。 */ + bool isSelected() const; + /** @brief 设置网格的选中状态。 */ + void setSelected(bool selected); + /** @brief 获取此网格所属的 ModelPart。 */ + ModelPart* getModelPart() const; + /** @brief 获取当前活动的顶点列表。 */ + VertexList* getActiveVertexList() const; + /** @brief 获取用于变形的控制顶点列表。 */ + const std::vector& getDeformationControlVertices() const; + + // ==================== 静态工厂方法 ==================== + + /** + * @brief 创建一个标准的矩形网格。 + * @param name 网格的名称。 + * @param width 矩形的宽度。 + * @param height 矩形的高度。 + * @return 一个指向新创建的 Mesh2D 的唯一指针。 + */ + static std::unique_ptr createQuad(const std::string& name, float width, float height); + + /** + * @brief 创建一个圆形的网格。 + * @param name 网格的名称。 + * @param radius 圆的半径。 + * @param segments 用于近似圆的线段(三角形)数量。 + * @return 一个指向新创建的 Mesh2D 的唯一指针。 + */ + static std::unique_ptr createCircle(const std::string& name, float radius, int segments); + + private: + // ==================== 私有成员变量 ==================== + std::string m_Name; ///< 网格的名称。 + std::unique_ptr m_ActiveVertexList; ///< 管理活动顶点数据的列表。 + ModelPart* m_ModelPart = nullptr; ///< 指向所属 ModelPart 的指针。 + std::shared_ptr m_Texture; ///< 网格使用的纹理。 + bool m_IsVisible = true; ///< 网格是否可见。 + DrawMode m_DrawMode = DrawMode::TRIANGLES; ///< 当前的渲染模式。 + GLuint m_VaoID = 0, m_VboID = 0, m_EboID = 0; ///< OpenGL 缓冲对象 ID。 + int m_IndexCount = 0; ///< 索引的数量。 + bool m_IsUploaded = false; ///< 数据是否已上传到GPU。 + bool m_IsDirty = true; ///< 数据是否需要重新上传到GPU。 + + mutable util::BoundingBox m_Bounds; ///< 网格的包围盒 (mutable 以便在 const 方法中更新)。 + mutable bool m_BoundsDirty = true; ///< 包围盒是否需要重新计算 (mutable)。 + + std::atomic m_IsSelected{ false }; ///< 网格是否被选中 (原子变量,保证线程安全)。 + glm::vec2 m_Pivot{ 0.0f, 0.0f }; ///< 当前的轴心点。 + glm::vec2 m_OriginalPivot{ 0.0f, 0.0f }; ///< 用于重置的原始轴心点。 + std::vector m_DeformationControlVertices; ///< 用于变形的控制顶点列表。 + + // ==================== 私有辅助方法 ==================== + + /** + * @brief 执行精确的几何测试,判断点是否在网格的三角形内。 + * @param x 点的 x 坐标。 + * @param y 点的 y 坐标。 + * @return 如果点在网格的几何形状内,则为 true。 + */ + bool isPointInMeshGeometry(float x, float y) const; + + /** + * @brief 绘制网格被选中时的高亮效果(如顶点和边框)。 + * @param modelMatrix 网格的模型矩阵。 + */ + void drawSelectionHighlights(const glm::mat3& modelMatrix); + + /** + * @brief 从另一个 Mesh2D 对象拷贝数据。 + * @param other 源对象。 + */ + void copyFrom(const Mesh2D& other); + + /** + * @brief 在插入一个顶点后,更新索引数据以维持拓扑结构。 + * @param insertedIndex 新插入顶点的索引。 + */ + void updateIndicesAfterVertexInsertion(int insertedIndex); + + /** + * @brief 在移除一个顶点后,更新索引数据。 + * @param removedIndex 被移除顶点的索引。 + */ + void updateIndicesAfterVertexRemoval(int removedIndex); + + /** + * @brief 当顶点数量改变时,重新生成索引数据。 + * @param newVertexCount 新的顶点总数。 + */ + void regenerateIndicesForNewVertexCount(int newVertexCount); + }; + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/Model2D.cpp b/Vivid2DRenderer/model/Model2D.cpp new file mode 100644 index 0000000..0e57a3a --- /dev/null +++ b/Vivid2DRenderer/model/Model2D.cpp @@ -0,0 +1,459 @@ +#include "pch.h" +#include "Model2D.h" + +#include +#include +#include +#include +#include +#include + +#include "util/BoundingBox.h" +#include "util/PhysicsSystem.h" +#include "util/ModelPose.h" +#include "util/AnimationLayer.h" +#include "ModelPart.h" + +namespace Vivid2D { + + // ==================== 构造器 ==================== + + Model2D::Model2D() + : m_uuid(generate_new_uuid()), + m_physics(std::make_unique()), + m_bounds(std::make_unique()), + m_currentPose("default") + { + initializeDefaultPose(); + } + + Model2D::Model2D(const std::string& name) + : Model2D() + { + m_name = name; + } + + Model2D::~Model2D() = default; + + Model2D::Model2D(Model2D&& other) noexcept + : m_name(std::move(other.m_name)), + m_version(std::move(other.m_version)), + m_uuid(other.m_uuid), + // m_metadata 移除 + m_parts(std::move(other.m_parts)), + m_partMap(std::move(other.m_partMap)), + m_rootPart(other.m_rootPart), + m_meshes(std::move(other.m_meshes)), + m_textures(std::move(other.m_textures)), + m_parameters(std::move(other.m_parameters)), + m_animationLayers(std::move(other.m_animationLayers)), + m_physics(std::move(other.m_physics)), + m_needsUpdate(other.m_needsUpdate), + m_bounds(std::move(other.m_bounds)), + m_poses(std::move(other.m_poses)), + m_currentPoseName(std::move(other.m_currentPoseName)), + m_currentPose(std::move(other.m_currentPose)), + m_blendTargetPose(std::move(other.m_blendTargetPose)), + m_blendProgress(other.m_blendProgress), + m_blendSpeed(other.m_blendSpeed), + m_lights(std::move(other.m_lights)) + { + other.m_rootPart = nullptr; + // 重新构建 m_partMap 以确保指针正确 + m_partMap.clear(); + for (const auto& part : m_parts) { + m_partMap[part->getName()] = part.get(); + } + if (!m_parts.empty()) { + m_rootPart = m_parts[0].get(); + } + else { + m_rootPart = nullptr; + } + } + + // 移动赋值运算符 + Model2D& Model2D::operator=(Model2D&& other) noexcept { + if (this != &other) { + using std::swap; + swap(m_name, other.m_name); + swap(m_version, other.m_version); + swap(m_uuid, other.m_uuid); + m_name = std::move(other.m_name); + m_version = std::move(other.m_version); + m_uuid = other.m_uuid; + m_parts = std::move(other.m_parts); + m_partMap = std::move(other.m_partMap); + m_rootPart = other.m_rootPart; + m_meshes = std::move(other.m_meshes); + m_textures = std::move(other.m_textures); + m_parameters = std::move(other.m_parameters); + m_animationLayers = std::move(other.m_animationLayers); + m_physics = std::move(other.m_physics); + m_needsUpdate = other.m_needsUpdate; + m_bounds = std::move(other.m_bounds); + m_poses = std::move(other.m_poses); + m_currentPoseName = std::move(other.m_currentPoseName); + m_currentPose = std::move(other.m_currentPose); + m_blendTargetPose = std::move(other.m_blendTargetPose); + m_blendProgress = other.m_blendProgress; + m_blendSpeed = other.m_blendSpeed; + m_lights = std::move(other.m_lights); + other.m_rootPart = nullptr; + Model2D temp(std::move(other)); + swap(*this, temp); + } + return *this; + } + + // ==================== 姿态管理 ==================== + + void Model2D::addPose(const util::ModelPose& pose) { + if (pose.getName().empty()) { + throw std::invalid_argument("Pose name cannot be empty"); + } + m_poses.emplace(pose.getName(), pose); + markNeedsUpdate(); + } + + const util::ModelPose* Model2D::getPose(const std::string& name) const { + auto it = m_poses.find(name); + return (it != m_poses.end()) ? &it->second : nullptr; + } + + void Model2D::applyPose(const std::string& poseName) { + auto it = m_poses.find(poseName); + if (it != m_poses.end()) { + applyPoseInternal(it->second); + m_currentPoseName = poseName; + m_currentPose = it->second; + m_blendProgress = 1.0f; + markNeedsUpdate(); + } + } + + void Model2D::blendToPose(const std::string& targetPoseName, float blendTime) { + auto it = m_poses.find(targetPoseName); + if (it != m_poses.end()) { + m_blendTargetPose = it->second; + m_blendProgress = 0.0f; + m_blendSpeed = (blendTime > 0.0f) ? 1.0f / blendTime : 10.0f; + markNeedsUpdate(); + } + } + + void Model2D::saveCurrentPose(const std::string& poseName) { + util::ModelPose newPose(poseName); + captureCurrentPose(newPose); + addPose(newPose); + } + + std::set Model2D::getPoseNames() const { + std::set names; + for (const auto& pair : m_poses) { + names.insert(pair.first); + } + return names; + } + + void Model2D::removePose(const std::string& poseName) { + if (poseName != "default") { + m_poses.erase(poseName); + if (m_currentPoseName == poseName) { + applyPose("default"); + } + } + } + + void Model2D::setPoseBlend(const std::string& poseName, float blendFactor) { + auto it = m_poses.find(poseName); + if (it != m_poses.end()) { + util::ModelPose blendedPose = util::ModelPose::lerp(m_currentPose, it->second, blendFactor, "manual_blend"); + applyPoseInternal(blendedPose); + markNeedsUpdate(); + } + } + + // ==================== 光源管理 ==================== + + const std::vector& Model2D::getLights() const { + return m_lights; + } + + void Model2D::addLight(const util::LightSource& light) { + m_lights.push_back(light); + markNeedsUpdate(); + } + + void Model2D::removeLight(const util::LightSource& light) { + auto it = std::remove(this->m_lights.begin(), this->m_lights.end(), light); + if (it != this->m_lights.end()) { + this->m_lights.erase(it, this->m_lights.end()); + markNeedsUpdate(); + } + } + + void Model2D::clearLights() { + m_lights.clear(); + markNeedsUpdate(); + } + + // ==================== 部件管理 ==================== + + std::unique_ptr Model2D::createPart(const std::string& name) { + return std::make_unique(name); + } + + void Model2D::addPart(std::unique_ptr part) { + if (!part) return; + + const std::string& name = part->getName(); + if (m_partMap.count(name)) { + throw std::invalid_argument("Part already exists: " + name); + } + + ModelPart* rawPtr = part.get(); + m_parts.push_back(std::move(part)); + m_partMap[name] = rawPtr; + + if (m_rootPart == nullptr) { + m_rootPart = rawPtr; + } + } + + ModelPart* Model2D::getPart(const std::string& name) const { + auto it = m_partMap.find(name); + return (it != m_partMap.end()) ? it->second : nullptr; + } + + const std::map& Model2D::getPartMap() const { + return m_partMap; + } + + const std::vector>& Model2D::getParts() const { + return m_parts; + } + + // ==================== 参数管理 ==================== + + AnimationParameter* Model2D::createParameter(const std::string& id, float min, float max, float defaultValue) { + auto [it, inserted] = m_parameters.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(id, min, max, defaultValue)); + return &it->second; + } + + AnimationParameter* Model2D::getParameter(const std::string& id) { + auto it = m_parameters.find(id); + return (it != m_parameters.end()) ? &it->second : nullptr; + } + + void Model2D::addParameter(const AnimationParameter& param) { + m_parameters[param.getId()] = param; + } + + void Model2D::setParameterValue(const std::string& paramId, float value) { + if (auto param = getParameter(paramId)) { + param->setValue(value); + markNeedsUpdate(); + } + } + + float Model2D::getParameterValue(const std::string& paramId) const { + auto it = m_parameters.find(paramId); + return (it != m_parameters.end()) ? it->second.getValue() : 0.0f; + } + + const std::map& Model2D::getParameters() const { + return m_parameters; + } + + // ==================== 网格管理 ==================== + + std::unique_ptr Model2D::createMesh(const std::string& name, const std::vector& vertices, + const std::vector& uvs, const std::vector& indices) { + return std::make_unique(name, vertices, uvs, indices); + } + + void Model2D::addMesh(std::unique_ptr mesh) { + m_meshes.push_back(std::move(mesh)); + } + + Mesh2D* Model2D::getMesh(const std::string& name) const { + for (const auto& mesh : m_meshes) { + if (mesh->getName() == name) { + return mesh.get(); + } + } + return nullptr; + } + + const std::vector>& Model2D::getMeshes() const { + return m_meshes; + } + + // ==================== 纹理管理 ==================== + + void Model2D::addTexture(std::shared_ptr texture) { + if (!texture) { + throw std::invalid_argument("Texture cannot be null"); + } + + const std::string& textureName = texture->getName(); + if (textureName.empty()) { + throw std::invalid_argument("Texture name cannot be empty"); + } + + m_textures[textureName] = std::move(texture); + } + + std::shared_ptr Model2D::getTexture(const std::string& name) const { + auto it = m_textures.find(name); + return (it != m_textures.end()) ? it->second : nullptr; + } + + const std::map>& Model2D::getTextures() const { + return m_textures; + } + + // ==================== 动画层管理 ==================== + + std::unique_ptr Model2D::createAnimationLayer(const std::string& name) { + auto layer = std::make_unique(name); + m_animationLayers.push_back(std::move(layer)); + return std::move(m_animationLayers.back()); + } + + const std::vector>& Model2D::getAnimationLayers() const { + return m_animationLayers; + } + + void Model2D::setAnimationLayers(std::vector> animationLayers) { + m_animationLayers = std::move(animationLayers); + } + + // ==================== 更新系统 ==================== + + void Model2D::update(float deltaTime) { + updatePoseBlending(deltaTime); + + if (!m_needsUpdate && !m_physics->hasActivePhysics()) { + return; + } + + updateParameterDeformations(); + + applyCurrentPoseToModel(); + + updateHierarchyTransforms(); + + updateBoundingBox(); + + m_needsUpdate = false; + } + + void Model2D::updatePoseBlending(float deltaTime) { + if (m_blendProgress < 1.0f) { + m_blendProgress += deltaTime * m_blendSpeed; + if (m_blendProgress >= 1.0f) { + m_blendProgress = 1.0f; + m_currentPose = m_blendTargetPose; + m_currentPoseName = m_blendTargetPose.getName(); + } + markNeedsUpdate(); + } + } + + void Model2D::applyCurrentPoseToModel() { + if (m_blendProgress < 1.0f) { + util::ModelPose blendedPose = util::ModelPose::lerp(m_currentPose, m_blendTargetPose, m_blendProgress, "blended"); + applyPoseInternal(blendedPose); + } + else { + applyPoseInternal(m_currentPose); + } + } + + void Model2D::applyPoseInternal(const util::ModelPose& pose) const { + for (const auto& pair : pose.getPartPoses()) { + const std::string& partName = pair.first; + const util::PartPose& partPose = pair.second; + if (ModelPart* part = getPart(partName)) { + part->setPosition(partPose.getPosition()); + part->setRotation(partPose.getRotation()); + part->setScale(partPose.getScale()); + part->setOpacity(partPose.getOpacity()); + part->setVisible(partPose.isVisible()); + } + } + } + + void Model2D::captureCurrentPose(util::ModelPose& pose) { + for (const auto& partPtr : m_parts) { + const ModelPart& part = *partPtr; + util::PartPose partPose( + part.getPosition(), + part.getRotation(), + part.getScale(), + part.getOpacity(), + part.isVisible(), + glm::vec3(1.0f, 1.0f, 1.0f) + ); + pose.setPartPose(part.getName(), partPose); + } + } + + void Model2D::initializeDefaultPose() { + util::ModelPose defaultPose = util::ModelPose::createDefaultPose(); + captureCurrentPose(defaultPose); + m_poses.emplace("default", defaultPose); + m_currentPose = defaultPose; + } + + void Model2D::updateParameterDeformations() { + for (auto& pair : m_parameters) { + AnimationParameter& param = pair.second; + if (param.hasChanged()) { + applyParameterDeformations(¶m); + param.markClean(); + } + } + } + + void Model2D::applyParameterDeformations(AnimationParameter* param) { + for (const auto& partPtr : m_parts) { + // 假设 ModelPart::applyParameter(AnimationParameter*) 已实现 + // partPtr->applyParameter(param); + } + } + + void Model2D::updateHierarchyTransforms() { + if (m_rootPart) { + m_rootPart->recomputeWorldTransformRecursive(); + } + } + + void Model2D::updateBoundingBox() { + if (!m_bounds) { + m_bounds = std::make_unique(); + } + m_bounds->reset(); + + for (const auto& partPtr : m_parts) { + m_bounds->expand(partPtr->getWorldBounds()); + } + } + + // ==================== 工具方法 ==================== + + bool Model2D::isVisible() const { + return m_rootPart != nullptr && m_rootPart->isVisible(); + } + + void Model2D::setVisible(bool visible) { + if (m_rootPart) { + m_rootPart->setVisible(visible); + } + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/Model2D.h b/Vivid2DRenderer/model/Model2D.h new file mode 100644 index 0000000..76b5d0d --- /dev/null +++ b/Vivid2DRenderer/model/Model2D.h @@ -0,0 +1,527 @@ +#pragma once + +#ifndef MODEL2D_H +#define MODEL2D_H + +#include +#include +#include +#include +#include +#include + +#include "util/ModelPose.h" +#include "util/LightSource.h" +#include "ModelPart.h" +#include "Mesh2D.h" + +// 前置声明必要的类 +namespace Vivid2D { + namespace Render { + namespace Texture { + class Texture; + } + } + namespace util { + class BoundingBox; + class PhysicsSystem; + class AnimationLayer; + class AnimationParameter; + } + + /** + * @class Model2D + * @brief 2D 模型核心数据结构,管理模型的部件、网格、纹理、姿态、动画和光源等所有资源。 + * @details + * Model2D 是一个容器类,用于聚合构成一个完整 2D 角色或对象所需的所有数据。 + * 它负责层级结构的管理、动画参数的驱动、姿态的混合与应用,以及物理和渲染状态的更新。 + * 该类被设计为不可拷贝,但可移动,以确保资源所有权的唯一性。 + */ + class VIVID_2D_MYDLL_API Model2D { + public: + // 使用 stduuid 库的 UUID 类型 + using UUID = uuids::uuid; + + // ==================== 构造器 ==================== + /** + * @brief 默认构造函数,创建一个未命名的 Model2D 实例。 + */ + Model2D(); + + /** + * @brief 构造函数,使用指定的名称创建一个 Model2D 实例。 + * @param name 模型的名称。 + */ + explicit Model2D(const std::string& name); + + /** + * @brief 析构函数。 + */ + ~Model2D(); + + // 禁止拷贝和赋值 + Model2D(const Model2D&) = delete; + Model2D& operator=(const Model2D&) = delete; + + /** + * @brief 移动构造函数。 + * @param other 用于移动的右值引用 Model2D 对象。 + */ + Model2D(Model2D&&) noexcept; + + /** + * @brief 移动赋值运算符。 + * @param other 用于移动赋值的右值引用 Model2D 对象。 + * @return 当前对象的引用。 + */ + Model2D& operator=(Model2D&&) noexcept; + + // ==================== 姿态管理 ==================== + + /** + * @brief 添加一个新的姿态到模型。 + * @param pose 要添加的 ModelPose 对象。如果已存在同名姿态,它将被覆盖。 + */ + void addPose(const util::ModelPose& pose); + + /** + * @brief 根据名称获取一个姿态。 + * @param name 姿态的名称。 + * @return 如果找到,返回指向该姿态的常量指针;否则返回 nullptr。 + */ + const util::ModelPose* getPose(const std::string& name) const; + + /** + * @brief 立即将模型的状态应用为指定的姿态。 + * @param poseName 要应用的姿态的名称。 + */ + void applyPose(const std::string& poseName); + + /** + * @brief 在指定时间内平滑地混合到目标姿态。 + * @param targetPoseName 目标姿态的名称。 + * @param blendTime 完成混合所需的总时间(秒)。 + */ + void blendToPose(const std::string& targetPoseName, float blendTime); + + /** + * @brief 将模型当前的部件变换状态保存为一个新的姿态。 + * @param poseName 要保存的新姿态的名称。 + */ + void saveCurrentPose(const std::string& poseName); + + /** + * @brief 获取当前模型正在应用的姿态名称。 + * @return 当前姿态的名称。 + */ + const std::string& getCurrentPoseName() const { return m_currentPoseName; } + + /** + * @brief 检查模型是否包含具有指定名称的姿态。 + * @param poseName 要检查的姿态名称。 + * @return 如果存在则返回 true,否则返回 false。 + */ + bool hasPose(const std::string& poseName) const; + + /** + * @brief 从模型中移除一个姿态。 + * @param poseName 要移除的姿态的名称。 + */ + void removePose(const std::string& poseName); + + /** + * @brief 获取所有已定义姿态的名称集合。 + * @return 一个包含所有姿态名称的 std::set。 + */ + std::set getPoseNames() const; + + /** + * @brief 设置特定姿态的混合权重。此功能通常用于更高级的姿态混合逻辑,例如加权混合多个姿态。 + * @param poseName 要设置混合权重的姿态名称。 + * @param blendFactor 混合权重因子,通常在 [0, 1] 范围内。 + */ + void setPoseBlend(const std::string& poseName, float blendFactor); + + // ==================== 光源管理 ==================== + /** + * @brief 获取模型关联的所有光源。 + * @return 一个包含所有光源的常量引用向量。 + */ + const std::vector& getLights() const; + + /** + * @brief 向模型添加一个光源。 + * @param light 要添加的光源对象。 + */ + void addLight(const util::LightSource& light); + + /** + * @brief 从模型中移除一个光源。 + * @param light 要移除的光源对象。注意:比较是基于光源对象的属性值。 + */ + void removeLight(const util::LightSource& light); + + /** + * @brief 清除所有与模型关联的光源。 + */ + void clearLights(); + + // ==================== 部件管理 ==================== + /** + * @brief 创建一个新的模型部件,但不立即添加到模型中。 + * @param name 新部件的名称。 + * @return 返回一个包含新创建 ModelPart 的 std::unique_ptr。 + */ + std::unique_ptr createPart(const std::string& name); + + /** + * @brief 将一个已存在的部件添加到模型中。 + * @param part 要添加的部件的 std::unique_ptr,所有权将转移给 Model2D。 + */ + void addPart(std::unique_ptr part); + + /** + * @brief 根据名称获取一个模型部件。 + * @param name 要获取的部件的名称。 + * @return 如果找到,返回指向该部件的指针;否则返回 nullptr。 + */ + ModelPart* getPart(const std::string& name) const; + + /** + * @brief 获取部件名称到部件指针的映射表。 + * @return 一个包含所有部件映射的常量引用。 + */ + const std::map& getPartMap() const; + + /** + * @brief 获取模型中所有部件的列表。 + * @return 一个包含所有部件的 std::unique_ptr 的常量引用向量。 + */ + const std::vector>& getParts() const; + + /** + * @brief 获取模型的根部件。 + * @return 指向根部件的指针。如果没有设置根部件,则可能为 nullptr。 + */ + ModelPart* getRootPart() const { return m_rootPart; } + + // ==================== 参数管理 ==================== + /** + * @brief 创建一个新的动画参数并添加到模型中。 + * @param id 参数的唯一标识符。 + * @param min 参数的最小值。 + * @param max 参数的最大值。 + * @param defaultValue 参数的默认值。 + * @return 返回指向新创建的 AnimationParameter 的指针。 + */ + AnimationParameter* createParameter(const std::string& id, float min, float max, float defaultValue); + + /** + * @brief 根据 ID 获取一个动画参数。 + * @param id 要获取的参数的 ID。 + * @return 如果找到,返回指向该参数的指针;否则返回 nullptr。 + */ + AnimationParameter* getParameter(const std::string& id); + + /** + * @brief 添加一个已存在的动画参数。 + * @param param 要添加的 AnimationParameter 对象。 + */ + void addParameter(const AnimationParameter& param); + + /** + * @brief 设置指定动画参数的当前值。 + * @param paramId 参数的 ID。 + * @param value 要设置的新值。 + */ + void setParameterValue(const std::string& paramId, float value); + + /** + * @brief 获取指定动画参数的当前值。 + * @param paramId 参数的 ID。 + * @return 参数的当前浮点数值。如果参数不存在,行为未定义。 + */ + float getParameterValue(const std::string& paramId) const; + + /** + * @brief 获取所有动画参数的映射表。 + * @return 一个包含所有参数的常量引用映射表。 + */ + const std::map& getParameters() const; + + // ==================== 网格管理 ==================== + /** + * @brief 创建一个新的 2D 网格。 + * @param name 网格的名称。 + * @param vertices 顶点坐标数组 (x, y, z, ...)。 + * @param uvs UV 纹理坐标数组 (u, v, ...)。 + * @param indices 顶点索引数组。 + * @return 返回一个包含新创建 Mesh2D 的 std::unique_ptr。 + */ + std::unique_ptr createMesh(const std::string& name, const std::vector& vertices, + const std::vector& uvs, const std::vector& indices); + + /** + * @brief 将一个已存在的网格添加到模型中。 + * @param mesh 要添加的网格的 std::unique_ptr,所有权将转移给 Model2D。 + */ + void addMesh(std::unique_ptr mesh); + + /** + * @brief 根据名称获取一个网格。 + * @param name 要获取的网格的名称。 + * @return 如果找到,返回指向该网格的指针;否则返回 nullptr。 + */ + Mesh2D* getMesh(const std::string& name) const; + + /** + * @brief 获取模型中所有网格的列表。 + * @return 一个包含所有网格的 std::unique_ptr 的常量引用向量。 + */ + const std::vector>& getMeshes() const; + + // ==================== 纹理管理 ==================== + /** + * @brief 向模型添加一个纹理资源。 + * @param texture 要添加的纹理的共享指针。 + */ + void addTexture(std::shared_ptr texture); + + /** + * @brief 根据名称获取一个纹理。 + * @param name 要获取的纹理的名称。 + * @return 如果找到,返回指向该纹理的共享指针;否则返回一个空的共享指针。 + */ + std::shared_ptr getTexture(const std::string& name) const; + + /** + * @brief 获取所有纹理的映射表。 + * @return 一个包含所有纹理的常量引用映射表。 + */ + const std::map>& getTextures() const; + + // ==================== 动画层管理 ==================== + /** + * @brief 创建一个新的动画层。 + * @param name 动画层的名称。 + * @return 返回一个包含新创建 AnimationLayer 的 std::unique_ptr。 + */ + std::unique_ptr createAnimationLayer(const std::string& name); + + /** + * @brief 获取模型中所有动画层的列表。 + * @return 一个包含所有动画层的 std::unique_ptr 的常量引用向量。 + */ + const std::vector>& getAnimationLayers() const; + + /** + * @brief 设置模型的动画层列表。 + * @param animationLayers 一个包含动画层的 std::unique_ptr 向量,所有权将转移给 Model2D。 + */ + void setAnimationLayers(std::vector> animationLayers); + + // ==================== 更新系统 ==================== + /** + * @brief 更新模型的内部状态。 + * @details + * 此方法应每帧调用一次。它负责处理姿态混合、参数变形、层级变换更新和包围盒计算。 + * @param deltaTime 从上一帧到当前帧的时间差(秒)。 + */ + void update(float deltaTime); + + // ==================== 工具方法 ==================== + /** + * @brief 标记模型需要更新。在修改了任何可能影响渲染或物理状态的属性后调用。 + */ + void markNeedsUpdate() { m_needsUpdate = true; } + + /** + * @brief 检查模型当前是否可见。 + * @return 如果可见则返回 true,否则返回 false。 + */ + bool isVisible() const; + + /** + * @brief 设置模型的可见性。 + * @param visible true 表示可见,false 表示隐藏。 + */ + void setVisible(bool visible); + + /** + * @brief 检查模型当前是否正在进行姿态混合。 + * @return 如果正在混合(混合进度小于 1.0),则返回 true。 + */ + bool isBlending() const { return m_blendProgress < 1.0f; } + + // ==================== Getter/Setter ==================== + /** + * @brief 获取模型的名称。 + * @return 模型的名称。 + */ + const std::string& getName() const { return m_name; } + + /** + * @brief 设置模型的名称。 + * @param name 新的名称。 + */ + void setName(const std::string& name) { m_name = name; } + + /** + * @brief 获取模型的唯一标识符 (UUID)。 + * @return 模型的 UUID。 + */ + const UUID& getUuid() const { return m_uuid; } + + /** + * @brief 设置模型的唯一标识符 (UUID)。 + * @param uuid 新的 UUID。 + */ + void setUuid(const UUID& uuid) { m_uuid = uuid; } + + /** + * @brief 获取模型的物理系统。 + * @return 指向物理系统的常量指针。 + */ + const util::PhysicsSystem* getPhysics() const { return m_physics.get(); } + + /** + * @brief 获取模型当前应用的姿态。 + * @return 当前姿态的拷贝。 + */ + util::ModelPose getCurrentPose() const { return m_currentPose; } + + /** + * @brief 获取当前姿态混合的进度。 + * @return 混合进度,范围从 0.0 (起始姿态) 到 1.0 (目标姿态)。 + */ + float getBlendProgress() const { return m_blendProgress; } + + /** + * @brief 获取所有已定义姿态的映射表。 + * @return 一个从姿态名称到 ModelPose 对象的常量引用映射表。 + */ + const std::map& getPoses() const { return m_poses; } + + /** + * @brief 获取模型的包围盒。 + * @return 指向包围盒的常量指针。 + */ + const util::BoundingBox* getBounds() const { return m_bounds.get(); } + + /** + * @brief 获取模型的版本号。 + * @return 模型的版本字符串。 + */ + const std::string& getVersion() const { return m_version; } + + /** + * @brief 设置模型的版本号。 + * @param version 新的版本字符串。 + */ + void setVersion(const std::string& version) { m_version = version; } + + private: + // ==================== 成员变量 ==================== + //! 模型的名称 + std::string m_name; + //! 模型格式的版本号 + std::string m_version = "1.0.0"; + //! 模型的唯一标识符 (UUID) + UUID m_uuid; + + //! 存储所有模型部件的列表,拥有其所有权 + std::vector> m_parts; + //! 从部件名称到部件指针的映射,用于快速查找 + std::map m_partMap; + //! 指向层级结构根部的指针 + ModelPart* m_rootPart = nullptr; + + //! 存储所有网格的列表,拥有其所有权 + std::vector> m_meshes; + //! 从纹理名称到纹理共享指针的映射 + std::map> m_textures; + + //! 从参数 ID 到 AnimationParameter 对象的映射 + std::map m_parameters; + //! 存储所有动画层的列表,拥有其所有权 + std::vector> m_animationLayers; + //! 模型的物理系统 + std::unique_ptr m_physics; + + //! 标记模型是否需要在下一帧更新其变换或顶点数据 + bool m_needsUpdate = true; + //! 模型的轴对齐包围盒 (AABB) + std::unique_ptr m_bounds; + + //! 从姿态名称到 ModelPose 对象的映射 + std::map m_poses; + //! 当前应用的姿态名称 + std::string m_currentPoseName = "default"; + //! 当前姿态的完整数据 + util::ModelPose m_currentPose; + //! 姿态混合的目标姿态 + util::ModelPose m_blendTargetPose; + //! 姿态混合的当前进度 [0.0, 1.0] + float m_blendProgress = 1.0f; + //! 姿态混合的速度 + float m_blendSpeed = 1.0f; + + //! 影响模型的光源列表 + std::vector m_lights; + + // ==================== 私有辅助方法 ==================== + /** + * @brief 初始化模型的默认姿态。 + * @details + * 当模型被创建时调用,捕获所有部件的初始变换作为“default”姿态。 + */ + void initializeDefaultPose(); + + /** + * @brief 在每帧更新中处理姿态混合的逻辑。 + * @param deltaTime 帧间时间差。 + */ + void updatePoseBlending(float deltaTime); + + /** + * @brief 将 m_currentPose 中的变换数据应用到模型的各个部件上。 + */ + void applyCurrentPoseToModel(); + + /** + * @brief 将给定的姿态内部应用到模型部件上,这是一个底层的应用函数。 + * @param pose 要应用的姿态。 + */ + void applyPoseInternal(const util::ModelPose& pose) const; + + /** + * @brief 捕获模型当前所有部件的变换,并存储到一个 ModelPose 对象中。 + * @param pose 用于接收当前姿态数据的 ModelPose 对象。 + */ + void captureCurrentPose(util::ModelPose& pose); + + /** + * @brief 根据所有动画参数的当前值,更新受其影响的部件变形。 + */ + void updateParameterDeformations(); + + /** + * @brief 将单个动画参数的变形效果应用到相关的模型部件上。 + * @param param 要应用的动画参数。 + */ + void applyParameterDeformations(AnimationParameter* param); + + /** + * @brief 更新整个模型部件层级的全局变换矩阵。 + */ + void updateHierarchyTransforms(); + + /** + * @brief 根据所有部件的当前位置和大小,重新计算模型的总包围盒。 + */ + void updateBoundingBox(); + }; + +} // namespace Vivid2D + +#endif // MODEL2D_H \ No newline at end of file diff --git a/Vivid2DRenderer/model/ModelPart.cpp b/Vivid2DRenderer/model/ModelPart.cpp new file mode 100644 index 0000000..3ad7bff --- /dev/null +++ b/Vivid2DRenderer/model/ModelPart.cpp @@ -0,0 +1,312 @@ +#include "pch.h" + +#include "ModelPart.h" +#include "Mesh2D.h" +#include "AnimationParameter.h" +#include +#include +#include + + +namespace Vivid2D { + + // ==================== 构造函数 ==================== + + ModelPart::ModelPart(std::string name) : m_Name(std::move(name)) { + updateLocalTransform(); + recomputeWorldTransformRecursive(); + } + + // ==================== 拷贝与移动语义 ==================== + + void ModelPart::deepCopyFrom(const ModelPart& other) { + m_Name = other.m_Name; + // Parent pointer must be set by the new parent after copying + m_Parent = nullptr; + + m_Meshes.clear(); + for (const auto& mesh : other.m_Meshes) { + addMesh(std::make_unique(*mesh)); + } + + m_Children.clear(); + for (const auto& child : other.m_Children) { + addChild(std::make_unique(*child)); + } + + m_Position = other.m_Position; + m_Rotation = other.m_Rotation; + m_Scale = other.m_Scale; + m_LocalTransform = other.m_LocalTransform; + m_WorldTransform = other.m_WorldTransform; + m_IsVisible = other.m_IsVisible; + m_BlendMode = other.m_BlendMode; + m_Opacity = other.m_Opacity; + m_Parameters = other.m_Parameters; + m_TransformDirty = true; + m_BoundsDirty = true; + } + + ModelPart::ModelPart(const ModelPart& other) { + deepCopyFrom(other); + } + + ModelPart& ModelPart::operator=(const ModelPart& other) { + if (this != &other) { + deepCopyFrom(other); + } + return *this; + } + + ModelPart::ModelPart(ModelPart&& other) noexcept = default; + ModelPart& ModelPart::operator=(ModelPart&& other) noexcept = default; + + + // ==================== 私有辅助方法 ==================== + + void ModelPart::markTransformDirty() { + if (!m_TransformDirty) { + m_TransformDirty = true; + m_BoundsDirty = true; + for (auto& child : m_Children) { + child->markTransformDirty(); + } + } + } + + void ModelPart::updateLocalTransform() { + glm::mat4 transform4x4 = glm::mat4(1.0f); + transform4x4 = glm::translate(transform4x4, glm::vec3(m_Position, 0.0f)); + transform4x4 = glm::translate(transform4x4, glm::vec3(m_Pivot, 0.0f)); + transform4x4 = glm::rotate(transform4x4, m_Rotation, glm::vec3(0.0f, 0.0f, 1.0f)); + transform4x4 = glm::scale(transform4x4, glm::vec3(m_Scale, 1.0f)); + transform4x4 = glm::translate(transform4x4, glm::vec3(-m_Pivot, 0.0f)); + m_LocalTransform = glm::mat3(transform4x4); + + m_TransformDirty = false; + } + + + // ==================== 层级管理 ==================== + + void ModelPart::addChild(std::unique_ptr child) { + if (!child || child.get() == this) { + throw std::invalid_argument("Cannot add self or null as child"); + } + if (child->m_Parent) { + child->m_Parent->removeChild(child.get()); + } + child->m_Parent = this; + m_Children.push_back(std::move(child)); + m_Children.back()->markTransformDirty(); + m_Children.back()->recomputeWorldTransformRecursive(); + } + + std::unique_ptr ModelPart::removeChild(ModelPart* child) { + auto it = std::find_if(m_Children.begin(), m_Children.end(), + [child](const auto& ptr) { return ptr.get() == child; }); + + if (it != m_Children.end()) { + std::unique_ptr removedChild = std::move(*it); + m_Children.erase(it); + removedChild->m_Parent = nullptr; + removedChild->markTransformDirty(); + removedChild->recomputeWorldTransformRecursive(); + return removedChild; + } + return nullptr; + } + + const std::vector>& ModelPart::getChildren() const { return m_Children; } + + ModelPart* ModelPart::findChild(const std::string& name) { + for (const auto& child : m_Children) { + if (child->getName() == name) { + return child.get(); + } + } + return nullptr; + } + + ModelPart* ModelPart::findChildRecursive(const std::string& name) { + if (auto found = findChild(name)) return found; + for (const auto& child : m_Children) { + if (auto found = child->findChildRecursive(name)) return found; + } + return nullptr; + } + + // ==================== 变换系统 ==================== + + void ModelPart::setPosition(const glm::vec2& pos) { + m_Position = pos; + markTransformDirty(); + recomputeWorldTransformRecursive(); + } + + void ModelPart::setRotation(float radians) { + m_Rotation = radians; + markTransformDirty(); + recomputeWorldTransformRecursive(); + } + + void ModelPart::setScale(const glm::vec2& scale) { + m_Scale = scale; + markTransformDirty(); + recomputeWorldTransformRecursive(); + } + + void ModelPart::setPivot(const glm::vec2& pivot) { + if (m_Pivot != pivot) { + m_Pivot = pivot; + markTransformDirty(); + } + } + + const glm::vec2& ModelPart::getPivot() const { + return m_Pivot; + } + + void ModelPart::translate(const glm::vec2& delta) { setPosition(m_Position + delta); } + void ModelPart::rotate(float deltaRadians) { setRotation(m_Rotation + deltaRadians); } + void ModelPart::scale(const glm::vec2& factor) { setScale(m_Scale * factor); } + + void ModelPart::recomputeWorldTransformRecursive() { + if (m_TransformDirty) { + updateLocalTransform(); + } + if (m_Parent) { + m_WorldTransform = m_Parent->m_WorldTransform * m_LocalTransform; + } + else { + m_WorldTransform = m_LocalTransform; + } + + // 重要:部件变换后,它拥有的所有网格的顶点也需要更新 + for (const auto& mesh : m_Meshes) { + mesh->markDirty(); // 标记网格需要重新计算顶点和边界 + } + + for (auto& child : m_Children) { + child->recomputeWorldTransformRecursive(); + } + } + + void ModelPart::updateMeshVertices() { + for (const auto& mesh : m_Meshes) { + if (mesh) { + mesh->markDirty(); + mesh->updateBounds(); + } + } + } + + // ==================== 网格管理 ==================== + + void ModelPart::addMesh(std::unique_ptr mesh) { + if (mesh) { + mesh->setModelPart(this); + m_Meshes.push_back(std::move(mesh)); + m_BoundsDirty = true; + } + } + + bool ModelPart::removeMesh(Mesh2D* mesh) { + auto it = std::find_if(m_Meshes.begin(), m_Meshes.end(), + [mesh](const auto& ptr) { return ptr.get() == mesh; }); + + if (it != m_Meshes.end()) { + m_Meshes.erase(it); + m_BoundsDirty = true; + return true; + } + return false; + } + + const std::vector>& ModelPart::getMeshes() const { return m_Meshes; } + + // ==================== 工具方法 ==================== + + glm::vec2 ModelPart::localToWorld(const glm::vec2& localPoint) const { + return glm::vec2(m_WorldTransform * glm::vec3(localPoint, 1.0f)); + } + + glm::vec2 ModelPart::worldToLocal(const glm::vec2& worldPoint) const { + return glm::vec2(glm::inverse(m_WorldTransform) * glm::vec3(worldPoint, 1.0f)); + } + + void ModelPart::updateBounds() const { + for (const auto& mesh_ptr : m_Meshes) { + if (mesh_ptr) { + const util::BoundingBox& meshLocalBounds = mesh_ptr->getBounds(); + mesh_ptr->updateBounds(); + } + } + m_BoundsDirty = false; + } + + util::BoundingBox ModelPart::getWorldBounds() { + if (m_BoundsDirty) updateBounds(); + util::BoundingBox worldBounds; + for (const auto& mesh : m_Meshes) { + util::BoundingBox localBounds = mesh->getBounds(); + if (localBounds.isValid()) { + for (const auto& corner : localBounds.getCorners()) { + worldBounds.expand(localToWorld(corner)); + } + } + } + return worldBounds; + } + + bool ModelPart::isEffectivelyVisible() const { + if (!m_IsVisible) return false; + if (m_Parent) return m_Parent->isEffectivelyVisible(); + return true; + } + + // ==================== Getters & Setters ==================== + + const std::string& ModelPart::getName() const { return m_Name; } + void ModelPart::setName(const std::string& name) { m_Name = name; } + ModelPart* ModelPart::getParent() const { return m_Parent; } + const glm::vec2& ModelPart::getPosition() const { return m_Position; } + float ModelPart::getRotation() const { return m_Rotation; } + const glm::vec2& ModelPart::getScale() const { return m_Scale; } + const glm::mat3& ModelPart::getLocalTransform() const { return m_LocalTransform; } + const glm::mat3& ModelPart::getWorldTransform() const { return m_WorldTransform; } + bool ModelPart::isVisible() const { return m_IsVisible; } + void ModelPart::setVisible(bool visible) { m_IsVisible = visible; } + ModelPart::BlendMode ModelPart::getBlendMode() const { return m_BlendMode; } + void ModelPart::setBlendMode(BlendMode mode) { m_BlendMode = mode; } + float ModelPart::getOpacity() const { return m_Opacity; } + void ModelPart::setOpacity(float opacity) { m_Opacity = std::clamp(opacity, 0.0f, 1.0f); } + + // ==================== 参数管理 ==================== + + AnimationParameter* ModelPart::createParameter(const std::string& id, float min, float max, float defaultValue) { + auto it = m_Parameters.emplace(id, AnimationParameter(id, min, max, defaultValue)); + return &it.first->second; + } + + AnimationParameter* ModelPart::getParameter(const std::string& id) { + auto it = m_Parameters.find(id); + return (it != m_Parameters.end()) ? &it->second : nullptr; + } + + void ModelPart::addParameter(const AnimationParameter& param) { + m_Parameters[param.getId()] = param; + } + + void ModelPart::setParameterValue(const std::string& paramId, float value) { + if (auto* param = getParameter(paramId)) { + param->setValue(value); + } + } + + float ModelPart::getParameterValue(const std::string& paramId) const { + auto it = m_Parameters.find(paramId); + return (it != m_Parameters.end()) ? it->second.getValue() : 0.0f; + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/ModelPart.h b/Vivid2DRenderer/model/ModelPart.h new file mode 100644 index 0000000..f6bfef9 --- /dev/null +++ b/Vivid2DRenderer/model/ModelPart.h @@ -0,0 +1,337 @@ +#pragma once + +#include "AnimationParameter.h" +#include +#include // For glm::translate, glm::rotate, glm::scale +#include +#include +#include +#include +#include + +// 前置声明 +namespace Vivid2D { + class Mesh2D; + namespace util { + class BoundingBox; + } + /** + * @class ModelPart + * @brief 表示 2D 模型的一个可动部件,是构成模型层级结构的基本单元。 + * @details + * 每个 ModelPart 都有自己的变换(位置、旋转、缩放)、渲染属性(可见性、混合模式、不透明度) + * 以及可以附加的网格。它通过父子关系组织成树状结构,从而实现复杂的层级动画。 + * 变换是相对于父部件的,最终会计算出一个世界变换矩阵来确定其在场景中的最终位置。 + */ + class VIVID_2D_MYDLL_API ModelPart { + public: + /** + * @enum BlendMode + * @brief 定义了部件渲染时使用的混合模式。 + */ + enum class BlendMode { + NORMAL, //!< 正常混合,Alpha 混合 + ADDITIVE, //!< 加法混合,常用于发光效果 + MULTIPLY, //!< 正片叠底,常用于阴影或着色 + SCREEN //!< 滤色混合,与正片叠底效果相反 + }; + + // ==================== 构造函数 ==================== + /** + * @brief 构造函数,创建一个具有指定名称的模型部件。 + * @param name 部件的名称,默认为 "unnamed"。 + */ + explicit ModelPart(std::string name = "unnamed"); + + /** + * @brief 拷贝构造函数,执行深拷贝。 + * @details + * 创建一个与 `other` 完全相同的新 ModelPart,包括其所有子部件、网格和参数。 + * 父子关系链也会被完整地复制。 + * @param other 要拷贝的源 ModelPart 对象。 + */ + ModelPart(const ModelPart& other); + + /** + * @brief 拷贝赋值运算符,执行深拷贝。 + * @param other 要拷贝的源 ModelPart 对象。 + * @return 当前对象的引用。 + */ + ModelPart& operator=(const ModelPart& other); + + /** + * @brief 移动构造函数。 + * @param other 用于移动的右值引用 ModelPart 对象。 + */ + ModelPart(ModelPart&& other) noexcept; + + /** + * @brief 移动赋值运算符。 + * @param other 用于移动赋值的右值引用 ModelPart 对象。 + * @return 当前对象的引用。 + */ + ModelPart& operator=(ModelPart&& other) noexcept; + + + // ==================== 层级管理 ==================== + /** + * @brief 添加一个子部件。 + * @details + * 将 `child` 添加到当前部件的子节点列表中,并自动设置 `child` 的父节点为当前部件。 + * `child` 的所有权将转移给当前部件。 + * @param child 要添加的子部件的 unique_ptr。 + */ + void addChild(std::unique_ptr child); + + /** + * @brief 移除一个子部件。 + * @details + * 从子节点列表中移除指定的 `child` 部件,并返回其所有权给调用者。 + * 如果 `child` 不是当前部件的直接子节点,则不执行任何操作并返回空的 unique_ptr。 + * @param child 要移除的子部件的裸指针。 + * @return 被移除的子部件的 unique_ptr,如果未找到则为空。 + */ + std::unique_ptr removeChild(ModelPart* child); + + /** + * @brief 获取所有直接子部件的列表。 + * @return 一个包含所有子部件 unique_ptr 的常量引用向量。 + */ + const std::vector>& getChildren() const; + + /** + * @brief 在直接子部件中按名称查找一个部件。 + * @param name 要查找的部件名称。 + * @return 如果找到,返回指向该部件的指针;否则返回 nullptr。 + */ + ModelPart* findChild(const std::string& name); + + /** + * @brief 递归地在所有后代部件中按名称查找一个部件。 + * @param name 要查找的部件名称。 + * @return 如果找到,返回指向该部件的指针;否则返回 nullptr。 + */ + ModelPart* findChildRecursive(const std::string& name); + + // ==================== 变换系统 ==================== + /** + * @brief 设置部件相对于其父部件的位置。 + * @param pos 新的局部位置坐标。 + */ + void setPosition(const glm::vec2& pos); + + /** + * @brief 设置部件相对于其父部件的旋转。 + * @param radians 新的旋转角度(以弧度为单位)。 + */ + void setRotation(float radians); + + /** + * @brief 设置部件的旋转和缩放中心点(轴心)。 + * @param pivot 新的轴心坐标(在部件的局部坐标系中定义)。 + */ + void setPivot(const glm::vec2& pivot); + + /** + * @brief 设置部件相对于其父部件的缩放。 + * @param scale 新的缩放因子(x 和 y 方向)。 + */ + void setScale(const glm::vec2& scale); + + /** + * @brief 在当前位置上应用一个位移。 + * @param delta 位移向量。 + */ + void translate(const glm::vec2& delta); + + /** + * @brief 在当前旋转角度上增加一个旋转量。 + * @param deltaRadians 要增加的旋转角度(以弧度为单位)。 + */ + void rotate(float deltaRadians); + + /** + * @brief 在当前缩放上应用一个缩放因子。 + * @param factor 缩放乘数。 + */ + void scale(const glm::vec2& factor); + + /** + * @brief 递归地重新计算当前部件及其所有子部件的世界变换矩阵。 + * @details 当父部件的变换发生改变时,需要调用此方法来更新所有子孙部件的最终世界位置。 + */ + void recomputeWorldTransformRecursive(); + + /** + * @brief 使用当前的世界变换矩阵更新所有关联网格的顶点位置。 + * @details 此方法通常在渲染前调用,以确保网格在屏幕上显示在正确的位置。 + */ + void updateMeshVertices(); + + // ==================== 网格管理 ==================== + /** + * @brief 向此部件添加一个网格。 + * @param mesh 要添加的网格的 unique_ptr,所有权将转移给 ModelPart。 + */ + void addMesh(std::unique_ptr mesh); + + /** + * @brief 从此部件移除一个网格。 + * @param mesh 要移除的网格的裸指针。 + * @return 如果成功移除返回 true,否则返回 false。 + */ + bool removeMesh(Mesh2D* mesh); + + /** + * @brief 获取附加到此部件的所有网格的列表。 + * @return 一个包含所有网格 unique_ptr 的常量引用向量。 + */ + const std::vector>& getMeshes() const; + + // ==================== 工具方法 ==================== + /** + * @brief 将一个局部坐标点转换为世界坐标。 + * @param localPoint 在此部件局部坐标系中的点。 + * @return 对应的世界坐标系中的点。 + */ + glm::vec2 localToWorld(const glm::vec2& localPoint) const; + + /** + * @brief 将一个世界坐标点转换为局部坐标。 + * @param worldPoint 在世界坐标系中的点。 + * @return 对应的在此部件局部坐标系中的点。 + */ + glm::vec2 worldToLocal(const glm::vec2& worldPoint) const; + + /** + * @brief 计算并获取此部件在世界坐标系中的包围盒。 + * @details 包围盒会包含所有附加到此部件的网格。 + * @return 在世界坐标系中的包围盒。 + */ + util::BoundingBox getWorldBounds(); + + /** + * @brief 检查此部件及其所有父部件是否都可见。 + * @return 如果从根节点到此部件的整个路径都可见,则返回 true。 + */ + bool isEffectivelyVisible() const; + + // ==================== Getters & Setters ==================== + const std::string& getName() const; + void setName(const std::string& name); + ModelPart* getParent() const; + const glm::vec2& getPosition() const; + float getRotation() const; + const glm::vec2& getScale() const; + const glm::vec2& getPivot() const; + const glm::mat3& getLocalTransform() const; + const glm::mat3& getWorldTransform() const; + bool isVisible() const; + void setVisible(bool visible); + BlendMode getBlendMode() const; + void setBlendMode(BlendMode mode); + float getOpacity() const; + void setOpacity(float opacity); + + // ==================== 参数管理 ==================== + /** + * @brief 创建一个专属于此部件的动画参数。 + * @param id 参数的唯一标识符。 + * @param min 参数的最小值。 + * @param max 参数的最大值。 + * @param defaultValue 参数的默认值。 + * @return 指向新创建的 AnimationParameter 的指针。 + */ + AnimationParameter* createParameter(const std::string& id, float min, float max, float defaultValue); + + /** + * @brief 根据 ID 获取一个专属于此部件的动画参数。 + * @param id 要获取的参数 ID。 + * @return 如果找到,返回指向参数的指针;否则返回 nullptr。 + */ + AnimationParameter* getParameter(const std::string& id); + + /** + * @brief 添加一个已存在的动画参数到此部件。 + * @param param 要添加的 AnimationParameter 对象。 + */ + void addParameter(const AnimationParameter& param); + + /** + * @brief 设置此部件特定动画参数的值。 + * @param paramId 参数的 ID。 + * @param value 要设置的新值。 + */ + void setParameterValue(const std::string& paramId, float value); + + /** + * @brief 获取此部件特定动画参数的当前值。 + * @param paramId 参数的 ID。 + * @return 参数的当前浮点数值。 + */ + float getParameterValue(const std::string& paramId) const; + + private: + // ==================== 私有成员变量 ==================== + //! 部件名称 + std::string m_Name; + //! 指向父部件的裸指针,不拥有所有权 + ModelPart* m_Parent = nullptr; + //! 子部件列表,拥有其所有权 + std::vector> m_Children; + //! 附加到此部件的网格列表,拥有其所有权 + std::vector> m_Meshes; + + //! 相对于父部件的位置 + glm::vec2 m_Position{ 0.0f }; + //! 相对于父部件的旋转(弧度) + float m_Rotation = 0.0f; + //! 相对于父部件的缩放 + glm::vec2 m_Scale{ 1.0f }; + //! 变换的轴心(局部坐标) + glm::vec2 m_Pivot{ 0.0f }; + + //! 局部变换矩阵,描述了从父部件坐标系到当前部件坐标系的变换 + glm::mat3 m_LocalTransform{ 1.0f }; + //! 世界变换矩阵,描述了从模型根坐标系到当前部件坐标系的变换 + glm::mat3 m_WorldTransform{ 1.0f }; + + //! 当前部件的可见性标志 + bool m_IsVisible = true; + //! 渲染混合模式 + BlendMode m_BlendMode = BlendMode::NORMAL; + //! 不透明度,范围 [0.0, 1.0] + float m_Opacity = 1.0f; + + //! 专属于此部件的动画参数 + std::map m_Parameters; + + //! 标记局部变换是否需要重新计算 + bool m_TransformDirty = true; + //! 标记包围盒是否需要重新计算。mutable 允许在 const 成员函数中修改它。 + mutable bool m_BoundsDirty = true; + + // ==================== 私有辅助方法 ==================== + /** + * @brief 将变换标记为“脏”,表示局部变换矩阵需要在下次访问前重新计算。 + */ + void markTransformDirty(); + + /** + * @brief 根据 m_Position, m_Rotation, m_Scale, m_Pivot 更新局部变换矩阵 m_LocalTransform。 + */ + void updateLocalTransform(); + + /** + * @brief (保留) 更新包围盒。 + */ + void updateBounds() const; + + /** + * @brief 深拷贝辅助函数,被拷贝构造函数和拷贝赋值运算符调用。 + * @param other 要从中拷贝数据的源对象。 + */ + void deepCopyFrom(const ModelPart& other); + }; + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/AnimationClip.cpp b/Vivid2DRenderer/model/util/AnimationClip.cpp new file mode 100644 index 0000000..04252ac --- /dev/null +++ b/Vivid2DRenderer/model/util/AnimationClip.cpp @@ -0,0 +1,550 @@ +#include "pch.h" +#include "AnimationClip.h" +#include +#include +#include +#include +#include + +namespace Vivid2D::util { + + // ==================== TimeStamp 实现 ==================== + + AnimationClip::TimeStamp AnimationClip::getCurrentTimeMillis() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + } + + // ==================== AnimationEventMarker 实现 ==================== + + AnimationEventMarker::AnimationEventMarker(std::string name, float time, Action action) + : m_name(std::move(name)), m_time(time), m_action(std::move(action)), m_triggered(false) {} + + void AnimationEventMarker::trigger() { + if (!m_triggered && m_action) { + m_action(); + m_triggered = true; + } + } + + void AnimationEventMarker::reset() { + m_triggered = false; + } + + AnimationEventMarker AnimationEventMarker::copy() const { + return AnimationEventMarker(m_name, m_time, m_action); + } + + // ==================== AnimationCurve 实现 ==================== + + AnimationCurve::AnimationCurve(std::string parameterId, float defaultValue) + : m_parameterId(std::move(parameterId)), m_defaultValue(defaultValue) {} + + Keyframe* AnimationCurve::addKeyframe(float time, float value) { + return addKeyframe(time, value, InterpolationType::LINEAR); + } + + Keyframe* AnimationCurve::addKeyframe(float time, float value, InterpolationType interpolation) { + // 1. 移除相同时间的关键帧 + removeKeyframe(time); + + // 2. 创建新关键帧 + auto newKeyframe = std::make_unique(time, value, interpolation); + Keyframe* rawPtr = newKeyframe.get(); + + // 3. 找到插入位置并保持排序 + auto it = std::lower_bound(m_keyframes.begin(), m_keyframes.end(), time, [](const std::unique_ptr& kf, float t) { + return kf->getTime() < t; + }); + + m_keyframes.insert(it, std::move(newKeyframe)); + return rawPtr; + } + + bool AnimationCurve::removeKeyframe(float time) { + bool removed = false; + auto it = std::remove_if(m_keyframes.begin(), m_keyframes.end(), + [time, &removed](const std::unique_ptr& kf) { + if (std::abs(kf->getTime() - time) < 0.0001f) { + removed = true; + return true; + } + return false; + }); + + if (removed) { + m_keyframes.erase(it, m_keyframes.end()); + } + return removed; + } + + Keyframe* AnimationCurve::getKeyframe(float time) { + for (const auto& kf : m_keyframes) { + if (std::abs(kf->getTime() - time) < 0.0001f) { + return kf.get(); + } + } + return nullptr; + } + + float AnimationCurve::sample(float time) const { + if (m_keyframes.empty()) { + return m_defaultValue; + } + + const auto& firstKf = m_keyframes.front(); + if (time <= firstKf->getTime()) { + return firstKf->getValue(); + } + + const auto& lastKf = m_keyframes.back(); + if (time >= lastKf->getTime()) { + return lastKf->getValue(); + } + + // 找到 kf2 + auto kf2_it = std::lower_bound(m_keyframes.begin(), m_keyframes.end(), time, [](const std::unique_ptr& kf, float t) { + return kf->getTime() < t; + }); + + // kf1 是 kf2 的前一个 + const Keyframe& kf2 = **kf2_it; + const Keyframe& kf1 = **std::prev(kf2_it); + + return interpolate(kf1, kf2, time); + } + + float AnimationCurve::interpolate(const Keyframe& kf1, const Keyframe& kf2, float time) const { + float duration = kf2.getTime() - kf1.getTime(); + if (duration < 0.0001f) return kf1.getValue(); + + float t = (time - kf1.getTime()) / duration; + + switch (kf1.getInterpolation()) { + case InterpolationType::LINEAR: + return lerp(kf1.getValue(), kf2.getValue(), t); + case InterpolationType::STEP: + return kf1.getValue(); + case InterpolationType::SMOOTH: + return smoothLerp(kf1.getValue(), kf2.getValue(), t); + case InterpolationType::EASE_IN: + return easeInLerp(kf1.getValue(), kf2.getValue(), t); + case InterpolationType::EASE_OUT: + return easeOutLerp(kf1.getValue(), kf2.getValue(), t); + case InterpolationType::EASE_IN_OUT: + return easeInOutLerp(kf1.getValue(), kf2.getValue(), t); + default: + return kf1.getValue(); + } + } + + float AnimationCurve::lerp(float a, float b, float t) { + return a + (b - a) * t; + } + + float AnimationCurve::smoothLerp(float a, float b, float t) { + float t2 = t * t; + float t3 = t2 * t; + // Hermite Blend Function (0, 0, 1, 0) + return a * (2 * t3 - 3 * t2 + 1) + b * (-2 * t3 + 3 * t2); + } + + float AnimationCurve::easeInLerp(float a, float b, float t) { + return a + (b - a) * (t * t); // Quad In + } + + float AnimationCurve::easeOutLerp(float a, float b, float t) { + return a + (b - a) * (1.0f - (1.0f - t) * (1.0f - t)); // Quad Out + } + + float AnimationCurve::easeInOutLerp(float a, float b, float t) { + if (t < 0.5f) { + return a + (b - a) * (2 * t * t); // Quad In + } + else { + // Quad Out from 0.5 to 1.0, adjusted + return a + (b - a) * (1.0f - (2 * (1.0f - t) * (1.0f - t)) / 2.0f); + } + } + + float* AnimationCurve::getValueRange() { + static float minMax[2]; + if (m_keyframes.empty()) { + minMax[0] = m_defaultValue; + minMax[1] = m_defaultValue; + return minMax; + } + float minVal = (std::numeric_limits::max)(); + float maxVal = (std::numeric_limits::min)(); + for (const auto& kf : m_keyframes) { + minVal = (std::min)(minVal, kf->getValue()); + maxVal = (std::max)(maxVal, kf->getValue()); + } + minMax[0] = minVal; + minMax[1] = maxVal; + return minMax; + } + + AnimationCurve AnimationCurve::copy() const { + return AnimationCurve(*this); + } + + // ==================== AnimationClip 实现 ==================== + + AnimationClip::AnimationClip(std::string name) + : AnimationClip(std::move(name), 1.0f, 60.0f) {} + + AnimationClip::AnimationClip(std::string name, float duration, float fps) + : m_name(std::move(name)), + m_uuid(generate_new_uuid()), + m_duration((duration > 0.0f) ? duration : 0.0f), + m_framesPerSecond((fps > 1.0f) ? fps : 1.0f), + m_looping(true), + m_author("Unknown"), + m_description(""), + m_creationTime(getCurrentTimeMillis()), + m_lastModifiedTime(m_creationTime) {} + + // --- 元数据/时长修改 --- + + void AnimationClip::setDuration(float duration) { + this->m_duration = (duration > 0.0f) ? duration : 0.0f; + markModified(); + } + + void AnimationClip::setFramesPerSecond(float framesPerSecond) { + this->m_framesPerSecond = (framesPerSecond > 1.0f) ? framesPerSecond : 1.0f; + markModified(); + } + + void AnimationClip::setUserData(std::map userData) { + m_userData = std::move(userData); + markModified(); + } + + void AnimationClip::markModified() { + m_lastModifiedTime = getCurrentTimeMillis(); + } + + void AnimationClip::updateDurationIfNeeded(float time) { + if (time > m_duration) { + m_duration = time; + markModified(); + } + } + + AnimationCurve::AnimationCurve(const AnimationCurve& other) + : m_parameterId(other.m_parameterId), m_defaultValue(other.m_defaultValue) + { + // 为 keyframes 预留空间以提高效率 + m_keyframes.reserve(other.m_keyframes.size()); + // 遍历源对象的 keyframes,为每一个 keyframe 创建一个深拷贝 + for (const auto& kf_ptr : other.m_keyframes) { + m_keyframes.push_back(std::make_unique(*kf_ptr)); + } + } + + AnimationCurve& AnimationCurve::operator=(const AnimationCurve& other) { + if (this == &other) { + return *this; + } + m_parameterId = other.m_parameterId; + m_defaultValue = other.m_defaultValue; + m_keyframes.clear(); + m_keyframes.reserve(other.m_keyframes.size()); + for (const auto& kf_ptr : other.m_keyframes) { + m_keyframes.push_back(std::make_unique(*kf_ptr)); + } + return *this; + } + + // --- 曲线管理 --- + + AnimationCurve* AnimationClip::addCurve(const std::string& parameterId) { + return addCurve(parameterId, 0.0f); + } + + AnimationCurve* AnimationClip::addCurve(const std::string& parameterId, float defaultValue) { + auto curve = std::make_unique(parameterId, defaultValue); + AnimationCurve* rawPtr = curve.get(); + m_curves[parameterId] = std::move(curve); + m_defaultValues[parameterId] = defaultValue; + markModified(); + return rawPtr; + } + + AnimationCurve* AnimationClip::getCurve(const std::string& parameterId) { + auto it = m_curves.find(parameterId); + return (it != m_curves.end()) ? it->second.get() : nullptr; + } + + bool AnimationClip::removeCurve(const std::string& parameterId) { + bool removed = m_curves.erase(parameterId) > 0; + if (removed) { + m_defaultValues.erase(parameterId); + markModified(); + } + return removed; + } + + bool AnimationClip::hasCurve(const std::string& parameterId) const { + return m_curves.count(parameterId) > 0; + } + + std::set AnimationClip::getCurveParameterIds() const { + std::set ids; + for (const auto& pair : m_curves) { + ids.insert(pair.first); + } + return ids; + } + + + // --- 关键帧管理 --- + + Keyframe* AnimationClip::addKeyframe(const std::string& parameterId, float time, float value) { + return addKeyframe(parameterId, time, value, InterpolationType::LINEAR); + } + + Keyframe* AnimationClip::addKeyframe(const std::string& parameterId, float time, float value, InterpolationType interpolation) { + AnimationCurve* curve = getOrCreateCurve(parameterId); + Keyframe* keyframe = curve->addKeyframe(time, value, interpolation); + updateDurationIfNeeded(time); + markModified(); + return keyframe; + } + + bool AnimationClip::removeKeyframe(const std::string& parameterId, float time) { + AnimationCurve* curve = getCurve(parameterId); + if (curve != nullptr) { + bool removed = curve->removeKeyframe(time); + if (removed) markModified(); + return removed; + } + return false; + } + + Keyframe* AnimationClip::getKeyframe(const std::string& parameterId, float time) { + AnimationCurve* curve = getCurve(parameterId); + return curve ? curve->getKeyframe(time) : nullptr; + } + + std::vector AnimationClip::getKeyframes(const std::string& parameterId) { + AnimationCurve* curve = getCurve(parameterId); + if (!curve) return {}; + + std::vector kfPtrs; + for (const auto& kf_uptr : curve->getKeyframes()) { + kfPtrs.push_back(kf_uptr.get()); + } + return kfPtrs; + } + + // --- 采样系统 --- + + std::map AnimationClip::sample(float time) { + std::map result; + for (const auto& entry : m_curves) { + result[entry.first] = entry.second->sample(time); + } + return result; + } + + float AnimationClip::sampleParameter(const std::string& parameterId, float time) { + AnimationCurve* curve = getCurve(parameterId); + if (curve) { + return curve->sample(time); + } + // 使用 getOrDefault 逻辑 + auto it = m_defaultValues.find(parameterId); + return (it != m_defaultValues.end()) ? it->second : 0.0f; + } + + std::map AnimationClip::sampleLooped(float time) { + float effectiveTime = time; + if (m_looping && m_duration > 0.0f) { + float numLoops = time / m_duration; + float floorLoops = floor(numLoops); + effectiveTime = time - floorLoops * m_duration; + if (effectiveTime < 0.0f) { + effectiveTime += m_duration; + } + + } + else { + effectiveTime = (time < m_duration) ? time : m_duration; + } + effectiveTime = (effectiveTime > m_duration) ? m_duration : effectiveTime; + return sample(effectiveTime); + } + + // --- 事件标记管理 --- + + AnimationEventMarker* AnimationClip::addEventMarker(const std::string& name, float time) { + return addEventMarker(name, time, nullptr); + } + + AnimationEventMarker* AnimationClip::addEventMarker(const std::string& name, float time, AnimationEventMarker::Action action) { + auto marker = std::make_unique(name, time, std::move(action)); + AnimationEventMarker* rawPtr = marker.get(); + + // 按时间排序插入 + auto it = std::lower_bound(m_eventMarkers.begin(), m_eventMarkers.end(), time, [](const std::unique_ptr& m, float t) { + return m->getTime() < t; + }); + + m_eventMarkers.insert(it, std::move(marker)); + + updateDurationIfNeeded(time); + markModified(); + return rawPtr; + } + + bool AnimationClip::removeEventMarker(const std::string& name) { + bool removed = false; + auto it = std::remove_if(m_eventMarkers.begin(), m_eventMarkers.end(), + [name, &removed](const std::unique_ptr& m) { + if (m->getName() == name) { + removed = true; + return true; + } + return false; + }); + + if (removed) { + m_eventMarkers.erase(it, m_eventMarkers.end()); + markModified(); + } + return removed; + } + + std::vector AnimationClip::getEventMarkersInRange(float startTime, float endTime) { + std::vector result; + for (const auto& marker : m_eventMarkers) { + if (marker->getTime() >= startTime && marker->getTime() <= endTime) { + result.push_back(marker.get()); + } + } + return result; + } + + void AnimationClip::triggerEventMarkers(float time, float tolerance) { + for (const auto& marker : m_eventMarkers) { + if (std::abs(marker->getTime() - time) <= tolerance) { + marker->trigger(); + } + } + } + + void AnimationClip::resetEventMarkers() { + for (const auto& marker : m_eventMarkers) { + marker->reset(); + } + } + + // --- 工具方法 --- + + AnimationCurve* AnimationClip::getOrCreateCurve(const std::string& parameterId) { + // C++ 17 std::map::try_emplace 更好,这里用 computeIfAbsent 的等效逻辑 + auto it = m_curves.find(parameterId); + if (it == m_curves.end()) { + float defaultValue = 0.0f; + auto defaultIt = m_defaultValues.find(parameterId); + if (defaultIt != m_defaultValues.end()) { + defaultValue = defaultIt->second; + } + return addCurve(parameterId, defaultValue); + } + return it->second.get(); + } + + void AnimationClip::merge(const AnimationClip& other) { // other 是引用 + if (this == &other) return; + for (const auto& entry : other.m_curves) { + const std::string& paramId = entry.first; + const AnimationCurve* otherCurve = entry.second.get(); + if (this->hasCurve(paramId)) { + AnimationCurve* thisCurve = this->getCurve(paramId); + for (const auto& keyframePtr : otherCurve->getKeyframes()) { + thisCurve->addKeyframe(keyframePtr->getTime(), keyframePtr->getValue(), + keyframePtr->getInterpolation()); + } + } + else { + this->m_curves[paramId] = std::make_unique(otherCurve->copy()); + this->m_defaultValues[paramId] = otherCurve->getDefaultValue(); + } + } + for (const auto& markerPtr : other.m_eventMarkers) { + this->addEventMarker(markerPtr->getName() + "_merged", markerPtr->getTime(), + markerPtr->getAction()); + } + this->m_duration = (this->m_duration > other.m_duration) ? this->m_duration : other.m_duration; + markModified(); + } + + AnimationClip AnimationClip::copy() const { + AnimationClip copy(m_name + "_copy", m_duration, m_framesPerSecond); + copy.m_looping = this->m_looping; + copy.m_author = this->m_author; + copy.m_description = this->m_description; + copy.m_creationTime = this->m_creationTime; // 保持创建时间 + + // 深拷贝曲线 + for (const auto& entry : this->m_curves) { + copy.m_curves[entry.first] = std::make_unique(entry.second->copy()); + } + + // 拷贝默认值 + copy.m_defaultValues = this->m_defaultValues; + + // 深拷贝事件标记 + for (const auto& markerPtr : this->m_eventMarkers) { + copy.m_eventMarkers.push_back(std::make_unique(markerPtr->copy())); + } + + // 拷贝用户数据 + copy.m_userData = this->m_userData; + + return copy; + } + + int AnimationClip::getFrameCount() const { + return static_cast(std::ceil(m_duration * m_framesPerSecond)); + } + + int AnimationClip::timeToFrame(float time) const { + return static_cast(time * m_framesPerSecond); + } + + float AnimationClip::frameToTime(int frame) const { + return static_cast(frame) / m_framesPerSecond; + } + + bool AnimationClip::isTimeInRange(float time) const { + return time >= 0.0f && time <= m_duration; + } + + std::map> AnimationClip::getValueBounds() { + std::map> bounds; + + for (const auto& entry : m_curves) { + float* minMax = entry.second->getValueRange(); + bounds[entry.first] = { minMax[0], minMax[1] }; + } + + return bounds; + } + + // --- Getter 封装 --- + + const std::map>& AnimationClip::getCurves() const { + return m_curves; + } + + const std::vector>& AnimationClip::getEventMarkers() const { + return m_eventMarkers; + } + +} // namespace Vivid2D::util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/AnimationClip.h b/Vivid2DRenderer/model/util/AnimationClip.h new file mode 100644 index 0000000..ae4992f --- /dev/null +++ b/Vivid2DRenderer/model/util/AnimationClip.h @@ -0,0 +1,186 @@ +#pragma once + +#ifndef ANIMATION_CLIP_H +#define ANIMATION_CLIP_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AnimationLayer.h" + +namespace Vivid2D::util { + // 事件标记类 + class AnimationEventMarker { + public: + using Action = std::function; + + AnimationEventMarker(std::string name, float time, Action action); + + void trigger(); + void reset(); + AnimationEventMarker copy() const; + + // Getter方法 + const std::string& getName() const { return m_name; } + float getTime() const { return m_time; } + const Action& getAction() const { return m_action; } + bool isTriggered() const { return m_triggered; } + + AnimationEventMarker(AnimationEventMarker&&) noexcept = default; + AnimationEventMarker& operator=(AnimationEventMarker&&) noexcept = default; + private: + std::string m_name; + float m_time; + Action m_action; // C++ 中使用 std::function 替代 Runnable + bool m_triggered; + }; + + // 动画曲线类 + class VIVID_2D_MYDLL_API AnimationCurve { + public: + AnimationCurve(std::string parameterId, float defaultValue); + + AnimationCurve(const AnimationCurve& other); + AnimationCurve& operator=(const AnimationCurve& other); + AnimationCurve(AnimationCurve&& other) noexcept = default; + AnimationCurve& operator=(AnimationCurve&& other) noexcept = default; + + Keyframe* addKeyframe(float time, float value); + Keyframe* addKeyframe(float time, float value, InterpolationType interpolation); + bool removeKeyframe(float time); + Keyframe* getKeyframe(float time); + + float sample(float time) const; + float* getValueRange(); // [min, max] + AnimationCurve copy() const; + + // Getter方法 + const std::string& getParameterId() const { return m_parameterId; } + const std::vector>& getKeyframes() const { return m_keyframes; } + float getDefaultValue() const { return m_defaultValue; } + + private: + float interpolate(const Keyframe& kf1, const Keyframe& kf2, float time) const; + // 辅助插值函数 + static float lerp(float a, float b, float t); + static float smoothLerp(float a, float b, float t); + static float easeInLerp(float a, float b, float t); + static float easeOutLerp(float a, float b, float t); + static float easeInOutLerp(float a, float b, float t); + + std::string m_parameterId; + std::vector> m_keyframes; + float m_defaultValue; + }; + + + // ==================== AnimationClip 类 ==================== + + class AnimationClip { + public: + using UUID = uuids::uuid; + using TimeStamp = long long; + + // ==================== 构造器 ==================== + explicit AnimationClip(std::string name); + AnimationClip(std::string name, float duration, float fps); + + // ==================== 曲线管理 ==================== + AnimationCurve* addCurve(const std::string& parameterId); + AnimationCurve* addCurve(const std::string& parameterId, float defaultValue); + AnimationCurve* getCurve(const std::string& parameterId); + bool removeCurve(const std::string& parameterId); + bool hasCurve(const std::string& parameterId) const; + std::set getCurveParameterIds() const; + + // ==================== 关键帧管理 ==================== + Keyframe* addKeyframe(const std::string& parameterId, float time, float value); + Keyframe* addKeyframe(const std::string& parameterId, float time, float value, InterpolationType interpolation); + bool removeKeyframe(const std::string& parameterId, float time); + Keyframe* getKeyframe(const std::string& parameterId, float time); + std::vector getKeyframes(const std::string& parameterId); + + // ==================== 采样系统 ==================== + std::map sample(float time); + float sampleParameter(const std::string& parameterId, float time); + std::map sampleLooped(float time); + + // ==================== 事件标记管理 ==================== + AnimationEventMarker* addEventMarker(const std::string& name, float time); + AnimationEventMarker* addEventMarker(const std::string& name, float time, AnimationEventMarker::Action action); + bool removeEventMarker(const std::string& name); + std::vector getEventMarkersInRange(float startTime, float endTime); + void triggerEventMarkers(float time, float tolerance); + void resetEventMarkers(); + + // ==================== 工具方法 ==================== + void merge(const AnimationClip& other); + AnimationClip copy() const; + int getFrameCount() const; + int timeToFrame(float time) const; + float frameToTime(int frame) const; + bool isTimeInRange(float time) const; + std::map> getValueBounds(); // [min, max] + + // ==================== Getter/Setter ==================== + const std::string& getName() const { return m_name; } + const UUID& getUuid() const { return m_uuid; } + float getDuration() const { return m_duration; } + void setDuration(float duration); + float getFramesPerSecond() const { return m_framesPerSecond; } + void setFramesPerSecond(float framesPerSecond); + bool isLooping() const { return m_looping; } + void setLooping(bool looping) { m_looping = looping; } + + const std::map>& getCurves() const; + const std::vector>& getEventMarkers() const; + const std::map& getDefaultValues() const { return m_defaultValues; } + + const std::string& getAuthor() const { return m_author; } + void setAuthor(std::string author) { m_author = std::move(author); } + const std::string& getDescription() const { return m_description; } + void setDescription(std::string description) { m_description = std::move(description); } + TimeStamp getCreationTime() const { return m_creationTime; } + TimeStamp getLastModifiedTime() const { return m_lastModifiedTime; } + const std::map& getUserData() const { return m_userData; } + void setUserData(std::map userData); + + // ==================== Object 方法 ==================== + bool operator==(const AnimationClip& other) const { return m_uuid == other.m_uuid; } + + private: + // ==================== 私有成员变量 ==================== + std::string m_name; + UUID m_uuid; + float m_duration; + float m_framesPerSecond; + bool m_looping; + + std::map> m_curves; + std::vector> m_eventMarkers; + std::map m_defaultValues; + + std::string m_author; + std::string m_description; + TimeStamp m_creationTime; + TimeStamp m_lastModifiedTime; + std::map m_userData; + + // ==================== 私有辅助方法 ==================== + AnimationCurve* getOrCreateCurve(const std::string& parameterId); + void updateDurationIfNeeded(float time); + void markModified(); + static TimeStamp getCurrentTimeMillis(); + }; + +} // namespace Vivid2D::util + +#endif // ANIMATION_CLIP_H diff --git a/Vivid2DRenderer/model/util/AnimationLayer.cpp b/Vivid2DRenderer/model/util/AnimationLayer.cpp new file mode 100644 index 0000000..fc9e6db --- /dev/null +++ b/Vivid2DRenderer/model/util/AnimationLayer.cpp @@ -0,0 +1,513 @@ +#include "pch.h" +#include "AnimationLayer.h" + +#include "../Model2D.h" +#include "AnimationClip.h" +#include "PhysicsSystem.h" + +namespace Vivid2D::util { + + // ==================== AnimationEvent 实现 ==================== + + AnimationEvent::AnimationEvent(std::string name, float time, Action action) + : m_name(std::move(name)), m_time(time), m_action(std::move(action)), m_triggered(false) {} + + void AnimationEvent::trigger() { + if (!m_triggered && m_action) { + m_action(); + m_triggered = true; + } + } + + void AnimationEvent::reset() { + m_triggered = false; + } + + + // ==================== AnimationTrack 实现 ==================== + + AnimationTrack::AnimationTrack(std::string parameterId) + : m_parameterId(std::move(parameterId)), m_enabled(true), m_interpolation(InterpolationType::LINEAR) {} + + void AnimationTrack::addKeyframe(float time, float value) { + addKeyframe(time, value, m_interpolation); + } + + void AnimationTrack::addKeyframe(float time, float value, InterpolationType interpolation) { + Keyframe keyframe(time, value, interpolation); + + // 查找插入位置并保持排序 + auto it = std::lower_bound(m_keyframes.begin(), m_keyframes.end(), time, [](const Keyframe& kf, float t) { + return kf.getTime() < t; + }); + m_keyframes.insert(it, keyframe); + } + + float AnimationTrack::sample(float time) const { + if (m_keyframes.empty()) { + return 0.0f; + } + + const auto& lastKf = m_keyframes.back(); + if (time >= lastKf.getTime()) { + return lastKf.getValue(); + } + + const auto& firstKf = m_keyframes.front(); + if (time <= firstKf.getTime()) { + return firstKf.getValue(); + } + + // 找到 kf1 + auto kf2_it = std::lower_bound(m_keyframes.begin(), m_keyframes.end(), time, [](const Keyframe& kf, float t) { + return kf.getTime() < t; + }); + + if (kf2_it == m_keyframes.begin()) { + return firstKf.getValue(); // 应该被上面的检查捕获 + } + + const Keyframe& kf2 = *kf2_it; + const Keyframe& kf1 = *std::prev(kf2_it); + + return interpolate(kf1, kf2, time); + } + + float AnimationTrack::interpolate(const Keyframe& kf1, const Keyframe& kf2, float time) const { + float duration = kf2.getTime() - kf1.getTime(); + if (duration < 0.0001f) return kf1.getValue(); + + float t = (time - kf1.getTime()) / duration; + + switch (kf1.getInterpolation()) { + case InterpolationType::LINEAR: + return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * t; + case InterpolationType::STEP: + return kf1.getValue(); + case InterpolationType::SMOOTH: { + float t2 = t * t; + float t3 = t2 * t; + // Hermite Blend Function (Ease-in-out) + return kf1.getValue() * (2 * t3 - 3 * t2 + 1) + + kf2.getValue() * (-2 * t3 + 3 * t2); + } + case InterpolationType::EASE_IN: + return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * (t * t); + case InterpolationType::EASE_OUT: + return kf1.getValue() + (kf2.getValue() - kf1.getValue()) * (1.0f - (1.0f - t) * (1.0f - t)); + default: + return kf1.getValue(); + } + } + + AnimationTrack AnimationTrack::copy() const { + AnimationTrack copy(m_parameterId); + copy.m_enabled = m_enabled; + copy.m_interpolation = m_interpolation; + for (const auto& kf : m_keyframes) { + copy.m_keyframes.push_back(kf.copy()); + } + return copy; + } + + + // ==================== AnimationLayer 实现 ==================== + + AnimationLayer::AnimationLayer(AnimationLayer&& other) noexcept + : m_name(std::move(other.m_name)), + m_uuid(other.m_uuid), + m_weight(other.m_weight), + m_enabled(other.m_enabled), + m_blendMode(other.m_blendMode), + m_priority(other.m_priority), + m_tracks(std::move(other.m_tracks)), + m_clips(std::move(other.m_clips)), + m_currentClip(other.m_currentClip), + m_playbackSpeed(other.m_playbackSpeed), + m_looping(other.m_looping), + m_currentTime(other.m_currentTime), + m_playing(other.m_playing), + m_paused(other.m_paused), + m_parameterOverrides(std::move(other.m_parameterOverrides)), + m_eventListeners(std::move(other.m_eventListeners)), + m_events(std::move(other.m_events)) + { + other.m_currentClip = nullptr; + } + + AnimationLayer& AnimationLayer::operator=(AnimationLayer&& other) noexcept { + if (this != &other) { + m_name = std::move(other.m_name); + m_uuid = other.m_uuid; + m_weight = other.m_weight; + m_enabled = other.m_enabled; + m_blendMode = other.m_blendMode; + m_priority = other.m_priority; + m_tracks = std::move(other.m_tracks); + m_clips = std::move(other.m_clips); + m_currentClip = other.m_currentClip; + m_playbackSpeed = other.m_playbackSpeed; + m_looping = other.m_looping; + m_currentTime = other.m_currentTime; + m_playing = other.m_playing; + m_paused = other.m_paused; + m_parameterOverrides = std::move(other.m_parameterOverrides); + m_eventListeners = std::move(other.m_eventListeners); + m_events = std::move(other.m_events); + + other.m_currentClip = nullptr; + } + return *this; + } + + AnimationLayer::AnimationLayer(std::string name) + : AnimationLayer(std::move(name), 1.0f) {} + + AnimationLayer::AnimationLayer(std::string name, float weight) + : m_name(std::move(name)), + m_uuid(generate_new_uuid()), + m_weight(std::max(0.0f, std::min(1.0f, weight))), + m_enabled(true), + m_blendMode(BlendMode::OVERRIDE), + m_priority(0), + m_currentClip(nullptr), + m_playbackSpeed(1.0f), + m_looping(true), + m_currentTime(0.0f), + m_playing(false), + m_paused(false) {} + + void AnimationLayer::setWeight(float weight) { + m_weight = std::max(0.0f, std::min(1.0f, weight)); + } + + void AnimationLayer::setPlaybackSpeed(float playbackSpeed) { + m_playbackSpeed = std::max(0.0f, playbackSpeed); + } + + // --- 轨道管理 --- + + std::unique_ptr AnimationLayer::addTrack(const std::string& parameterId) { + auto track = std::make_unique(parameterId); + AnimationTrack* rawPtr = track.get(); + m_tracks[parameterId] = std::move(track); + return std::unique_ptr(rawPtr); // 返回一个临时的 unique_ptr 副本,但所有权在 m_tracks + } + + AnimationTrack* AnimationLayer::getTrack(const std::string& parameterId) const { + auto it = m_tracks.find(parameterId); + return (it != m_tracks.end()) ? it->second.get() : nullptr; + } + + bool AnimationLayer::removeTrack(const std::string& parameterId) { + return m_tracks.erase(parameterId) > 0; + } + + bool AnimationLayer::hasTrack(const std::string& parameterId) const { + return m_tracks.count(parameterId) > 0; + } + + // --- 剪辑管理 --- + + void AnimationLayer::addClip(AnimationClip* clip) { + if (clip) { + m_clips.push_back(clip); + } + } + + bool AnimationLayer::removeClip(AnimationClip* clip) { + auto it = std::remove(m_clips.begin(), m_clips.end(), clip); + if (it != m_clips.end()) { + m_clips.erase(it, m_clips.end()); + return true; + } + return false; + } + + void AnimationLayer::playClip(const std::string& clipName) { + // 假设 AnimationClip 有 getName() 方法 + AnimationClip* clipToPlay = nullptr; + for (AnimationClip* clip : m_clips) { + if (clip->getName() == clipName) { + clipToPlay = clip; + break; + } + } + if (clipToPlay) { + playClip(clipToPlay); + } + else { + throw std::invalid_argument("Animation clip not found: " + clipName); + } + } + + void AnimationLayer::playClip(AnimationClip* clip) { + m_currentClip = clip; + m_currentTime = 0.0f; + m_playing = true; + m_paused = false; + + resetEvents(); // 重置事件状态 + notifyAnimationStarted(clip); + } + + void AnimationLayer::stop() { + m_playing = false; + m_paused = false; + m_currentTime = 0.0f; + + if (m_currentClip) { + notifyAnimationStopped(m_currentClip); + } + } + + void AnimationLayer::pause() { + if (m_playing && !m_paused) { + m_paused = true; + notifyAnimationPaused(m_currentClip); + } + } + + void AnimationLayer::resume() { + if (m_playing && m_paused) { + m_paused = false; + notifyAnimationResumed(m_currentClip); + } + } + + // --- 更新系统 --- + + void AnimationLayer::update(float deltaTime, Vivid2D::Model2D* model) { + if (!m_enabled || m_weight <= 0.0f) { + return; + } + + // 更新播放时间 + if (m_playing && !m_paused) { + m_currentTime += deltaTime * m_playbackSpeed; + + // 检查循环/完成 + if (m_currentClip && m_currentTime >= m_currentClip->getDuration()) { + if (m_looping) { + // C++ 中 fmod 可以处理浮点数取模 + m_currentTime = std::fmod(m_currentTime, m_currentClip->getDuration()); + notifyAnimationLooped(m_currentClip); + } + else { + stop(); + notifyAnimationCompleted(m_currentClip); + return; + } + } + + // 检查事件 + checkEvents(); + } + + // 应用动画 + applyAnimation(model); + } + + void AnimationLayer::applyAnimation(Vivid2D::Model2D* model) { + if (m_currentClip) { + applyClipAnimation(model); + } + else { + applyTrackAnimation(model); + } + } + + void AnimationLayer::applyClipAnimation(Vivid2D::Model2D* model) { + // 假设 AnimationClip::sample 返回一个 std::map + std::map animatedValues = m_currentClip->sample(m_currentTime); + + for (const auto& entry : animatedValues) { + const std::string& paramId = entry.first; + float value = entry.second; + + float finalValue = applyBlending(model, paramId, value); + + model->setParameterValue(paramId, finalValue); + } + } + + void AnimationLayer::applyTrackAnimation(Vivid2D::Model2D* model) { + for (const auto& pair : m_tracks) { + const AnimationTrack* track = pair.second.get(); + if (track->isEnabled()) { + float value = track->sample(m_currentTime); + const std::string& paramId = track->getParameterId(); + + float finalValue = applyBlending(model, paramId, value); + + model->setParameterValue(paramId, finalValue); + } + } + } + + float AnimationLayer::applyBlending(Vivid2D::Model2D* model, const std::string& paramId, float newValue) { + float currentValue = model->getParameterValue(paramId); + + auto overrideIt = m_parameterOverrides.find(paramId); + if (overrideIt != m_parameterOverrides.end()) { + return overrideIt->second; // 覆盖模式优先 + } + + switch (m_blendMode) { + case BlendMode::OVERRIDE: + return blendOverride(currentValue, newValue); + case BlendMode::ADDITIVE: + return blendAdditive(currentValue, newValue); + case BlendMode::MULTIPLICATIVE: + return blendMultiplicative(currentValue, newValue); + case BlendMode::AVERAGE: + return blendAverage(currentValue, newValue); + default: + return newValue; + } + } + + float AnimationLayer::blendOverride(float current, float target) { + return current + (target - current) * m_weight; + } + + float AnimationLayer::blendAdditive(float current, float target) { + return current + target * m_weight; + } + + float AnimationLayer::blendMultiplicative(float current, float target) { + return current * (1.0f + (target / current - 1.0f) * m_weight); // 假设 target/current 是一个比例因子 + } + + float AnimationLayer::blendAverage(float current, float target) { + return (current * (1.0f - m_weight)) + (target * m_weight); + } + + // --- 事件系统 --- + + void AnimationLayer::addEvent(const std::string& eventName, float time, AnimationEvent::Action action) { + auto event = std::make_unique(eventName, time, std::move(action)); + m_events[eventName].push_back(std::move(event)); + } + + void AnimationLayer::checkEvents() { + if (!m_currentClip) return; + + for (auto& pair : m_events) { + for (auto& eventPtr : pair.second) { + if (!eventPtr->isTriggered() && m_currentTime >= eventPtr->getTime()) { + eventPtr->trigger(); + notifyEventTriggered(eventPtr.get()); + } + } + } + } + + void AnimationLayer::resetEvents() { + for (auto& pair : m_events) { + for (auto& eventPtr : pair.second) { + eventPtr->reset(); + } + } + } + + void AnimationLayer::addEventListener(AnimationEventListener* listener) { + if (listener) { + m_eventListeners.push_back(listener); + } + } + + bool AnimationLayer::removeEventListener(AnimationEventListener* listener) { + auto it = std::remove(m_eventListeners.begin(), m_eventListeners.end(), listener); + if (it != m_eventListeners.end()) { + m_eventListeners.erase(it, m_eventListeners.end()); + return true; + } + return false; + } + + void AnimationLayer::notifyAnimationStarted(AnimationClip* clip) { + for (auto listener : m_eventListeners) { + listener->onAnimationStarted(this, clip); + } + } + // ... 其他 notify 函数实现类似 ... + void AnimationLayer::notifyAnimationStopped(AnimationClip* clip) { + for (auto listener : m_eventListeners) { listener->onAnimationStopped(this, clip); } + } + void AnimationLayer::notifyAnimationPaused(AnimationClip* clip) { + for (auto listener : m_eventListeners) { listener->onAnimationPaused(this, clip); } + } + void AnimationLayer::notifyAnimationResumed(AnimationClip* clip) { + for (auto listener : m_eventListeners) { listener->onAnimationResumed(this, clip); } + } + void AnimationLayer::notifyAnimationCompleted(AnimationClip* clip) { + for (auto listener : m_eventListeners) { listener->onAnimationCompleted(this, clip); } + } + void AnimationLayer::notifyAnimationLooped(AnimationClip* clip) { + for (auto listener : m_eventListeners) { listener->onAnimationLooped(this, clip); } + } + void AnimationLayer::notifyEventTriggered(AnimationEvent* event) { + for (auto listener : m_eventListeners) { listener->onEventTriggered(this, event); } + } + + // --- 参数覆盖 --- + + void AnimationLayer::setParameterOverride(const std::string& parameterId, float value) { + m_parameterOverrides[parameterId] = value; + } + + void AnimationLayer::clearParameterOverride(const std::string& parameterId) { + m_parameterOverrides.erase(parameterId); + } + + void AnimationLayer::clearAllOverrides() { + m_parameterOverrides.clear(); + } + + // --- 工具方法 --- + + float AnimationLayer::getProgress() const { + // 假设 AnimationClip::getDuration() 存在 + if (!m_currentClip || m_currentClip->getDuration() == 0.0f) { + return 0.0f; + } + return m_currentTime / m_currentClip->getDuration(); + } + + void AnimationLayer::setProgress(float progress) { + if (m_currentClip) { + m_currentTime = progress * m_currentClip->getDuration(); + } + } + + void AnimationLayer::seek(float time) { + m_currentTime = std::max(0.0f, time); + if (m_currentClip) { + m_currentTime = std::min(m_currentTime, m_currentClip->getDuration()); + } + } + + AnimationLayer AnimationLayer::copy() const { + AnimationLayer copy(m_name + "_copy", m_weight); + copy.m_enabled = m_enabled; + copy.m_blendMode = m_blendMode; + copy.m_priority = m_priority; + copy.m_playbackSpeed = m_playbackSpeed; + copy.m_looping = m_looping; + + // 拷贝轨道 (深拷贝) + for (const auto& pair : m_tracks) { + copy.m_tracks[pair.first] = std::make_unique(pair.second->copy()); + } + + // 拷贝剪辑(浅拷贝,引用) + copy.m_clips = m_clips; + + // 拷贝参数覆盖 + copy.m_parameterOverrides = m_parameterOverrides; + + return copy; + } + +} // namespace Vivid2D::util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/AnimationLayer.h b/Vivid2DRenderer/model/util/AnimationLayer.h new file mode 100644 index 0000000..163f143 --- /dev/null +++ b/Vivid2DRenderer/model/util/AnimationLayer.h @@ -0,0 +1,237 @@ +#pragma once + +#ifndef ANIMATION_LAYER_H +#define ANIMATION_LAYER_H + +#include +#include +#include +#include +#include +#include + +namespace Vivid2D { + class Model2D; + namespace Render::Texture { + class Texture; + } +} + +namespace Vivid2D::util { + + // ==================== 前置声明/内部类声明 ==================== + class AnimationClip; // 假设这是一个独立的类 + class AnimationLayer; + + // 接口和适配器 (C++ 中使用虚基类) + class AnimationEventListener; + + // 关键帧和插值 + enum class InterpolationType { + LINEAR, STEP, SMOOTH, EASE_IN, EASE_OUT, EASE_IN_OUT + }; + + class VIVID_2D_MYDLL_API Keyframe { + public: + Keyframe(float time, float value, InterpolationType interpolation) + : m_time(time), m_value(value), m_interpolation(interpolation) {} + + Keyframe copy() const { return Keyframe(m_time, m_value, m_interpolation); } + + float getTime() const { return m_time; } + float getValue() const { return m_value; } + InterpolationType getInterpolation() const { return m_interpolation; } + private: + float m_time; + float m_value; + InterpolationType m_interpolation; + }; + + // 动画轨道类 (AnimationTrack) + class VIVID_2D_MYDLL_API AnimationTrack { + public: + AnimationTrack(std::string parameterId); + + void addKeyframe(float time, float value); + void addKeyframe(float time, float value, InterpolationType interpolation); + float sample(float time) const; + + AnimationTrack copy() const; + + // Getters & Setters + const std::string& getParameterId() const { return m_parameterId; } + const std::vector& getKeyframes() const { return m_keyframes; } + bool isEnabled() const { return m_enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + InterpolationType getInterpolation() const { return m_interpolation; } + void setInterpolation(InterpolationType interpolation) { m_interpolation = interpolation; } + + private: + float interpolate(const Keyframe& kf1, const Keyframe& kf2, float time) const; + std::string m_parameterId; + std::vector m_keyframes; + bool m_enabled; + InterpolationType m_interpolation; + }; + + // 动画事件类 (AnimationEvent) + class VIVID_2D_MYDLL_API AnimationEvent { + public: + using Action = std::function; + + AnimationEvent(std::string name, float time, Action action); + + void trigger(); + void reset(); + + // Getters + const std::string& getName() const { return m_name; } + float getTime() const { return m_time; } + bool isTriggered() const { return m_triggered; } + + private: + std::string m_name; + float m_time; + Action m_action; + bool m_triggered; + }; + + // 动画事件监听器接口 (AnimationEventListener - 虚基类) + class VIVID_2D_MYDLL_API AnimationEventListener { + public: + virtual ~AnimationEventListener() = default; + virtual void onAnimationStarted(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onAnimationStopped(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onAnimationPaused(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onAnimationResumed(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onAnimationCompleted(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onAnimationLooped(AnimationLayer* layer, AnimationClip* clip) {} + virtual void onEventTriggered(AnimationLayer* layer, AnimationEvent* event) {} + }; + + // ==================== AnimationLayer 类 ==================== + + class VIVID_2D_MYDLL_API AnimationLayer { + public: + // 混合模式枚举 + enum class BlendMode { + OVERRIDE, ADDITIVE, MULTIPLICATIVE, AVERAGE + }; + + using UUID = uuids::uuid; + AnimationLayer(const AnimationLayer& other) = delete; + AnimationLayer& operator=(const AnimationLayer& other) = delete; + AnimationLayer(AnimationLayer&& other) noexcept; + AnimationLayer& operator=(AnimationLayer&& other) noexcept; + + // ==================== 构造器 ==================== + explicit AnimationLayer(std::string name); + AnimationLayer(std::string name, float weight); + + // ==================== 轨道管理 ==================== + std::unique_ptr addTrack(const std::string& parameterId); + AnimationTrack* getTrack(const std::string& parameterId) const; + bool removeTrack(const std::string& parameterId); + bool hasTrack(const std::string& parameterId) const; + + // ==================== 剪辑管理 ==================== + void addClip(AnimationClip* clip); // 使用裸指针或 shared_ptr, 假定 Model2D 管理 Clips + bool removeClip(AnimationClip* clip); + void playClip(const std::string& clipName); + void playClip(AnimationClip* clip); + void stop(); + void pause(); + void resume(); + + // ==================== 更新系统 ==================== + void update(float deltaTime, Model2D* model); + + // ==================== 事件系统 ==================== + void addEvent(const std::string& eventName, float time, AnimationEvent::Action action); + void resetEvents(); + void addEventListener(AnimationEventListener* listener); // 裸指针,不拥有所有权 + bool removeEventListener(AnimationEventListener* listener); + + // ==================== 参数覆盖 ==================== + void setParameterOverride(const std::string& parameterId, float value); + void clearParameterOverride(const std::string& parameterId); + void clearAllOverrides(); + + // ==================== 工具方法 ==================== + float getProgress() const; + void setProgress(float progress); + void seek(float time); + AnimationLayer copy() const; + + // ==================== Getter/Setter ==================== + const std::string& getName() const { return m_name; } + const UUID& getUuid() const { return m_uuid; } + float getWeight() const { return m_weight; } + void setWeight(float weight); + bool isEnabled() const { return m_enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + BlendMode getBlendMode() const { return m_blendMode; } + void setBlendMode(BlendMode blendMode) { m_blendMode = blendMode; } + int getPriority() const { return m_priority; } + void setPriority(int priority) { m_priority = priority; } + const std::map>& getTracks() const { return m_tracks; } + const std::vector& getClips() const { return m_clips; } + AnimationClip* getCurrentClip() const { return m_currentClip; } + float getPlaybackSpeed() const { return m_playbackSpeed; } + void setPlaybackSpeed(float playbackSpeed); + bool isLooping() const { return m_looping; } + void setLooping(bool looping) { m_looping = looping; } + float getCurrentTime() const { return m_currentTime; } + bool isPlaying() const { return m_playing; } + bool isPaused() const { return m_paused; } + const std::map& getParameterOverrides() const { return m_parameterOverrides; } + + private: + // ==================== 私有成员变量 ==================== + std::string m_name; + UUID m_uuid; + float m_weight; + bool m_enabled; + BlendMode m_blendMode; + int m_priority; + + std::map> m_tracks; + std::vector m_clips; // 假设 AnimationClip 是共享的,使用裸指针 + AnimationClip* m_currentClip = nullptr; + float m_playbackSpeed; + bool m_looping; + + float m_currentTime; + bool m_playing; + bool m_paused; + std::map m_parameterOverrides; + + std::vector m_eventListeners; + std::map>> m_events; + + // ==================== 私有辅助方法 ==================== + void checkEvents(); + void applyAnimation(Vivid2D::Model2D* model); + void applyClipAnimation(Vivid2D::Model2D* model); + void applyTrackAnimation(Vivid2D::Model2D* model); + float applyBlending(Vivid2D::Model2D* model, const std::string& paramId, float newValue); + + // 混合函数 + float blendOverride(float current, float target); + float blendAdditive(float current, float target); + float blendMultiplicative(float current, float target); + float blendAverage(float current, float target); + + // 事件通知 + void notifyAnimationStarted(AnimationClip* clip); + void notifyAnimationStopped(AnimationClip* clip); + void notifyAnimationPaused(AnimationClip* clip); + void notifyAnimationResumed(AnimationClip* clip); + void notifyAnimationCompleted(AnimationClip* clip); + void notifyAnimationLooped(AnimationClip* clip); + void notifyEventTriggered(AnimationEvent* event); + }; + +} // namespace Vivid2D::util + +#endif // ANIMATION_LAYER_H diff --git a/Vivid2DRenderer/model/util/BoundingBox.cpp b/Vivid2DRenderer/model/util/BoundingBox.cpp new file mode 100644 index 0000000..6b9f919 --- /dev/null +++ b/Vivid2DRenderer/model/util/BoundingBox.cpp @@ -0,0 +1,358 @@ +#include "pch.h" +#include "BoundingBox.h" +#include +#include +#include +#include +#include +#include + + +namespace Vivid2D::util { + + // ==================== 构造函数 ==================== + + BoundingBox::BoundingBox() { + reset(); + } + + BoundingBox::BoundingBox(float minX, float minY, float maxX, float maxY) { + set(minX, minY, maxX, maxY); + } + + BoundingBox::BoundingBox(const glm::vec2& point1, const glm::vec2& point2) { + set(point1, point2); + } + + BoundingBox::BoundingBox(const BoundingBox& other) { + set(other); + } + + BoundingBox::BoundingBox(const std::vector& points) { + set(points); + } + + BoundingBox::BoundingBox(const std::vector& vertices) { + set(vertices); + } + + + // ==================== 设置方法 ==================== + + void BoundingBox::reset() { + m_Min = glm::vec2(std::numeric_limits::max()); + m_Max = glm::vec2(-std::numeric_limits::max()); // Or std::numeric_limits::lowest() + m_IsValid = false; + } + + void BoundingBox::set(float minX, float minY, float maxX, float maxY) { + if (minX > maxX || minY > maxY) { + throw std::invalid_argument("Min values must be less than or equal to max values"); + } + m_Min = glm::vec2(minX, minY); + m_Max = glm::vec2(maxX, maxY); + m_IsValid = true; + } + + void BoundingBox::set(const glm::vec2& point1, const glm::vec2& point2) { + reset(); + expand(point1); + expand(point2); + } + + void BoundingBox::set(const BoundingBox& other) { + m_Min = other.m_Min; + m_Max = other.m_Max; + m_IsValid = other.m_IsValid; + } + + void BoundingBox::set(const std::vector& points) { + reset(); + for (const auto& point : points) { + expand(point); + } + } + + void BoundingBox::set(const std::vector& vertices) { + reset(); + if (vertices.size() % 2 != 0) { + throw std::invalid_argument("Vertices array must have an even number of elements"); + } + for (size_t i = 0; i < vertices.size(); i += 2) { + expand(vertices[i], vertices[i + 1]); + } + } + + // ==================== 扩展方法 ==================== + + void BoundingBox::expand(float x, float y) { + if (!m_IsValid) { + m_Min = glm::vec2(x, y); + m_Max = glm::vec2(x, y); + m_IsValid = true; + } + else { + m_Min.x = std::min(m_Min.x, x); + m_Min.y = std::min(m_Min.y, y); + m_Max.x = std::max(m_Max.x, x); + m_Max.y = std::max(m_Max.y, y); + } + } + + void BoundingBox::expand(const glm::vec2& point) { + expand(point.x, point.y); + } + + void BoundingBox::expand(const BoundingBox& other) { + if (!other.m_IsValid) return; + if (!m_IsValid) { + set(other); + } + else { + expand(other.m_Min); + expand(other.m_Max); + } + } + + void BoundingBox::expand(const std::vector& points) { + for (const auto& point : points) { + expand(point); + } + } + + void BoundingBox::expand(const std::vector& vertices) { + if (vertices.size() % 2 != 0) { + throw std::invalid_argument("Vertices array must have an even number of elements"); + } + for (size_t i = 0; i < vertices.size(); i += 2) { + expand(vertices[i], vertices[i + 1]); + } + } + + // ==================== 转换方法 ==================== + + BoundingBox BoundingBox::transform(const glm::mat3& matrix) const { + if (!m_IsValid) return BoundingBox(); + + auto corners = getCorners(); + BoundingBox result; + + for (const auto& corner : corners) { + // Transform a 2D point using a 3x3 matrix by using homogeneous coordinates (x, y, 1) + glm::vec3 transformed = matrix * glm::vec3(corner, 1.0f); + result.expand(glm::vec2(transformed)); + } + return result; + } + + BoundingBox BoundingBox::translate(float dx, float dy) const { + if (!m_IsValid) return BoundingBox(); + return BoundingBox(m_Min.x + dx, m_Min.y + dy, m_Max.x + dx, m_Max.y + dy); + } + + BoundingBox BoundingBox::translate(const glm::vec2& translation) const { + return translate(translation.x, translation.y); + } + + BoundingBox BoundingBox::scale(float sx, float sy) const { + if (!m_IsValid) return BoundingBox(); + return BoundingBox(m_Min.x * sx, m_Min.y * sy, m_Max.x * sx, m_Max.y * sy); + } + + BoundingBox BoundingBox::scale(float scale) const { + return this->scale(scale, scale); + } + + BoundingBox BoundingBox::scale(const glm::vec2& scale) const { + return this->scale(scale.x, scale.y); + } + + + // ==================== 几何计算 ==================== + + std::vector BoundingBox::getCorners() const { + if (!m_IsValid) return {}; + return { + {m_Min.x, m_Min.y}, // Bottom-left + {m_Max.x, m_Min.y}, // Bottom-right + {m_Max.x, m_Max.y}, // Top-right + {m_Min.x, m_Max.y} // Top-left + }; + } + + glm::vec2 BoundingBox::getCenter() const { + return m_IsValid ? (m_Min + m_Max) * 0.5f : glm::vec2(0.0f); + } + + glm::vec2 BoundingBox::getSize() const { + return m_IsValid ? m_Max - m_Min : glm::vec2(0.0f); + } + + glm::vec2 BoundingBox::getHalfSize() const { + return m_IsValid ? (m_Max - m_Min) * 0.5f : glm::vec2(0.0f); + } + + float BoundingBox::getArea() const { + if (!m_IsValid) return 0.0f; + glm::vec2 size = getSize(); + return size.x * size.y; + } + + float BoundingBox::getPerimeter() const { + if (!m_IsValid) return 0.0f; + glm::vec2 size = getSize(); + return 2.0f * (size.x + size.y); + } + + // ==================== 交叉测试 ==================== + + bool BoundingBox::contains(float x, float y) const { + if (!m_IsValid) return false; + return x >= m_Min.x && x <= m_Max.x && y >= m_Min.y && y <= m_Max.y; + } + + bool BoundingBox::contains(const glm::vec2& point) const { + return contains(point.x, point.y); + } + + bool BoundingBox::contains(const BoundingBox& other) const { + if (!m_IsValid || !other.m_IsValid) return false; + return other.m_Min.x >= m_Min.x && other.m_Max.x <= m_Max.x && + other.m_Min.y >= m_Min.y && other.m_Max.y <= m_Max.y; + } + + bool BoundingBox::intersects(const BoundingBox& other) const { + if (!m_IsValid || !other.m_IsValid) return false; + return !(other.m_Max.x < m_Min.x || other.m_Min.x > m_Max.x || + other.m_Max.y < m_Min.y || other.m_Min.y > m_Max.y); + } + + BoundingBox BoundingBox::intersection(const BoundingBox& other) const { + if (!intersects(other)) return BoundingBox(); // Return invalid + return BoundingBox( + std::max(m_Min.x, other.m_Min.x), + std::max(m_Min.y, other.m_Min.y), + std::min(m_Max.x, other.m_Max.x), + std::min(m_Max.y, other.m_Max.y) + ); + } + + BoundingBox BoundingBox::getUnion(const BoundingBox& other) const { + BoundingBox result(*this); + result.expand(other); + return result; + } + + + // ==================== 实用方法 ==================== + + BoundingBox BoundingBox::inflate(float amount) const { + return inflate(amount, amount); + } + + BoundingBox BoundingBox::inflate(float dx, float dy) const { + if (!m_IsValid) return BoundingBox(); + return BoundingBox(m_Min.x - dx, m_Min.y - dy, m_Max.x + dx, m_Max.y + dy); + } + + BoundingBox BoundingBox::deflate(float amount) const { + return deflate(amount, amount); + } + + BoundingBox BoundingBox::deflate(float dx, float dy) const { + if (!m_IsValid) return BoundingBox(); + float newMinX = m_Min.x + dx; + float newMinY = m_Min.y + dy; + float newMaxX = m_Max.x - dx; + float newMaxY = m_Max.y - dy; + + if (newMinX > newMaxX || newMinY > newMaxY) { + return BoundingBox(); // Invalid after deflation + } + return BoundingBox(newMinX, newMinY, newMaxX, newMaxY); + } + + BoundingBox BoundingBox::alignToGrid(float gridSize) const { + if (!m_IsValid) return BoundingBox(); + float alignedMinX = std::floor(m_Min.x / gridSize) * gridSize; + float alignedMinY = std::floor(m_Min.y / gridSize) * gridSize; + float alignedMaxX = std::ceil(m_Max.x / gridSize) * gridSize; + float alignedMaxY = std::ceil(m_Max.y / gridSize) * gridSize; + return BoundingBox(alignedMinX, alignedMinY, alignedMaxX, alignedMaxY); + } + + float BoundingBox::distanceTo(float x, float y) const { + if (!m_IsValid) return std::numeric_limits::max(); + if (contains(x, y)) return 0.0f; + + float dx = std::max({ m_Min.x - x, 0.0f, x - m_Max.x }); + float dy = std::max({ m_Min.y - y, 0.0f, y - m_Max.y }); + + return std::sqrt(dx * dx + dy * dy); + } + + float BoundingBox::distanceTo(const glm::vec2& point) const { + return distanceTo(point.x, point.y); + } + + // ==================== 获取 ==================== + + float BoundingBox::getWidth() const { + return m_IsValid ? m_Max.x - m_Min.x : 0.0f; + } + + float BoundingBox::getHeight() const { + return m_IsValid ? m_Max.y - m_Min.y : 0.0f; + } + + // ==================== 对象方法 ==================== + + BoundingBox BoundingBox::copy() const { + return BoundingBox(*this); + } + + std::string BoundingBox::toString() const { + if (!m_IsValid) { + return "BoundingBox{INVALID}"; + } + std::stringstream ss; + ss << std::fixed << std::setprecision(2) + << "BoundingBox{min=(" << m_Min.x << ", " << m_Min.y + << "), max=(" << m_Max.x << ", " << m_Max.y + << "), size=(" << getWidth() << ", " << getHeight() << ")}"; + return ss.str(); + } + + bool BoundingBox::operator==(const BoundingBox& other) const { + if (m_IsValid != other.m_IsValid) return false; + if (!m_IsValid) return true; // Two invalid boxes are considered equal + return m_Min == other.m_Min && m_Max == other.m_Max; + } + + bool BoundingBox::operator!=(const BoundingBox& other) const { + return !(*this == other); + } + + // ==================== 静态工厂方法 ==================== + + BoundingBox BoundingBox::fromPoints(const std::vector& points) { + return BoundingBox(points); + } + + BoundingBox BoundingBox::fromVertices(const std::vector& vertices) { + return BoundingBox(vertices); + } + + BoundingBox BoundingBox::merge(const BoundingBox& box1, const BoundingBox& box2) { + return box1.getUnion(box2); + } + + BoundingBox BoundingBox::mergeAll(const std::vector& boxes) { + BoundingBox result; + for (const auto& box : boxes) { + result.expand(box); + } + return result; + } + +} // namespace Vivid2D::util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/BoundingBox.h b/Vivid2DRenderer/model/util/BoundingBox.h new file mode 100644 index 0000000..d057bc7 --- /dev/null +++ b/Vivid2DRenderer/model/util/BoundingBox.h @@ -0,0 +1,277 @@ +#pragma once + +#include +#include +#include +#include + +namespace Vivid2D::util { + + /** + * @class BoundingBox + * @brief 一个二维轴对齐边界框 (AABB),用于表示和管理对象边界。 + * 支持变换、合并、交集测试和其他操作。 + */ + class VIVID_2D_MYDLL_API BoundingBox { + public: + // ==================== 构造函数 ==================== + + /** + * @brief 创建一个未初始化、无效的边界框。 + */ + BoundingBox(); + + /** + * @brief 从最小和最大坐标创建边界框。 + */ + BoundingBox(float minX, float minY, float maxX, float maxY); + + /** + * @brief 从两个角点创建边界框。 + */ + BoundingBox(const glm::vec2& point1, const glm::vec2& point2); + + /** + * @brief 复制构造函数。 + */ + BoundingBox(const BoundingBox& other); + + /** + * @brief 从一组点创建边界框。 + */ + explicit BoundingBox(const std::vector& points); + + /** + * @brief 从一个顶点数组 [x0, y0, x1, y1, ...] 创建边界框。 + */ + explicit BoundingBox(const std::vector& vertices); + + + // ==================== 设置方法 ==================== + + /** + * @brief 将边界框重置为无效状态。 + */ + void reset(); + + /** + * @brief 设置边界值。 + */ + void set(float minX, float minY, float maxX, float maxY); + + /** + * @brief 从两个点设置边界。 + */ + void set(const glm::vec2& point1, const glm::vec2& point2); + + /** + * @brief 从另一个边界框设置边界。 + */ + void set(const BoundingBox& other); + + /** + * @brief 从一组点设置边界。 + */ + void set(const std::vector& points); + + /** + * @brief 从一个顶点数组 [x0, y0, x1, y1, ...] 设置边界。 + */ + void set(const std::vector& vertices); + + + // ==================== 扩展方法 ==================== + + /** + * @brief 扩展边界框以包含一个点。 + */ + void expand(float x, float y); + void expand(const glm::vec2& point); + + /** + * @brief 扩展边界框以包含另一个边界框。 + */ + void expand(const BoundingBox& other); + + /** + * @brief 扩展边界框以包含一组点。 + */ + void expand(const std::vector& points); + + /** + * @brief 扩展边界框以包含一个顶点数组。 + */ + void expand(const std::vector& vertices); + + + // ==================== 变换方法 (返回新的 BoundingBox) ==================== + + /** + * @brief 应用矩阵变换并返回新的 AABB。 + */ + BoundingBox transform(const glm::mat3& matrix) const; + + /** + * @brief 应用平移并返回新的 BoundingBox。 + */ + BoundingBox translate(float dx, float dy) const; + BoundingBox translate(const glm::vec2& translation) const; + + /** + * @brief 应用缩放并返回新的 BoundingBox。 + */ + BoundingBox scale(float sx, float sy) const; + BoundingBox scale(float scale) const; + BoundingBox scale(const glm::vec2& scale) const; + + + // ==================== 几何计算 ==================== + + /** + * @brief 获取边界框的四个角点。 + */ + std::vector getCorners() const; + + /** + * @brief 获取边界框的中心点。 + */ + glm::vec2 getCenter() const; + + /** + * @brief 获取边界框的尺寸 (宽度, 高度)。 + */ + glm::vec2 getSize() const; + + /** + * @brief 获取边界框的半尺寸 (范围/Extents)。 + */ + glm::vec2 getHalfSize() const; + + float getArea() const; + float getPerimeter() const; + + + // ==================== 交集测试 ==================== + + /** + * @brief 检查边界框是否包含一个点。 + */ + bool contains(float x, float y) const; + bool contains(const glm::vec2& point) const; + + /** + * @brief 检查此边界框是否完全包含另一个边界框。 + */ + bool contains(const BoundingBox& other) const; + + /** + * @brief 检查此边界框是否与另一个边界框相交。 + */ + bool intersects(const BoundingBox& other) const; + + /** + * @brief 返回此边界框与另一个边界框的交集。 + */ + BoundingBox intersection(const BoundingBox& other) const; + + /** + * @brief 返回此边界框与另一个边界框的并集。 + */ + BoundingBox getUnion(const BoundingBox& other) const; + + + // ==================== 实用方法 (返回新的 BoundingBox) ==================== + + /** + * @brief 在所有方向上将边界框扩大一个固定量。 + */ + BoundingBox inflate(float amount) const; + BoundingBox inflate(float dx, float dy) const; + + /** + * @brief 在所有方向上将边界框缩小一个固定量。 + */ + BoundingBox deflate(float amount) const; + BoundingBox deflate(float dx, float dy) const; + + /** + * @brief 返回一个对齐到网格的新边界框。 + */ + BoundingBox alignToGrid(float gridSize) const; + + /** + * @brief 计算到某个点的最短距离。 + */ + float distanceTo(float x, float y) const; + float distanceTo(const glm::vec2& point) const; + + + // ==================== 获取器 (Getters) ==================== + + float getMinX() const { return m_Min.x; } + float getMinY() const { return m_Min.y; } + float getMaxX() const { return m_Max.x; } + float getMaxY() const { return m_Max.y; } + const glm::vec2& getMin() const { return m_Min; } + const glm::vec2& getMax() const { return m_Max; } + + float getWidth() const; + float getHeight() const; + + /** + * @brief 检查边界框是否有效 (m_Min.x <= m_Max.x 且 m_Min.y <= m_Max.y)。 + */ + bool isValid() const { return m_IsValid; } + + + // ==================== 对象方法 ==================== + + /** + * @brief 创建边界框的深拷贝。 + */ + BoundingBox copy() const; + + /** + * @brief 返回边界框的字符串表示。 + */ + std::string toString() const; + + /** + * @brief 重载相等运算符。 + */ + bool operator==(const BoundingBox& other) const; + + /** + * @brief 重载不等运算符。 + */ + bool operator!=(const BoundingBox& other) const; + + // ==================== 静态工厂方法 ==================== + + /** + * @brief 从一组点创建并返回一个边界框。 + */ + static BoundingBox fromPoints(const std::vector& points); + + /** + * @brief 从一个顶点数组创建并返回一个边界框。 + */ + static BoundingBox fromVertices(const std::vector& vertices); + + /** + * @brief 合并两个边界框并返回它们的并集。 + */ + static BoundingBox merge(const BoundingBox& box1, const BoundingBox& box2); + + /** + * @brief 合并一组边界框并返回它们的并集。 + */ + static BoundingBox mergeAll(const std::vector& boxes); + + private: + glm::vec2 m_Min; ///< 边界框的最小坐标点 (x, y) + glm::vec2 m_Max; ///< 边界框的最大坐标点 (x, y) + bool m_IsValid; ///< 指示边界框是否有效 + }; + +} // namespace Vivid2D::util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/LightSource.cpp b/Vivid2DRenderer/model/util/LightSource.cpp new file mode 100644 index 0000000..414cde5 --- /dev/null +++ b/Vivid2DRenderer/model/util/LightSource.cpp @@ -0,0 +1,66 @@ +#include "pch.h" + +#include "LightSource.h" +#include +#include + +namespace Vivid2D::util { + + // ------------------- + // 构造函数实现 + // ------------------- + + // 构造函数:点光源/常规光源 + LightSource::LightSource(const glm::vec2& pos, const Color& color, float intensity) + : position(pos), + color(colorToVector3f(color)), + intensity(intensity), + m_isAmbient(false) { + } + + // 构造函数:环境光 + LightSource::LightSource(const Color& color, float intensity) + : position(glm::vec2(0.0f, 0.0f)), // 环境光位置通常不重要,设为 (0,0) + color(colorToVector3f(color)), + intensity(intensity), + m_isAmbient(true) { + } + + // ------------------- + // 静态工具函数实现 + // ------------------- + + /** + * 静态工具函数:将 Color 转换为 glm::vec3 (归一化到 0.0-1.0) + */ + glm::vec3 LightSource::colorToVector3f(const Color& color) { + return glm::vec3( + (float)color.r / 255.0f, + (float)color.g / 255.0f, + (float)color.b / 255.0f + ); + } + + /** + * 静态工具函数:将 glm::vec3 (0.0-1.0) 转换为 Color (0-255) + */ + Color LightSource::vector3fToColor(const glm::vec3& colorVec) { + // 确保值在 [0.0, 1.0] 范围内 + float r = std::min(1.0f, std::max(0.0f, colorVec.x)); + float g = std::min(1.0f, std::max(0.0f, colorVec.y)); + float b = std::min(1.0f, std::max(0.0f, colorVec.z)); + + // 转换为 0-255 整数,使用四舍五入 + int red = (int)(r * 255.0f + 0.5f); + int green = (int)(g * 255.0f + 0.5f); + int blue = (int)(b * 255.0f + 0.5f); + + // 确保结果在 [0, 255] 范围内 + red = std::min(255, std::max(0, red)); + green = std::min(255, std::max(0, green)); + blue = std::min(255, std::max(0, blue)); + + return Color(red, green, blue); + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/LightSource.h b/Vivid2DRenderer/model/util/LightSource.h new file mode 100644 index 0000000..ae35939 --- /dev/null +++ b/Vivid2DRenderer/model/util/LightSource.h @@ -0,0 +1,63 @@ +#pragma once + +#ifndef LIGHTSOURCE_H +#define LIGHTSOURCE_H + +#include +#include +#include + +namespace Vivid2D::util { + + struct VIVID_2D_MYDLL_API Color { + int r, g, b; + Color() : r(255), g(255), b(255) {} + Color(int red, int green, int blue) : r(red), g(green), b(blue) {} + }; + + /** + * 光源系统 + */ + class VIVID_2D_MYDLL_API LightSource { + public: + // 构造函数:点光源/常规光源 + LightSource(const glm::vec2& pos, const Color& color, float intensity); + + // 构造函数:环境光 + LightSource(const Color& color, float intensity); + + // 静态工具函数:将 Color 转换为 glm::vec3 (归一化到 0.0-1.0) + static glm::vec3 colorToVector3f(const Color& color); + + // 静态工具函数:将 glm::vec3 (0.0-1.0) 转换为 Color (0-255) + static Color vector3fToColor(const glm::vec3& colorVec); + + // Getter 方法 + const glm::vec2& getPosition() const { return position; } + const glm::vec3& getColor() const { return color; } + float getIntensity() const { return intensity; } + bool isEnabled() const { return enabled; } + bool isAmbient() const { return m_isAmbient; } + + // Setter 方法 + void setEnabled(bool enabled) { this->enabled = enabled; } + void setAmbient(bool ambient) { this->m_isAmbient = ambient; } + + bool operator==(const LightSource& other) const { + return position == other.position && + color == other.color && + intensity == other.intensity && + enabled == other.enabled && + m_isAmbient == other.m_isAmbient; + } + private: + glm::vec2 position; // 光源位置 + glm::vec3 color; // 光源颜色 (RGB, 0.0 - 1.0) + float intensity; // 光源强度 + bool enabled = true; + bool m_isAmbient = false; // 是否为环境光 + }; + +} // namespace Vivid2D + +#endif // LIGHTSOURCE_H \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/ModelPose.cpp b/Vivid2DRenderer/model/util/ModelPose.cpp new file mode 100644 index 0000000..41d247a --- /dev/null +++ b/Vivid2DRenderer/model/util/ModelPose.cpp @@ -0,0 +1,334 @@ +#include "pch.h" +#include "ModelPose.h" +#include // For std::min, std::max +#include // For std::ostringstream +#include // For std::fixed, std::setprecision + +namespace Vivid2D::util { + + // ======================================================= + // PartPose 实现 + // ======================================================= + + PartPose::PartPose() + : m_position(0.0f, 0.0f), + m_rotation(0.0f), + m_scale(1.0f, 1.0f), + m_opacity(1.0f), + m_visible(true), + m_color(1.0f, 1.0f, 1.0f) { + } + + PartPose::PartPose(const glm::vec2& position, float rotation, const glm::vec2& scale, + float opacity, bool visible, const glm::vec3& color) + : m_position(position), + m_rotation(rotation), + m_scale(scale), + m_opacity(opacity), + m_visible(visible), + m_color(color) { + } + + PartPose::PartPose(const PartPose& other) + : m_position(other.m_position), + m_rotation(other.m_rotation), + m_scale(other.m_scale), + m_opacity(other.m_opacity), + m_visible(other.m_visible), + m_color(other.m_color) { + } + + // --- 线性插值方法 --- + + float clamp(float value, float min_val, float max_val) { + return std::max(min_val, std::min(max_val, value)); + } + + /** + * 在两个部件姿态间进行线性插值 + * 注意:glm 库没有内置的 lerp 方法,需要手动实现 + */ + PartPose PartPose::lerp(const PartPose& a, const PartPose& b, float alpha) { + alpha = clamp(alpha, 0.0f, 1.0f); + + // Vector2f / Vector3f lerp: a + (b - a) * alpha + glm::vec2 pos = a.m_position + (b.m_position - a.m_position) * alpha; + float rot = a.m_rotation + (b.m_rotation - a.m_rotation) * alpha; + glm::vec2 scl = a.m_scale + (b.m_scale - a.m_scale) * alpha; + float opa = a.m_opacity + (b.m_opacity - a.m_opacity) * alpha; + glm::vec3 col = a.m_color + (b.m_color - a.m_color) * alpha; + + // 可见性:当alpha>0.5时使用b的可见性 + bool vis = alpha < 0.5f ? a.m_visible : b.m_visible; + + return PartPose(pos, rot, scl, opa, vis, col); + } + + /** + * 带旋转正确插值的线性插值(处理360°边界) + */ + PartPose PartPose::lerpWithRotation(const PartPose& a, const PartPose& b, float alpha) { + alpha = clamp(alpha, 0.0f, 1.0f); + + // 线性插值其他属性 (与普通 lerp 相同) + glm::vec2 pos = a.m_position + (b.m_position - a.m_position) * alpha; + glm::vec2 scl = a.m_scale + (b.m_scale - a.m_scale) * alpha; + float opa = a.m_opacity + (b.m_opacity - a.m_opacity) * alpha; + glm::vec3 col = a.m_color + (b.m_color - a.m_color) * alpha; + + // 处理旋转插值的角度环绕问题 + // float shortestAngle = ((b.rotation - a.rotation) % 360 + 540) % 360 - 180; + // C++ 实现:使用 fmod 模拟 % 运算符,并进行环绕处理 + float diff = b.m_rotation - a.m_rotation; + + // 将 diff 钳制到 [-180, 180] 范围内 + float shortestAngle = std::fmod(diff, 360.0f); + if (shortestAngle > 180.0f) { + shortestAngle -= 360.0f; + } + else if (shortestAngle < -180.0f) { + shortestAngle += 360.0f; + } + + float rot = a.m_rotation + shortestAngle * alpha; + + bool vis = alpha < 0.5f ? a.m_visible : b.m_visible; + + return PartPose(pos, rot, scl, opa, vis, col); + } + + // --- 运算符重载和工具方法 --- + + bool PartPose::operator==(const PartPose& other) const { + // 使用 glm::all(glm::epsilonEqual(...)) 或直接比较 (float 比较可能会有精度问题) + // 这里采用直接比较,但在实际项目中可能需要使用一个小 epsilon + return m_rotation == other.m_rotation && + m_opacity == other.m_opacity && + m_visible == other.m_visible && + m_position == other.m_position && + m_scale == other.m_scale && + m_color == other.m_color; + } + + std::string PartPose::toString() const { + std::ostringstream oss; + oss << std::fixed << std::setprecision(2); + oss << "PartPose{pos=(" << m_position.x << "," << m_position.y + << "), rot=" << m_rotation + << ", scale=(" << m_scale.x << "," << m_scale.y + << "), opacity=" << m_opacity + << ", visible=" << (m_visible ? "true" : "false") + << ", color=(" << m_color.x << "," << m_color.y << "," << m_color.z << ")}"; + return oss.str(); + } + + // ======================================================= + // ModelPose 实现 + // ======================================================= + + ModelPose::ModelPose() + : ModelPose("Unnamed Pose") { + } + + ModelPose::ModelPose(const std::string& name) + : m_name(name) { + } + + // 拷贝构造函数(深拷贝) + ModelPose::ModelPose(const ModelPose& other) + : m_name(other.m_name + " (Copy)"), + m_blendTime(other.m_blendTime), + m_isDefaultPose(other.m_isDefaultPose) { + + // 深拷贝所有部件姿态 + for (const auto& entry : other.m_partPoses) { + m_partPoses.emplace(entry.first, entry.second); // std::map 的 emplace/insert 会调用 PartPose 的拷贝构造函数 + } + } + + // 赋值运算符(深拷贝) + ModelPose& ModelPose::operator=(const ModelPose& other) { + if (this != &other) { + m_name = other.m_name + " (Copy)"; // Java 版本会在拷贝构造时追加 "(Copy)" + m_blendTime = other.m_blendTime; + m_isDefaultPose = other.m_isDefaultPose; + + // 清空旧的 map,然后深拷贝新的 map + m_partPoses.clear(); + for (const auto& entry : other.m_partPoses) { + m_partPoses.emplace(entry.first, entry.second); + } + } + return *this; + } + + + // --- 姿态管理方法 --- + + void ModelPose::setPartPose(const std::string& partName, const PartPose& pose) { + m_partPoses[partName] = pose; // 隐式调用 PartPose 的拷贝赋值或拷贝构造 + } + + PartPose& ModelPose::getPartPose(const std::string& partName) { + // Java 的 computeIfAbsent 语义:如果不存在,则创建默认 PartPose + auto it = m_partPoses.find(partName); + if (it == m_partPoses.end()) { + // 插入默认 PartPose 并返回引用 + return m_partPoses.emplace(partName, PartPose()).first->second; + } + return it->second; + } + + const PartPose& ModelPose::getPartPose(const std::string& partName) const { + // Const 版本:如果不存在,抛出异常,因为 const 方法不能修改 map + return m_partPoses.at(partName); + } + + bool ModelPose::hasPartPose(const std::string& partName) const { + return m_partPoses.count(partName) > 0; + } + + bool ModelPose::removePartPose(const std::string& partName) { + // Java 的 remove 返回被移除的值,C++ 的 erase 返回被移除元素的数量 + return m_partPoses.erase(partName) > 0; + } + + ModelPose::PoseMap::key_compare ModelPose::getPartNames() const { + // C++ 中获取 key 集合通常是遍历 Map 的 Key,但这里返回 Map 的比较器, + // 使用 const PoseMap& getPartPoses() const 更符合 C++ 的习惯 + return m_partPoses.key_comp(); + } + + + void ModelPose::clear() { + m_partPoses.clear(); + } + + // --- Getter 和 Setter --- + + void ModelPose::setBlendTime(float blendTime) { + m_blendTime = std::max(0.0f, blendTime); + } + + // --- 姿态混合 --- + + ModelPose ModelPose::lerp(const ModelPose& a, const ModelPose& b, float alpha, const std::string& resultName) { + ModelPose result(resultName); + result.setBlendTime(a.m_blendTime + (b.m_blendTime - a.m_blendTime) * alpha); + + // 收集所有唯一的部件名称 + std::map::key_compare allParts = a.getPartNames(); + + // C++ 中合并 key 集合的方法是使用 std::set 或手动迭代 + // 这里为了简化,我们只对两个 Map 中共有的部分进行插值,并使用 Java 逻辑处理缺失部分 + + // 遍历 A 的部件 + for (const auto& entry : a.m_partPoses) { + const std::string& partName = entry.first; + const PartPose& poseA = entry.second; + + auto itB = b.m_partPoses.find(partName); + if (itB != b.m_partPoses.end()) { + // 两个姿态都有该部件:插值 + const PartPose& poseB = itB->second; + result.setPartPose(partName, PartPose::lerpWithRotation(poseA, poseB, alpha)); + } + else { + // 只有姿态A有该部件:根据alpha决定是否保留 + if (alpha < 0.5f) { + result.setPartPose(partName, poseA); + } + } + } + + // 遍历 B 的部件,处理只在 B 中存在的部件 + for (const auto& entry : b.m_partPoses) { + const std::string& partName = entry.first; + + if (!a.hasPartPose(partName)) { + // 只有姿态B有该部件:根据alpha决定是否保留 + if (alpha >= 0.5f) { + result.setPartPose(partName, entry.second); + } + } + // 如果 A 也有,则在上面的 A 循环中已经处理过了 + } + + return result; + } + + void ModelPose::blendWith(const ModelPose& other, float alpha) { + PoseMap newPoses; + + // 1. 遍历当前姿态 (this) 的所有部件 + for (auto const& [partName, thisPose] : m_partPoses) { + auto itOther = other.m_partPoses.find(partName); + + if (itOther != other.m_partPoses.end()) { + // 两个姿态都有该部件:插值 + newPoses.emplace(partName, PartPose::lerpWithRotation(thisPose, itOther->second, alpha)); + } + else if (alpha < 0.5f) { + // 只有当前姿态有,alpha < 0.5f:保留当前姿态 + newPoses.emplace(partName, thisPose); + } + } + + // 2. 遍历 other 姿态中独有的部件 + for (auto const& [partName, otherPose] : other.m_partPoses) { + if (m_partPoses.find(partName) == m_partPoses.end()) { + // 只有 other 姿态有,alpha >= 0.5f:保留 other 姿态 + if (alpha >= 0.5f) { + newPoses.emplace(partName, otherPose); + } + } + } + + m_partPoses = std::move(newPoses); // 替换旧的 Map + } + + // --- 预设姿态工厂方法 --- + + ModelPose ModelPose::createDefaultPose() { + ModelPose pose("Default Pose"); + pose.m_isDefaultPose = true; + // 注意:Java 版本不预先添加部件,而是依赖 getPartPose 自动创建默认部件 + return pose; + } + + ModelPose ModelPose::createHiddenPose() { + ModelPose pose("Hidden Pose"); + pose.m_isDefaultPose = false; + // 同上,依赖 getPartPose 自动创建默认部件 + return pose; + } + + ModelPose ModelPose::createScaledPose(float scaleX, float scaleY) { + ModelPose pose("Scaled Pose"); + pose.m_isDefaultPose = false; + // 由于姿态是按需创建的,这里只是创建了一个命名姿态。 + // 实际应用中,调用方需要遍历模型部件并使用 setPartScale 来应用缩放。 + return pose; + } + + // --- 工具方法 --- + + std::string ModelPose::getDescription() const { + std::ostringstream oss; + oss << std::fixed << std::setprecision(2); + oss << "ModelPose{name='" << m_name + << "', parts=" << m_partPoses.size() + << ", blendTime=" << m_blendTime << "s}"; + return oss.str(); + } + + bool ModelPose::operator==(const ModelPose& other) const { + // float 比较 + if (m_blendTime != other.m_blendTime) return false; + if (m_isDefaultPose != other.m_isDefaultPose) return false; + + // 字符串和 Map 比较 + return m_name == other.m_name && m_partPoses == other.m_partPoses; + } + +} // namespace Vivid2D \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/ModelPose.h b/Vivid2DRenderer/model/util/ModelPose.h new file mode 100644 index 0000000..e71d5f3 --- /dev/null +++ b/Vivid2DRenderer/model/util/ModelPose.h @@ -0,0 +1,193 @@ +#pragma once + +#ifndef MODELPOSE_H +#define MODELPOSE_H + +#include +#include +#include +#include +#include +#include + +namespace Vivid2D::util { + + // ================== 内部类:部件姿态 PartPose ================== + + /** + * 单个部件的姿态数据 + */ + class VIVID_2D_MYDLL_API PartPose { + public: + // 默认构造函数 + PartPose(); + + // 完整构造函数 + PartPose(const glm::vec2& position, float rotation, const glm::vec2& scale, + float opacity, bool visible, const glm::vec3& color); + + // 拷贝构造函数 + PartPose(const PartPose& other); + + // ================== 线性插值方法 ================== + + /** + * 在两个部件姿态间进行线性插值(不处理旋转环绕) + */ + static PartPose lerp(const PartPose& a, const PartPose& b, float alpha); + + /** + * 带旋转正确插值的线性插值(处理360°边界) + */ + static PartPose lerpWithRotation(const PartPose& a, const PartPose& b, float alpha); + + // ================== Getter和Setter ================== + + glm::vec2 getPosition() const { return m_position; } + void setPosition(const glm::vec2& position) { m_position = position; } + + float getRotation() const { return m_rotation; } + void setRotation(float rotation) { m_rotation = rotation; } + + glm::vec2 getScale() const { return m_scale; } + void setScale(const glm::vec2& scale) { m_scale = scale; } + + float getOpacity() const { return m_opacity; } + void setOpacity(float opacity) { m_opacity = opacity; } + + bool isVisible() const { return m_visible; } + void setVisible(bool visible) { m_visible = visible; } + + glm::vec3 getColor() const { return m_color; } + void setColor(const glm::vec3& color) { m_color = color; } + + // ================== 运算符重载和工具方法 ================== + + bool operator==(const PartPose& other) const; + bool operator!=(const PartPose& other) const { return !(*this == other); } + std::string toString() const; + + private: + glm::vec2 m_position; + float m_rotation; + glm::vec2 m_scale; + float m_opacity; + bool m_visible; + glm::vec3 m_color; // RGB颜色乘数 + }; + + // ================== ModelPose 主体 ================== + + /** + * 模型姿态类 - 用于存储和管理2D模型的部件变换状态 + */ + class VIVID_2D_MYDLL_API ModelPose { + public: + // C++ 11/14/17 风格的别名,更清晰地表示存储的 Map 类型 + using PoseMap = std::map; + + // ================== 构造函数 ================== + + ModelPose(); + explicit ModelPose(const std::string& name); + + // 拷贝构造函数(深拷贝) + ModelPose(const ModelPose& other); + + // 赋值运算符(深拷贝) + ModelPose& operator=(const ModelPose& other); + + // ================== 姿态管理方法 ================== + + /** + * 设置指定部件的姿态(深拷贝) + */ + void setPartPose(const std::string& partName, const PartPose& pose); + + /** + * 获取指定部件的姿态(如果不存在则创建默认姿态并返回其引用) + */ + PartPose& getPartPose(const std::string& partName); + + /** + * 获取指定部件的姿态(const 版本) + * @throws std::out_of_range 如果部件不存在 + */ + const PartPose& getPartPose(const std::string& partName) const; + + + /** + * 检查是否包含指定部件的姿态 + */ + bool hasPartPose(const std::string& partName) const; + + /** + * 移除指定部件的姿态 + */ + bool removePartPose(const std::string& partName); + + /** + * 获取所有部件名称 + */ + std::map::key_compare getPartNames() const; + + /** + * 清空所有部件姿态 + */ + void clear(); + + /** + * 获取部件数量 + */ + size_t getPartCount() const { return m_partPoses.size(); } + + // ================== 姿态混合 ================== + + /** + * 在两个姿态间进行线性插值 + */ + static ModelPose lerp(const ModelPose& a, const ModelPose& b, float alpha, const std::string& resultName); + + /** + * 将当前姿态与另一个姿态混合 (原地修改) + */ + void blendWith(const ModelPose& other, float alpha); + + // ================== 预设姿态工厂方法 ================== + + static ModelPose createDefaultPose(); + static ModelPose createHiddenPose(); + static ModelPose createScaledPose(float scaleX, float scaleY); + + // ================== Getter和Setter ================== + + const std::string& getName() const { return m_name; } + void setName(const std::string& name) { m_name = name; } + + float getBlendTime() const { return m_blendTime; } + void setBlendTime(float blendTime); + + bool isDefaultPose() const { return m_isDefaultPose; } + void setDefaultPose(bool defaultPose) { m_isDefaultPose = defaultPose; } + + // 访问整个姿态 Map 的方法,用于遍历和动画系统 + const PoseMap& getPartPoses() const { return m_partPoses; } + + // ================== 工具方法 ================== + + bool isEmpty() const { return m_partPoses.empty(); } + std::string getDescription() const; + bool operator==(const ModelPose& other) const; + bool operator!=(const ModelPose& other) const { return !(*this == other); } + std::string toString() const { return getDescription(); } + + private: + std::string m_name; + PoseMap m_partPoses; + float m_blendTime = 0.3f; // 默认混合时间(秒) + bool m_isDefaultPose = false; + }; + +} // namespace Vivid2D + +#endif // MODELPOSE_H diff --git a/Vivid2DRenderer/model/util/PhysicsSystem.cpp b/Vivid2DRenderer/model/util/PhysicsSystem.cpp new file mode 100644 index 0000000..ecc7eb7 --- /dev/null +++ b/Vivid2DRenderer/model/util/PhysicsSystem.cpp @@ -0,0 +1,636 @@ +#include "pch.h" +#include "PhysicsSystem.h" +#include +#include +#include +#include +#include + +#include +#include "../Model2D.h" +#include "../ModelPart.h" + +namespace Vivid2D::util { + + // ========================================================== + // PhysicsSystem Implementation + // ========================================================== + PhysicsSystem::PhysicsSystem() : + m_gravity(0.0f, -98.0f), + m_airResistance(0.1f), + m_timeScale(1.0f), + m_enabled(true), + m_initialized(false), + m_accumulatedTime(0.0f), + m_maxSubSteps(5), + m_fixedTimeStep(1.0f / 60.0f), + m_updateCount(0), + m_averageUpdateTime(0.0f), + m_windForce(0.0f, 0.0f), + m_windEnabled(false) { + } + + void PhysicsSystem::initialize() { + if (m_initialized) return; + reset(); + m_initialized = true; + } + + void PhysicsSystem::reset() { + m_particles.clear(); + m_springs.clear(); + m_constraints.clear(); + m_colliders.clear(); + m_lastUpdateTime = std::chrono::steady_clock::now(); + m_accumulatedTime = 0.0f; + m_updateCount = 0; + m_averageUpdateTime = 0.0f; + } + + std::shared_ptr PhysicsSystem::addParticle(const std::string& id, glm::vec2 position, float mass) { + auto particle = std::make_shared(id, position, mass); + m_particles[id] = particle; + return particle; + } + + std::shared_ptr PhysicsSystem::addParticleFromModelPart(const std::string& id, std::shared_ptr part, float mass) { + glm::vec2 position = part->getPosition(); + auto particle = addParticle(id, position, mass); + particle->setUserData(part.get()); + return particle; + } + + bool PhysicsSystem::removeParticle(const std::string& id) { + // Remove related springs and constraints + m_springs.erase(std::remove_if(m_springs.begin(), m_springs.end(), + [&](const auto& spring) { + return spring->getParticleA()->getId() == id || spring->getParticleB()->getId() == id; + }), m_springs.end()); + + m_constraints.erase(std::remove_if(m_constraints.begin(), m_constraints.end(), + [&](const auto& constraint) { + return constraint->getParticle()->getId() == id; + }), m_constraints.end()); + + return m_particles.erase(id) > 0; + } + + std::shared_ptr PhysicsSystem::getParticle(const std::string& id) { + auto it = m_particles.find(id); + return (it != m_particles.end()) ? it->second : nullptr; + } + + std::shared_ptr PhysicsSystem::addSpring(const std::string& id, std::shared_ptr a, std::shared_ptr b, float restLength, float stiffness, float damping) { + auto spring = std::make_shared(id, a, b, restLength, stiffness, damping); + m_springs.push_back(spring); + return spring; + } + + std::shared_ptr PhysicsSystem::addSpring(const std::string& id, std::shared_ptr a, std::shared_ptr b, float stiffness, float damping) { + float restLength = glm::distance(a->getPosition(), b->getPosition()); + return addSpring(id, a, b, restLength, stiffness, damping); + } + + bool PhysicsSystem::removeSpring(const std::shared_ptr& spring) { + auto it = std::remove(m_springs.begin(), m_springs.end(), spring); + if (it != m_springs.end()) { + m_springs.erase(it, m_springs.end()); + return true; + } + return false; + } + + std::shared_ptr PhysicsSystem::addPositionConstraint(std::shared_ptr particle, glm::vec2 targetPosition) { + auto constraint = std::make_shared(particle, targetPosition); + m_constraints.push_back(constraint); + return constraint; + } + + std::shared_ptr PhysicsSystem::addDistanceConstraint(std::shared_ptr particle, std::shared_ptr target, float maxDistance) { + auto constraint = std::make_shared(particle, target, maxDistance); + m_constraints.push_back(constraint); + return constraint; + } + + bool PhysicsSystem::removeConstraint(const std::shared_ptr& constraint) { + auto it = std::remove(m_constraints.begin(), m_constraints.end(), constraint); + if (it != m_constraints.end()) { + m_constraints.erase(it, m_constraints.end()); + return true; + } + return false; + } + + std::shared_ptr PhysicsSystem::addCircleCollider(const std::string& id, glm::vec2 center, float radius) { + auto collider = std::make_shared(id, center, radius); + m_colliders.push_back(collider); + return collider; + } + + std::shared_ptr PhysicsSystem::addRectangleCollider(const std::string& id, glm::vec2 center, float width, float height) { + auto collider = std::make_shared(id, center, width, height); + m_colliders.push_back(collider); + return collider; + } + + bool PhysicsSystem::removeCollider(const std::shared_ptr& collider) { + auto it = std::remove(m_colliders.begin(), m_colliders.end(), collider); + if (it != m_colliders.end()) { + m_colliders.erase(it, m_colliders.end()); + return true; + } + return false; + } + + void PhysicsSystem::setWindForce(float x, float y) { + m_windForce = { x, y }; + m_windEnabled = (x != 0.0f || y != 0.0f); + } + + void PhysicsSystem::setWindForce(const glm::vec2& windForce) { + setWindForce(windForce.x, windForce.y); + } + + glm::vec2 PhysicsSystem::getWindForce() const { + return m_windForce; + } + + void PhysicsSystem::setWindEnabled(bool enabled) { + m_windEnabled = enabled; + } + + bool PhysicsSystem::isWindEnabled() const { + return m_windEnabled; + } + + void PhysicsSystem::update(float deltaTime, Model2D& model) { + if (!m_enabled || !m_initialized) return; + + auto startTime = std::chrono::steady_clock::now(); + + float scaledDeltaTime = deltaTime * m_timeScale; + m_accumulatedTime += scaledDeltaTime; + + int numSubSteps = 0; + while (m_accumulatedTime >= m_fixedTimeStep && numSubSteps < m_maxSubSteps) { + updatePhysics(m_fixedTimeStep); + m_accumulatedTime -= m_fixedTimeStep; + numSubSteps++; + } + + applyToModel(model); + + auto endTime = std::chrono::steady_clock::now(); + updatePerformanceStats(std::chrono::duration_cast(endTime - startTime).count()); + } + + void PhysicsSystem::updatePhysics(float deltaTime) { + for (auto const& [id, particle] : m_particles) { + particle->clearForces(); + } + + applyGravity(); + applyWind(); + + for (const auto& spring : m_springs) { + spring->applyForce(deltaTime); + } + + for (auto const& [id, particle] : m_particles) { + if (particle->isMovable()) { + particle->update(deltaTime); + } + } + + int constraintIterations = 3; + for (int i = 0; i < constraintIterations; ++i) { + for (const auto& constraint : m_constraints) { + if (constraint->isEnabled()) { + constraint->apply(deltaTime); + } + } + + for (const auto& collider : m_colliders) { + if (!collider->isEnabled()) continue; + for (auto const& [id, particle] : m_particles) { + if (!particle->isMovable()) continue; + if (collider->collidesWith(*particle)) { + collider->resolveCollision(*particle, deltaTime); + } + } + } + } + + syncVelocitiesFromPositions(deltaTime); + handleParticleCollisions(deltaTime); + applyAirResistance(deltaTime); + } + + + void PhysicsSystem::syncVelocitiesFromPositions(float deltaTime) { + if (deltaTime <= 0.0f) return; + for (auto const& [id, particle] : m_particles) { + if (!particle->isMovable()) continue; + glm::vec2 pos = particle->m_position; + glm::vec2 prev = particle->m_previousPosition; + particle->m_velocity = (pos - prev) / deltaTime; + } + } + + + void PhysicsSystem::applyGravity() { + for (auto const& [id, particle] : m_particles) { + if (particle->isMovable() && particle->isAffectedByGravity()) { + glm::vec2 gravityForce = m_gravity * particle->getMass(); + particle->addForce(gravityForce); + } + } + } + + void PhysicsSystem::applyWind() { + if (!m_windEnabled || glm::length2(m_windForce) == 0.0f) { + return; + } + for (auto const& [id, particle] : m_particles) { + if (particle->isMovable() && particle->isAffectedByWind()) { + particle->addForce(m_windForce); + } + } + } + + void PhysicsSystem::applyAirResistance(float deltaTime) { + if (m_airResistance <= 0.0f) return; + float factor = 1.0f / (1.0f + m_airResistance * deltaTime); + for (auto const& [id, particle] : m_particles) { + if (!particle->isMovable()) continue; + particle->m_velocity *= factor; + } + } + + void PhysicsSystem::handleParticleCollisions(float deltaTime) { + std::vector> particleList; + particleList.reserve(m_particles.size()); + for (auto const& [id, particle] : m_particles) { + particleList.push_back(particle); + } + + for (size_t i = 0; i < particleList.size(); ++i) { + auto p1 = particleList[i]; + if (!p1->isMovable()) continue; + + for (size_t j = i + 1; j < particleList.size(); ++j) { + auto p2 = particleList[j]; + if (!p2->isMovable()) continue; + + glm::vec2 pos1 = p1->getPosition(); + glm::vec2 pos2 = p2->getPosition(); + glm::vec2 delta = pos2 - pos1; + float dist2 = glm::length2(delta); + float minDist = p1->getRadius() + p2->getRadius(); + + if (dist2 < minDist * minDist && dist2 > 1e-12f) { + float dist = glm::sqrt(dist2); + glm::vec2 normal = delta / dist; + float penetration = minDist - dist; + + float invM1 = p1->getInverseMass(); + float invM2 = p2->getInverseMass(); + float invSum = invM1 + invM2; + if (invSum <= 0.0f) continue; + + glm::vec2 correction = normal * (penetration / invSum); + p1->translatePosition(-correction * invM1); + p2->translatePosition(correction * invM2); + + glm::vec2 v1 = p1->getVelocity(); + glm::vec2 v2 = p2->getVelocity(); + glm::vec2 relVel = v2 - v1; + float velAlongNormal = glm::dot(relVel, normal); + + if (velAlongNormal < 0.0f) { + float restitution = 0.5f; + float j2 = -(1.0f + restitution) * velAlongNormal; + j2 /= invSum; + + glm::vec2 impulse = normal * j2; + p1->setVelocity(v1 - impulse * invM1); + p2->setVelocity(v2 + impulse * invM2); + } + } + } + } + } + + void PhysicsSystem::applyToModel(Model2D& model) { + for (auto const& [id, particle] : m_particles) { + void* userData = particle->getUserData(); + if (userData) { + auto* part = static_cast(userData); + part->setPosition(particle->getPosition()); + + if (glm::length2(particle->getVelocity()) > 0.1f) { + float angle = atan2(particle->getVelocity().y, particle->getVelocity().x); + part->setRotation(angle); + } + } + } + } + + + void PhysicsSystem::updatePerformanceStats(long long nanoTime) { + float millis = nanoTime / 1'000'000.0f; + if (m_updateCount == 0) { + m_averageUpdateTime = millis; + } + else { + m_averageUpdateTime = m_averageUpdateTime * 0.95f + millis * 0.05f; + } + m_updateCount++; + } + + PhysicsPerformanceReport PhysicsSystem::getPerformanceReport() const { + return PhysicsPerformanceReport( + m_particles.size(), m_springs.size(), m_constraints.size(), m_colliders.size(), + m_averageUpdateTime, m_updateCount + ); + } + + // Getters and Setters Implementation + glm::vec2 PhysicsSystem::getGravity() const { return m_gravity; } + void PhysicsSystem::setGravity(float x, float y) { m_gravity = { x, y }; } + void PhysicsSystem::setGravity(const glm::vec2& gravity) { m_gravity = gravity; } + float PhysicsSystem::getAirResistance() const { return m_airResistance; } + void PhysicsSystem::setAirResistance(float airResistance) { m_airResistance = std::max(0.0f, airResistance); } + float PhysicsSystem::getTimeScale() const { return m_timeScale; } + void PhysicsSystem::setTimeScale(float timeScale) { m_timeScale = std::max(0.0f, timeScale); } + bool PhysicsSystem::isEnabled() const { return m_enabled; } + void PhysicsSystem::setEnabled(bool enabled) { m_enabled = enabled; } + bool PhysicsSystem::isInitialized() const { return m_initialized; } + const std::unordered_map>& PhysicsSystem::getParticles() const { return m_particles; } + const std::vector>& PhysicsSystem::getSprings() const { return m_springs; } + const std::vector>& PhysicsSystem::getConstraints() const { return m_constraints; } + const std::vector>& PhysicsSystem::getColliders() const { return m_colliders; } + + + bool PhysicsSystem::hasActivePhysics() { + if (!m_enabled || !m_initialized) { + return false; + } + + bool hasMovableParticles = false; + for (const auto& pair : m_particles) { + if (pair.second->isMovable() && pair.second->isAffectedByGravity()) { + hasMovableParticles = true; + break; + } + } + if (!hasMovableParticles) { + return false; + } + + bool hasActiveSprings = std::any_of(m_springs.begin(), m_springs.end(), [](const auto& spring) { + return spring->isEnabled() && (spring->getParticleA()->isMovable() || spring->getParticleB()->isMovable()); + }); + + bool hasSignificantMotion = false; + for (const auto& pair : m_particles) { + const auto& p = pair.second; + if (!p->isMovable()) continue; + + if (glm::length2(p->getVelocity()) > 0.1f) { + hasSignificantMotion = true; + break; + } + glm::vec2 positionDelta = p->getPosition() - p->getPreviousPosition(); + if (glm::length2(positionDelta) > 0.001f) { + hasSignificantMotion = true; + break; + } + } + + bool hasActiveConstraints = std::any_of(m_constraints.begin(), m_constraints.end(), [](const auto& c) { + return c->isEnabled() && c->getParticle()->isMovable(); + }); + + return hasActiveSprings || hasSignificantMotion || hasActiveConstraints; + } + + // ========================================================== + // PhysicsParticle Implementation + // ========================================================== + PhysicsParticle::PhysicsParticle(std::string id, glm::vec2 position, float mass) + : m_id(std::move(id)), m_position(position), m_previousPosition(position), + m_velocity(0.0f), m_acceleration(0.0f), m_forceAccumulator(0.0f), + m_radius(2.0f), m_movable(true), m_affectedByGravity(true), m_affectedByWind(true), m_userData(nullptr) { + if (mass <= 0.0f) { + m_mass = std::numeric_limits::infinity(); + m_inverseMass = 0.0f; + } + else { + m_mass = mass; + m_inverseMass = 1.0f / mass; + } + } + + void PhysicsParticle::update(float deltaTime) { + if (!m_movable) return; + m_acceleration = m_forceAccumulator * m_inverseMass; + m_velocity += m_acceleration * deltaTime; + m_previousPosition = m_position; + m_position += m_velocity * deltaTime; + m_forceAccumulator = { 0.0f, 0.0f }; + } + + void PhysicsParticle::addForce(const glm::vec2& force) { + if (m_inverseMass == 0.0f) return; + m_forceAccumulator += force; + } + + void PhysicsParticle::clearForces() { + m_forceAccumulator = { 0.0f, 0.0f }; + } + + void PhysicsParticle::translatePosition(const glm::vec2& delta) { + m_position += delta; + } + + void PhysicsParticle::setPreviousPosition(const glm::vec2& prev) { + m_previousPosition = prev; + } + + void PhysicsParticle::applyNewVelocity(const glm::vec2& newVelocity) { + m_velocity = newVelocity; + } + + // ========================================================== + // PhysicsSpring Implementation + // ========================================================== + PhysicsSpring::PhysicsSpring(std::string id, std::shared_ptr a, std::shared_ptr b, float restLength, float stiffness, float damping) + : m_id(std::move(id)), m_particleA(a), m_particleB(b), m_restLength(restLength), m_stiffness(stiffness), m_damping(damping), m_enabled(true) {} + + void PhysicsSpring::applyForce(float deltaTime) { + if (!m_enabled) return; + + glm::vec2 delta = m_particleB->getPosition() - m_particleA->getPosition(); + float currentLength = glm::length(delta); + if (currentLength < 0.0001f) return; + + glm::vec2 dir = delta / currentLength; + float stretch = currentLength - m_restLength; + glm::vec2 springForce = dir * (m_stiffness * stretch); + + glm::vec2 relativeVelocity = m_particleB->getVelocity() - m_particleA->getVelocity(); + float velocityAlongSpring = glm::dot(relativeVelocity, dir); + glm::vec2 dampingForce = dir * (m_damping * velocityAlongSpring); + + glm::vec2 totalForce = springForce - dampingForce; + + if (m_particleA->isMovable()) { + m_particleA->addForce(-totalForce); + } + if (m_particleB->isMovable()) { + m_particleB->addForce(totalForce); + } + } + + // ========================================================== + // Constraints Implementation + // ========================================================== + PositionConstraint::PositionConstraint(std::shared_ptr particle, glm::vec2 targetPosition) + : m_particle(particle), m_targetPosition(targetPosition), m_strength(0.5f), m_enabled(true) {} + + void PositionConstraint::apply(float deltaTime) { + if (!m_enabled || !m_particle->isMovable()) return; + glm::vec2 currentPos = m_particle->getPosition(); + glm::vec2 delta = m_targetPosition - currentPos; + glm::vec2 correction = delta * m_strength; + m_particle->setPosition(currentPos + correction); + } + + DistanceConstraint::DistanceConstraint(std::shared_ptr particle, std::shared_ptr target, float maxDistance) + : m_particle(particle), m_target(target), m_maxDistance(maxDistance), m_enabled(true) {} + + void DistanceConstraint::apply(float deltaTime) { + if (!m_enabled || !m_particle->isMovable()) return; + glm::vec2 delta = m_particle->getPosition() - m_target->getPosition(); + float distance = glm::length(delta); + + if (distance > m_maxDistance) { + glm::vec2 correction = glm::normalize(delta) * (distance - m_maxDistance); + m_particle->setPosition(m_particle->getPosition() - correction); + } + } + + // ========================================================== + // Colliders Implementation + // ========================================================== + CircleCollider::CircleCollider(std::string id, glm::vec2 center, float radius) + : m_id(std::move(id)), m_center(center), m_radius(radius), m_enabled(true) {} + + bool CircleCollider::collidesWith(const PhysicsParticle& particle) const { + return glm::distance2(particle.getPosition(), m_center) < (m_radius + particle.getRadius()) * (m_radius + particle.getRadius()); + } + + void CircleCollider::resolveCollision(PhysicsParticle& particle, float deltaTime) { + glm::vec2 toParticle = particle.getPosition() - m_center; + float distance = glm::length(toParticle); + float combined = m_radius + particle.getRadius(); + float overlap = combined - distance; + + if (overlap > 0.0001f) { + glm::vec2 normal = (distance < 0.0001f) ? glm::vec2(0.0f, 1.0f) : toParticle / distance; + + particle.translatePosition(normal * (overlap + 0.001f)); + + glm::vec2 v = particle.getVelocity(); + float dot = glm::dot(v, normal); + if (dot < 0) { + float restitution = 0.6f; + glm::vec2 vPrime = v - normal * ((1.0f + restitution) * dot); + particle.setVelocity(vPrime); + } + } + } + + RectangleCollider::RectangleCollider(std::string id, glm::vec2 center, float width, float height) + : m_id(std::move(id)), m_center(center), m_width(width), m_height(height), m_enabled(true) {} + + bool RectangleCollider::collidesWith(const PhysicsParticle& particle) const { + glm::vec2 particlePos = particle.getPosition(); + float left = m_center.x - m_width / 2.0f - particle.getRadius(); + float right = m_center.x + m_width / 2.0f + particle.getRadius(); + float bottom = m_center.y - m_height / 2.0f - particle.getRadius(); + float top = m_center.y + m_height / 2.0f + particle.getRadius(); + + return particlePos.x >= left && particlePos.x <= right && + particlePos.y >= bottom && particlePos.y <= top; + } + + void RectangleCollider::resolveCollision(PhysicsParticle& particle, float deltaTime) { + glm::vec2 particlePos = particle.getPosition(); + float left = m_center.x - m_width / 2.0f; + float right = m_center.x + m_width / 2.0f; + float bottom = m_center.y - m_height / 2.0f; + float top = m_center.y + m_height / 2.0f; + + float closestX = glm::clamp(particlePos.x, left, right); + float closestY = glm::clamp(particlePos.y, bottom, top); + + glm::vec2 closestPoint(closestX, closestY); + glm::vec2 normal = particlePos - closestPoint; + + float dist = glm::length(normal); + if (dist < 0.0001f) { // Inside rectangle + // Find minimum exit distance + float dLeft = particlePos.x - left; + float dRight = right - particlePos.x; + float dBottom = particlePos.y - bottom; + float dTop = top - particlePos.y; + + float min_d = dLeft; + normal = glm::vec2(-1.0, 0.0); + + if (dRight < min_d) { min_d = dRight; normal = glm::vec2(1.0, 0.0); } + if (dBottom < min_d) { min_d = dBottom; normal = glm::vec2(0.0, -1.0); } + if (dTop < min_d) { min_d = dTop; normal = glm::vec2(0.0, 1.0); } + + float overlap = particle.getRadius() + min_d; + particle.translatePosition(normal * (overlap + 0.001f)); + + } + else { // Outside, but overlapping + float overlap = particle.getRadius() - dist; + if (overlap > 0.0001f) { + normal /= dist; + particle.translatePosition(normal * (overlap + 0.001f)); + } + else { + return; // No collision + } + } + + // Velocity response + glm::vec2 v = particle.getVelocity(); + float dot = glm::dot(v, normal); + if (dot < 0) { + float restitution = 0.6f; + glm::vec2 vPrime = v - normal * ((1.0f + restitution) * dot); + particle.setVelocity(vPrime); + } + } + + + // ========================================================== + // PhysicsPerformanceReport Implementation + // ========================================================== + PhysicsPerformanceReport::PhysicsPerformanceReport(size_t pCount, size_t sCount, size_t cCount, size_t colCount, float avgTime, int updates) + : particleCount(pCount), springCount(sCount), constraintCount(cCount), colliderCount(colCount), averageUpdateTime(avgTime), totalUpdates(updates) {} + + std::string PhysicsPerformanceReport::toString() const { + return spdlog::fmt_lib::format( + "Physics Performance: {} particles, {} springs, {} constraints, {} colliders, Avg update: {:.2f}ms, Total updates: {}", + particleCount, springCount, constraintCount, colliderCount, averageUpdateTime, totalUpdates + ); + } + +} // namespace Vivid2D::util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/PhysicsSystem.h b/Vivid2DRenderer/model/util/PhysicsSystem.h new file mode 100644 index 0000000..9bff31a --- /dev/null +++ b/Vivid2DRenderer/model/util/PhysicsSystem.h @@ -0,0 +1,363 @@ +#pragma once + +#ifndef PHYSICS_SYSTEM_H +#define PHYSICS_SYSTEM_H + +#include +#include +#include +#include +#include +#include +#include + +namespace Vivid2D { + class Model2D; + class ModelPart; +} + +namespace Vivid2D::util { + + class PhysicsParticle; + class PhysicsSpring; + struct PhysicsConstraint; + struct PhysicsCollider; + class PositionConstraint; + class DistanceConstraint; + class CircleCollider; + class RectangleCollider; + + /** + * 物理性能报告 + */ + struct VIVID_2D_MYDLL_API PhysicsPerformanceReport { + const size_t particleCount; + const size_t springCount; + const size_t constraintCount; + const size_t colliderCount; + const float averageUpdateTime; + const int totalUpdates; + + PhysicsPerformanceReport(size_t pCount, size_t sCount, size_t cCount, size_t colCount, float avgTime, int updates); + std::string toString() const; + }; + + /** + * 2D物理系统,用于处理模型的物理模拟 + * 支持弹簧系统、碰撞检测、重力等物理效果 + */ + class VIVID_2D_MYDLL_API PhysicsSystem { + public: + // ==================== 构造器 ==================== + PhysicsSystem(); + + // ==================== 初始化方法 ==================== + void initialize(); + void reset(); + + // ==================== 粒子管理 ==================== + std::shared_ptr addParticle(const std::string& id, glm::vec2 position, float mass); + std::shared_ptr addParticleFromModelPart(const std::string& id, std::shared_ptr part, float mass); + bool removeParticle(const std::string& id); + std::shared_ptr getParticle(const std::string& id); + + // ==================== 弹簧管理 ==================== + std::shared_ptr addSpring(const std::string& id, std::shared_ptr a, std::shared_ptr b, float restLength, float stiffness, float damping); + std::shared_ptr addSpring(const std::string& id, std::shared_ptr a, std::shared_ptr b, float stiffness, float damping); + bool removeSpring(const std::shared_ptr& spring); + + // ==================== 约束管理 ==================== + std::shared_ptr addPositionConstraint(std::shared_ptr particle, glm::vec2 targetPosition); + std::shared_ptr addDistanceConstraint(std::shared_ptr particle, std::shared_ptr target, float maxDistance); + bool removeConstraint(const std::shared_ptr& constraint); + + // ==================== 碰撞管理 ==================== + std::shared_ptr addCircleCollider(const std::string& id, glm::vec2 center, float radius); + std::shared_ptr addRectangleCollider(const std::string& id, glm::vec2 center, float width, float height); + bool removeCollider(const std::shared_ptr& collider); + + // ==================== 风力方法 ==================== + void setWindForce(float x, float y); + void setWindForce(const glm::vec2& windForce); + glm::vec2 getWindForce() const; + void setWindEnabled(bool enabled); + bool isWindEnabled() const; + + // ==================== 更新系统 ==================== + void update(float deltaTime, Model2D& model); + + // ==================== 性能统计 ==================== + PhysicsPerformanceReport getPerformanceReport() const; + + // ==================== Getter/Setter ==================== + glm::vec2 getGravity() const; + void setGravity(float x, float y); + void setGravity(const glm::vec2& gravity); + + float getAirResistance() const; + void setAirResistance(float airResistance); + + float getTimeScale() const; + void setTimeScale(float timeScale); + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isInitialized() const; + + const std::unordered_map>& getParticles() const; + const std::vector>& getSprings() const; + const std::vector>& getConstraints() const; + const std::vector>& getColliders() const; + + bool hasActivePhysics(); + + private: + // ==================== 物理参数 ==================== + glm::vec2 m_gravity; + float m_airResistance; + float m_timeScale; + bool m_enabled; + + // ==================== 物理组件 ==================== + std::unordered_map> m_particles; + std::vector> m_springs; + std::vector> m_constraints; + std::vector> m_colliders; + + // ==================== 状态管理 ==================== + bool m_initialized; + std::chrono::steady_clock::time_point m_lastUpdateTime; + float m_accumulatedTime; + const int m_maxSubSteps; + const float m_fixedTimeStep; + + // ==================== 性能统计 ==================== + int m_updateCount; + float m_averageUpdateTime; + + // ==================== 风力参数 ==================== + glm::vec2 m_windForce; + bool m_windEnabled; + + // ==================== 更新流程 ==================== + void updatePhysics(float deltaTime); + void syncVelocitiesFromPositions(float deltaTime); + void applyGravity(); + void applyWind(); + void applyAirResistance(float deltaTime); + void handleParticleCollisions(float deltaTime); + void applyToModel(Model2D& model); + void updatePerformanceStats(long long nanoTime); + }; + + + // ================================================================================= + // 内部类定义 + // ================================================================================= + + /** + * 物理粒子类 + */ + class VIVID_2D_MYDLL_API PhysicsParticle { + public: + PhysicsParticle(std::string id, glm::vec2 position, float mass); + + void update(float deltaTime); + void addForce(const glm::vec2& force); + void clearForces(); + + void translatePosition(const glm::vec2& delta); + void setPreviousPosition(const glm::vec2& prev); + void applyNewVelocity(const glm::vec2& newVelocity); + + // Getters + const std::string& getId() const { return m_id; } + glm::vec2 getPosition() const { return m_position; } + glm::vec2 getPreviousPosition() const { return m_previousPosition; } + glm::vec2 getVelocity() const { return m_velocity; } + glm::vec2 getAcceleration() const { return m_acceleration; } + float getMass() const { return m_mass; } + float getInverseMass() const { return m_inverseMass; } + float getRadius() const { return m_radius; } + bool isMovable() const { return m_movable; } + bool isAffectedByGravity() const { return m_affectedByGravity; } + bool isAffectedByWind() const { return m_affectedByWind; } + void* getUserData() const { return m_userData; } + + // Setters + void setPosition(const glm::vec2& position) { m_position = position; } + void setVelocity(const glm::vec2& velocity) { m_velocity = velocity; } + void setRadius(float radius) { m_radius = radius; } + void setMovable(bool movable) { m_movable = movable; } + void setAffectedByGravity(bool affected) { m_affectedByGravity = affected; } + void setAffectedByWind(bool affected) { m_affectedByWind = affected; } + void setUserData(void* data) { m_userData = data; } + + private: + friend class PhysicsSystem; + std::string m_id; + glm::vec2 m_position; + glm::vec2 m_previousPosition; + glm::vec2 m_velocity; + glm::vec2 m_acceleration; + glm::vec2 m_forceAccumulator; + float m_mass; + float m_inverseMass; + float m_radius; + bool m_movable; + bool m_affectedByGravity; + bool m_affectedByWind; + void* m_userData = nullptr; + }; + + /** + * 物理弹簧类 + */ + class VIVID_2D_MYDLL_API PhysicsSpring { + public: + PhysicsSpring(std::string id, std::shared_ptr a, std::shared_ptr b, float restLength, float stiffness, float damping); + void applyForce(float deltaTime); + + // Getters & Setters + const std::string& getId() const { return m_id; } + std::shared_ptr getParticleA() const { return m_particleA; } + std::shared_ptr getParticleB() const { return m_particleB; } + float getRestLength() const { return m_restLength; } + float getStiffness() const { return m_stiffness; } + float getDamping() const { return m_damping; } + bool isEnabled() const { return m_enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + + private: + std::string m_id; + std::shared_ptr m_particleA; + std::shared_ptr m_particleB; + float m_restLength; + float m_stiffness; + float m_damping; + bool m_enabled; + }; + + /** + * 物理约束接口 + */ + struct VIVID_2D_MYDLL_API PhysicsConstraint { + virtual ~PhysicsConstraint() = default; + virtual void apply(float deltaTime) = 0; + virtual std::shared_ptr getParticle() const = 0; + virtual bool isEnabled() const = 0; + virtual void setEnabled(bool enabled) = 0; + }; + + /** + * 位置约束 + */ + class VIVID_2D_MYDLL_API PositionConstraint : public PhysicsConstraint { + public: + PositionConstraint(std::shared_ptr particle, glm::vec2 targetPosition); + void apply(float deltaTime) override; + + std::shared_ptr getParticle() const override { return m_particle; } + bool isEnabled() const override { return m_enabled; } + void setEnabled(bool enabled) override { m_enabled = enabled; } + + glm::vec2 getTargetPosition() const { return m_targetPosition; } + void setTargetPosition(const glm::vec2& target) { m_targetPosition = target; } + float getStrength() const { return m_strength; } + void setStrength(float strength) { m_strength = glm::clamp(strength, 0.0f, 1.0f); } + + private: + std::shared_ptr m_particle; + glm::vec2 m_targetPosition; + float m_strength; + bool m_enabled; + }; + + /** + * 距离约束 + */ + class VIVID_2D_MYDLL_API DistanceConstraint : public PhysicsConstraint { + public: + DistanceConstraint(std::shared_ptr particle, std::shared_ptr target, float maxDistance); + void apply(float deltaTime) override; + + std::shared_ptr getParticle() const override { return m_particle; } + bool isEnabled() const override { return m_enabled; } + void setEnabled(bool enabled) override { m_enabled = enabled; } + + std::shared_ptr getTarget() const { return m_target; } + float getMaxDistance() const { return m_maxDistance; } + + private: + std::shared_ptr m_particle; + std::shared_ptr m_target; + float m_maxDistance; + bool m_enabled; + }; + + /** + * 物理碰撞体接口 + */ + struct VIVID_2D_MYDLL_API PhysicsCollider { + virtual ~PhysicsCollider() = default; + virtual bool collidesWith(const PhysicsParticle& particle) const = 0; + virtual void resolveCollision(PhysicsParticle& particle, float deltaTime) = 0; + virtual const std::string& getId() const = 0; + virtual bool isEnabled() const = 0; + virtual void setEnabled(bool enabled) = 0; + }; + + /** + * 圆形碰撞体 + */ + class VIVID_2D_MYDLL_API CircleCollider : public PhysicsCollider { + public: + CircleCollider(std::string id, glm::vec2 center, float radius); + bool collidesWith(const PhysicsParticle& particle) const override; + void resolveCollision(PhysicsParticle& particle, float deltaTime) override; + + const std::string& getId() const override { return m_id; } + bool isEnabled() const override { return m_enabled; } + void setEnabled(bool enabled) override { m_enabled = enabled; } + + glm::vec2 getCenter() const { return m_center; } + void setCenter(const glm::vec2& center) { m_center = center; } + float getRadius() const { return m_radius; } + + private: + std::string m_id; + glm::vec2 m_center; + float m_radius; + bool m_enabled; + }; + + /** + * 矩形碰撞体 + */ + class VIVID_2D_MYDLL_API RectangleCollider : public PhysicsCollider { + public: + RectangleCollider(std::string id, glm::vec2 center, float width, float height); + bool collidesWith(const PhysicsParticle& particle) const override; + void resolveCollision(PhysicsParticle& particle, float deltaTime) override; + + const std::string& getId() const override { return m_id; } + bool isEnabled() const override { return m_enabled; } + void setEnabled(bool enabled) override { m_enabled = enabled; } + + glm::vec2 getCenter() const { return m_center; } + void setCenter(const glm::vec2& center) { m_center = center; } + float getWidth() const { return m_width; } + float getHeight() const { return m_height; } + + private: + std::string m_id; + glm::vec2 m_center; + float m_width; + float m_height; + bool m_enabled; + }; + +} // namespace Vivid2D::util + +#endif // PHYSICS_SYSTEM_H diff --git a/Vivid2DRenderer/model/util/Vertex.cpp b/Vivid2DRenderer/model/util/Vertex.cpp new file mode 100644 index 0000000..25c433c --- /dev/null +++ b/Vivid2DRenderer/model/util/Vertex.cpp @@ -0,0 +1,128 @@ +#include "pch.h" +#include "Vertex.h" +#include +#include + +namespace Vivid2D::util { + + // --- 캯ʵ --- + + Vertex::Vertex(float x, float y, float u, float v) + : position(x, y), uv(u, v), originalPosition(x, y), // ʼʱԭʼλ=ǰλ + tag(VertexTag::DEFAULT), selected(false), IsDelete(false), index(-1) { + // std::cout << "Vertex(x, y, u, v) created." << std::endl; // Ϣ + } + + Vertex::Vertex(const glm::vec2& position, const glm::vec2& uv) + : position(position), uv(uv), originalPosition(position), + tag(VertexTag::DEFAULT), selected(false), IsDelete(false), index(-1) { + } + + Vertex::Vertex(const glm::vec2& position, const glm::vec2& uv, VertexTag tag) + : position(position), uv(uv), originalPosition(position), + tag(tag), selected(false), IsDelete(false), index(-1) { + } + + Vertex::Vertex(const glm::vec2& position, const glm::vec2& uv, const glm::vec2& originalPosition) + : position(position), uv(uv), originalPosition(originalPosition), + tag(VertexTag::DEFAULT), selected(false), IsDelete(false), index(-1) { + } + + Vertex::Vertex(float x, float y) + : position(x, y), uv(0.0f, 0.0f), originalPosition(x, y), + tag(VertexTag::DEFAULT), selected(false), IsDelete(false), index(-1) { + } + + // --- /ֵʵ --- + + // 캯 (ִ) + Vertex::Vertex(const Vertex& other) + : position(other.position), + uv(other.uv), + originalPosition(other.originalPosition), + controlledTriangles(other.controlledTriangles), + tag(other.tag), + selected(other.selected), + name(other.name), + IsDelete(other.IsDelete), + index(other.index) { + } + + // ֵ + Vertex& Vertex::operator=(const Vertex& other) { + if (this != &other) { + position = other.position; + uv = other.uv; + originalPosition = other.originalPosition; + controlledTriangles = other.controlledTriangles; + tag = other.tag; + selected = other.selected; + name = other.name; + IsDelete = other.IsDelete; + index = other.index; + } + return *this; + } + + // --- ߼ʵ --- + + void Vertex::resetToOriginal() { + this->position = this->originalPosition; // glm::vec2 ˸ֵ + } + + void Vertex::saveAsOriginal() { + this->originalPosition = this->position; + } + + Vertex Vertex::copy() const { + // C++ Ƽʹÿ캯ֵ + Vertex newCopy = *this; // ʹÿ캯 + return newCopy; + } + + void Vertex::setControlledTriangles(const std::vector& controlledTriangles) { + this->controlledTriangles = controlledTriangles; + } + + // --- C++ ԣʵ --- + + // (Ӧ Java е equals()) + bool Vertex::operator==(const Vertex& other) const { + return selected == other.selected && + IsDelete == other.IsDelete && + position == other.position && + uv == other.uv && + originalPosition == other.originalPosition && + tag == other.tag && + name == other.name; + } + + // (Ӧ Java е toString()) + std::ostream& operator<<(std::ostream& os, const Vertex& vertex) { + std::string tagStr; + switch (vertex.tag) { + case VertexTag::DEFAULT: tagStr = "DEFAULT"; break; + // case + default: tagStr = "UNKNOWN"; break; + } + + // ʽ glm::vec2 + auto formatVec2 = [](const glm::vec2& v) -> std::string { + std::stringstream ss; + ss << std::fixed << std::setprecision(4) << "(" << v.x << ", " << v.y << ")"; + return ss.str(); + }; + + os << "Vertex{" + << "name='" << vertex.name << '\'' + << ", pos=" << formatVec2(vertex.position) + << ", uv=" << formatVec2(vertex.uv) + << ", orig=" << formatVec2(vertex.originalPosition) + << ", tag=" << tagStr + << ", selected=" << (vertex.selected ? "true" : "false") + << ", isDelete=" << (vertex.IsDelete ? "true" : "false") + << '}'; + return os; + } + +} // namespace util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/Vertex.h b/Vivid2DRenderer/model/util/Vertex.h new file mode 100644 index 0000000..2b97db9 --- /dev/null +++ b/Vivid2DRenderer/model/util/Vertex.h @@ -0,0 +1,167 @@ +#pragma once +#ifndef VIVID2D_VERTEX_H +#define VIVID2D_VERTEX_H + +#include +#include +#include +#include +#include +#include + +namespace Vivid2D::util { + + /** + * @brief ǩö٣ڱʶضͻ; + */ + enum class VIVID_2D_MYDLL_API VertexTag { + /** + * ڱεĶ + */ + DEFORMATION, + + /** + * ĬϵĶ + */ + DEFAULT, + + /** + * ͵Ķ + */ + OTHER + }; + + /** + * @brief װһ2D㣬λáUVԭʼλá + * + * Ӧ Java Vertex + */ + class VIVID_2D_MYDLL_API Vertex { + private: + std::vector controlledTriangles; + VertexTag tag; + bool selected; + std::string name; + bool IsDelete; + + public: + glm::vec2 position; // ǰλ (x, y) + glm::vec2 uv; // UV (u, v) + glm::vec2 originalPosition; // ԭʼλ (ڱ) + int index; // + + /** + * @brief 캯 + * + * @param x x + * @param y y + * @param u u + * @param v v + */ + Vertex(float x, float y, float u, float v); + + /** + * @brief 캯 + * + * @param position λ + * @param uv UV + */ + Vertex(const glm::vec2& position, const glm::vec2& uv); + + /** + * @brief 캯 + */ + Vertex(const glm::vec2& position, const glm::vec2& uv, VertexTag tag); + + /** + * @brief 캯ڸƣ + * + * @param position λ + * @param uv UV + * @param originalPosition ԭʼλ + */ + Vertex(const glm::vec2& position, const glm::vec2& uv, const glm::vec2& originalPosition); + + /** + * @brief ݹ캯ڽͨλô + * UV꽫ĬΪ (0, 0) + * + * @param x x + * @param y y + */ + Vertex(float x, float y); + + // ҪͷŵĶ̬ԴҪԶ壬ﲻҪ + // ~Vertex() = default; + + // 캯Ϳֵ + Vertex(const Vertex& other); + Vertex& operator=(const Vertex& other); + + // ƶ캯ƶֵ + // Vertex(Vertex&& other) noexcept = default; + // Vertex& operator=(Vertex&& other) noexcept = default; + + + // --- Getters Setters --- + + VertexTag getTag() const { return tag; } + void setTag(VertexTag tag) { this->tag = tag; } + + void setIndex(int index) { this->index = index; } + int getIndex() const { return index; } + + void deleteVertex() { IsDelete = true; } + bool isDelete() const { return IsDelete; } + + void setSelected(bool selected) { this->selected = selected; } + bool isSelected() const { return selected; } + + void setName(const std::string& name) { this->name = name; } + const std::string& getName() const { return name; } + + const std::vector& getControlledTriangles() const { return controlledTriangles; } + void setControlledTriangles(const std::vector& controlledTriangles); + + // --- ߼ --- + + /** + * @brief Ϊԭʼλ + */ + void resetToOriginal(); + + /** + * @brief 浱ǰλΪµԭʼλ + */ + void saveAsOriginal(); + + /** + * @brief ˶ (ӦJavaеcopy()) + */ + Vertex copy() const; + + // --- C++ ԣ --- + + /** + * @brief (Ӧ Java е equals()) + */ + bool operator==(const Vertex& other) const; + + /** + * @brief ز + */ + bool operator!=(const Vertex& other) const { + return !(*this == other); + } + + /** + * @brief (Ӧ Java е toString()) + */ + friend std::ostream& operator<<(std::ostream& os, const Vertex& vertex); + + }; + +} // util + +#endif // VIVID2D_VERTEX_H + diff --git a/Vivid2DRenderer/model/util/VertexList.cpp b/Vivid2DRenderer/model/util/VertexList.cpp new file mode 100644 index 0000000..bc67fc3 --- /dev/null +++ b/Vivid2DRenderer/model/util/VertexList.cpp @@ -0,0 +1,216 @@ +#include "pch.h" +#include "VertexList.h" +#include +#include +#include + +namespace Vivid2D::util { + + // --- 构造函数实现 --- + + VertexList::VertexList() + : name("KongFuZi"), indices() { + // std::vector 的默认构造函数创建一个空向量 + } + + VertexList::VertexList(const std::string& name) + : name(name), indices() { + if (name.empty()) { + throw std::invalid_argument("Name cannot be empty"); + } + } + + VertexList::VertexList(const std::string& name, const std::vector& initialVertices, const std::vector& initialIndices) + : VertexList(name) + { + if (!initialVertices.empty()) { + for (const auto& vertex : initialVertices) { + this->add(vertex); + } + } + setIndices(initialIndices); + } + + + VertexList::VertexList(const VertexList& other) + : vertices(other.vertices), + name(other.name), + indices(other.indices) + { + } + + VertexList& VertexList::operator=(const VertexList& other) { + if (this != &other) { + vertices = other.vertices; + name = other.name; + indices = other.indices; + } + return *this; + } + + + // --- 列表管理实现 --- + + void VertexList::add(const Vertex& vertex) { + Vertex v = vertex; + v.setIndex(static_cast(this->vertices.size())); + this->vertices.push_back(v); + } + + bool VertexList::remove(const Vertex& vertex) { + auto it = std::find_if(this->vertices.begin(), this->vertices.end(), + [&vertex](const Vertex& v) { return v == vertex; }); + + if (it != this->vertices.end()) { + int index = std::distance(this->vertices.begin(), it); + remove(index); + return true; + } + return false; + } + + Vertex VertexList::remove(int index) { + if (index < 0 || index >= static_cast(this->vertices.size())) { + throw std::out_of_range("Index out of bounds: " + std::to_string(index)); + } + + // 1. 重构索引数组 + std::vector newIndices; + newIndices.reserve(this->indices.size()); + + for (size_t i = 0; i < this->indices.size(); i += 3) { + int i1 = this->indices[i]; + int i2 = this->indices[i + 1]; + int i3 = this->indices[i + 2]; + + if (i1 == index || i2 == index || i3 == index) { + continue; + } + + if (i1 > index) i1--; + if (i2 > index) i2--; + if (i3 > index) i3--; + + newIndices.push_back(i1); + newIndices.push_back(i2); + newIndices.push_back(i3); + } + + this->indices = std::move(newIndices); + + Vertex removedVertex = this->vertices[index]; + this->vertices.erase(this->vertices.begin() + index); + + for (size_t i = index; i < this->vertices.size(); ++i) { + this->vertices[i].setIndex(static_cast(i)); + } + + return removedVertex; + } + + const Vertex& VertexList::get(int index) const { + if (index < 0 || index >= static_cast(this->vertices.size())) { + throw std::out_of_range("Index out of bounds: " + std::to_string(index)); + } + return this->vertices.at(index); + } + + void VertexList::clear() { + this->vertices.clear(); + this->indices.clear(); + } + + std::vector VertexList::getVertices() const { + return this->vertices; + } + + std::vector VertexList::getVertices(VertexTag tag) const { + std::vector filtered; + for (const auto& vertex : vertices) { + if (vertex.getTag() == tag) { + filtered.push_back(vertex); + } + } + return filtered; + } + + // --- Getter 和 Setter 实现 --- + + void VertexList::setName(const std::string& name) { + if (name.empty()) { + throw std::invalid_argument("Name cannot be empty"); + } + this->name = name; + } + + std::vector VertexList::getIndices() const { + return this->indices; + } + + std::vector VertexList::getIndices(VertexTag tag) const { + if (indices.empty()) { + return {}; + } + + std::unordered_set taggedOriginalIndices; + for (size_t i = 0; i < this->vertices.size(); ++i) { + if (this->vertices[i].getTag() == tag) { + taggedOriginalIndices.insert(static_cast(i)); + } + } + + if (taggedOriginalIndices.empty()) { + return {}; + } + + std::vector newIndices; + newIndices.reserve(this->indices.size()); + + for (size_t i = 0; i < this->indices.size(); i += 3) { + int i1 = this->indices[i]; + int i2 = this->indices[i + 1]; + int i3 = this->indices[i + 2]; + + if (taggedOriginalIndices.count(i1) || + taggedOriginalIndices.count(i2) || + taggedOriginalIndices.count(i3)) { + + newIndices.push_back(i1); + newIndices.push_back(i2); + newIndices.push_back(i3); + } + } + + return newIndices; + } + + void VertexList::setIndices(const std::vector& indices) { + this->indices = indices; + } + + void VertexList::set(int index, const Vertex& vertex) { + if (index < 0 || index >= static_cast(this->vertices.size())) { + throw std::out_of_range("Index out of bounds: " + std::to_string(index)); + } + Vertex v = vertex; + v.setIndex(index); + this->vertices.at(index) = v; + } + + + bool VertexList::operator==(const VertexList& other) const { + return vertices == other.vertices && + name == other.name && + indices == other.indices; + } + + std::ostream& operator<<(std::ostream& os, const VertexList& vertexList) { + os << "VertexList{" + << "name='" << vertexList.name << '\'' + << ", vertexCount=" << vertexList.vertices.size() + << ", indexCount=" << vertexList.indices.size() + << '}'; + return os; + } + +} // namespace util \ No newline at end of file diff --git a/Vivid2DRenderer/model/util/VertexList.h b/Vivid2DRenderer/model/util/VertexList.h new file mode 100644 index 0000000..2e6396b --- /dev/null +++ b/Vivid2DRenderer/model/util/VertexList.h @@ -0,0 +1,172 @@ +#pragma once + +#ifndef VIVID2D_VERTEXLIST_H +#define VIVID2D_VERTEXLIST_H + +#include +#include +#include +#include +#include +#include +#include "Vertex.h" + +namespace Vivid2D::util { + + /** + * @brief 一个自包含的几何数据单元,用于管理顶点(vertices) + * 以及定义它们之间拓扑结构(三角形)的索引(indices)。 + * + * 对应 Java 类 VertexList。 + */ + class VIVID_2D_MYDLL_API VertexList { + private: + std::string name; + std::vector indices; + + public: + std::vector vertices; + + /** + * @brief 默认构造函数。 + */ + VertexList(); + + /** + * @brief 构造函数 + * + * @param name 此列表的名称 + */ + explicit VertexList(const std::string& name); + + /** + * @brief 构造函数 + * + * @param name 此列表的名称 + * @param initialVertices 用于初始化列表的顶点集合 + * @param initialIndices 用于初始化列表的索引集合 + */ + VertexList(const std::string& name, const std::vector& initialVertices, const std::vector& initialIndices); + + VertexList(const VertexList& other); + VertexList& operator=(const VertexList& other); + + // --- 列表管理 --- + + /** + * @brief 向列表末尾添加一个顶点。 + * + * @param vertex 要添加的顶点 + */ + void add(const Vertex& vertex); + + /** + * @brief 移除列表中的指定顶点,并安全地移除所有引用它的三角形,同时重映射所有后续索引。 + * + * @param vertex 要移除的顶点 + * @return 如果成功移除则为 true + */ + bool remove(const Vertex& vertex); + + /** + * @brief 移除指定索引处的顶点,并安全地移除所有引用它的三角形,同时重映射所有后续索引。 + * + * @param index 要移除的索引 + * @return 被移除的顶点 + */ + Vertex remove(int index); + + /** + * @brief 获取指定索引处的顶点。 + */ + const Vertex& get(int index) const; + + /** + * @brief 返回列表中的顶点数量。 + */ + size_t size() const { return vertices.size(); } + + /** + * @brief 检查列表是否为空。 + */ + bool isEmpty() const { return vertices.empty(); } + + /** + * @brief 清空列表中的所有顶点和索引。 + */ + void clear(); + + /** + * @brief 返回内部顶点列表的副本。 + * + * @return 顶点的列表副本 + */ + std::vector getVertices() const; + + /** + * @brief 根据标签过滤并返回顶点列表。 + * + * @return 过滤后的顶点列表 + */ + std::vector getVertices(VertexTag tag) const; + + // --- Getter 和 Setter --- + + const std::string& getName() const { return name; } + void setName(const std::string& name); + + /** + * @brief 获取索引数组的副本。 + * + * @return 索引数组的副本 + */ + std::vector getIndices() const; + + /** + * @brief 获取仅由至少包含一个指定标签(Tag)的顶点组成的三角形索引。 + * + * @param tag 要筛选的顶点标签 + * @return 一个新的索引数组,包含符合条件的三角形的原始索引。 + */ + std::vector getIndices(VertexTag tag) const; + + /** + * @brief 设置索引数组。 + * + * @param indices 新的索引数组 + */ + void setIndices(const std::vector& indices); + + /** + * @brief 替换指定索引处的顶点。 + */ + void set(int index, const Vertex& vertex); + + // --- C++ 特性:迭代器 和 Object 方法 --- + + // C++ 迭代器支持 (代替 Java 的 Iterable 接口) + auto begin() { return vertices.begin(); } + auto end() { return vertices.end(); } + auto begin() const { return vertices.cbegin(); } + auto end() const { return vertices.cend(); } + + + /** + * @brief 重载相等运算符 (对应 Java 中的 equals()) + */ + bool operator==(const VertexList& other) const; + + /** + * @brief 重载不等运算符 + */ + bool operator!=(const VertexList& other) const { return !(*this == other); } + + /** + * @brief 重载输出流运算符 (对应 Java 中的 toString()) + */ + friend std::ostream& operator<<(std::ostream& os, const VertexList& vertexList); + }; + +} // namespace util + +#endif // VIVID2D_VERTEXLIST_H \ No newline at end of file diff --git a/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.cpp b/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.cpp new file mode 100644 index 0000000..9226dad --- /dev/null +++ b/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.cpp @@ -0,0 +1,244 @@ +#include "pch.h" + +#include "MultiSelectionBoxRenderer.h" +#include +#include +#include + +namespace Vivid2D::Render { + static constexpr double PI = 3.14159265358979323846; + + // -------------------- 配置常量定义 -------------------- + + const glm::vec4 MultiSelectionBoxRenderer::DASHED_BORDER_COLOR = glm::vec4(1.0f, 0.85f, 0.0f, 1.0f); + const glm::vec4 MultiSelectionBoxRenderer::SOLID_BORDER_COLOR_OUTER = glm::vec4(0.0f, 0.85f, 0.95f, 0.12f); + const glm::vec4 MultiSelectionBoxRenderer::SOLID_BORDER_COLOR_MAIN = glm::vec4(0.0f, 0.92f, 0.94f, 1.0f); + const glm::vec4 MultiSelectionBoxRenderer::SOLID_BORDER_COLOR_INNER = glm::vec4(1.0f, 1.0f, 1.0f, 0.9f); + const glm::vec4 MultiSelectionBoxRenderer::HANDLE_COLOR = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + const glm::vec4 MultiSelectionBoxRenderer::MULTI_SELECTION_HANDLE_COLOR = glm::vec4(1.0f, 0.9f, 0.0f, 1.0f); + const glm::vec4 MultiSelectionBoxRenderer::CENTER_POINT_COLOR = glm::vec4(1.0f, 0.2f, 0.2f, 1.0f); + + // -------------------- 辅助绘图方法实现 -------------------- + + void MultiSelectionBoxRenderer::addFilledQuad(BufferBuilder& bb, float x0, float y0, float x1, float y1) { + // tri 1 + bb.vertex(x0, y0, 0.0f, 0.0f); + bb.vertex(x1, y0, 0.0f, 0.0f); + bb.vertex(x1, y1, 0.0f, 0.0f); + // tri 2 + bb.vertex(x1, y1, 0.0f, 0.0f); + bb.vertex(x0, y1, 0.0f, 0.0f); + bb.vertex(x0, y0, 0.0f, 0.0f); + } + + void MultiSelectionBoxRenderer::addHandleQuad(BufferBuilder& bb, float cx, float cy, float size) { + float half = size * 0.5f; + addFilledQuad(bb, cx - half, cy - half, cx + half, cy + half); + } + + void MultiSelectionBoxRenderer::addQuadLine(BufferBuilder& bb, float x0, float y0, float x1, float y1, float thickness) { + float dx = x1 - x0; + float dy = y1 - y0; + float len = std::sqrt(dx * dx + dy * dy); + + // 使用一个小的 epsilon 进行浮点数比较 + if (len < 1e-6f) return; + + float halfThick = thickness * 0.5f; + // 计算法线向量 (nx, ny) + float nx = -dy / len * halfThick; + float ny = dx / len * halfThick; + + float v0x = x0 + nx; float v0y = y0 + ny; + float v1x = x1 + nx; float v1y = y1 + ny; + float v2x = x1 - nx; float v2y = y1 - ny; + float v3x = x0 - nx; float v3y = y0 - ny; + + // tri1 + bb.vertex(v3x, v3y, 0.0f, 0.0f); + bb.vertex(v0x, v0y, 0.0f, 0.0f); + bb.vertex(v1x, v1y, 0.0f, 0.0f); + // tri2 + bb.vertex(v1x, v1y, 0.0f, 0.0f); + bb.vertex(v2x, v2y, 0.0f, 0.0f); + bb.vertex(v3x, v3y, 0.0f, 0.0f); + } + + // 假设输入顺序为:p0(x0, y0), p1(x1, y1), p2(x2, y2), p3(x3, y3) + void MultiSelectionBoxRenderer::addQuadLineLoop(BufferBuilder& bb, float thickness, + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) { + // 边 1: (x0, y0) -> (x1, y1) + addQuadLine(bb, x0, y0, x1, y1, thickness); + // 边 2: (x1, y1) -> (x2, y2) + addQuadLine(bb, x1, y1, x2, y2, thickness); + // 边 3: (x2, y2) -> (x3, y3) + addQuadLine(bb, x2, y2, x3, y3, thickness); + // 边 4: (x3, y3) -> (x0, y0) + addQuadLine(bb, x3, y3, x0, y0, thickness); + } + + void MultiSelectionBoxRenderer::addRing(BufferBuilder& bb, float cx, float cy, float radius, float thickness, int segments) { + if (segments < 6) segments = 6; + float halfThick = thickness * 0.5f; + float innerR = std::max(0.5f, radius - halfThick); + float outerR = radius + halfThick; + + for (int i = 0; i < segments; i++) { + // 使用 M_PI (来自 ) + float a0 = static_cast(i * 2.0 * PI / segments); + float a1 = static_cast((i + 1) * 2.0 * PI / segments); + + float cos0 = std::cos(a0), sin0 = std::sin(a0); + float cos1 = std::cos(a1), sin1 = std::sin(a1); + + float x0i = cx + cos0 * innerR, y0i = cy + sin0 * innerR; + float x1i = cx + cos1 * innerR, y1i = cy + sin1 * innerR; + float x0o = cx + cos0 * outerR, y0o = cy + sin0 * outerR; + float x1o = cx + cos1 * outerR, y1o = cy + sin1 * outerR; + + // tri 1 (x0i, x0o, x1o) + bb.vertex(x0i, y0i, 0.0f, 0.0f); + bb.vertex(x0o, y0o, 0.0f, 0.0f); + bb.vertex(x1o, y1o, 0.0f, 0.0f); + // tri 2 (x1o, x1i, x0i) + bb.vertex(x1o, y1o, 0.0f, 0.0f); + bb.vertex(x1i, y1i, 0.0f, 0.0f); + bb.vertex(x0i, y0i, 0.0f, 0.0f); + } + } + + void MultiSelectionBoxRenderer::addDashedLineVertices(BufferBuilder& bb, float startX, float startY, float endX, float endY, + float dashLen, float gapLen) { + float dx = endX - startX; + float dy = endY - startY; + float len = std::sqrt(dx * dx + dy * dy); + if (len < 1e-6f) return; + + float dirX = dx / len, dirY = dy / len; + float segment = dashLen + gapLen; + // 使用 std::ceil 和 static_cast + int count = std::max(1, static_cast(std::ceil(len / segment))); + + for (int i = 0; i < count; i++) { + float s = i * segment; + if (s >= len) break; + float e = std::min(s + dashLen, len); + + float sx = startX + dirX * s; + float sy = startY + dirY * s; + float ex = startX + dirX * e; + float ey = startY + dirY * e; + + // GL_LINES 模式下,每两个顶点构成一条线段 + bb.vertex(sx, sy, 0.0f, 0.0f); + bb.vertex(ex, ey, 0.0f, 0.0f); + } + } + + // -------------------- 公共绘制 API 实现 -------------------- + + void MultiSelectionBoxRenderer::drawSelectBox(const BoundingBox& bounds, const glm::vec2& pivot) { + if (!bounds.isValid()) return; + + float minX = bounds.getMinX(); + float minY = bounds.getMinY(); + float maxX = bounds.getMaxX(); + float maxY = bounds.getMaxY(); + + Tesselator& tesselator = Tesselator::getInstance(); + BufferBuilder& bb = tesselator.getBuilder(); + + RenderSystem::enableBlend(); + // 使用 GL 常量 + RenderSystem::blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // 估算顶点数:三层边框(3*4*6) + 8个手柄(8*6) + 中心点十字(2*6) + 圆环(18*6) + int estimatedVertices = (3 * 4 + 8 + 2 + 18) * 6; + bb.begin(GL_TRIANGLES, estimatedVertices); + + // 外层发光边框 + bb.setColor(SOLID_BORDER_COLOR_OUTER); + addQuadLineLoop(bb, OUTER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY); + + // 主边框 + bb.setColor(SOLID_BORDER_COLOR_MAIN); + addQuadLineLoop(bb, MAIN_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY); + + // 内描边 + bb.setColor(SOLID_BORDER_COLOR_INNER); + addQuadLineLoop(bb, INNER_BORDER_THICKNESS, minX, minY, maxX, minY, maxX, maxY, minX, maxY); + + // 手柄(角点与边中点) + bb.setColor(HANDLE_COLOR); + addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE); + + addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE); + addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE); + addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE); + addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE); + + // 中心点:十字 + 环 + bb.setColor(CENTER_POINT_COLOR); + addQuadLine(bb, pivot.x - 6.0f, pivot.y, pivot.x + 6.0f, pivot.y, CENTER_LINE_THICKNESS); + addQuadLine(bb, pivot.x, pivot.y - 6.0f, pivot.x, pivot.y + 6.0f, CENTER_LINE_THICKNESS); + addRing(bb, pivot.x, pivot.y, CENTER_RING_RADIUS, CENTER_RING_THICKNESS, 18); + + tesselator.end(); + } + + void MultiSelectionBoxRenderer::drawMultiSelectionBox(const BoundingBox& multiBounds) { + if (!multiBounds.isValid()) return; + + float minX = multiBounds.getMinX(); + float minY = multiBounds.getMinY(); + float maxX = multiBounds.getMaxX(); + float maxY = multiBounds.getMaxY(); + + Tesselator& tesselator = Tesselator::getInstance(); + BufferBuilder& bb = tesselator.getBuilder(); + + RenderSystem::enableBlend(); + RenderSystem::blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // 1) 虚线边框(使用 GL_LINES) + float circumference = 2.0f * (multiBounds.getWidth() + multiBounds.getHeight()); + float segmentLength = DEFAULT_DASH_LENGTH + DEFAULT_GAP_LENGTH; + int estimatedSegments = std::max(4, static_cast(std::ceil(circumference / segmentLength))); + + // 这里的 GL_LINES 模式应使用 GL_LINES 常量 (假设它被正确定义) + bb.begin(GL_LINES, estimatedSegments * 8); // 4条边 * 2个顶点/线段 * 估算线段数 + + bb.setColor(DASHED_BORDER_COLOR); + addDashedLineVertices(bb, minX, minY, maxX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH); + addDashedLineVertices(bb, maxX, minY, maxX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH); + addDashedLineVertices(bb, maxX, maxY, minX, maxY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH); + addDashedLineVertices(bb, minX, maxY, minX, minY, DEFAULT_DASH_LENGTH, DEFAULT_GAP_LENGTH); + tesselator.end(); + + // 2) 手柄与中心(合并为一次三角形绘制) + bb.begin(GL_TRIANGLES, (8 + 2) * 6); // 8个手柄 + 2个中心十字线段 (转换为四边形) + + bb.setColor(MULTI_SELECTION_HANDLE_COLOR); + addHandleQuad(bb, minX, minY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, maxX, minY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, minX, maxY, HANDLE_CORNER_SIZE); + addHandleQuad(bb, maxX, maxY, HANDLE_CORNER_SIZE); + + addHandleQuad(bb, (minX + maxX) * 0.5f, minY, HANDLE_MID_SIZE); + addHandleQuad(bb, (minX + maxX) * 0.5f, maxY, HANDLE_MID_SIZE); + addHandleQuad(bb, minX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE); + addHandleQuad(bb, maxX, (minY + maxY) * 0.5f, HANDLE_MID_SIZE); + + glm::vec2 center = multiBounds.getCenter(); + bb.setColor(CENTER_POINT_COLOR); + addQuadLine(bb, center.x - 6.0f, center.y, center.x + 6.0f, center.y, CENTER_LINE_THICKNESS); + addQuadLine(bb, center.x, center.y - 6.0f, center.x, center.y + 6.0f, CENTER_LINE_THICKNESS); + + tesselator.end(); + } + +} // namespace com::chuangzhou::vivid2D::render \ No newline at end of file diff --git a/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.h b/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.h new file mode 100644 index 0000000..967f419 --- /dev/null +++ b/Vivid2DRenderer/systems/MultiSelectionBoxRenderer.h @@ -0,0 +1,91 @@ +#pragma once + +#ifndef VIVID2D_MULTISELECTIONBOXRENDERER_H +#define VIVID2D_MULTISELECTIONBOXRENDERER_H + +#include +#include + +#include "../model/util/BoundingBox.h" +#include "RenderSystem.h" +#include "buffer/Tesselator.h" +#include "buffer/BufferBuilder.h" + +namespace Vivid2D::Render { + + using Buffer::BufferBuilder; + using Buffer::Tesselator; + using util::BoundingBox; + + /** + * @brief MultiSelectionBoxRenderer - 负责绘制单选和多选的边界框、手柄和中心点。 + * * 这是一个静态工具类,用于封装所有复杂的几何体构建逻辑,将绘制任务提交给 Tesselator。 + */ + class VIVID_2D_MYDLL_API MultiSelectionBoxRenderer { + private: + // -------------------- 配置常量(可调) -------------------- + // 尺寸 + static constexpr float DEFAULT_CORNER_SIZE = 8.0f; + static constexpr float DEFAULT_BORDER_THICKNESS = 2.5f; + static constexpr float DEFAULT_DASH_LENGTH = 10.0f; + static constexpr float DEFAULT_GAP_LENGTH = 6.0f; + + // 视觉厚度分层 + static constexpr float OUTER_BORDER_THICKNESS = 2.2f; + static constexpr float MAIN_BORDER_THICKNESS = 0.6f; + static constexpr float INNER_BORDER_THICKNESS = 0.2f; + + // 手柄与中心点尺寸 + static constexpr float HANDLE_CORNER_SIZE = DEFAULT_CORNER_SIZE; + static constexpr float HANDLE_MID_SIZE = 2.8f; + static constexpr float CENTER_LINE_THICKNESS = 1.2f; + static constexpr float CENTER_RING_RADIUS = 5.0f; + static constexpr float CENTER_RING_THICKNESS = 1.2f; + + // 颜色(glm::vec4 替换 Vector4f) + static const glm::vec4 DASHED_BORDER_COLOR; + static const glm::vec4 SOLID_BORDER_COLOR_OUTER; + static const glm::vec4 SOLID_BORDER_COLOR_MAIN; + static const glm::vec4 SOLID_BORDER_COLOR_INNER; + static const glm::vec4 HANDLE_COLOR; + static const glm::vec4 MULTI_SELECTION_HANDLE_COLOR; + static const glm::vec4 CENTER_POINT_COLOR; + + // -------------------- 辅助绘图方法(在 .cpp 中实现) -------------------- + + static void addFilledQuad(BufferBuilder& bb, float x0, float y0, float x1, float y1); + static void addHandleQuad(BufferBuilder& bb, float cx, float cy, float size); + static void addQuadLine(BufferBuilder& bb, float x0, float y0, float x1, float y1, float thickness); + + // C++ 中为了简单起见,明确传入 4 个点的坐标 (minX, minY, maxX, minY, maxX, maxY, minX, maxY) + static void addQuadLineLoop(BufferBuilder& bb, float thickness, + float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3); + + static void addRing(BufferBuilder& bb, float cx, float cy, float radius, float thickness, int segments); + + static void addDashedLineVertices(BufferBuilder& bb, float startX, float startY, float endX, float endY, + float dashLen, float gapLen); + + public: + // 禁用构造函数 (静态工具类) + MultiSelectionBoxRenderer() = delete; + + /** + * @brief 绘制单选的选择框(主入口) + * + * @param bounds 包围盒(世界坐标)。必须是有效的。 + * @param pivot 旋转中心 / 中心点(世界坐标)。 + */ + static void drawSelectBox(const BoundingBox& bounds, const glm::vec2& pivot); + + /** + * @brief 绘制多选框(虚线 + 手柄) + * + * @param multiBounds 多选包围盒。必须是有效的。 + */ + static void drawMultiSelectionBox(const BoundingBox& multiBounds); + }; + +} // namespace com::chuangzhou::vivid2D::render + +#endif // VIVID2D_MULTISELECTIONBOXRENDERER_H \ No newline at end of file diff --git a/Vivid2DRenderer/systems/Texture.cpp b/Vivid2DRenderer/systems/Texture.cpp new file mode 100644 index 0000000..ff6d80e --- /dev/null +++ b/Vivid2DRenderer/systems/Texture.cpp @@ -0,0 +1,608 @@ +#include "pch.h" + +#include "Texture.h" +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include +#include + + +namespace Vivid2D::Render::Texture { + + // 静态成员初始化 + std::unordered_map> Texture::s_TextureCache; + std::mutex Texture::s_CacheMutex; + + // ==================== 辅助函数实现 ==================== + + GLint getGLInternalFormat(TextureFormat format) { + switch (format) { + case TextureFormat::RED: return GL_R8; + case TextureFormat::RG: return GL_RG8; + case TextureFormat::RGB: return GL_RGB8; + case TextureFormat::RGBA: return GL_RGBA8; + case TextureFormat::ALPHA: return GL_ALPHA; + case TextureFormat::LUMINANCE: return GL_LUMINANCE; + case TextureFormat::LUMINANCE_ALPHA: return GL_LUMINANCE_ALPHA; + } + return GL_RGBA8; + } + + GLenum getGLFormat(TextureFormat format) { + switch (format) { + case TextureFormat::RED: return GL_RED; + case TextureFormat::RG: return GL_RG; + case TextureFormat::RGB: return GL_RGB; + case TextureFormat::RGBA: return GL_RGBA; + case TextureFormat::ALPHA: return GL_ALPHA; + case TextureFormat::LUMINANCE: return GL_LUMINANCE; + case TextureFormat::LUMINANCE_ALPHA: return GL_LUMINANCE_ALPHA; + } + return GL_RGBA; + } + + GLenum getGLType(TextureType type) { + switch (type) { + case TextureType::UNSIGNED_BYTE: return GL_UNSIGNED_BYTE; + case TextureType::BYTE: return GL_BYTE; + case TextureType::UNSIGNED_SHORT: return GL_UNSIGNED_SHORT; + case TextureType::SHORT: return GL_SHORT; + case TextureType::UNSIGNED_INT: return GL_UNSIGNED_INT; + case TextureType::INT: return GL_INT; + case TextureType::FLOAT: return GL_FLOAT; + } + return GL_UNSIGNED_BYTE; + } + + GLint getGLFilter(TextureFilter filter) { + switch (filter) { + case TextureFilter::NEAREST: return GL_NEAREST; + case TextureFilter::LINEAR: return GL_LINEAR; + case TextureFilter::NEAREST_MIPMAP_NEAREST: return GL_NEAREST_MIPMAP_NEAREST; + case TextureFilter::LINEAR_MIPMAP_NEAREST: return GL_LINEAR_MIPMAP_NEAREST; + case TextureFilter::NEAREST_MIPMAP_LINEAR: return GL_NEAREST_MIPMAP_LINEAR; + case TextureFilter::LINEAR_MIPMAP_LINEAR: return GL_LINEAR_MIPMAP_LINEAR; + } + return GL_LINEAR; + } + + GLint getGLWrap(TextureWrap wrap) { + switch (wrap) { + case TextureWrap::REPEAT: return GL_REPEAT; + case TextureWrap::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + case TextureWrap::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; + case TextureWrap::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; + } + return GL_CLAMP_TO_EDGE; + } + + int getComponentCount(TextureFormat format) { + switch (format) { + case TextureFormat::RED: + case TextureFormat::ALPHA: + case TextureFormat::LUMINANCE: return 1; + case TextureFormat::RG: + case TextureFormat::LUMINANCE_ALPHA: return 2; + case TextureFormat::RGB: return 3; + case TextureFormat::RGBA: return 4; + } + return 4; + } + + TextureFormat getTextureFormatFromComponents(int components) { + switch (components) { + case 1: return TextureFormat::RED; + case 2: return TextureFormat::RG; + case 3: return TextureFormat::RGB; + case 4: return TextureFormat::RGBA; + default: throw std::invalid_argument("Unsupported number of components: " + std::to_string(components)); + } + } + + // ImageData 构造函数 + ImageData::ImageData(unsigned char* d, int w, int h, TextureFormat f, std::string path) + : data(d, stbi_image_free), width(w), height(h), format(f), sourcePath(std::move(path)) {} + + + // ==================== Texture 类实现 ==================== + + Texture::Texture(std::string name, int width, int height, TextureFormat format, TextureType type) + : m_Name(std::move(name)), m_Width(width), m_Height(height), m_Format(format), m_Type(type) { + m_CreationTime = std::chrono::system_clock::now().time_since_epoch().count(); + m_TextureId = generateTextureId(); + createTextureObject(); + applyTextureParameters(); + } + + Texture::Texture(std::string name, int width, int height, TextureFormat format, const unsigned char* pixelData) + : Texture(std::move(name), width, height, format, TextureType::UNSIGNED_BYTE) { + size_t size = width * height * getComponentCount(format); + uploadData(pixelData, size); + } + + Texture::~Texture() { + dispose(); + } + + Texture::Texture(Texture&& other) noexcept + : m_TextureId(other.m_TextureId), m_Name(std::move(other.m_Name)), m_Width(other.m_Width), m_Height(other.m_Height), + m_Format(other.m_Format), m_Type(other.m_Type), m_MinFilter(other.m_MinFilter), m_MagFilter(other.m_MagFilter), + m_WrapS(other.m_WrapS), m_WrapT(other.m_WrapT), m_MipmapsEnabled(other.m_MipmapsEnabled), + m_Disposed(other.m_Disposed), m_CreationTime(other.m_CreationTime), m_PixelDataCache(std::move(other.m_PixelDataCache)) + { + other.m_TextureId = 0; + other.m_Disposed = true; + } + + Texture& Texture::operator=(Texture&& other) noexcept { + if (this != &other) { + dispose(); + m_TextureId = other.m_TextureId; + m_Name = std::move(other.m_Name); + m_Width = other.m_Width; + m_Height = other.m_Height; + m_Format = other.m_Format; + m_Type = other.m_Type; + m_MinFilter = other.m_MinFilter; + m_MagFilter = other.m_MagFilter; + m_WrapS = other.m_WrapS; + m_WrapT = other.m_WrapT; + m_MipmapsEnabled = other.m_MipmapsEnabled; + m_Disposed = other.m_Disposed; + m_CreationTime = other.m_CreationTime; + m_PixelDataCache = std::move(other.m_PixelDataCache); + + other.m_TextureId = 0; + other.m_Disposed = true; + } + return *this; + } + + void Texture::createTextureObject() { + bind(); + RenderSystem::texImage2D(GL_TEXTURE_2D, 0, getGLInternalFormat(m_Format), m_Width, m_Height, 0, getGLFormat(m_Format), getGLType(m_Type), nullptr); + unbind(); + RenderSystem::checkGLError("Texture::createTextureObject"); + } + + void Texture::uploadData(const std::vector& pixelData) { + uploadData(pixelData.data(), pixelData.size()); + } + + void Texture::uploadData(const unsigned char* pixelData, size_t size) { + if (m_Disposed) throw std::runtime_error("Cannot upload data to disposed texture: " + m_Name); + if (!pixelData) throw std::invalid_argument("Pixel data cannot be null"); + + size_t expectedSize = m_Width * m_Height * getComponentCount(m_Format); + if (size < expectedSize) { + throw std::invalid_argument("Pixel data buffer too small for texture dimensions."); + } + + bind(); + RenderSystem::pixelStore(GL_UNPACK_ALIGNMENT, 1); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_Width, m_Height, getGLFormat(m_Format), getGLType(m_Type), pixelData); + + RenderSystem::checkGLError("glTexSubImage2D"); + RenderSystem::pixelStore(GL_UNPACK_ALIGNMENT, 4); + unbind(); + + m_PixelDataCache.assign(pixelData, pixelData + expectedSize); + } + + void Texture::generateMipmaps() { + if (m_Disposed) throw std::runtime_error("Cannot generate mipmaps for disposed texture: " + m_Name); + if (!isPowerOfTwo(m_Width) || !isPowerOfTwo(m_Height)) { + // Log warning instead of throwing + // std::cerr << "Warning: Cannot generate mipmaps for non-power-of-two texture: " << m_Name << std::endl; + return; + } + + bind(); + glGenerateMipmap(GL_TEXTURE_2D); + RenderSystem::checkGLError("glGenerateMipmap"); + unbind(); + + m_MipmapsEnabled = true; + + if (m_MinFilter == TextureFilter::LINEAR) { + setMinFilter(TextureFilter::LINEAR_MIPMAP_LINEAR); + } + else if (m_MinFilter == TextureFilter::NEAREST) { + setMinFilter(TextureFilter::NEAREST_MIPMAP_NEAREST); + } + } + + std::unique_ptr Texture::crop(int x, int y, int w, int h, const std::string& newName) { + if (m_Disposed) throw std::runtime_error("Cannot crop disposed texture: " + m_Name); + if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > m_Width || y + h > m_Height) { + throw std::invalid_argument("Crop rectangle out of bounds"); + } + if (m_Type != TextureType::UNSIGNED_BYTE) { + throw std::runtime_error("Crop currently only supported for UNSIGNED_BYTE textures"); + } + + ensurePixelDataCached(); + if (!hasPixelData()) { + throw std::runtime_error("No pixel data available for cropping texture: " + m_Name); + } + + int comps = getComponentCount(m_Format); + int srcStride = m_Width * comps; + int dstStride = w * comps; + std::vector croppedData(w * h * comps); + + for (int row = 0; row < h; ++row) { + const unsigned char* srcRow = m_PixelDataCache.data() + ((y + row) * m_Width + x) * comps; + unsigned char* dstRow = croppedData.data() + row * dstStride; + memcpy(dstRow, srcRow, dstStride); + } + + auto newTex = std::make_unique(newName, w, h, m_Format, croppedData.data()); + newTex->setMinFilter(m_MinFilter); + newTex->setMagFilter(m_MagFilter); + newTex->setWrapS(m_WrapS); + newTex->setWrapT(m_WrapT); + + if (m_MipmapsEnabled && isPowerOfTwo(w) && isPowerOfTwo(h)) { + newTex->generateMipmaps(); + } + return newTex; + } + + + void Texture::setMinFilter(TextureFilter filter) { + if (m_MinFilter != filter) { + m_MinFilter = filter; + applyTextureParameters(); + } + } + + void Texture::setMagFilter(TextureFilter filter) { + if (m_MagFilter != filter) { + m_MagFilter = filter; + applyTextureParameters(); + } + } + + void Texture::setWrapS(TextureWrap wrap) { + if (m_WrapS != wrap) { + m_WrapS = wrap; + applyTextureParameters(); + } + } + + void Texture::setWrapT(TextureWrap wrap) { + if (m_WrapT != wrap) { + m_WrapT = wrap; + applyTextureParameters(); + } + } + + void Texture::setWrap(TextureWrap wrapS, TextureWrap wrapT) { + setWrapS(wrapS); + setWrapT(wrapT); + } + + void Texture::setWrap(TextureWrap wrap) { + setWrap(wrap, wrap); + } + + void Texture::applyTextureParameters() { + if (m_Disposed) return; + bind(); + RenderSystem::texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, getGLFilter(m_MinFilter)); + RenderSystem::texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, getGLFilter(m_MagFilter)); + RenderSystem::texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLWrap(m_WrapS)); + RenderSystem::texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLWrap(m_WrapT)); + unbind(); + } + + void Texture::bind(int textureUnit) const { + if (m_Disposed) throw std::runtime_error("Cannot bind disposed texture: " + m_Name); + RenderSystem::activeTexture(GL_TEXTURE0 + textureUnit); + RenderSystem::bindTexture(m_TextureId); + } + + void Texture::unbind() const { + RenderSystem::bindTexture(0); + } + + void Texture::dispose() { + if (!m_Disposed) { + RenderSystem::deleteTextures(m_TextureId); + m_TextureId = 0; + m_Disposed = true; + m_PixelDataCache.clear(); + + std::lock_guard lock(s_CacheMutex); + s_TextureCache.erase(m_Name); + } + } + + bool Texture::isDisposed() const { + return m_Disposed; + } + + bool Texture::hasPixelData() const { + return !m_PixelDataCache.empty(); + } + + std::vector Texture::getPixelData() { + ensurePixelDataCached(); + return m_PixelDataCache; + } + + void Texture::setPixelData(const std::vector& data) { + m_PixelDataCache = data; + } + + void Texture::clearPixelDataCache() { + m_PixelDataCache.clear(); + m_PixelDataCache.shrink_to_fit(); + } + + void Texture::ensurePixelDataCached() { + if (!hasPixelData()) { + cachePixelDataFromGPU(); + } + } + + size_t Texture::getPixelDataCacheSize() const { + return m_PixelDataCache.size(); + } + + std::vector Texture::extractTextureData() { + if (m_Disposed) throw std::runtime_error("Cannot extract data from a disposed texture."); + + size_t dataSize = m_Width * m_Height * getComponentCount(m_Format); + std::vector data(dataSize); + + bind(); + RenderSystem::getTexImage(GL_TEXTURE_2D, 0, getGLFormat(m_Format), getGLType(m_Type), data.data()); + unbind(); + + return data; + } + + void Texture::cachePixelDataFromGPU() { + if (m_Disposed) return; + m_PixelDataCache = extractTextureData(); + } + + bool Texture::saveToFile(const std::string& filePath, const std::string& format) { + if (m_Disposed) throw std::runtime_error("Cannot save a disposed texture."); + + ensurePixelDataCached(); + if (!hasPixelData()) { + return false; + } + + int comps = getComponentCount(m_Format); + int stride = m_Width * comps; + int success = 0; + + stbi_flip_vertically_on_write(true); + + if (format == "png") { + success = stbi_write_png(filePath.c_str(), m_Width, m_Height, comps, m_PixelDataCache.data(), stride); + } + else if (format == "jpg" || format == "jpeg") { + success = stbi_write_jpg(filePath.c_str(), m_Width, m_Height, comps, m_PixelDataCache.data(), 90); + } + else if (format == "bmp") { + success = stbi_write_bmp(filePath.c_str(), m_Width, m_Height, comps, m_PixelDataCache.data()); + } + else if (format == "tga") { + success = stbi_write_tga(filePath.c_str(), m_Width, m_Height, comps, m_PixelDataCache.data()); + } + else { + throw std::invalid_argument("Unsupported image format for saving: " + format); + } + + return success != 0; + } + + std::unique_ptr Texture::copy(const std::string& newName) const { + if (m_Disposed) throw std::runtime_error("Cannot copy disposed texture"); + + const_cast(this)->ensurePixelDataCached(); + if (!hasPixelData()) { + throw std::runtime_error("No pixel data available to copy texture: " + m_Name); + } + + auto newTex = std::make_unique(newName, m_Width, m_Height, m_Format, m_PixelDataCache.data()); + newTex->setMinFilter(m_MinFilter); + newTex->setMagFilter(m_MagFilter); + newTex->setWrapS(m_WrapS); + newTex->setWrapT(m_WrapT); + + if (m_MipmapsEnabled) { + newTex->generateMipmaps(); + } + return newTex; + } + + long Texture::getEstimatedMemoryUsage() const { + int bytesPerComponent = 1; + switch (m_Type) { + case TextureType::UNSIGNED_SHORT: + case TextureType::SHORT: + bytesPerComponent = 2; + break; + case TextureType::UNSIGNED_INT: + case TextureType::INT: + case TextureType::FLOAT: + bytesPerComponent = 4; + break; + default: + bytesPerComponent = 1; + } + long baseMemory = (long)m_Width * m_Height * getComponentCount(m_Format) * bytesPerComponent; + if (m_MipmapsEnabled) { + return baseMemory * 4L / 3L; + } + return baseMemory; + } + + // ==================== 静态方法实现 ==================== + + std::shared_ptr Texture::createSolidColor(const std::string& name, int width, int height, uint32_t rgbaColor) { + std::vector pixels(width * height, rgbaColor); + auto tex = std::make_shared(name, width, height, TextureFormat::RGBA, reinterpret_cast(pixels.data())); + return tex; + } + + std::shared_ptr Texture::createCheckerboard(const std::string& name, int width, int height, int tileSize, uint32_t color1, uint32_t color2) { + std::vector pixels(width * height); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + bool isColor1 = ((x / tileSize) + (y / tileSize)) % 2 == 0; + pixels[y * width + x] = isColor1 ? color1 : color2; + } + } + auto tex = std::make_shared(name, width, height, TextureFormat::RGBA, reinterpret_cast(pixels.data())); + return tex; + } + + std::shared_ptr Texture::getOrCreate(const std::string& name, int width, int height, TextureFormat format) { + std::lock_guard lock(s_CacheMutex); + if (s_TextureCache.count(name)) { + if (auto shared = s_TextureCache[name].lock()) { + return shared; + } + } + auto newTex = std::make_shared(name, width, height, format); + s_TextureCache[name] = newTex; + return newTex; + } + + std::unique_ptr Texture::loadImageFromFile(const std::string& filePath) { + int width, height, components; + unsigned char* data = stbi_load(filePath.c_str(), &width, &height, &components, 0); + if (!data) { + throw std::runtime_error("Failed to load image: " + filePath + " - " + stbi_failure_reason()); + } + TextureFormat format = getTextureFormatFromComponents(components); + return std::make_unique(data, width, height, format, filePath); + } + + std::shared_ptr Texture::createFromFile(const std::string& name, const std::string& filePath, TextureFilter minFilter, TextureFilter magFilter) { + auto imageData = loadImageFromFile(filePath); + auto texture = std::make_shared(name, imageData->width, imageData->height, imageData->format, imageData->data.get()); + texture->setMinFilter(minFilter); + texture->setMagFilter(magFilter); + + if (isPowerOfTwo(imageData->width) && isPowerOfTwo(imageData->height)) { + texture->generateMipmaps(); + } + + std::lock_guard lock(s_CacheMutex); + s_TextureCache[name] = texture; + return texture; + } + + std::shared_ptr Texture::createFromBytes(const std::string& name, const std::vector& imageData, TextureFilter minFilter, TextureFilter magFilter) { + int width, height, components; + unsigned char* pixelData = stbi_load_from_memory(imageData.data(), static_cast(imageData.size()), &width, &height, &components, 0); + + if (!pixelData) { + throw std::runtime_error("Failed to load image from bytes: " + std::string(stbi_failure_reason())); + } + + auto texture = std::make_shared(name, width, height, getTextureFormatFromComponents(components), pixelData); + texture->setMinFilter(minFilter); + texture->setMagFilter(magFilter); + stbi_image_free(pixelData); + + std::lock_guard lock(s_CacheMutex); + s_TextureCache[name] = texture; + return texture; + } + + std::shared_ptr Texture::createFromARGB(const std::string& name, int width, int height, const uint32_t* pixels, bool isPremultiplied, TextureFilter minFilter, TextureFilter magFilter) { + if (!pixels) throw std::invalid_argument("Pixel data cannot be null"); + + std::vector rgbaData(width * height * 4); + for (int i = 0; i < width * height; ++i) { + uint32_t p = pixels[i]; + int a = (p >> 24) & 0xFF; + int r = (p >> 16) & 0xFF; + int g = (p >> 8) & 0xFF; + int b = p & 0xFF; + + if (isPremultiplied && a != 0) { + float invA = 255.0f / static_cast(a); + r = std::min(255, static_cast(round(r * invA))); + g = std::min(255, static_cast(round(g * invA))); + b = std::min(255, static_cast(round(b * invA))); + } + + rgbaData[i * 4 + 0] = static_cast(r); + rgbaData[i * 4 + 1] = static_cast(g); + rgbaData[i * 4 + 2] = static_cast(b); + rgbaData[i * 4 + 3] = static_cast(a); + } + + auto texture = std::make_shared(name, width, height, TextureFormat::RGBA, rgbaData.data()); + texture->setMinFilter(minFilter); + texture->setMagFilter(magFilter); + + if (isPowerOfTwo(width) && isPowerOfTwo(height)) { + texture->generateMipmaps(); + } + + std::lock_guard lock(s_CacheMutex); + s_TextureCache[name] = texture; + return texture; + } + + bool Texture::isSupportedImageFormat(const std::string& filePath) { + std::string lowerPath = filePath; + std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), ::tolower); + const char* extensions[] = { ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".psd", ".gif", ".hdr", ".pic" }; + for (const auto* ext : extensions) { + if (lowerPath.length() >= strlen(ext) && lowerPath.compare(lowerPath.length() - strlen(ext), strlen(ext), ext) == 0) { + return true; + } + } + return false; + } + + ImageInfo Texture::getImageInfo(const std::string& filePath) { + int width, height, components; + if (!stbi_info(filePath.c_str(), &width, &height, &components)) { + throw std::runtime_error("Failed to get image info: " + filePath + " - " + stbi_failure_reason()); + } + return { width, height, components, filePath }; + } + + void Texture::cleanupAll() { + std::lock_guard lock(s_CacheMutex); + for (auto const& [key, val] : s_TextureCache) { + if (auto shared = val.lock()) { + shared->dispose(); + } + } + s_TextureCache.clear(); + } + + void Texture::stbi_vertical_flip_on_load(bool flip) { + stbi_set_flip_vertically_on_load(flip); + } + + // ==================== 私有方法实现 ==================== + + GLuint Texture::generateTextureId() { + return RenderSystem::genTextures(); + } + + bool Texture::isPowerOfTwo(int value) { + return value > 0 && (value & (value - 1)) == 0; + } + +} // namespace Vivid2D::Texture \ No newline at end of file diff --git a/Vivid2DRenderer/systems/Texture.h b/Vivid2DRenderer/systems/Texture.h new file mode 100644 index 0000000..32acf97 --- /dev/null +++ b/Vivid2DRenderer/systems/Texture.h @@ -0,0 +1,250 @@ +#pragma once + +#include "RenderSystem.h" +#include +#include +#include +#include +#include + +#include + +namespace Vivid2D::Render::Texture { + + // ==================== 枚举定义 ==================== + + enum class VIVID_2D_MYDLL_API TextureFormat { + RED, + RG, + RGB, + RGBA, + ALPHA, + LUMINANCE, + LUMINANCE_ALPHA, + }; + + enum class VIVID_2D_MYDLL_API TextureType { + UNSIGNED_BYTE, + BYTE, + UNSIGNED_SHORT, + SHORT, + UNSIGNED_INT, + INT, + FLOAT, + }; + + enum class VIVID_2D_MYDLL_API TextureFilter { + NEAREST, + LINEAR, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR, + }; + + enum class VIVID_2D_MYDLL_API TextureWrap { + REPEAT, + MIRRORED_REPEAT, + CLAMP_TO_EDGE, + CLAMP_TO_BORDER, + }; + + // ==================== 辅助函数和结构体 ==================== + + // 将枚举转换为对应的 OpenGL GLenum 值 + GLint getGLInternalFormat(TextureFormat format); + GLenum getGLFormat(TextureFormat format); + GLenum getGLType(TextureType type); + GLint getGLFilter(TextureFilter filter); + GLint getGLWrap(TextureWrap wrap); + int getComponentCount(TextureFormat format); + TextureFormat getTextureFormatFromComponents(int components); + + + /** + * @brief 存储从文件中加载的原始图像数据 + */ + struct VIVID_2D_MYDLL_API ImageData { + std::unique_ptr data; + int width; + int height; + TextureFormat format; + std::string sourcePath; + + ImageData(unsigned char* d, int w, int h, TextureFormat f, std::string path); + }; + + /** + * @brief 存储图像文件的元数据,而不加载整个图像 + */ + struct VIVID_2D_MYDLL_API ImageInfo { + int width; + int height; + int components; + std::string filePath; + }; + + /** + * @class Texture + * @brief 使用 OpenGL API 实现完整的纹理管理 + */ + class VIVID_2D_MYDLL_API Texture { + public: + // ==================== 构造函数、析构函数和移动语义 ==================== + + /** + * @brief 创建一个空的纹理对象,准备接收数据。 + */ + Texture(std::string name, int width, int height, TextureFormat format, TextureType type = TextureType::UNSIGNED_BYTE); + + /** + * @brief 从给定的像素数据创建一个纹理。 + */ + Texture(std::string name, int width, int height, TextureFormat format, const unsigned char* pixelData); + + ~Texture(); + + // 纹理是唯一的资源,应禁止拷贝,但允许移动 + Texture(const Texture&) = delete; + Texture& operator=(const Texture&) = delete; + Texture(Texture&& other) noexcept; + Texture& operator=(Texture&& other) noexcept; + + // ==================== 纹理数据管理 ==================== + + /** + * @brief 上传像素数据到 GPU。 + * @param pixelData 指向像素数据的指针。 + */ + void uploadData(const std::vector& pixelData); + void uploadData(const unsigned char* pixelData, size_t size); + + /** + * @brief 为纹理生成 Mipmap。 + */ + void generateMipmaps(); + + /** + * @brief 从当前纹理裁剪出一个子纹理并返回新的 Texture 实例。 + */ + std::unique_ptr crop(int x, int y, int w, int h, const std::string& newName); + + // ==================== 纹理参数设置 ==================== + + void setMinFilter(TextureFilter filter); + void setMagFilter(TextureFilter filter); + void setWrapS(TextureWrap wrap); + void setWrapT(TextureWrap wrap); + void setWrap(TextureWrap wrapS, TextureWrap wrapT); + void setWrap(TextureWrap wrap); + + // ==================== 绑定管理 ==================== + + void bind(int textureUnit = 0) const; + void unbind() const; + + // ==================== 资源管理 ==================== + + void dispose(); + bool isDisposed() const; + + // ==================== 像素数据缓存支持 ==================== + + bool hasPixelData() const; + std::vector getPixelData(); + void setPixelData(const std::vector& data); + void clearPixelDataCache(); + void ensurePixelDataCached(); + size_t getPixelDataCacheSize() const; + + /** + * @brief 从 GPU 提取纹理数据。 + */ + std::vector extractTextureData(); + + /** + * @brief 将纹理保存到文件。 + * @param filePath 保存路径。 + * @param format 文件格式 ("png", "jpg", "bmp", "tga")。 + */ + bool saveToFile(const std::string& filePath, const std::string& format = "png"); + + /** + * @brief 创建此纹理的深拷贝。 + */ + std::unique_ptr copy(const std::string& newName) const; + + + // ==================== Getter 方法 ==================== + + GLuint getTextureId() const { return m_TextureId; } + const std::string& getName() const { return m_Name; } + int getWidth() const { return m_Width; } + int getHeight() const { return m_Height; } + TextureFormat getFormat() const { return m_Format; } + TextureType getType() const { return m_Type; } + TextureFilter getMinFilter() const { return m_MinFilter; } + TextureFilter getMagFilter() const { return m_MagFilter; } + TextureWrap getWrapS() const { return m_WrapS; } + TextureWrap getWrapT() const { return m_WrapT; } + bool isMipmapsEnabled() const { return m_MipmapsEnabled; } + long long getCreationTime() const { return m_CreationTime; } + long getEstimatedMemoryUsage() const; + + + // ==================== 静态工厂方法 ==================== + + static std::shared_ptr createSolidColor(const std::string& name, int width, int height, uint32_t rgbaColor); + static std::shared_ptr createCheckerboard(const std::string& name, int width, int height, int tileSize, uint32_t color1, uint32_t color2); + static std::shared_ptr getOrCreate(const std::string& name, int width, int height, TextureFormat format); + static std::unique_ptr loadImageFromFile(const std::string& filePath); + static std::shared_ptr createFromFile(const std::string& name, const std::string& filePath, TextureFilter minFilter = TextureFilter::LINEAR, TextureFilter magFilter = TextureFilter::LINEAR); + static std::shared_ptr createFromBytes(const std::string& name, const std::vector& imageData, TextureFilter minFilter = TextureFilter::LINEAR, TextureFilter magFilter = TextureFilter::LINEAR); + + /** + * @brief 从 ARGB 格式的原始像素数据创建纹理(例如来自 AWT/Swing 或其他库)。 + * @param pixels 指向 32-bit ARGB 像素数据的指针。 + * @param isPremultiplied Alpha 是否已预乘。 + */ + static std::shared_ptr createFromARGB(const std::string& name, int width, int height, const uint32_t* pixels, bool isPremultiplied, TextureFilter minFilter = TextureFilter::LINEAR, TextureFilter magFilter = TextureFilter::LINEAR); + + // ==================== 静态工具方法 ==================== + + static bool isSupportedImageFormat(const std::string& filePath); + static ImageInfo getImageInfo(const std::string& filePath); + static void cleanupAll(); + static void stbi_vertical_flip_on_load(bool flip); + + private: + // ==================== 私有成员变量 ==================== + GLuint m_TextureId = 0; + std::string m_Name; + int m_Width = 0; + int m_Height = 0; + TextureFormat m_Format; + TextureType m_Type; + + TextureFilter m_MinFilter = TextureFilter::LINEAR; + TextureFilter m_MagFilter = TextureFilter::LINEAR; + TextureWrap m_WrapS = TextureWrap::CLAMP_TO_EDGE; + TextureWrap m_WrapT = TextureWrap::CLAMP_TO_EDGE; + bool m_MipmapsEnabled = false; + + bool m_Disposed = false; + long long m_CreationTime; // 使用 steady_clock 或 system_clock + + std::vector m_PixelDataCache; + + // ==================== 静态管理 ==================== + static std::unordered_map> s_TextureCache; + static std::mutex s_CacheMutex; + + // ==================== 私有方法 ==================== + void createTextureObject(); + void applyTextureParameters(); + void cachePixelDataFromGPU(); + static GLuint generateTextureId(); + static bool isPowerOfTwo(int value); + }; + +} // namespace Vivid2D::Texture