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
This commit is contained in:
tzdwindows 7
2025-11-15 16:24:52 +08:00
parent 6dcd006f0e
commit f4e10bcccc
32 changed files with 9136 additions and 0 deletions

9
.idea/Vivid2DRenderer.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,341 @@
#include "pch.h"
#include "AnimationParameter.h"
#include "Mesh2D.h"
#include "ModelPart.h"
#include <algorithm>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <limits>
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<float>& AnimationParameter::getKeyframes() const {
return m_Keyframes;
}
void AnimationParameter::clearKeyframes() {
m_Keyframes.clear();
}
std::optional<float> 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<float> prev_key;
float distToPrev = std::numeric_limits<float>::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<float> next_key;
float distToNext = std::numeric_limits<float>::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<std::map<std::string, std::any>>(&value);
const auto* recordMap = std::get_if<std::map<std::string, std::any>>(&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<std::string>(&currentIdIt->second);
const std::string* recordId = std::any_cast<std::string>(&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<ParameterRecord> 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<ModelPart*, ParameterRecord>& 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<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) {
ss << "[Null]";
}
else if constexpr (std::is_same_v<T, float>) {
ss << arg;
}
else if constexpr (std::is_same_v<T, std::pair<float, float>>) {
ss << "(" << arg.first << ", " << arg.second << ")";
}
else if constexpr (std::is_same_v<T, std::map<std::string, std::any>>) {
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<std::string>(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

View File

@@ -0,0 +1,302 @@
#pragma once
#include <string>
#include <set>
#include <vector>
#include <optional>
#include <variant>
#include <map>
#include <any>
#include <sstream> // 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<float>& getKeyframes() const;
/**
* @brief 清除所有已设置的关键帧。
*/
void clearKeyframes();
/**
* @brief 查找距离给定值最近的关键帧。
* @param value 要在其附近查找的值。
* @param snapThreshold 查找范围的阈值。
* @return 如果在阈值范围内找到关键帧,则返回该关键帧的值;否则返回 std::nullopt。
*/
std::optional<float> 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<float> m_Keyframes;
};
/**
* @using ParameterValue
* @brief 定义一个可以存储不同类型参数值的变体类型。
* @details
* 这是一个灵活的数据容器,可以持有不同类型的动画数据:
* - `std::monostate`: 表示空或无效值。
* - `float`: 用于简单的单值参数,如旋转、不透明度。
* - `std::pair<float, float>`: 用于二维向量参数,如位置、缩放。
* - `std::map<std::string, std::any>`: 用于复杂的结构化数据,如网格顶点变形。
*/
using ParameterValue = std::variant<
std::monostate,
float,
std::pair<float, float>,
std::map<std::string, std::any>
>;
/**
* @struct ParameterRecord
* @brief 存储单个 ModelPart 在动画时间轴上的所有参数数据记录。
* @details
* 这个结构体聚合了与一个模型部件相关的所有动画信息,
* 包括它所关联的部件、控制参数、关键帧位置以及在这些关键帧上的具体参数值。
*/
struct VIVID_2D_MYDLL_API ParameterRecord {
//! 指向此记录所属的 ModelPart
ModelPart* modelPart = nullptr;
//! 描述动画状态的参数列表(例如,时间轴本身)
std::vector<AnimationParameter> animationParameters;
//! 与每个`values`条目对应的参数ID列表 (e.g., "rotate", "translateX")
std::vector<std::string> paramIds;
//! 在每个关键帧上,每个参数的具体值
std::vector<ParameterValue> values;
//! 所有关键帧在时间轴上的位置
std::vector<float> keyframes;
//! 标记每个时间点是否为关键帧
std::vector<bool> 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<ParameterRecord> 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<ModelPart*, ParameterRecord>& getAllRecords() const;
/**
* @brief 将所有记录转换为字符串形式,用于调试。
* @return 包含所有参数管理信息的字符串。
*/
std::string toString() const;
private:
//! 使用 map 存储每个 ModelPart 的参数记录,以实现高效查找。
//! 键是 ModelPart 的裸指针,值是其完整的动画数据记录。
std::map<ModelPart*, ParameterRecord> m_records;
};
} // namespace Vivid2D

View File

@@ -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 <string>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <optional>
namespace Vivid2D {
namespace { // 使用匿名命名空间来隐藏这些辅助函数,等同于 private static
// --- 辅助转换方法 ---
float toFloat(const ParameterValue& pv) {
if (const float* val = std::get_if<float>(&pv)) {
return *val;
}
return 0.0f;
}
glm::vec2 readVec2(const ParameterValue& pv) {
if (const auto* val = std::get_if<std::pair<float, float>>(&pv)) {
return { val->first, val->second };
}
if (const float* val = std::get_if<float>(&pv)) {
return { *val, *val };
}
return { 0.0f, 0.0f };
}
// --- 角度归一化 ---
float normalizeAngle(float a) {
while (a <= -glm::pi<float>()) a += 2 * glm::pi<float>();
while (a > glm::pi<float>()) a -= 2 * glm::pi<float>();
return a;
}
float normalizeAnimAngleUnits(float a) {
if (std::abs(a) > glm::pi<float>() * 2.1f) {
return glm::radians(a);
}
return a;
}
// --- 查找索引 ---
std::vector<size_t> findIndicesForParam(const ParameterRecord& fullParam, const std::string& paramId, const AnimationParameter& animParam) {
std::vector<size_t> 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<size_t> findIndicesForDeformationVertex(const ParameterRecord& fullParam, const std::string& vertexId, const AnimationParameter& animParam) {
std::vector<size_t> 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<std::map<std::string, std::any>>(&fullParam.values[i])) {
auto it = mapVal->find("id");
if (it != mapVal->end()) {
if (const auto* id = std::any_cast<std::string>(&it->second)) {
if (*id == vertexId) {
indices.push_back(i);
}
}
}
}
}
}
return indices;
}
// --- 查找关键帧并计算插值系数 T ---
std::pair<ptrdiff_t, ptrdiff_t> findSurroundingKeyframes(const ParameterRecord& param, const std::vector<size_t>& indices, float current) {
ptrdiff_t prevIndex = -1, nextIndex = -1;
float prevVal = -std::numeric_limits<float>::infinity();
float nextVal = std::numeric_limits<float>::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<glm::vec2> 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<float> 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<std::string, glm::vec2> computeMeshVerticesTarget(const ParameterRecord& fullParam, float current, const AnimationParameter& animParam) {
std::map<std::string, glm::vec2> targetDeformations;
std::set<std::string> 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<std::map<std::string, std::any>>(&fullParam.values[i])) {
if (auto it = mapVal->find("id"); it != mapVal->end()) {
if (const auto* id = std::any_cast<std::string>(&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<glm::vec2> finalPos;
auto getVertexPos = [&](ptrdiff_t index) -> glm::vec2 {
const auto& mapVal = std::get<std::map<std::string, std::any>>(fullParam.values[index]);
const auto& vertexVal = mapVal.at("Vertex"); // .at() will throw if not found
return std::any_cast<glm::vec2>(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<ModelPart*>& parts,
const AnimationParameter& currentAnimationParameter,
std::shared_ptr<spdlog::logger> 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

View File

@@ -0,0 +1,41 @@
#pragma once
#include "AnimationParameter.h"
#include <vector>
#include <memory>
// 向前声明以减少头文件依赖
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<ModelPart*>& parts,
const AnimationParameter& currentAnimationParameter,
std::shared_ptr<spdlog::logger> logger
);
};
} // namespace Vivid2D

View File

@@ -0,0 +1,470 @@
#include "pch.h"
#include "Mesh2D.h"
#include <stdexcept>
#include <algorithm>
#include <cmath>
#include <glm/gtx/norm.hpp>
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<util::VertexList>("DefaultList")) {}
Mesh2D::Mesh2D(std::string name, const std::vector<float>& vertices, const std::vector<float>& uvs, const std::vector<int>& 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<util::VertexList>(*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<TriangleInfo> 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<int> 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<Vertex*>& controlVertices) { m_DeformationControlVertices = controlVertices; }
void Mesh2D::applyLocalizedPush(const std::vector<Vertex>& 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<float>& vertices, const std::vector<float>& uvs, const std::vector<int>& 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<float>& 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<Vertex> 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<int> 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<Vivid2D::Render::Texture::Texture> 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<int> 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<void(glm::vec2&, int)>& 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<float> 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<float> Mesh2D::getVertices() const {
std::vector<float> 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<float> Mesh2D::getUVs() const {
std::vector<float> 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<float> Mesh2D::getOriginalVertices() const {
std::vector<float> 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<int> Mesh2D::getIndices() const { return m_ActiveVertexList ? m_ActiveVertexList->getIndices() : std::vector<int>{}; }
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<Vertex*>& Mesh2D::getDeformationControlVertices() const { return m_DeformationControlVertices; }
// ==================== 静态工厂方法 ====================
std::unique_ptr<Mesh2D> Mesh2D::createQuad(const std::string& name, float width, float height) {
float hw = width / 2.0f, hh = height / 2.0f;
std::vector<float> vertices = { -hw,-hh, hw,-hh, hw,hh, -hw,hh, 0,-hh, hw,0, 0,hh, -hw,0, 0,0 };
std::vector<float> 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<int> 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<Mesh2D>(name, vertices, uvs, indices);
}
std::unique_ptr<Mesh2D> Mesh2D::createCircle(const std::string& name, float radius, int segments) {
if (segments < 3) segments = 3;
std::vector<float> vertices, uvs; std::vector<int> 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<Mesh2D>(name, vertices, uvs, indices);
}
} // namespace Vivid2D

View File

@@ -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 <glm/glm.hpp>
#include <glm/mat3x3.hpp>
#include <string>
#include <vector>
#include <memory>
#include <atomic>
#include <map>
#include <functional>
/**
* @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<float>& vertices, const std::vector<float>& uvs, const std::vector<int>& 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<Vertex*>& controlVertices);
/**
* @brief 应用一个局部的推力效果来变形网格。
* @param baseVertexState 变形前的顶点状态快照。
* @param pushCenterWorld 推力中心的世界坐标。
* @param delta 推力的位移向量。
* @param radius 推力影响的半径范围。
*/
void applyLocalizedPush(const std::vector<Vertex>& baseVertexState, const glm::vec2& pushCenterWorld, const glm::vec2& delta, float radius);
// ==================== 网格数据设置 ====================
/**
* @brief 一次性设置网格的所有核心数据。
* @param vertices 顶点位置数据。
* @param uvs 纹理坐标数据。
* @param indices 索引数据。
*/
void setMeshData(const std::vector<float>& vertices, const std::vector<float>& uvs, const std::vector<int>& indices);
/**
* @brief 设置网格的顶点数据。
* @param vertices 新的顶点位置数据。
* @param updateOriginal 如果为 true则同时更新原始顶点状态用于 `resetToOriginal`)。
*/
void setVertices(const std::vector<float>& vertices, bool updateOriginal = false);
/**
* @brief 设置网格要使用的纹理。
* @param texture 指向纹理对象的共享指针。
*/
void setTexture(std::shared_ptr<Render::Texture::Texture> 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<void(glm::vec2&, int)>& 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<float> getVertices() const;
/** @brief 获取当前纹理坐标数据。 */
std::vector<float> getUVs() const;
/** @brief 获取原始(未变形)的顶点位置数据。 */
std::vector<float> getOriginalVertices() const;
/** @brief 获取索引数据。 */
std::vector<int> 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<Vertex*>& getDeformationControlVertices() const;
// ==================== 静态工厂方法 ====================
/**
* @brief 创建一个标准的矩形网格。
* @param name 网格的名称。
* @param width 矩形的宽度。
* @param height 矩形的高度。
* @return 一个指向新创建的 Mesh2D 的唯一指针。
*/
static std::unique_ptr<Mesh2D> createQuad(const std::string& name, float width, float height);
/**
* @brief 创建一个圆形的网格。
* @param name 网格的名称。
* @param radius 圆的半径。
* @param segments 用于近似圆的线段(三角形)数量。
* @return 一个指向新创建的 Mesh2D 的唯一指针。
*/
static std::unique_ptr<Mesh2D> createCircle(const std::string& name, float radius, int segments);
private:
// ==================== 私有成员变量 ====================
std::string m_Name; ///< 网格的名称。
std::unique_ptr<VertexList> m_ActiveVertexList; ///< 管理活动顶点数据的列表。
ModelPart* m_ModelPart = nullptr; ///< 指向所属 ModelPart 的指针。
std::shared_ptr<Render::Texture::Texture> 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<bool> m_IsSelected{ false }; ///< 网格是否被选中 (原子变量,保证线程安全)。
glm::vec2 m_Pivot{ 0.0f, 0.0f }; ///< 当前的轴心点。
glm::vec2 m_OriginalPivot{ 0.0f, 0.0f }; ///< 用于重置的原始轴心点。
std::vector<Vertex*> 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

View File

@@ -0,0 +1,459 @@
#include "pch.h"
#include "Model2D.h"
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <utility>
#include <set>
#include <stduuid/uuid.h>
#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<util::PhysicsSystem>()),
m_bounds(std::make_unique<util::BoundingBox>()),
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<std::string> Model2D::getPoseNames() const {
std::set<std::string> 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<util::LightSource>& 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<ModelPart> Model2D::createPart(const std::string& name) {
return std::make_unique<ModelPart>(name);
}
void Model2D::addPart(std::unique_ptr<ModelPart> 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<std::string, ModelPart*>& Model2D::getPartMap() const {
return m_partMap;
}
const std::vector<std::unique_ptr<ModelPart>>& 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<std::string, AnimationParameter>& Model2D::getParameters() const {
return m_parameters;
}
// ==================== 网格管理 ====================
std::unique_ptr<Mesh2D> Model2D::createMesh(const std::string& name, const std::vector<float>& vertices,
const std::vector<float>& uvs, const std::vector<int>& indices) {
return std::make_unique<Mesh2D>(name, vertices, uvs, indices);
}
void Model2D::addMesh(std::unique_ptr<Mesh2D> 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<std::unique_ptr<Mesh2D>>& Model2D::getMeshes() const {
return m_meshes;
}
// ==================== 纹理管理 ====================
void Model2D::addTexture(std::shared_ptr<Render::Texture::Texture> 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<Render::Texture::Texture> Model2D::getTexture(const std::string& name) const {
auto it = m_textures.find(name);
return (it != m_textures.end()) ? it->second : nullptr;
}
const std::map<std::string, std::shared_ptr<Render::Texture::Texture>>& Model2D::getTextures() const {
return m_textures;
}
// ==================== 动画层管理 ====================
std::unique_ptr<util::AnimationLayer> Model2D::createAnimationLayer(const std::string& name) {
auto layer = std::make_unique<util::AnimationLayer>(name);
m_animationLayers.push_back(std::move(layer));
return std::move(m_animationLayers.back());
}
const std::vector<std::unique_ptr<util::AnimationLayer>>& Model2D::getAnimationLayers() const {
return m_animationLayers;
}
void Model2D::setAnimationLayers(std::vector<std::unique_ptr<util::AnimationLayer>> 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(&param);
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<util::BoundingBox>();
}
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

View File

@@ -0,0 +1,527 @@
#pragma once
#ifndef MODEL2D_H
#define MODEL2D_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <set>
#include <stduuid/uuid.h>
#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<std::string> getPoseNames() const;
/**
* @brief 设置特定姿态的混合权重。此功能通常用于更高级的姿态混合逻辑,例如加权混合多个姿态。
* @param poseName 要设置混合权重的姿态名称。
* @param blendFactor 混合权重因子,通常在 [0, 1] 范围内。
*/
void setPoseBlend(const std::string& poseName, float blendFactor);
// ==================== 光源管理 ====================
/**
* @brief 获取模型关联的所有光源。
* @return 一个包含所有光源的常量引用向量。
*/
const std::vector<util::LightSource>& 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<ModelPart> createPart(const std::string& name);
/**
* @brief 将一个已存在的部件添加到模型中。
* @param part 要添加的部件的 std::unique_ptr所有权将转移给 Model2D。
*/
void addPart(std::unique_ptr<ModelPart> part);
/**
* @brief 根据名称获取一个模型部件。
* @param name 要获取的部件的名称。
* @return 如果找到,返回指向该部件的指针;否则返回 nullptr。
*/
ModelPart* getPart(const std::string& name) const;
/**
* @brief 获取部件名称到部件指针的映射表。
* @return 一个包含所有部件映射的常量引用。
*/
const std::map<std::string, ModelPart*>& getPartMap() const;
/**
* @brief 获取模型中所有部件的列表。
* @return 一个包含所有部件的 std::unique_ptr 的常量引用向量。
*/
const std::vector<std::unique_ptr<ModelPart>>& 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<std::string, AnimationParameter>& 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<Mesh2D> createMesh(const std::string& name, const std::vector<float>& vertices,
const std::vector<float>& uvs, const std::vector<int>& indices);
/**
* @brief 将一个已存在的网格添加到模型中。
* @param mesh 要添加的网格的 std::unique_ptr所有权将转移给 Model2D。
*/
void addMesh(std::unique_ptr<Mesh2D> mesh);
/**
* @brief 根据名称获取一个网格。
* @param name 要获取的网格的名称。
* @return 如果找到,返回指向该网格的指针;否则返回 nullptr。
*/
Mesh2D* getMesh(const std::string& name) const;
/**
* @brief 获取模型中所有网格的列表。
* @return 一个包含所有网格的 std::unique_ptr 的常量引用向量。
*/
const std::vector<std::unique_ptr<Mesh2D>>& getMeshes() const;
// ==================== 纹理管理 ====================
/**
* @brief 向模型添加一个纹理资源。
* @param texture 要添加的纹理的共享指针。
*/
void addTexture(std::shared_ptr<Render::Texture::Texture> texture);
/**
* @brief 根据名称获取一个纹理。
* @param name 要获取的纹理的名称。
* @return 如果找到,返回指向该纹理的共享指针;否则返回一个空的共享指针。
*/
std::shared_ptr<Render::Texture::Texture> getTexture(const std::string& name) const;
/**
* @brief 获取所有纹理的映射表。
* @return 一个包含所有纹理的常量引用映射表。
*/
const std::map<std::string, std::shared_ptr<Render::Texture::Texture>>& getTextures() const;
// ==================== 动画层管理 ====================
/**
* @brief 创建一个新的动画层。
* @param name 动画层的名称。
* @return 返回一个包含新创建 AnimationLayer 的 std::unique_ptr。
*/
std::unique_ptr<util::AnimationLayer> createAnimationLayer(const std::string& name);
/**
* @brief 获取模型中所有动画层的列表。
* @return 一个包含所有动画层的 std::unique_ptr 的常量引用向量。
*/
const std::vector<std::unique_ptr<util::AnimationLayer>>& getAnimationLayers() const;
/**
* @brief 设置模型的动画层列表。
* @param animationLayers 一个包含动画层的 std::unique_ptr 向量,所有权将转移给 Model2D。
*/
void setAnimationLayers(std::vector<std::unique_ptr<util::AnimationLayer>> 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<std::string, util::ModelPose>& 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<std::unique_ptr<ModelPart>> m_parts;
//! 从部件名称到部件指针的映射,用于快速查找
std::map<std::string, ModelPart*> m_partMap;
//! 指向层级结构根部的指针
ModelPart* m_rootPart = nullptr;
//! 存储所有网格的列表,拥有其所有权
std::vector<std::unique_ptr<Mesh2D>> m_meshes;
//! 从纹理名称到纹理共享指针的映射
std::map<std::string, std::shared_ptr<Render::Texture::Texture>> m_textures;
//! 从参数 ID 到 AnimationParameter 对象的映射
std::map<std::string, AnimationParameter> m_parameters;
//! 存储所有动画层的列表,拥有其所有权
std::vector<std::unique_ptr<util::AnimationLayer>> m_animationLayers;
//! 模型的物理系统
std::unique_ptr<util::PhysicsSystem> m_physics;
//! 标记模型是否需要在下一帧更新其变换或顶点数据
bool m_needsUpdate = true;
//! 模型的轴对齐包围盒 (AABB)
std::unique_ptr<util::BoundingBox> m_bounds;
//! 从姿态名称到 ModelPose 对象的映射
std::map<std::string, util::ModelPose> 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<util::LightSource> 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

View File

@@ -0,0 +1,312 @@
#include "pch.h"
#include "ModelPart.h"
#include "Mesh2D.h"
#include "AnimationParameter.h"
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
#include <stdexcept>
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<Mesh2D>(*mesh));
}
m_Children.clear();
for (const auto& child : other.m_Children) {
addChild(std::make_unique<ModelPart>(*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<ModelPart> 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> 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<ModelPart> removedChild = std::move(*it);
m_Children.erase(it);
removedChild->m_Parent = nullptr;
removedChild->markTransformDirty();
removedChild->recomputeWorldTransformRecursive();
return removedChild;
}
return nullptr;
}
const std::vector<std::unique_ptr<ModelPart>>& 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<Mesh2D> 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<std::unique_ptr<Mesh2D>>& 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

View File

@@ -0,0 +1,337 @@
#pragma once
#include "AnimationParameter.h"
#include <glm/glm.hpp>
#include <glm/gtx/matrix_transform_2d.hpp> // For glm::translate, glm::rotate, glm::scale
#include <glm/mat3x3.hpp>
#include <string>
#include <vector>
#include <memory>
#include <map>
// 前置声明
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<ModelPart> child);
/**
* @brief 移除一个子部件。
* @details
* 从子节点列表中移除指定的 `child` 部件,并返回其所有权给调用者。
* 如果 `child` 不是当前部件的直接子节点,则不执行任何操作并返回空的 unique_ptr。
* @param child 要移除的子部件的裸指针。
* @return 被移除的子部件的 unique_ptr如果未找到则为空。
*/
std::unique_ptr<ModelPart> removeChild(ModelPart* child);
/**
* @brief 获取所有直接子部件的列表。
* @return 一个包含所有子部件 unique_ptr 的常量引用向量。
*/
const std::vector<std::unique_ptr<ModelPart>>& 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<Mesh2D> mesh);
/**
* @brief 从此部件移除一个网格。
* @param mesh 要移除的网格的裸指针。
* @return 如果成功移除返回 true否则返回 false。
*/
bool removeMesh(Mesh2D* mesh);
/**
* @brief 获取附加到此部件的所有网格的列表。
* @return 一个包含所有网格 unique_ptr 的常量引用向量。
*/
const std::vector<std::unique_ptr<Mesh2D>>& 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<std::unique_ptr<ModelPart>> m_Children;
//! 附加到此部件的网格列表,拥有其所有权
std::vector<std::unique_ptr<Mesh2D>> 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<std::string, AnimationParameter> 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

View File

@@ -0,0 +1,550 @@
#include "pch.h"
#include "AnimationClip.h"
#include <stdexcept>
#include <cmath>
#include <algorithm>
#include <limits>
#include <set>
namespace Vivid2D::util {
// ==================== TimeStamp 实现 ====================
AnimationClip::TimeStamp AnimationClip::getCurrentTimeMillis() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
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<Keyframe>(time, value, interpolation);
Keyframe* rawPtr = newKeyframe.get();
// 3. 找到插入位置并保持排序
auto it = std::lower_bound(m_keyframes.begin(), m_keyframes.end(), time, [](const std::unique_ptr<Keyframe>& 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<Keyframe>& 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<Keyframe>& 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<float>::max)();
float maxVal = (std::numeric_limits<float>::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<std::string, std::string> 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<Keyframe>(*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<Keyframe>(*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<AnimationCurve>(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<std::string> AnimationClip::getCurveParameterIds() const {
std::set<std::string> 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<const Keyframe*> AnimationClip::getKeyframes(const std::string& parameterId) {
AnimationCurve* curve = getCurve(parameterId);
if (!curve) return {};
std::vector<const Keyframe*> kfPtrs;
for (const auto& kf_uptr : curve->getKeyframes()) {
kfPtrs.push_back(kf_uptr.get());
}
return kfPtrs;
}
// --- 采样系统 ---
std::map<std::string, float> AnimationClip::sample(float time) {
std::map<std::string, float> 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<std::string, float> 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<AnimationEventMarker>(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<AnimationEventMarker>& 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<AnimationEventMarker>& 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<AnimationEventMarker*> AnimationClip::getEventMarkersInRange(float startTime, float endTime) {
std::vector<AnimationEventMarker*> 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<AnimationCurve>(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<AnimationCurve>(entry.second->copy());
}
// 拷贝默认值
copy.m_defaultValues = this->m_defaultValues;
// 深拷贝事件标记
for (const auto& markerPtr : this->m_eventMarkers) {
copy.m_eventMarkers.push_back(std::make_unique<AnimationEventMarker>(markerPtr->copy()));
}
// 拷贝用户数据
copy.m_userData = this->m_userData;
return copy;
}
int AnimationClip::getFrameCount() const {
return static_cast<int>(std::ceil(m_duration * m_framesPerSecond));
}
int AnimationClip::timeToFrame(float time) const {
return static_cast<int>(time * m_framesPerSecond);
}
float AnimationClip::frameToTime(int frame) const {
return static_cast<float>(frame) / m_framesPerSecond;
}
bool AnimationClip::isTimeInRange(float time) const {
return time >= 0.0f && time <= m_duration;
}
std::map<std::string, std::vector<float>> AnimationClip::getValueBounds() {
std::map<std::string, std::vector<float>> 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<std::string, std::unique_ptr<AnimationCurve>>& AnimationClip::getCurves() const {
return m_curves;
}
const std::vector<std::unique_ptr<AnimationEventMarker>>& AnimationClip::getEventMarkers() const {
return m_eventMarkers;
}
} // namespace Vivid2D::util

View File

@@ -0,0 +1,186 @@
#pragma once
#ifndef ANIMATION_CLIP_H
#define ANIMATION_CLIP_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <algorithm>
#include <functional>
#include <cmath>
#include <chrono>
#include <stduuid/uuid.h>
#include <set>
#include "AnimationLayer.h"
namespace Vivid2D::util {
// 事件标记类
class AnimationEventMarker {
public:
using Action = std::function<void()>;
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<void()> 替代 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<std::unique_ptr<Keyframe>>& 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<std::unique_ptr<Keyframe>> 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<std::string> 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<const Keyframe*> getKeyframes(const std::string& parameterId);
// ==================== 采样系统 ====================
std::map<std::string, float> sample(float time);
float sampleParameter(const std::string& parameterId, float time);
std::map<std::string, float> 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<AnimationEventMarker*> 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<std::string, std::vector<float>> 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<std::string, std::unique_ptr<AnimationCurve>>& getCurves() const;
const std::vector<std::unique_ptr<AnimationEventMarker>>& getEventMarkers() const;
const std::map<std::string, float>& 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<std::string, std::string>& getUserData() const { return m_userData; }
void setUserData(std::map<std::string, std::string> 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<std::string, std::unique_ptr<AnimationCurve>> m_curves;
std::vector<std::unique_ptr<AnimationEventMarker>> m_eventMarkers;
std::map<std::string, float> m_defaultValues;
std::string m_author;
std::string m_description;
TimeStamp m_creationTime;
TimeStamp m_lastModifiedTime;
std::map<std::string, std::string> m_userData;
// ==================== 私有辅助方法 ====================
AnimationCurve* getOrCreateCurve(const std::string& parameterId);
void updateDurationIfNeeded(float time);
void markModified();
static TimeStamp getCurrentTimeMillis();
};
} // namespace Vivid2D::util
#endif // ANIMATION_CLIP_H

View File

@@ -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<AnimationTrack> AnimationLayer::addTrack(const std::string& parameterId) {
auto track = std::make_unique<AnimationTrack>(parameterId);
AnimationTrack* rawPtr = track.get();
m_tracks[parameterId] = std::move(track);
return std::unique_ptr<AnimationTrack>(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::string, float>
std::map<std::string, float> 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<AnimationEvent>(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<AnimationTrack>(pair.second->copy());
}
// 拷贝剪辑(浅拷贝,引用)
copy.m_clips = m_clips;
// 拷贝参数覆盖
copy.m_parameterOverrides = m_parameterOverrides;
return copy;
}
} // namespace Vivid2D::util

View File

@@ -0,0 +1,237 @@
#pragma once
#ifndef ANIMATION_LAYER_H
#define ANIMATION_LAYER_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <functional>
#include <stduuid/uuid.h>
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<Keyframe>& 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<Keyframe> m_keyframes;
bool m_enabled;
InterpolationType m_interpolation;
};
// 动画事件类 (AnimationEvent)
class VIVID_2D_MYDLL_API AnimationEvent {
public:
using Action = std::function<void()>;
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<AnimationTrack> 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<std::string, std::unique_ptr<AnimationTrack>>& getTracks() const { return m_tracks; }
const std::vector<AnimationClip*>& 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<std::string, float>& 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<std::string, std::unique_ptr<AnimationTrack>> m_tracks;
std::vector<AnimationClip*> m_clips; // 假设 AnimationClip 是共享的,使用裸指针
AnimationClip* m_currentClip = nullptr;
float m_playbackSpeed;
bool m_looping;
float m_currentTime;
bool m_playing;
bool m_paused;
std::map<std::string, float> m_parameterOverrides;
std::vector<AnimationEventListener*> m_eventListeners;
std::map<std::string, std::vector<std::unique_ptr<AnimationEvent>>> 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

View File

@@ -0,0 +1,358 @@
#include "pch.h"
#include "BoundingBox.h"
#include <limits>
#include <stdexcept>
#include <algorithm>
#include <cmath>
#include <sstream>
#include <iomanip>
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<glm::vec2>& points) {
set(points);
}
BoundingBox::BoundingBox(const std::vector<float>& vertices) {
set(vertices);
}
// ==================== 设置方法 ====================
void BoundingBox::reset() {
m_Min = glm::vec2(std::numeric_limits<float>::max());
m_Max = glm::vec2(-std::numeric_limits<float>::max()); // Or std::numeric_limits<float>::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<glm::vec2>& points) {
reset();
for (const auto& point : points) {
expand(point);
}
}
void BoundingBox::set(const std::vector<float>& 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<glm::vec2>& points) {
for (const auto& point : points) {
expand(point);
}
}
void BoundingBox::expand(const std::vector<float>& 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<glm::vec2> 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<float>::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<glm::vec2>& points) {
return BoundingBox(points);
}
BoundingBox BoundingBox::fromVertices(const std::vector<float>& vertices) {
return BoundingBox(vertices);
}
BoundingBox BoundingBox::merge(const BoundingBox& box1, const BoundingBox& box2) {
return box1.getUnion(box2);
}
BoundingBox BoundingBox::mergeAll(const std::vector<BoundingBox>& boxes) {
BoundingBox result;
for (const auto& box : boxes) {
result.expand(box);
}
return result;
}
} // namespace Vivid2D::util

View File

@@ -0,0 +1,277 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/mat3x3.hpp>
#include <vector>
#include <string>
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<glm::vec2>& points);
/**
* @brief 从一个顶点数组 [x0, y0, x1, y1, ...] 创建边界框。
*/
explicit BoundingBox(const std::vector<float>& 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<glm::vec2>& points);
/**
* @brief 从一个顶点数组 [x0, y0, x1, y1, ...] 设置边界。
*/
void set(const std::vector<float>& 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<glm::vec2>& points);
/**
* @brief 扩展边界框以包含一个顶点数组。
*/
void expand(const std::vector<float>& 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<glm::vec2> 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<glm::vec2>& points);
/**
* @brief 从一个顶点数组创建并返回一个边界框。
*/
static BoundingBox fromVertices(const std::vector<float>& vertices);
/**
* @brief 合并两个边界框并返回它们的并集。
*/
static BoundingBox merge(const BoundingBox& box1, const BoundingBox& box2);
/**
* @brief 合并一组边界框并返回它们的并集。
*/
static BoundingBox mergeAll(const std::vector<BoundingBox>& boxes);
private:
glm::vec2 m_Min; ///< 边界框的最小坐标点 (x, y)
glm::vec2 m_Max; ///< 边界框的最大坐标点 (x, y)
bool m_IsValid; ///< 指示边界框是否有效
};
} // namespace Vivid2D::util

View File

@@ -0,0 +1,66 @@
#include "pch.h"
#include "LightSource.h"
#include <algorithm>
#include <cmath>
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

View File

@@ -0,0 +1,63 @@
#pragma once
#ifndef LIGHTSOURCE_H
#define LIGHTSOURCE_H
#include <glm/glm.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
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

View File

@@ -0,0 +1,334 @@
#include "pch.h"
#include "ModelPose.h"
#include <algorithm> // For std::min, std::max
#include <sstream> // For std::ostringstream
#include <iomanip> // 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<std::string, PartPose>::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

View File

@@ -0,0 +1,193 @@
#pragma once
#ifndef MODELPOSE_H
#define MODELPOSE_H
#include <glm/glm.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <map>
#include <string>
#include <memory>
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<std::string, PartPose>;
// ================== 构造函数 ==================
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<std::string, PartPose>::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

View File

@@ -0,0 +1,636 @@
#include "pch.h"
#include "PhysicsSystem.h"
#include <algorithm>
#include <vector>
#include <limits>
#include <cmath>
#include <spdlog/spdlog.h>
#include <glm/vec2.hpp>
#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<PhysicsParticle> PhysicsSystem::addParticle(const std::string& id, glm::vec2 position, float mass) {
auto particle = std::make_shared<PhysicsParticle>(id, position, mass);
m_particles[id] = particle;
return particle;
}
std::shared_ptr<PhysicsParticle> PhysicsSystem::addParticleFromModelPart(const std::string& id, std::shared_ptr<ModelPart> 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<PhysicsParticle> PhysicsSystem::getParticle(const std::string& id) {
auto it = m_particles.find(id);
return (it != m_particles.end()) ? it->second : nullptr;
}
std::shared_ptr<PhysicsSpring> PhysicsSystem::addSpring(const std::string& id, std::shared_ptr<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> b, float restLength, float stiffness, float damping) {
auto spring = std::make_shared<PhysicsSpring>(id, a, b, restLength, stiffness, damping);
m_springs.push_back(spring);
return spring;
}
std::shared_ptr<PhysicsSpring> PhysicsSystem::addSpring(const std::string& id, std::shared_ptr<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> 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<PhysicsSpring>& 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<PositionConstraint> PhysicsSystem::addPositionConstraint(std::shared_ptr<PhysicsParticle> particle, glm::vec2 targetPosition) {
auto constraint = std::make_shared<PositionConstraint>(particle, targetPosition);
m_constraints.push_back(constraint);
return constraint;
}
std::shared_ptr<DistanceConstraint> PhysicsSystem::addDistanceConstraint(std::shared_ptr<PhysicsParticle> particle, std::shared_ptr<PhysicsParticle> target, float maxDistance) {
auto constraint = std::make_shared<DistanceConstraint>(particle, target, maxDistance);
m_constraints.push_back(constraint);
return constraint;
}
bool PhysicsSystem::removeConstraint(const std::shared_ptr<PhysicsConstraint>& 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<CircleCollider> PhysicsSystem::addCircleCollider(const std::string& id, glm::vec2 center, float radius) {
auto collider = std::make_shared<CircleCollider>(id, center, radius);
m_colliders.push_back(collider);
return collider;
}
std::shared_ptr<RectangleCollider> PhysicsSystem::addRectangleCollider(const std::string& id, glm::vec2 center, float width, float height) {
auto collider = std::make_shared<RectangleCollider>(id, center, width, height);
m_colliders.push_back(collider);
return collider;
}
bool PhysicsSystem::removeCollider(const std::shared_ptr<PhysicsCollider>& 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<std::chrono::nanoseconds>(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<std::shared_ptr<PhysicsParticle>> 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<ModelPart*>(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<std::string, std::shared_ptr<PhysicsParticle>>& PhysicsSystem::getParticles() const { return m_particles; }
const std::vector<std::shared_ptr<PhysicsSpring>>& PhysicsSystem::getSprings() const { return m_springs; }
const std::vector<std::shared_ptr<PhysicsConstraint>>& PhysicsSystem::getConstraints() const { return m_constraints; }
const std::vector<std::shared_ptr<PhysicsCollider>>& 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<float>::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<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> 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<PhysicsParticle> 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<PhysicsParticle> particle, std::shared_ptr<PhysicsParticle> 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

View File

@@ -0,0 +1,363 @@
#pragma once
#ifndef PHYSICS_SYSTEM_H
#define PHYSICS_SYSTEM_H
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <chrono>
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<PhysicsParticle> addParticle(const std::string& id, glm::vec2 position, float mass);
std::shared_ptr<PhysicsParticle> addParticleFromModelPart(const std::string& id, std::shared_ptr<ModelPart> part, float mass);
bool removeParticle(const std::string& id);
std::shared_ptr<PhysicsParticle> getParticle(const std::string& id);
// ==================== 弹簧管理 ====================
std::shared_ptr<PhysicsSpring> addSpring(const std::string& id, std::shared_ptr<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> b, float restLength, float stiffness, float damping);
std::shared_ptr<PhysicsSpring> addSpring(const std::string& id, std::shared_ptr<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> b, float stiffness, float damping);
bool removeSpring(const std::shared_ptr<PhysicsSpring>& spring);
// ==================== 约束管理 ====================
std::shared_ptr<PositionConstraint> addPositionConstraint(std::shared_ptr<PhysicsParticle> particle, glm::vec2 targetPosition);
std::shared_ptr<DistanceConstraint> addDistanceConstraint(std::shared_ptr<PhysicsParticle> particle, std::shared_ptr<PhysicsParticle> target, float maxDistance);
bool removeConstraint(const std::shared_ptr<PhysicsConstraint>& constraint);
// ==================== 碰撞管理 ====================
std::shared_ptr<CircleCollider> addCircleCollider(const std::string& id, glm::vec2 center, float radius);
std::shared_ptr<RectangleCollider> addRectangleCollider(const std::string& id, glm::vec2 center, float width, float height);
bool removeCollider(const std::shared_ptr<PhysicsCollider>& 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<std::string, std::shared_ptr<PhysicsParticle>>& getParticles() const;
const std::vector<std::shared_ptr<PhysicsSpring>>& getSprings() const;
const std::vector<std::shared_ptr<PhysicsConstraint>>& getConstraints() const;
const std::vector<std::shared_ptr<PhysicsCollider>>& getColliders() const;
bool hasActivePhysics();
private:
// ==================== 物理参数 ====================
glm::vec2 m_gravity;
float m_airResistance;
float m_timeScale;
bool m_enabled;
// ==================== 物理组件 ====================
std::unordered_map<std::string, std::shared_ptr<PhysicsParticle>> m_particles;
std::vector<std::shared_ptr<PhysicsSpring>> m_springs;
std::vector<std::shared_ptr<PhysicsConstraint>> m_constraints;
std::vector<std::shared_ptr<PhysicsCollider>> 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<PhysicsParticle> a, std::shared_ptr<PhysicsParticle> b, float restLength, float stiffness, float damping);
void applyForce(float deltaTime);
// Getters & Setters
const std::string& getId() const { return m_id; }
std::shared_ptr<PhysicsParticle> getParticleA() const { return m_particleA; }
std::shared_ptr<PhysicsParticle> 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<PhysicsParticle> m_particleA;
std::shared_ptr<PhysicsParticle> 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<PhysicsParticle> 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<PhysicsParticle> particle, glm::vec2 targetPosition);
void apply(float deltaTime) override;
std::shared_ptr<PhysicsParticle> 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<PhysicsParticle> 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<PhysicsParticle> particle, std::shared_ptr<PhysicsParticle> target, float maxDistance);
void apply(float deltaTime) override;
std::shared_ptr<PhysicsParticle> getParticle() const override { return m_particle; }
bool isEnabled() const override { return m_enabled; }
void setEnabled(bool enabled) override { m_enabled = enabled; }
std::shared_ptr<PhysicsParticle> getTarget() const { return m_target; }
float getMaxDistance() const { return m_maxDistance; }
private:
std::shared_ptr<PhysicsParticle> m_particle;
std::shared_ptr<PhysicsParticle> 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

View File

@@ -0,0 +1,128 @@
#include "pch.h"
#include "Vertex.h"
#include <sstream>
#include <iomanip>
namespace Vivid2D::util {
// --- <20><><EFBFBD><EFBFBD><ECBAAF>ʵ<EFBFBD><CAB5> ---
Vertex::Vertex(float x, float y, float u, float v)
: position(x, y), uv(u, v), originalPosition(x, y), // <20><>ʼʱ<CABC><CAB1>ԭʼλ<CABC><CEBB>=<3D><>ǰλ<C7B0><CEBB>
tag(VertexTag::DEFAULT), selected(false), IsDelete(false), index(-1) {
// std::cout << "Vertex(x, y, u, v) created." << std::endl; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ
}
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) {
}
// --- <20><><EFBFBD><EFBFBD>/<2F><>ֵʵ<D6B5><CAB5> ---
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><ECBAAF><><D6B4><EFBFBD><EFBFBD><EEBFBD>)
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) {
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
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;
}
// --- <20><><EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD><DFBC><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5> ---
void Vertex::resetToOriginal() {
this->position = this->originalPosition; // glm::vec2 <20><><EFBFBD><EFBFBD><EFBFBD>˸<EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
}
void Vertex::saveAsOriginal() {
this->originalPosition = this->position;
}
Vertex Vertex::copy() const {
// C++ <20><><EFBFBD>Ƽ<EFBFBD>ʹ<EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><ECBAAF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Vertex newCopy = *this; // ʹ<>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><ECBAAF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EEBFBD>
return newCopy;
}
void Vertex::setControlledTriangles(const std::vector<int>& controlledTriangles) {
this->controlledTriangles = controlledTriangles;
}
// --- C++ <20><><EFBFBD>ԣ<EFBFBD><D4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5> ---
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>Ӧ Java <20>е<EFBFBD> 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;
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>Ӧ Java <20>е<EFBFBD> toString())
std::ostream& operator<<(std::ostream& os, const Vertex& vertex) {
std::string tagStr;
switch (vertex.tag) {
case VertexTag::DEFAULT: tagStr = "DEFAULT"; break;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> case
default: tagStr = "UNKNOWN"; break;
}
// <20><>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 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

View File

@@ -0,0 +1,167 @@
#pragma once
#ifndef VIVID2D_VERTEX_H
#define VIVID2D_VERTEX_H
#include <glm/glm.hpp>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <stdexcept>
namespace Vivid2D::util {
/**
* @brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩö<C7A9>٣<EFBFBD><D9A3><EFBFBD><EFBFBD>ڱ<EFBFBD>ʶ<EFBFBD><CAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ض<EFBFBD><D8B6><EFBFBD><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>;<EFBFBD><CDBE>
*/
enum class VIVID_2D_MYDLL_API VertexTag {
/**
* <20><><EFBFBD>ڱ<EFBFBD><DAB1>εĶ<CEB5><C4B6><EFBFBD>
*/
DEFORMATION,
/**
* Ĭ<>ϵĶ<CFB5><C4B6><EFBFBD>
*/
DEFAULT,
/**
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͵Ķ<CDB5><C4B6><EFBFBD>
*/
OTHER
};
/**
* @brief <20><>װһ<D7B0><D2BB>2D<32><44><EFBFBD><EFBFBD><E3A3AC><EFBFBD><EFBFBD>λ<EFBFBD>á<EFBFBD>UV<55><56><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԭʼλ<CABC>á<EFBFBD>
*
* <20><>Ӧ Java <20><> Vertex<65><78>
*/
class VIVID_2D_MYDLL_API Vertex {
private:
std::vector<int> controlledTriangles;
VertexTag tag;
bool selected;
std::string name;
bool IsDelete;
public:
glm::vec2 position; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB> (x, y)
glm::vec2 uv; // UV<55><56><EFBFBD><EFBFBD> (u, v)
glm::vec2 originalPosition; // ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD><CEBB> (<28><><EFBFBD>ڱ<EFBFBD><DAB1><EFBFBD>)
int index; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/**
* @brief <20><><EFBFBD><EFBFBD><ECBAAF>
*
* @param x <20><><EFBFBD><EFBFBD> x <20><><EFBFBD><EFBFBD>
* @param y <20><><EFBFBD><EFBFBD> y <20><><EFBFBD><EFBFBD>
* @param u <20><><EFBFBD><EFBFBD> u <20><><EFBFBD><EFBFBD>
* @param v <20><><EFBFBD><EFBFBD> v <20><><EFBFBD><EFBFBD>
*/
Vertex(float x, float y, float u, float v);
/**
* @brief <20><><EFBFBD><EFBFBD><ECBAAF>
*
* @param position <20><><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
* @param uv UV<55><56><EFBFBD><EFBFBD>
*/
Vertex(const glm::vec2& position, const glm::vec2& uv);
/**
* @brief <20><><EFBFBD><EFBFBD><ECBAAF>
*/
Vertex(const glm::vec2& position, const glm::vec2& uv, VertexTag tag);
/**
* @brief <20><><EFBFBD><EFBFBD><ECBAAF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD><DAB8>ƣ<EFBFBD>
*
* @param position <20><><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
* @param uv UV<55><56><EFBFBD><EFBFBD>
* @param originalPosition ԭʼλ<CABC><CEBB>
*/
Vertex(const glm::vec2& position, const glm::vec2& uv, const glm::vec2& originalPosition);
/**
* @brief <20><><EFBFBD>ݹ<EFBFBD><DDB9><EFBFBD><ECBAAF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڽ<EFBFBD>ͨ<EFBFBD><CDA8>λ<EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* UV<55><56><EFBFBD>꽫Ĭ<EABDAB><C4AC>Ϊ (0, 0)<29><>
*
* @param x <20><><EFBFBD><EFBFBD> x <20><><EFBFBD><EFBFBD>
* @param y <20><><EFBFBD><EFBFBD> y <20><><EFBFBD><EFBFBD>
*/
Vertex(float x, float y);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ͷŵĶ<C5B5>̬<EFBFBD><CCAC>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><E5A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFB2BB>Ҫ<EFBFBD><D2AA>
// ~Vertex() = default;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><ECBAAF><EFBFBD>Ϳ<EFBFBD><CDBF><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EEBFBD><EFBFBD><EFBFBD>
Vertex(const Vertex& other);
Vertex& operator=(const Vertex& other);
// <20>ƶ<EFBFBD><C6B6><EFBFBD><EFBFBD><EFBFBD><ECBAAF><EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD><C6B6><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Vertex(Vertex&& other) noexcept = default;
// Vertex& operator=(Vertex&& other) noexcept = default;
// --- Getters <20><> 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<int>& getControlledTriangles() const { return controlledTriangles; }
void setControlledTriangles(const std::vector<int>& controlledTriangles);
// --- <20><><EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD><DFBC><EFBFBD><EFBFBD><EFBFBD> ---
/**
* @brief <20><><EFBFBD><EFBFBD>Ϊԭʼλ<CABC><CEBB>
*/
void resetToOriginal();
/**
* @brief <20><><EFBFBD>浱ǰλ<C7B0><CEBB>Ϊ<EFBFBD>µ<EFBFBD>ԭʼλ<CABC><CEBB>
*/
void saveAsOriginal();
/**
* @brief <20><><EFBFBD><EFBFBD><EFBFBD>˶<EFBFBD><CBB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EEBFBD> (<28><>ӦJava<76>е<EFBFBD>copy())
*/
Vertex copy() const;
// --- C++ <20><><EFBFBD>ԣ<EFBFBD><D4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ---
/**
* @brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>Ӧ Java <20>е<EFBFBD> equals())
*/
bool operator==(const Vertex& other) const;
/**
* @brief <20><><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
bool operator!=(const Vertex& other) const {
return !(*this == other);
}
/**
* @brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>Ӧ Java <20>е<EFBFBD> toString())
*/
friend std::ostream& operator<<(std::ostream& os, const Vertex& vertex);
};
} // util
#endif // VIVID2D_VERTEX_H

View File

@@ -0,0 +1,216 @@
#include "pch.h"
#include "VertexList.h"
#include <sstream>
#include <algorithm>
#include <numeric>
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<Vertex>& initialVertices, const std::vector<int>& 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<int>(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<int>(this->vertices.size())) {
throw std::out_of_range("Index out of bounds: " + std::to_string(index));
}
// 1. 重构索引数组
std::vector<int> 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<int>(i));
}
return removedVertex;
}
const Vertex& VertexList::get(int index) const {
if (index < 0 || index >= static_cast<int>(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<Vertex> VertexList::getVertices() const {
return this->vertices;
}
std::vector<Vertex> VertexList::getVertices(VertexTag tag) const {
std::vector<Vertex> 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<int> VertexList::getIndices() const {
return this->indices;
}
std::vector<int> VertexList::getIndices(VertexTag tag) const {
if (indices.empty()) {
return {};
}
std::unordered_set<int> taggedOriginalIndices;
for (size_t i = 0; i < this->vertices.size(); ++i) {
if (this->vertices[i].getTag() == tag) {
taggedOriginalIndices.insert(static_cast<int>(i));
}
}
if (taggedOriginalIndices.empty()) {
return {};
}
std::vector<int> 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<int>& indices) {
this->indices = indices;
}
void VertexList::set(int index, const Vertex& vertex) {
if (index < 0 || index >= static_cast<int>(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

View File

@@ -0,0 +1,172 @@
#pragma once
#ifndef VIVID2D_VERTEXLIST_H
#define VIVID2D_VERTEXLIST_H
#include <vector>
#include <string>
#include <algorithm>
#include <stdexcept>
#include <numeric>
#include <unordered_set>
#include "Vertex.h"
namespace Vivid2D::util {
/**
* @brief 一个自包含的几何数据单元用于管理顶点vertices
* 以及定义它们之间拓扑结构三角形的索引indices
*
* 对应 Java 类 VertexList。
*/
class VIVID_2D_MYDLL_API VertexList {
private:
std::string name;
std::vector<int> indices;
public:
std::vector<Vertex> 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<Vertex>& initialVertices, const std::vector<int>& 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<Vertex> getVertices() const;
/**
* @brief 根据标签过滤并返回顶点列表。
*
* @return 过滤后的顶点列表
*/
std::vector<Vertex> getVertices(VertexTag tag) const;
// --- Getter 和 Setter ---
const std::string& getName() const { return name; }
void setName(const std::string& name);
/**
* @brief 获取索引数组的副本。
*
* @return 索引数组的副本
*/
std::vector<int> getIndices() const;
/**
* @brief 获取仅由至少包含一个指定标签Tag的顶点组成的三角形索引。
*
* @param tag 要筛选的顶点标签
* @return 一个新的索引数组,包含符合条件的三角形的原始索引。
*/
std::vector<int> getIndices(VertexTag tag) const;
/**
* @brief 设置索引数组。
*
* @param indices 新的索引数组
*/
void setIndices(const std::vector<int>& 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

View File

@@ -0,0 +1,244 @@
#include "pch.h"
#include "MultiSelectionBoxRenderer.h"
#include <cmath>
#include <algorithm>
#include <iostream>
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 (来自 <cmath>)
float a0 = static_cast<float>(i * 2.0 * PI / segments);
float a1 = static_cast<float>((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<int>(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<int>(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

View File

@@ -0,0 +1,91 @@
#pragma once
#ifndef VIVID2D_MULTISELECTIONBOXRENDERER_H
#define VIVID2D_MULTISELECTIONBOXRENDERER_H
#include <glm/glm.hpp>
#include <algorithm>
#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

View File

@@ -0,0 +1,608 @@
#include "pch.h"
#include "Texture.h"
#include <stdexcept>
#include <algorithm>
#include <cmath>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image.h>
#include <stb_image_write.h>
namespace Vivid2D::Render::Texture {
// 静态成员初始化
std::unordered_map<std::string, std::weak_ptr<Texture>> 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<unsigned char>& 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> 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<unsigned char> 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<Texture>(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<std::mutex> 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<unsigned char> Texture::getPixelData() {
ensurePixelDataCached();
return m_PixelDataCache;
}
void Texture::setPixelData(const std::vector<unsigned char>& 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<unsigned char> 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<unsigned char> 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> Texture::copy(const std::string& newName) const {
if (m_Disposed) throw std::runtime_error("Cannot copy disposed texture");
const_cast<Texture*>(this)->ensurePixelDataCached();
if (!hasPixelData()) {
throw std::runtime_error("No pixel data available to copy texture: " + m_Name);
}
auto newTex = std::make_unique<Texture>(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> Texture::createSolidColor(const std::string& name, int width, int height, uint32_t rgbaColor) {
std::vector<uint32_t> pixels(width * height, rgbaColor);
auto tex = std::make_shared<Texture>(name, width, height, TextureFormat::RGBA, reinterpret_cast<const unsigned char*>(pixels.data()));
return tex;
}
std::shared_ptr<Texture> Texture::createCheckerboard(const std::string& name, int width, int height, int tileSize, uint32_t color1, uint32_t color2) {
std::vector<uint32_t> 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<Texture>(name, width, height, TextureFormat::RGBA, reinterpret_cast<const unsigned char*>(pixels.data()));
return tex;
}
std::shared_ptr<Texture> Texture::getOrCreate(const std::string& name, int width, int height, TextureFormat format) {
std::lock_guard<std::mutex> lock(s_CacheMutex);
if (s_TextureCache.count(name)) {
if (auto shared = s_TextureCache[name].lock()) {
return shared;
}
}
auto newTex = std::make_shared<Texture>(name, width, height, format);
s_TextureCache[name] = newTex;
return newTex;
}
std::unique_ptr<ImageData> 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<ImageData>(data, width, height, format, filePath);
}
std::shared_ptr<Texture> Texture::createFromFile(const std::string& name, const std::string& filePath, TextureFilter minFilter, TextureFilter magFilter) {
auto imageData = loadImageFromFile(filePath);
auto texture = std::make_shared<Texture>(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<std::mutex> lock(s_CacheMutex);
s_TextureCache[name] = texture;
return texture;
}
std::shared_ptr<Texture> Texture::createFromBytes(const std::string& name, const std::vector<unsigned char>& imageData, TextureFilter minFilter, TextureFilter magFilter) {
int width, height, components;
unsigned char* pixelData = stbi_load_from_memory(imageData.data(), static_cast<int>(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<Texture>(name, width, height, getTextureFormatFromComponents(components), pixelData);
texture->setMinFilter(minFilter);
texture->setMagFilter(magFilter);
stbi_image_free(pixelData);
std::lock_guard<std::mutex> lock(s_CacheMutex);
s_TextureCache[name] = texture;
return texture;
}
std::shared_ptr<Texture> 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<unsigned char> 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<float>(a);
r = std::min(255, static_cast<int>(round(r * invA)));
g = std::min(255, static_cast<int>(round(g * invA)));
b = std::min(255, static_cast<int>(round(b * invA)));
}
rgbaData[i * 4 + 0] = static_cast<unsigned char>(r);
rgbaData[i * 4 + 1] = static_cast<unsigned char>(g);
rgbaData[i * 4 + 2] = static_cast<unsigned char>(b);
rgbaData[i * 4 + 3] = static_cast<unsigned char>(a);
}
auto texture = std::make_shared<Texture>(name, width, height, TextureFormat::RGBA, rgbaData.data());
texture->setMinFilter(minFilter);
texture->setMagFilter(magFilter);
if (isPowerOfTwo(width) && isPowerOfTwo(height)) {
texture->generateMipmaps();
}
std::lock_guard<std::mutex> 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<std::mutex> 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

View File

@@ -0,0 +1,250 @@
#pragma once
#include "RenderSystem.h"
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <chrono>
#include <glad/glad.h>
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<unsigned char, void(*)(void*)> 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<unsigned char>& pixelData);
void uploadData(const unsigned char* pixelData, size_t size);
/**
* @brief 为纹理生成 Mipmap。
*/
void generateMipmaps();
/**
* @brief 从当前纹理裁剪出一个子纹理并返回新的 Texture 实例。
*/
std::unique_ptr<Texture> 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<unsigned char> getPixelData();
void setPixelData(const std::vector<unsigned char>& data);
void clearPixelDataCache();
void ensurePixelDataCached();
size_t getPixelDataCacheSize() const;
/**
* @brief 从 GPU 提取纹理数据。
*/
std::vector<unsigned char> 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<Texture> 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<Texture> createSolidColor(const std::string& name, int width, int height, uint32_t rgbaColor);
static std::shared_ptr<Texture> createCheckerboard(const std::string& name, int width, int height, int tileSize, uint32_t color1, uint32_t color2);
static std::shared_ptr<Texture> getOrCreate(const std::string& name, int width, int height, TextureFormat format);
static std::unique_ptr<ImageData> loadImageFromFile(const std::string& filePath);
static std::shared_ptr<Texture> createFromFile(const std::string& name, const std::string& filePath, TextureFilter minFilter = TextureFilter::LINEAR, TextureFilter magFilter = TextureFilter::LINEAR);
static std::shared_ptr<Texture> createFromBytes(const std::string& name, const std::vector<unsigned char>& 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<Texture> 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<unsigned char> m_PixelDataCache;
// ==================== 静态管理 ====================
static std::unordered_map<std::string, std::weak_ptr<Texture>> s_TextureCache;
static std::mutex s_CacheMutex;
// ==================== 私有方法 ====================
void createTextureObject();
void applyTextureParameters();
void cachePixelDataFromGPU();
static GLuint generateTextureId();
static bool isPowerOfTwo(int value);
};
} // namespace Vivid2D::Texture