载入中...
搜索中...
未找到
SpriteStackTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <utility>
6
8namespace {
9const EditorValue* field(const EditorValue& value, const char* key) {
10 const auto* object = value.getIf<EditorValue::Object>();
11 if (!object) return nullptr;
12 const auto it = object->find(key);
13 return it == object->end() ? nullptr : &it->second;
14}
15PropertyDescriptor prop(std::string path, PropertyType type, EditorValue value, const char* category,
16 double minimum = -1e9, double maximum = 1e9) {
19 d.displayNameKey = "editor.spritestack." + path;
20 d.category = category;
21 d.type = type;
22 d.defaultValue = std::move(value);
23 d.flags = PropertyFlag::EditorOnly;
24 d.numeric.minimum = minimum;
25 d.numeric.maximum = maximum;
26 return d;
27}
28bool errors(const std::vector<EditorDiagnostic>& values) {
29 return std::any_of(values.begin(), values.end(),
30 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; });
31}
32} // namespace
33
36 return {TargetId(id_),
37 "spritestack-asset",
39 false,
40 {CapabilityId("eve.editor.target.spritestack-properties")}};
41}
43 return c == CapabilityId("eve.editor.target.spritestack-properties") ? static_cast<IPropertyProvider*>(this)
44 : nullptr;
45}
46bool SpriteStackDocumentTarget::matches(const SelectionSnapshot& s) const {
47 return s.items.size() == 1 && s.items.front().target == TargetId(id_) && s.items.front().item.value() == id_;
48}
57 s.typeId = "spritestack.asset";
58 s.properties = {prop("source.kind", PropertyType::Enum, "primitive", "source"),
59 prop("source.value", PropertyType::AssetRef, "box", "source"),
60 prop("bake.layers", PropertyType::Int, int64_t{16}, "bake", 1, 512),
61 prop("bake.width", PropertyType::Int, int64_t{128}, "bake", 1, 4096),
62 prop("bake.height", PropertyType::Int, int64_t{128}, "bake", 1, 4096),
63 prop("bake.axis", PropertyType::Enum, "y", "bake"),
64 prop("bake.thickness", PropertyType::Float, 0.0, "bake", 0, 100000),
65 prop("bake.padding", PropertyType::Float, .04, "bake", 0, .49),
66 prop("bake.shade", PropertyType::Bool, true, "bake"),
67 prop("bake.tint", PropertyType::Color, EditorValue::Array{1.0, 1.0, 1.0, 1.0}, "bake", 0, 1),
68 prop("display.size", PropertyType::Vec2, EditorValue::Array{64.0, 64.0}, "display", .01, 100000),
69 prop("display.layerOffset", PropertyType::Float, 1.0, "display", .001, 100000),
70 prop("display.shadow", PropertyType::Bool, true, "display"),
71 prop("display.shadowOpacity", PropertyType::Float, .3, "display", 0, 1),
72 prop("display.outlineWidth", PropertyType::Float, 0.0, "display", 0, 10000)};
73 s.properties[0].enumItems = {"primitive", "model"};
74 s.properties[1].assetTypeFilters = {"model3d"};
75 s.properties[5].enumItems = {"x", "y", "z"};
76 return s;
77}
78EditorValue SpriteStackDocumentTarget::contentValue() const {
79 const auto& v = value_;
80 return EditorValue::Object{{"sourceKind", v.sourceKind},
81 {"source", v.source},
82 {"layers", int64_t{v.bake.layerCount}},
83 {"width", int64_t{v.bake.imageW}},
84 {"height", int64_t{v.bake.imageH}},
85 {"axis", v.bake.axis},
86 {"thickness", v.bake.thickness},
87 {"padding", v.bake.padding},
88 {"shade", v.bake.shade},
89 {"tint", EditorValue::Array{v.bake.tintR, v.bake.tintG, v.bake.tintB, 1.0}},
90 {"displaySize", EditorValue::Array{v.displayWidth, v.displayHeight}},
91 {"layerOffset", v.layerOffset},
92 {"shadow", v.shadow},
93 {"shadowOpacity", v.shadowOpacity},
94 {"outlineWidth", v.outlineWidth}};
95}
97 if (!matches(s) || !schema(s).find(p)) return {};
98 const auto& v = value_;
100 if (p == PropertyPath("source.kind"))
102 else if (p == PropertyPath("source.value"))
103 value = v.source;
104 else if (p == PropertyPath("bake.layers"))
105 value = int64_t{v.bake.layerCount};
106 else if (p == PropertyPath("bake.width"))
107 value = int64_t{v.bake.imageW};
108 else if (p == PropertyPath("bake.height"))
109 value = int64_t{v.bake.imageH};
110 else if (p == PropertyPath("bake.axis"))
111 value = v.bake.axis;
112 else if (p == PropertyPath("bake.thickness"))
114 else if (p == PropertyPath("bake.padding"))
116 else if (p == PropertyPath("bake.shade"))
117 value = v.bake.shade;
118 else if (p == PropertyPath("bake.tint"))
119 value = EditorValue::Array{v.bake.tintR, v.bake.tintG, v.bake.tintB, 1.0};
120 else if (p == PropertyPath("display.size"))
121 value = EditorValue::Array{v.displayWidth, v.displayHeight};
122 else if (p == PropertyPath("display.layerOffset"))
124 else if (p == PropertyPath("display.shadow"))
125 value = v.shadow;
126 else if (p == PropertyPath("display.shadowOpacity"))
128 else
130 return {PropertyReadState::Value, std::move(value), {}};
131}
132Result<DomainOperation> SpriteStackDocumentTarget::replacement(EditorValue content,
133 std::string property) const {
135 op.type = "spritestack.document.replace.v1";
136 op.inverseType = op.type;
137 op.target = TargetId(id_);
138 op.payload = std::move(content);
139 op.inverse = contentValue();
140 op.hasInverse = true;
141 op.affectedProperties.push_back(property);
142 op.mergeKey = "spritestack:" + id_ + ":" + property;
143 return eve::editing::applied<DomainOperation>(std::move(op));
144}
146 const EditorValue& value, PropertySetMode mode) const {
147 if (mode == PropertySetMode::Reset) return makeReset(s, p);
148 auto d = schema(s).find(p);
149 if (!matches(s) || !d || mode != PropertySetMode::Absolute || !validatePropertyValue(*d, value).ok())
150 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.spritestack.set"),
151 "SpriteStack property edit is invalid");
152 auto candidate = *this;
153 auto& v = candidate.value_;
154 if (p == PropertyPath("source.kind"))
155 v.sourceKind = *value.getIf<std::string>();
156 else if (p == PropertyPath("source.value"))
157 v.source = *value.getIf<std::string>();
158 else if (p == PropertyPath("bake.layers"))
159 v.bake.layerCount = static_cast<int>(*value.getIf<int64_t>());
160 else if (p == PropertyPath("bake.width"))
161 v.bake.imageW = static_cast<int>(*value.getIf<int64_t>());
162 else if (p == PropertyPath("bake.height"))
163 v.bake.imageH = static_cast<int>(*value.getIf<int64_t>());
164 else if (p == PropertyPath("bake.axis"))
165 v.bake.axis = *value.getIf<std::string>();
166 else if (p == PropertyPath("bake.thickness"))
167 v.bake.thickness = static_cast<float>(*value.getIf<double>());
168 else if (p == PropertyPath("bake.padding"))
169 v.bake.padding = static_cast<float>(*value.getIf<double>());
170 else if (p == PropertyPath("bake.shade"))
171 v.bake.shade = *value.getIf<bool>();
172 else if (p == PropertyPath("bake.tint")) {
173 const auto& a = *value.getIf<EditorValue::Array>();
174 v.bake.tintR = static_cast<float>(*a[0].getIf<double>());
175 v.bake.tintG = static_cast<float>(*a[1].getIf<double>());
176 v.bake.tintB = static_cast<float>(*a[2].getIf<double>());
177 } else if (p == PropertyPath("display.size")) {
178 const auto& a = *value.getIf<EditorValue::Array>();
179 v.displayWidth = static_cast<float>(*a[0].getIf<double>());
180 v.displayHeight = static_cast<float>(*a[1].getIf<double>());
181 } else if (p == PropertyPath("display.layerOffset"))
182 v.layerOffset = static_cast<float>(*value.getIf<double>());
183 else if (p == PropertyPath("display.shadow"))
184 v.shadow = *value.getIf<bool>();
185 else if (p == PropertyPath("display.shadowOpacity"))
186 v.shadowOpacity = static_cast<float>(*value.getIf<double>());
187 else
188 v.outlineWidth = static_cast<float>(*value.getIf<double>());
189 if (errors(candidate.validate()))
190 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.spritestack.invalid"),
191 "SpriteStack edit violates bake budgets");
192 return replacement(candidate.contentValue(), p.value());
193}
195 const PropertyPath& p) const {
196 auto d = schema(s).find(p);
197 if (!d)
198 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.spritestack.property"),
199 "Unknown SpriteStack property");
200 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
201}
202std::vector<EditorDiagnostic> SpriteStackDocumentTarget::validate() const {
203 std::vector<EditorDiagnostic> d;
204 const auto& v = value_;
205 if (v.sourceKind != "primitive" && v.sourceKind != "model")
207 RuleId("editor.spritestack.source-kind"), DiagnosticSeverity::Error,
208 "SpriteStack source kind must be primitive or model"));
209 if (v.source.empty())
211 RuleId("editor.spritestack.source"), DiagnosticSeverity::Error, "SpriteStack source must not be empty"));
212 if (v.sourceKind == "primitive" && v.source != "box" && v.source != "sphere" && v.source != "cylinder" &&
213 v.source != "cone")
215 RuleId("editor.spritestack.primitive"), DiagnosticSeverity::Error, "Unknown SpriteStack primitive"));
216 const bool axisInvalid = v.bake.axis != "x" && v.bake.axis != "y" && v.bake.axis != "z";
217 if (v.bake.layerCount < 1 || v.bake.layerCount > 512 || v.bake.imageW < 1 || v.bake.imageW > 4096 ||
218 v.bake.imageH < 1 || v.bake.imageH > 4096 || axisInvalid || !std::isfinite(v.bake.thickness) ||
219 v.bake.thickness < 0 || !std::isfinite(v.bake.padding) || v.bake.padding < 0 || v.bake.padding >= .5f)
221 RuleId("editor.spritestack.bake-range"), DiagnosticSeverity::Error,
222 "SpriteStack bake settings are invalid"));
223 const std::uint64_t bytes = static_cast<std::uint64_t>(std::max(0, v.bake.layerCount)) *
224 std::max(0, v.bake.imageW) * std::max(0, v.bake.imageH) * 4ull;
225 if (bytes > 512ull * 1024 * 1024)
227 RuleId("editor.spritestack.memory"), DiagnosticSeverity::Error,
228 "SpriteStack output exceeds the 512 MiB decoded budget"));
229 else if (bytes > 128ull * 1024 * 1024)
231 RuleId("editor.spritestack.memory-warning"), DiagnosticSeverity::Warning,
232 "SpriteStack output exceeds 128 MiB decoded"));
233 if (!std::isfinite(v.displayWidth) || v.displayWidth <= 0 || !std::isfinite(v.displayHeight) ||
234 v.displayHeight <= 0 || !std::isfinite(v.layerOffset) || v.layerOffset <= 0 ||
235 !std::isfinite(v.shadowOpacity) || v.shadowOpacity < 0 || v.shadowOpacity > 1 ||
236 !std::isfinite(v.outlineWidth) || v.outlineWidth < 0)
238 RuleId("editor.spritestack.display"), DiagnosticSeverity::Error,
239 "SpriteStack presentation values are invalid"));
240 return d;
241}
243 if (op.target != TargetId(id_) || op.type != "spritestack.document.replace.v1" ||
244 !op.payload.isWithinLimits(5, 64, 4096))
245 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.operation"),
246 "SpriteStack operation is invalid");
248 const auto * sKind = field(op.payload, "sourceKind"), *source = field(op.payload, "source"),
249 *axis = field(op.payload, "axis"), *shade = field(op.payload, "shade"),
250 *shadow = field(op.payload, "shadow");
251 const auto* sk = sKind ? sKind->getIf<std::string>() : nullptr;
252 const auto* sv = source ? source->getIf<std::string>() : nullptr;
253 const auto* av = axis ? axis->getIf<std::string>() : nullptr;
254 const auto* sh = shade ? shade->getIf<bool>() : nullptr;
255 const auto* sd = shadow ? shadow->getIf<bool>() : nullptr;
256 if (!sk || !sv || !av || !sh || !sd)
257 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.payload"),
258 "SpriteStack text or bool field is invalid");
259 v.sourceKind = *sk;
260 v.source = *sv;
261 v.bake.axis = *av;
262 v.bake.shade = *sh;
263 v.shadow = *sd;
264 int* ints[] = {&v.bake.layerCount, &v.bake.imageW, &v.bake.imageH};
265 const char* intFields[] = {"layers", "width", "height"};
266 for (int i = 0; i < 3; ++i) {
267 const auto* x = field(op.payload, intFields[i]);
268 const auto* n = x ? x->getIf<int64_t>() : nullptr;
269 if (!n)
270 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.integer"),
271 "SpriteStack integer is invalid");
272 *ints[i] = static_cast<int>(*n);
273 }
274 float* floats[] = {&v.bake.thickness, &v.bake.padding, &v.layerOffset, &v.shadowOpacity, &v.outlineWidth};
275 const char* floatFields[] = {"thickness", "padding", "layerOffset", "shadowOpacity", "outlineWidth"};
276 for (int i = 0; i < 5; ++i) {
277 const auto* x = field(op.payload, floatFields[i]);
278 const auto* n = x ? x->getIf<double>() : nullptr;
279 if (!n || !std::isfinite(*n))
280 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.number"),
281 "SpriteStack number is invalid");
282 *floats[i] = static_cast<float>(*n);
283 }
284 const auto *tintValue = field(op.payload, "tint"), *sizeValue = field(op.payload, "displaySize");
285 const auto* tint = tintValue ? tintValue->getIf<EditorValue::Array>() : nullptr;
286 const auto* size = sizeValue ? sizeValue->getIf<EditorValue::Array>() : nullptr;
287 if (!tint || tint->size() != 4 || !size || size->size() != 2)
288 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.vector"),
289 "SpriteStack vector is invalid");
290 float* tints[] = {&v.bake.tintR, &v.bake.tintG, &v.bake.tintB};
291 for (int i = 0; i < 3; ++i) {
292 const auto* n = (*tint)[i].getIf<double>();
293 if (!n || !std::isfinite(*n))
294 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.tint"),
295 "SpriteStack tint is invalid");
296 *tints[i] = static_cast<float>(*n);
297 }
298 const auto *w = (*size)[0].getIf<double>(), *h = (*size)[1].getIf<double>();
299 if (!w || !h)
300 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.size"),
301 "SpriteStack display size is invalid");
302 v.displayWidth = static_cast<float>(*w);
303 v.displayHeight = static_cast<float>(*h);
304 SpriteStackDocumentTarget candidate(id_);
305 candidate.value_ = v;
306 if (errors(candidate.validate()))
307 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.spritestack.invalid"),
308 "SpriteStack document validation failed");
309 value_ = std::move(v);
310 bumpRevision();
311 widenDirty(0, 0);
312 return eve::editing::applied<void>();
313}
314std::unique_ptr<IDomainOperationTarget> SpriteStackDocumentTarget::cloneDomainState() const {
315 return std::make_unique<SpriteStackDocumentTarget>(*this);
316}
318 std::unique_ptr<IDomainOperationTarget> candidate) {
319 auto* typed = dynamic_cast<SpriteStackDocumentTarget*>(candidate.get());
320 if (!typed || typed->id_ != id_)
321 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.spritestack.candidate"),
322 "SpriteStack candidate mismatch");
323 *this = *typed;
324 return eve::editing::applied<void>();
325}
327 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"content", contentValue()}};
328}
330 const auto *v = field(snapshot, "schemaVersion"), *content = field(snapshot, "content");
331 const auto* version = v ? v->getIf<int64_t>() : nullptr;
332 if (!version || *version != 1 || !content)
333 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.spritestack.snapshot"),
334 "Unsupported SpriteStack snapshot");
336 op.target = TargetId(id_);
337 op.type = "spritestack.document.replace.v1";
338 op.payload = *content;
339 auto result = applyDomainOperation(op);
340 if (result.ok()) clearDirtyRegion();
341 return result;
342}
343
344} // namespace eve::spritestack_editing
double value
float w
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
std::uint32_t key
glm::vec4 tint
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
int h
std::uint64_t bytes
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
float size
Definition TreeMesh.cpp:156
const char * category
const UnitySourceAsset & source
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
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
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
virtual void clearDirtyRegion()=0
Clears dirty region.
UI-independent property access for an immutable selection snapshot.
Dotted property path (layout.size) compared and persisted as text.
Definition EditingIds.h:78
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
bool isWithinLimits(size_t maxDepth, size_t maxElements, size_t maxStringBytes) const
Validate nesting, element count and approximate string byte limits.
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Revisioned SpriteStack bake preset with reusable Inspector metadata.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
TargetDescriptor describe() const override
Describe.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
SpriteStackDocumentTarget(std::string id)
Sprite stack document target.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads read.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
std::vector< EditorDiagnostic > validate() const
Validate source, sampling limits, output memory and presentation values.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
const SpriteStackAssetValue & value() const
Value.
Result< void > loadSnapshot(const EditorValue &snapshot)
Loads snapshot.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
editing::PropertyDescriptor PropertyDescriptor
editing::PropertyPath PropertyPath
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
const EditorValue * field(const EditorValue &value, const char *name)
DomainOperation public API.
std::vector< std::string > affectedProperties
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.
Complete authored sprite-stack bake and presentation preset.