载入中...
搜索中...
未找到
ActionParameterCurve.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <string_view>
6#include <utility>
7
8namespace eve::action {
9namespace {
10
11Result<double> number(const Value& value, std::string path) {
12 double result = 0.0;
13 if (const auto* integer = value.getIf<std::int64_t>())
14 result = static_cast<double>(*integer);
15 else if (const auto* decimal = value.getIf<double>())
16 result = *decimal;
17 else
19 "parameter curve field must be numeric", path));
20 if (!std::isfinite(result))
22 "parameter curve field must be finite", path));
23 return Result<double>::success(result);
24}
25
26std::optional<ActionParameterInterpolation> parseInterpolation(std::string_view value) {
27 if (value == "step") return ActionParameterInterpolation::Step;
28 if (value == "linear") return ActionParameterInterpolation::Linear;
29 if (value == "cubic") return ActionParameterInterpolation::Cubic;
30 return std::nullopt;
31}
32
33std::optional<ActionParameterOperation> parseOperation(std::string_view value) {
34 if (value == "replace") return ActionParameterOperation::Replace;
35 if (value == "add") return ActionParameterOperation::Add;
36 if (value == "multiply") return ActionParameterOperation::Multiply;
37 return std::nullopt;
38}
39
40} // namespace
41
43 switch (value) {
44 case ActionParameterInterpolation::Step: return "step";
45 case ActionParameterInterpolation::Linear: return "linear";
46 case ActionParameterInterpolation::Cubic: return "cubic";
47 }
48 return "linear";
49}
50
51std::optional<ActionParameterInterpolation> actionParameterInterpolationFromName(
52 std::string_view value) noexcept {
53 return parseInterpolation(value);
54}
55
57 switch (value) {
58 case ActionParameterOperation::Replace: return "replace";
59 case ActionParameterOperation::Add: return "add";
60 case ActionParameterOperation::Multiply: return "multiply";
61 }
62 return "replace";
63}
64
67 const auto target = payload.find("target");
68 if (target == payload.end() || !target->second.getIf<std::string>())
70 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter target must be text", "target"));
71 auto parsedTarget = LogicalId::parse(*target->second.getIf<std::string>());
72 if (!parsedTarget)
74 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter target must be a LogicalId", "target"));
75 candidate.target = std::move(*parsedTarget);
76
77 if (const auto found = payload.find("operation"); found != payload.end()) {
78 const auto* text = found->second.getIf<std::string>();
79 if (!text || !parseOperation(*text))
81 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter operation is unknown", "operation"));
82 candidate.operation = *parseOperation(*text);
83 }
84
85 const auto foundKeys = payload.find("keys");
86 const auto* keys = foundKeys == payload.end() ? nullptr : foundKeys->second.getIf<Value::Array>();
87 if (!keys || keys->size() < 2 || keys->size() > 1024)
89 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter curve requires 2 to 1024 keys", "keys"));
90 candidate.keys.reserve(keys->size());
91 for (std::size_t index = 0; index < keys->size(); ++index) {
92 const auto* object = (*keys)[index].getIf<Value::Object>();
93 const std::string path = "keys[" + std::to_string(index) + "]";
94 if (!object)
96 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter key must be an object", path));
97 const auto time = object->find("time");
98 const auto value = object->find("value");
99 if (time == object->end() || value == object->end())
101 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter key requires time and value", path));
102 auto parsedTime = number(time->second, path + ".time");
103 if (!parsedTime) return Result<ActionParameterCurveBinding>::failure(parsedTime.status());
104 auto parsedValue = number(value->second, path + ".value");
105 if (!parsedValue) return Result<ActionParameterCurveBinding>::failure(parsedValue.status());
106 if (parsedTime.value() < 0.0 || parsedTime.value() > 1.0 ||
107 (!candidate.keys.empty() && parsedTime.value() <= candidate.keys.back().time))
110 "parameter key times must be strictly ordered in [0, 1]", path + ".time"));
112 key.time = parsedTime.value();
113 key.value = parsedValue.value();
114 if (const auto tangent = object->find("inTangent"); tangent != object->end()) {
115 auto parsed = number(tangent->second, path + ".inTangent");
116 if (!parsed) return Result<ActionParameterCurveBinding>::failure(parsed.status());
117 key.inTangent = parsed.value();
118 }
119 if (const auto tangent = object->find("outTangent"); tangent != object->end()) {
120 auto parsed = number(tangent->second, path + ".outTangent");
121 if (!parsed) return Result<ActionParameterCurveBinding>::failure(parsed.status());
122 key.outTangent = parsed.value();
123 }
124 if (const auto mode = object->find("interpolation"); mode != object->end()) {
125 const auto* text = mode->second.getIf<std::string>();
126 if (!text || !parseInterpolation(*text))
128 DiagnosticCode::InvalidArgument, "parameter interpolation is unknown", path + ".interpolation"));
129 key.interpolation = *parseInterpolation(*text);
130 }
131 candidate.keys.push_back(key);
132 }
133 if (candidate.keys.front().time != 0.0 || candidate.keys.back().time != 1.0)
135 Diagnostic::error(DiagnosticCode::InvalidArgument, "parameter curve endpoints must be at 0 and 1", "keys"));
136 return Result<ActionParameterCurveBinding>::success(std::move(candidate));
137}
138
140 extensions["target"] = target.format();
141 extensions["operation"] = std::string(actionParameterOperationName(operation));
142 Value::Array encoded;
143 encoded.reserve(keys.size());
144 for (const auto& key : keys)
145 encoded.emplace_back(Value::Object{{"time", key.time},
146 {"value", key.value},
147 {"inTangent", key.inTangent},
148 {"outTangent", key.outTangent},
149 {"interpolation", std::string(actionParameterInterpolationName(
150 key.interpolation))}});
151 extensions["keys"] = Value(std::move(encoded));
152 return extensions;
153}
154
155double ActionParameterCurveBinding::sample(double progress) const noexcept {
156 if (keys.empty()) return 0.0;
157 progress = std::clamp(progress, 0.0, 1.0);
158 const auto upper = std::upper_bound(keys.begin(), keys.end(), progress,
159 [](double value, const ActionParameterKey& key) { return value < key.time; });
160 if (upper == keys.begin()) return keys.front().value;
161 if (upper == keys.end()) return keys.back().value;
162 const auto& left = *(upper - 1);
163 const auto& right = *upper;
164 if (left.interpolation == ActionParameterInterpolation::Step) return left.value;
165 const double span = right.time - left.time;
166 const double t = (progress - left.time) / span;
167 if (left.interpolation == ActionParameterInterpolation::Linear)
168 return left.value + (right.value - left.value) * t;
169 const double t2 = t * t;
170 const double t3 = t2 * t;
171 return (2.0 * t3 - 3.0 * t2 + 1.0) * left.value +
172 (t3 - 2.0 * t2 + t) * left.outTangent * span +
173 (-2.0 * t3 + 3.0 * t2) * right.value +
174 (t3 - t2) * right.inTangent * span;
175}
176
177} // namespace eve::action
double value
Typed continuous parameter-curve action block.
Value::Object payload
Vec3 tangent
Definition CaveMesh.cpp:80
std::uint32_t key
HexVec3 left
HexVec3 right
std::string text
std::string path
Definition PlayHost.cpp:110
float t
double number
bool found
Json object
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
ActionParameterInterpolation
Interpolation used from one parameter key to the following key.
std::optional< ActionParameterInterpolation > actionParameterInterpolationFromName(std::string_view value) noexcept
Parse a stable persisted interpolation spelling.
std::string_view actionParameterOperationName(ActionParameterOperation value) noexcept
Stable persisted operation spelling.
ActionParameterOperation
Composition operation applied by a target-specific parameter sink.
std::string_view actionParameterInterpolationName(ActionParameterInterpolation value) noexcept
Stable persisted interpolation spelling.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
Validated owning continuous parameter binding.
ActionParameterOperation operation
Composition operation applied at the target.
LogicalId target
Stable sink-owned parameter target.
static Result< ActionParameterCurveBinding > fromPayload(const Value::Object &payload)
Decode and validate target, operation and normalized ordered keys.
Value::Object toPayload(Value::Object extensions={}) const
Encode the canonical fields while preserving unrelated extension fields.
std::vector< ActionParameterKey > keys
Strictly ordered normalized keys, including endpoints 0 and 1.
One normalized, owning parameter-curve key.
double time
Normalized key time in [0, 1].