diff --git a/.idea/Vivid2DRenderer.iml b/.idea/Vivid2DRenderer.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/Vivid2DRenderer.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Vivid2DRenderer/.idea/Vivid2DRenderer.iml b/Vivid2DRenderer/.idea/Vivid2DRenderer.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/Vivid2DRenderer/.idea/Vivid2DRenderer.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Vivid2DRenderer/model/AnimationParameter.cpp b/Vivid2DRenderer/model/AnimationParameter.cpp
new file mode 100644
index 0000000..4df87ba
--- /dev/null
+++ b/Vivid2DRenderer/model/AnimationParameter.cpp
@@ -0,0 +1,341 @@
+#include "pch.h"
+
+#include "AnimationParameter.h"
+#include "Mesh2D.h"
+#include "ModelPart.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Vivid2D {
+
+ // --- AnimationParameter 实现 (与你提供的代码相同) ---
+ AnimationParameter::AnimationParameter(std::string id, float min, float max, float defaultValue)
+ : m_Id(std::move(id)),
+ m_DefaultValue(defaultValue),
+ m_MinValue(min),
+ m_MaxValue(max) {
+ m_Value = defaultValue;
+ }
+
+ void AnimationParameter::setValue(float value) {
+ float clamped = std::clamp(value, m_MinValue, m_MaxValue);
+ if (m_Value != clamped) {
+ m_Value = clamped;
+ m_Changed = true;
+ }
+ }
+
+ AnimationParameter AnimationParameter::copy() const {
+ AnimationParameter cpy(m_Id, m_MinValue, m_MaxValue, m_DefaultValue);
+ cpy.m_Value = m_Value;
+ cpy.m_Changed = m_Changed;
+ cpy.m_Keyframes = m_Keyframes;
+ return cpy;
+ }
+
+ bool AnimationParameter::hasChanged() const { return m_Changed; }
+ void AnimationParameter::markClean() { m_Changed = false; }
+ float AnimationParameter::getValue() const { return m_Value; }
+ const std::string& AnimationParameter::getId() const { return m_Id; }
+ float AnimationParameter::getMinValue() const { return m_MinValue; }
+ float AnimationParameter::getMaxValue() const { return m_MaxValue; }
+ float AnimationParameter::getDefaultValue() const { return m_DefaultValue; }
+ void AnimationParameter::reset() { setValue(m_DefaultValue); }
+
+ float AnimationParameter::getNormalizedValue() const {
+ float range = m_MaxValue - m_MinValue;
+ if (range == 0.0f) return 0.0f;
+ return (m_Value - m_MinValue) / range;
+ }
+
+ void AnimationParameter::setNormalizedValue(float normalized) {
+ float newValue = m_MinValue + normalized * (m_MaxValue - m_MinValue);
+ setValue(newValue);
+ }
+
+ bool AnimationParameter::addKeyframe(float frameValue) {
+ float clamped = std::clamp(frameValue, m_MinValue, m_MaxValue);
+ return m_Keyframes.insert(clamped).second;
+ }
+
+ bool AnimationParameter::removeKeyframe(float frameValue) {
+ return m_Keyframes.erase(frameValue) > 0;
+ }
+
+ bool AnimationParameter::isKeyframe(float frameValue) const {
+ return m_Keyframes.count(frameValue) > 0;
+ }
+
+ const std::set& AnimationParameter::getKeyframes() const {
+ return m_Keyframes;
+ }
+
+ void AnimationParameter::clearKeyframes() {
+ m_Keyframes.clear();
+ }
+
+ std::optional AnimationParameter::getNearestKeyframe(float value, float snapThreshold) const {
+ if (snapThreshold <= 0 || m_Keyframes.empty()) {
+ return std::nullopt;
+ }
+ auto it_next = m_Keyframes.lower_bound(value);
+ std::optional prev_key;
+ float distToPrev = std::numeric_limits::max();
+ if (it_next != m_Keyframes.begin()) {
+ auto it_prev = std::prev(it_next);
+ prev_key = *it_prev;
+ distToPrev = std::abs(*it_prev - value);
+ }
+ std::optional next_key;
+ float distToNext = std::numeric_limits::max();
+ if (it_next != m_Keyframes.end()) {
+ next_key = *it_next;
+ distToNext = std::abs(*it_next - value);
+ }
+ if (distToPrev < snapThreshold && distToPrev <= distToNext) {
+ return prev_key;
+ }
+ if (distToNext < snapThreshold && distToNext < distToPrev) {
+ return next_key;
+ }
+ return std::nullopt;
+ }
+
+ bool AnimationParameter::operator==(const AnimationParameter& other) const {
+ return m_Id == other.m_Id &&
+ m_DefaultValue == other.m_DefaultValue &&
+ m_MinValue == other.m_MinValue &&
+ m_MaxValue == other.m_MaxValue &&
+ m_Keyframes == other.m_Keyframes;
+ }
+
+ std::string AnimationParameter::toString() const {
+ std::stringstream ss;
+ ss << std::fixed << std::setprecision(3);
+ ss << "AnimationParameter[ID=" << m_Id
+ << ", Value=" << m_Value
+ << (m_Changed ? " (Changed)" : "")
+ << ", Range=[" << m_MinValue << ", " << m_MaxValue << "]"
+ << ", Default=" << m_DefaultValue
+ << ", Keyframes=[";
+ for (auto it = m_Keyframes.begin(); it != m_Keyframes.end(); ++it) {
+ ss << (it == m_Keyframes.begin() ? "" : ", ") << *it;
+ }
+ ss << "]]";
+ return ss.str();
+ }
+
+ // --- 新增:ParametersManagement 和相关实现 ---
+
+ void ParametersManagement::broadcast(ModelPart* modelPart, const AnimationParameter& currentAnimParam, float currentKeyframe, const std::string& paramId, const ParameterValue& value) {
+ if (!modelPart) return;
+
+ bool isKeyframe = currentAnimParam.isKeyframe(currentKeyframe);
+
+ // 查找或创建 modelPart 对应的记录
+ auto& record = m_records[modelPart];
+ record.modelPart = modelPart;
+
+ // 检查是否存在完全匹配的记录 (keyframe, paramId, animParam, and special id for mesh)
+ long existingIndex = -1;
+ for (size_t i = 0; i < record.keyframes.size(); ++i) {
+ bool keyframeMatches = record.keyframes[i] == currentKeyframe;
+ bool paramIdMatches = record.paramIds[i] == paramId;
+ bool animParamMatches = record.animationParameters[i] == currentAnimParam;
+ bool idMatches = true;
+
+ // 对 meshVertices 进行特殊处理
+ if (paramIdMatches && paramId == "meshVertices") {
+ const auto* currentMap = std::get_if>(&value);
+ const auto* recordMap = std::get_if>(&record.values[i]);
+ if (currentMap && recordMap) {
+ auto currentIdIt = currentMap->find("id");
+ auto recordIdIt = recordMap->find("id");
+ if (currentIdIt != currentMap->end() && recordIdIt != recordMap->end()) {
+ const std::string* currentId = std::any_cast(¤tIdIt->second);
+ const std::string* recordId = std::any_cast(&recordIdIt->second);
+ idMatches = (currentId && recordId && *currentId == *recordId);
+ }
+ else {
+ idMatches = false; // 如果任一map中没有id,则认为不匹配
+ }
+ }
+ else {
+ idMatches = false;
+ }
+ }
+
+ if (keyframeMatches && paramIdMatches && animParamMatches && idMatches) {
+ existingIndex = i;
+ break;
+ }
+ }
+
+ if (existingIndex != -1) {
+ // 更新现有值
+ record.values[existingIndex] = value;
+ record.isKeyframes[existingIndex] = isKeyframe; // 状态也可能变化
+ }
+ else {
+ // 添加新记录
+ record.animationParameters.push_back(currentAnimParam);
+ record.paramIds.push_back(paramId);
+ record.values.push_back(value);
+ record.keyframes.push_back(currentKeyframe);
+ record.isKeyframes.push_back(isKeyframe);
+ }
+ }
+
+ void ParametersManagement::removeParameterAt(ModelPart* targetModelPart, size_t indexToRemove) {
+ auto it = m_records.find(targetModelPart);
+ if (it != m_records.end()) {
+ ParameterRecord& record = it->second;
+ if (indexToRemove < record.paramIds.size()) {
+ record.animationParameters.erase(record.animationParameters.begin() + indexToRemove);
+ record.paramIds.erase(record.paramIds.begin() + indexToRemove);
+ record.values.erase(record.values.begin() + indexToRemove);
+ record.keyframes.erase(record.keyframes.begin() + indexToRemove);
+ record.isKeyframes.erase(record.isKeyframes.begin() + indexToRemove);
+ }
+ // 如果记录变空,则从 map 中移除
+ if (record.paramIds.empty()) {
+ m_records.erase(it);
+ }
+ }
+ }
+
+ void ParametersManagement::removeParameter(ModelPart* modelPart, const std::string& paramId) {
+ auto it = m_records.find(modelPart);
+ if (it == m_records.end()) return;
+
+ if (paramId == "all") {
+ m_records.erase(it);
+ return;
+ }
+
+ ParameterRecord& record = it->second;
+ // 从后往前遍历以安全删除
+ for (int i = record.paramIds.size() - 1; i >= 0; --i) {
+ if (record.paramIds[i] == paramId) {
+ record.animationParameters.erase(record.animationParameters.begin() + i);
+ record.paramIds.erase(record.paramIds.begin() + i);
+ record.values.erase(record.values.begin() + i);
+ record.keyframes.erase(record.keyframes.begin() + i);
+ record.isKeyframes.erase(record.isKeyframes.begin() + i);
+ }
+ }
+
+ if (record.paramIds.empty()) {
+ m_records.erase(it);
+ }
+ }
+
+ std::optional ParametersManagement::getValue(ModelPart* modelPart, const std::string& paramId) const {
+ auto it = m_records.find(modelPart);
+ if (it == m_records.end()) {
+ return std::nullopt;
+ }
+
+ const ParameterRecord& sourceRecord = it->second;
+ ParameterRecord resultRecord;
+ resultRecord.modelPart = modelPart;
+
+ for (size_t i = 0; i < sourceRecord.paramIds.size(); ++i) {
+ if (sourceRecord.paramIds[i] == paramId) {
+ resultRecord.animationParameters.push_back(sourceRecord.animationParameters[i]);
+ resultRecord.paramIds.push_back(sourceRecord.paramIds[i]);
+ resultRecord.values.push_back(sourceRecord.values[i]);
+ resultRecord.keyframes.push_back(sourceRecord.keyframes[i]);
+ resultRecord.isKeyframes.push_back(sourceRecord.isKeyframes[i]);
+ }
+ }
+
+ if (resultRecord.paramIds.empty()) {
+ return std::nullopt;
+ }
+
+ return resultRecord;
+ }
+
+ const ParameterRecord* ParametersManagement::getModelPartParameters(ModelPart* modelPart) const {
+ auto it = m_records.find(modelPart);
+ if (it != m_records.end()) {
+ return &it->second;
+ }
+ return nullptr;
+ }
+
+ const std::map& ParametersManagement::getAllRecords() const {
+ return m_records;
+ }
+
+ // --- toString 实现 ---
+
+ // 辅助函数,用于将 ParameterValue 转换为字符串
+ std::string parameterValueToString(const ParameterValue& value) {
+ std::stringstream ss;
+ std::visit([&ss](auto&& arg) {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v) {
+ ss << "[Null]";
+ }
+ else if constexpr (std::is_same_v) {
+ ss << arg;
+ }
+ else if constexpr (std::is_same_v>) {
+ ss << "(" << arg.first << ", " << arg.second << ")";
+ }
+ else if constexpr (std::is_same_v>) {
+ ss << "{";
+ for (auto it = arg.begin(); it != arg.end(); ++it) {
+ ss << (it == arg.begin() ? "" : ", ") << it->first << ":";
+ if (it->second.type() == typeid(std::string)) {
+ ss << "\"" << std::any_cast(it->second) << "\"";
+ }
+ else {
+ ss << "[... complex data ...]";
+ }
+ }
+ ss << "}";
+ }
+ }, value);
+ return ss.str();
+ }
+
+ std::string ParameterRecord::toString() const {
+ std::stringstream ss;
+ std::string partName = (modelPart != nullptr) ? modelPart->getName() : "[NULL ModelPart]";
+ ss << "ParameterRecord[Part=" << partName << ", Details=[";
+ for (size_t i = 0; i < paramIds.size(); ++i) {
+ if (i > 0) ss << "; ";
+ ss << "{ID=" << paramIds[i]
+ << ", V=" << parameterValueToString(values[i])
+ << ", KF=" << keyframes[i]
+ << ", IsKF=" << (isKeyframes[i] ? "true" : "false")
+ << "}";
+ }
+ ss << "]]";
+ return ss.str();
+ }
+
+ std::string ParametersManagement::toString() const {
+ std::stringstream ss;
+ ss << "ParametersManagement State:\n";
+ if (m_records.empty()) {
+ ss << " No recorded parameters.\n";
+ }
+ else {
+ int recordIndex = 0;
+ for (const auto& pair : m_records) {
+ ss << " --- Record " << recordIndex++ << " ---\n";
+ ss << " " << pair.second.toString() << "\n";
+ }
+ }
+ return ss.str();
+ }
+
+} // namespace Vivid2D
\ No newline at end of file
diff --git a/Vivid2DRenderer/model/AnimationParameter.h b/Vivid2DRenderer/model/AnimationParameter.h
new file mode 100644
index 0000000..943bff3
--- /dev/null
+++ b/Vivid2DRenderer/model/AnimationParameter.h
@@ -0,0 +1,302 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include