载入中...
搜索中...
未找到
SceneTarget.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9namespace eve::scene_editing {
10namespace {
11
12const EditorValue* field(const EditorValue& value, const char* name) {
13 const auto* object = value.getIf<EditorValue::Object>();
14 if (!object) return nullptr;
15 auto found = object->find(name);
16 return found == object->end() ? nullptr : &found->second;
17}
18
19} // namespace
20
21SceneTargetBase::SceneTargetBase(std::string id, std::string type) : id_(std::move(id)), type_(std::move(type)) {}
22
35
36void* SceneTargetBase::queryCapability(const CapabilityId& capability) {
38 return static_cast<ISceneHierarchyEditTarget*>(this);
41 return static_cast<eve::editing::IEditingSnapshotProvider*>(this);
43 return static_cast<ISceneComponentPayloadTarget*>(componentPayloads_);
44 return nullptr;
45}
46
48 if (operation.target != TargetId(id_))
49 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.target-mismatch"),
50 "Scene operation targets another backend");
51 if (operation.type == "scene.snapshot.restore.v1") {
52 auto parsed = parseSnapshot(operation.payload);
53 if (!parsed.ok()) return Result<void>::failure(parsed.status());
54 objects_ = std::move(parsed.value());
55 } else if (operation.type == "scene.object.create.v1") {
56 Result<SceneObjectSnapshot> parsed = parseObject(operation.payload);
57 if (!parsed.ok()) return Result<void>::failure(parsed.status());
58 const SceneObjectSnapshot& object = parsed.value();
59 if (object.id.empty() || objects_.contains(object.id))
60 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.scene.object-exists"),
61 "Scene object id is empty or already exists");
62 if (!object.parent.empty() && !objects_.contains(object.parent))
63 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.parent-not-found"),
64 "Scene object parent does not exist");
65 objects_.emplace(object.id, object);
66 } else if (operation.type == "scene.object.delete.v1") {
67 Result<SceneObjectSnapshot> parsed = parseObject(operation.payload);
68 if (!parsed.ok()) return Result<void>::failure(parsed.status());
69 const ObjectId& id = parsed.value().id;
70 if (!objects_.contains(id))
71 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
72 "Scene object does not exist");
73 for (const auto& [childId, child] : objects_) {
74 (void)childId;
75 if (child.parent == id)
76 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.object-has-children"),
77 "Scene object with children cannot be deleted by this operation");
78 }
79 objects_.erase(id);
80 } else if (operation.type == "scene.transform.set.v1") {
81 const EditorValue* idValue = field(operation.payload, "id");
82 const EditorValue* transform = field(operation.payload, "transform");
83 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
84 if (!id || !transform)
85 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.transform-payload"),
86 "Transform operation requires id and transform");
87 auto object = objects_.find(ObjectId(*id));
88 if (object == objects_.end())
89 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
90 "Scene object does not exist");
91 Result<SceneTransformValue> parsed = parseTransform(*transform);
92 if (!parsed.ok()) return Result<void>::failure(parsed.status());
93 object->second.transform = parsed.value();
94 } else if (operation.type == "scene.transform.multi.set.v1") {
95 const auto* entries = operation.payload.getIf<EditorValue::Array>();
96 if (!entries)
97 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.multi-transform-payload"),
98 "Multi-transform operation requires an array payload");
99 std::vector<std::pair<ObjectId, SceneTransformValue>> updates;
100 updates.reserve(entries->size());
101 for (const EditorValue& entry : *entries) {
102 const EditorValue* idValue = field(entry, "id");
103 const EditorValue* transform = field(entry, "transform");
104 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
105 if (!id || !transform || !objects_.contains(ObjectId(*id)))
106 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.multi-transform-object"),
107 "Multi-transform entry references an invalid scene object");
108 auto parsed = parseTransform(*transform);
109 if (!parsed.ok())
110 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.multi-transform-value"),
111 "Multi-transform entry contains an invalid transform");
112 updates.emplace_back(ObjectId(*id), parsed.value());
113 }
114 for (const auto& [id, transform] : updates) objects_.at(id).transform = transform;
115 } else if (operation.type == "scene.object.rename.v1") {
116 const EditorValue* idValue = field(operation.payload, "id");
117 const EditorValue* nameValue = field(operation.payload, "name");
118 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
119 const auto* name = nameValue ? nameValue->getIf<std::string>() : nullptr;
120 if (!id || !name || name->empty())
121 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.rename-payload"),
122 "Rename operation requires id and a non-empty name");
123 auto object = objects_.find(ObjectId(*id));
124 if (object == objects_.end())
125 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
126 "Scene object does not exist");
127 object->second.name = *name;
128 } else if (operation.type == "scene.object.reparent.v1") {
129 const EditorValue* idValue = field(operation.payload, "id");
130 const EditorValue* parentValue = field(operation.payload, "parent");
131 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
132 const auto* parent = parentValue ? parentValue->getIf<std::string>() : nullptr;
133 if (!id || !parent)
134 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.reparent-payload"),
135 "Reparent operation requires id and parent");
136 auto object = objects_.find(ObjectId(*id));
137 if (object == objects_.end())
138 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
139 "Scene object does not exist");
140 if (!parent->empty() && !objects_.contains(ObjectId(*parent)))
141 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.parent-not-found"),
142 "Scene object parent does not exist");
143 ObjectId ancestor(*parent);
144 while (!ancestor.empty()) {
145 if (ancestor == object->first)
146 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.hierarchy-cycle"),
147 "Reparenting would create a scene hierarchy cycle");
148 auto node = objects_.find(ancestor);
149 if (node == objects_.end()) break;
150 ancestor = node->second.parent;
151 }
152 object->second.parent = ObjectId(*parent);
153 } else {
154 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.scene.operation-unsupported"),
155 "Scene target does not support operation: " + operation.type);
156 }
157 bumpRevision();
158 widenDirty(0, 0);
159 return eve::editing::applied<void>();
160}
161
162std::unique_ptr<IDomainOperationTarget> SceneTargetBase::cloneDomainState() const {
163 return std::make_unique<SceneTargetBase>(*this);
164}
165
166Result<void> SceneTargetBase::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate) {
167 auto* staged = dynamic_cast<SceneTargetBase*>(candidate.get());
168 if (!staged || staged->id_ != id_ || staged->type_ != type_)
169 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.scene.candidate-mismatch"),
170 "Scene candidate does not belong to this target");
171 objects_.swap(staged->objects_);
172 setRevision(staged->revisionValue());
173 setDirtyRegion(staged->dirtyRegion());
174 return eve::editing::applied<void>();
175}
176
178 auto object = objects_.find(id);
179 if (object == objects_.end())
180 return eve::editing::failed<SceneObjectSnapshot>(EditorStatus::NotFound,
181 RuleId("editor.scene.object-not-found"),
182 "Scene object does not exist: " + id.value());
183 return eve::editing::applied<SceneObjectSnapshot>(object->second);
184}
185
186std::vector<ObjectId> SceneTargetBase::sceneChildren(const ObjectId& parent) const {
187 std::vector<ObjectId> children;
188 for (const auto& [id, object] : objects_)
189 if (object.parent == parent) children.push_back(id);
190 return children;
191}
192
194 auto validTransform = parseTransform(transformValue(request.transform));
195 if (!validTransform.ok()) return Result<DomainOperation>::failure(validTransform.status());
196 if (request.id.empty())
197 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.missing-object-id"),
198 "Scene object id is required");
199 if (objects_.contains(request.id))
200 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.scene.object-exists"),
201 "Scene object already exists: " + request.id.value());
202 if (!request.parent.empty() && !objects_.contains(request.parent))
203 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.parent-not-found"),
204 "Scene object parent does not exist");
205 if (request.name.empty())
206 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.empty-name"),
207 "Scene name must not be empty");
208 SceneObjectSnapshot object{request.id, request.parent, request.name, request.transform};
210 operation.type = "scene.object.create.v1";
211 operation.inverseType = "scene.object.delete.v1";
212 operation.target = TargetId(id_);
213 operation.payload = objectValue(object);
214 operation.inverse = objectValue(object);
215 operation.hasInverse = true;
216 operation.affectedObjects.push_back({TargetId(id_), request.id.value(), 0});
217 return eve::editing::applied<DomainOperation>(std::move(operation));
218}
219
221 auto object = objects_.find(id);
222 if (object == objects_.end())
223 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
224 "Scene object does not exist: " + id.value());
225 if (!sceneChildren(id).empty())
226 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.object-has-children"),
227 "Delete or reparent child objects before deleting their parent");
229 operation.type = "scene.object.delete.v1";
230 operation.inverseType = "scene.object.create.v1";
231 operation.target = TargetId(id_);
232 operation.payload = objectValue(object->second);
233 operation.inverse = operation.payload;
234 operation.hasInverse = true;
235 operation.affectedObjects.push_back({TargetId(id_), id.value(), 0});
236 return eve::editing::applied<DomainOperation>(std::move(operation));
237}
238
239Result<DomainOperation> SceneTargetBase::makeRename(const ObjectId& id, const std::string& name) const {
240 auto object = objects_.find(id);
241 if (object == objects_.end())
242 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
243 "Scene object does not exist: " + id.value());
244 if (name.empty())
245 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.empty-name"),
246 "Scene object name must not be empty");
247 auto value = [&](const std::string& valueName) {
249 payload["id"] = id.value();
250 payload["name"] = valueName;
251 return EditorValue(std::move(payload));
252 };
254 operation.type = "scene.object.rename.v1";
255 operation.target = TargetId(id_);
256 operation.payload = value(name);
257 operation.inverse = value(object->second.name);
258 operation.hasInverse = true;
259 operation.affectedObjects.push_back({TargetId(id_), id.value(), 0});
260 operation.affectedProperties.push_back("name");
261 operation.mergeKey = "scene.name:" + id.value();
262 return eve::editing::applied<DomainOperation>(std::move(operation));
263}
264
265Result<DomainOperation> SceneTargetBase::makeReparent(const ObjectId& id, const ObjectId& parent) const {
266 auto object = objects_.find(id);
267 if (object == objects_.end())
268 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
269 "Scene object does not exist: " + id.value());
270 if (!parent.empty() && !objects_.contains(parent))
271 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.parent-not-found"),
272 "Scene object parent does not exist: " + parent.value());
273 ObjectId ancestor = parent;
274 while (!ancestor.empty()) {
275 if (ancestor == id)
276 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.hierarchy-cycle"),
277 "Reparenting would create a scene hierarchy cycle");
278 auto node = objects_.find(ancestor);
279 if (node == objects_.end()) break;
280 ancestor = node->second.parent;
281 }
282 auto value = [&](const ObjectId& valueParent) {
284 payload["id"] = id.value();
285 payload["parent"] = valueParent.value();
286 return EditorValue(std::move(payload));
287 };
289 operation.type = "scene.object.reparent.v1";
290 operation.target = TargetId(id_);
291 operation.payload = value(parent);
292 operation.inverse = value(object->second.parent);
293 operation.hasInverse = true;
294 operation.affectedObjects.push_back({TargetId(id_), id.value(), 0});
295 operation.affectedProperties.push_back("parent");
296 return eve::editing::applied<DomainOperation>(std::move(operation));
297}
298
300 auto object = sceneObject(id);
301 if (!object.ok()) return Result<SceneTransformValue>::failure(object.status());
302 return eve::editing::applied<SceneTransformValue>(object.value().transform);
303}
304
306 const SceneTransformValue& transform) const {
307 auto validTransform = parseTransform(transformValue(transform));
308 if (!validTransform.ok()) return Result<DomainOperation>::failure(validTransform.status());
309 auto object = objects_.find(id);
310 if (object == objects_.end())
311 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.object-not-found"),
312 "Scene object does not exist: " + id.value());
314 payload["id"] = id.value();
315 payload["transform"] = transformValue(transform);
316 EditorValue::Object inverse;
317 inverse["id"] = id.value();
318 inverse["transform"] = transformValue(object->second.transform);
320 operation.type = "scene.transform.set.v1";
321 operation.target = TargetId(id_);
322 operation.payload = EditorValue(std::move(payload));
323 operation.inverse = EditorValue(std::move(inverse));
324 operation.hasInverse = true;
325 operation.affectedObjects.push_back({TargetId(id_), id.value(), 0});
326 operation.affectedProperties.push_back("transform");
327 operation.mergeKey = "scene.transform:" + id.value();
328 return eve::editing::applied<DomainOperation>(std::move(operation));
329}
330
332 EditorValue::Array objects;
333 objects.reserve(objects_.size());
334 for (const auto& [id, object] : objects_) {
335 (void)id;
336 objects.push_back(objectValue(object));
337 }
339 scene["schemaVersion"] = 1;
340 scene["schemaId"] = "eve.scene.hierarchy";
341 scene["objects"] = EditorValue(std::move(objects));
342 return EditorValue(std::move(scene));
343}
344
345EditorValue SceneTargetBase::transformValue(const SceneTransformValue& transform) {
347 value["x"] = transform.x;
348 value["y"] = transform.y;
349 value["z"] = transform.z;
350 value["rotationX"] = transform.rotationX;
351 value["rotationY"] = transform.rotationY;
352 value["rotationZ"] = transform.rotationZ;
353 value["scaleX"] = transform.scaleX;
354 value["scaleY"] = transform.scaleY;
355 value["scaleZ"] = transform.scaleZ;
356 return EditorValue(std::move(value));
357}
358
359Result<SceneTransformValue> SceneTargetBase::parseTransform(const EditorValue& value) {
360 const auto* fields = value.getIf<EditorValue::Object>();
361 if (!fields)
362 return eve::editing::failed<SceneTransformValue>(EditorStatus::Rejected, RuleId("editor.scene.transform-value"),
363 "Transform must be an object");
364 for (const auto& [key, entry] : *fields) {
365 const auto* number = entry.getIf<double>();
366 if (!number || !std::isfinite(*number) || std::abs(*number) > std::numeric_limits<float>::max() ||
367 (key.starts_with("scale") && static_cast<float>(*number) == 0.f))
368 return eve::editing::failed<SceneTransformValue>(
369 EditorStatus::Rejected, RuleId("editor.scene.transform-number"),
370 "TRS must be finite float-range numbers with nonzero scale");
371 }
372 const EditorValue* xValue = field(value, "x");
373 const EditorValue* yValue = field(value, "y");
374 const EditorValue* zValue = field(value, "z");
375 const auto* x = xValue ? xValue->getIf<double>() : nullptr;
376 const auto* y = yValue ? yValue->getIf<double>() : nullptr;
377 const auto* z = zValue ? zValue->getIf<double>() : nullptr;
378 if (!x || !y || !z)
379 return eve::editing::failed<SceneTransformValue>(EditorStatus::Rejected, RuleId("editor.scene.transform-value"),
380 "Transform requires numeric x, y and z fields");
381 SceneTransformValue result;
382 result.x = *x;
383 result.y = *y;
384 result.z = *z;
385 auto optionalNumber = [&](const char* name, double fallback) {
386 const EditorValue* entry = field(value, name);
387 if (!entry) return fallback;
388 const auto* number = entry->getIf<double>();
389 return number ? *number : fallback;
390 };
391 result.rotationX = optionalNumber("rotationX", 0.0);
392 result.rotationY = optionalNumber("rotationY", 0.0);
393 result.rotationZ = optionalNumber("rotationZ", 0.0);
394 result.scaleX = optionalNumber("scaleX", 1.0);
395 result.scaleY = optionalNumber("scaleY", 1.0);
396 result.scaleZ = optionalNumber("scaleZ", 1.0);
397 return eve::editing::applied<SceneTransformValue>(result);
398}
399
400Result<SceneObjectSnapshot> SceneTargetBase::parseObject(const EditorValue& value) {
401 const EditorValue* idValue = field(value, "id");
402 const EditorValue* parentValue = field(value, "parent");
403 const EditorValue* nameValue = field(value, "name");
404 const EditorValue* transform = field(value, "transform");
405 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
406 const auto* parent = parentValue ? parentValue->getIf<std::string>() : nullptr;
407 const auto* name = nameValue ? nameValue->getIf<std::string>() : nullptr;
408 if (!id || !parent || !name || !transform)
409 return eve::editing::failed<SceneObjectSnapshot>(EditorStatus::Rejected, RuleId("editor.scene.object-value"),
410 "Scene object payload is incomplete");
411 Result<SceneTransformValue> parsedTransform = parseTransform(*transform);
412 if (!parsedTransform.ok()) return Result<SceneObjectSnapshot>::failure(parsedTransform.status());
413 return eve::editing::applied<SceneObjectSnapshot>(
414 {ObjectId(*id), ObjectId(*parent), *name, parsedTransform.value()});
415}
416
417EditorValue SceneTargetBase::objectValue(const SceneObjectSnapshot& object) {
419 value["id"] = object.id.value();
420 value["parent"] = object.parent.value();
421 value["name"] = object.name;
422 value["transform"] = transformValue(object.transform);
423 return EditorValue(std::move(value));
424}
425
427 const CreateSceneObjectRequest& request) const {
428 auto* hierarchy = static_cast<ISceneHierarchyEditTarget*>(
430 if (!hierarchy)
431 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
432 RuleId("editor.scene.hierarchy-unavailable"),
433 "Target does not expose scene hierarchy editing");
434 return hierarchy->makeCreate(request);
435}
436
438 const SceneTransformValue& transform) const {
439 auto* transforms =
441 if (!transforms)
442 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
443 RuleId("editor.scene.transform-unavailable"),
444 "Target does not expose transform editing");
445 return transforms->makeSetTransform(object, transform);
446}
447
449 const ObjectId& object) const {
450 auto* hierarchy = static_cast<ISceneHierarchyEditTarget*>(
452 if (!hierarchy)
453 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
454 RuleId("editor.scene.hierarchy-unavailable"),
455 "Target does not expose scene hierarchy editing");
456 return hierarchy->makeDelete(object);
457}
458
460 const ObjectId& object,
461 const std::string& name) const {
462 auto* hierarchy = static_cast<ISceneHierarchyEditTarget*>(
464 if (!hierarchy)
465 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
466 RuleId("editor.scene.hierarchy-unavailable"),
467 "Target does not expose scene hierarchy editing");
468 return hierarchy->makeRename(object, name);
469}
470
472 const ObjectId& object,
473 const ObjectId& parent) const {
474 auto* hierarchy = static_cast<ISceneHierarchyEditTarget*>(
476 if (!hierarchy)
477 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
478 RuleId("editor.scene.hierarchy-unavailable"),
479 "Target does not expose scene hierarchy editing");
480 return hierarchy->makeReparent(object, parent);
481}
482
484 if (!target_)
486 eve::DiagnosticCode::PreconditionViolation, "Scene property target is unavailable",
487 "editor.scene.property.target", {}, "editor.ScenePropertyProvider"));
489}
490
492 PropertySchema result;
493 result.typeId = "scene.object";
494 auto addTrs = [&](const char* path, const char* name, const EditorValue& defaultValue) {
497 descriptor.displayNameKey = name;
498 descriptor.category = "transform";
499 descriptor.type = PropertyType::Vec3;
500 descriptor.flags = PropertyFlag::Runtime | PropertyFlag::MultiEdit;
501 descriptor.defaultValue = defaultValue;
502 descriptor.numeric.step = 0.01;
503 result.properties.push_back(std::move(descriptor));
504 };
505 addTrs("transform.position", "editor.transform.position", EditorValue::Array{0.0, 0.0, 0.0});
506 addTrs("transform.rotation", "editor.transform.rotation", EditorValue::Array{0.0, 0.0, 0.0});
507 addTrs("transform.scale", "editor.transform.scale", EditorValue::Array{1.0, 1.0, 1.0});
508 return result;
509}
510
512 const PropertyPath& path) const {
513 if (!target_ || selection.items.empty()) return {};
514 auto component = [&](const SceneTransformValue& value) -> EditorValue {
515 if (path == PropertyPath("transform.position")) return EditorValue::Array{value.x, value.y, value.z};
516 if (path == PropertyPath("transform.rotation"))
517 return EditorValue::Array{value.rotationX, value.rotationY, value.rotationZ};
518 if (path == PropertyPath("transform.scale"))
519 return EditorValue::Array{value.scaleX, value.scaleY, value.scaleZ};
520 return {};
521 };
522 std::optional<EditorValue> common;
523 for (const SelectionItem& item : selection.items) {
524 if (item.target != TargetId(target_->targetId())) return {};
525 auto transform = target_->readTransform(ObjectId(item.item.value()));
526 if (!transform.ok()) return {PropertyReadState::Error, {}, transform.diagnostics()};
527 EditorValue value = component(transform.value());
528 if (value.type() == EditorValue::Type::Null) return {};
529 if (!common) common = value;
530 else if (*common != value) return {PropertyReadState::Mixed, {}, {}};
531 }
532 return common ? PropertyReadResult{PropertyReadState::Value, *common, {}} : PropertyReadResult{};
533}
534
536 const PropertyPath& path,
537 const EditorValue& value,
538 PropertySetMode mode) const {
539 if (!target_ || mode != PropertySetMode::Absolute)
540 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.scene.property-mode"),
541 "Scene properties currently require absolute assignment");
542 const auto* tuple = value.getIf<EditorValue::Array>();
543 if (!tuple || tuple->size() != 3)
544 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.property-value"),
545 "Scene TRS property requires three numeric values");
546 const auto* a = (*tuple)[0].getIf<double>();
547 const auto* b = (*tuple)[1].getIf<double>();
548 const auto* c = (*tuple)[2].getIf<double>();
549 if (!a || !b || !c)
550 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.property-value"),
551 "Scene TRS property requires three numeric values");
553 EditorValue::Array inverse;
555 for (const SelectionItem& item : selection.items) {
556 if (item.target != TargetId(target_->targetId()))
557 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.scene.property-target"),
558 "Selection contains objects from another target");
559 auto current = target_->readTransform(ObjectId(item.item.value()));
560 if (!current.ok())
561 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.scene.property-object"),
562 "Selected scene object does not exist");
563 SceneTransformValue changed = current.value();
564 if (path == PropertyPath("transform.position")) {
565 changed.x = *a; changed.y = *b; changed.z = *c;
566 } else if (path == PropertyPath("transform.rotation")) {
567 changed.rotationX = *a; changed.rotationY = *b; changed.rotationZ = *c;
568 } else if (path == PropertyPath("transform.scale")) {
569 changed.scaleX = *a; changed.scaleY = *b; changed.scaleZ = *c;
570 } else {
571 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
572 RuleId("editor.scene.property-path"),
573 "Scene property path is unsupported: " + path.value());
574 }
575 EditorValue::Object nextEntry;
576 nextEntry["id"] = item.item.value();
577 nextEntry["transform"] = SceneTargetBase::transformValue(changed);
578 payload.emplace_back(std::move(nextEntry));
579 EditorValue::Object previousEntry;
580 previousEntry["id"] = item.item.value();
581 previousEntry["transform"] = SceneTargetBase::transformValue(current.value());
582 inverse.emplace_back(std::move(previousEntry));
583 operation.affectedObjects.push_back({TargetId(target_->targetId()), item.item.value(), 0});
584 }
585 operation.type = "scene.transform.multi.set.v1";
586 operation.target = TargetId(target_->targetId());
587 operation.payload = EditorValue(std::move(payload));
588 operation.inverse = EditorValue(std::move(inverse));
589 operation.hasInverse = true;
590 operation.affectedProperties.push_back(path.value());
591 operation.mergeKey = "scene.selection:" + path.value();
592 return eve::editing::applied<DomainOperation>(std::move(operation));
593}
594
596 const PropertyPath& path) const {
597 if (path == PropertyPath("transform.scale"))
598 return makeSet(selection, path, EditorValue::Array{1.0, 1.0, 1.0}, PropertySetMode::Absolute);
599 return makeSet(selection, path, EditorValue::Array{0.0, 0.0, 0.0}, PropertySetMode::Absolute);
600}
601
602} // namespace eve::scene_editing
LogicalId target
ActionParameterOperation operation
double value
Value::Object payload
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string descriptor
std::unordered_map< std::string, QuestRuntime > entries
const GltfImportRequest & request
std::uint32_t key
wgpu::PopErrorScopeStatus status
std::int32_t c
std::int32_t parent
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
std::weak_ptr< PrimitiveScene > scene
const RoadNode * node
double number
bool found
double current
Json object
int children
Definition TreeMesh.cpp:295
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
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:255
const Status & status() const noexcept
Inspect the structured operation status.
Definition Result.h:269
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 setRevision(Revision revision)
Adopt a revision observed elsewhere, without advancing it.
void bumpRevision()
Advance the revision without touching the dirty region.
void setDirtyRegion(const EditRegion &region)
Replace the dirty region wholesale, without advancing the revision.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
std::uint64_t revision() const override
Return the current revision.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
IEditableTarget public API.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
Optional capability exposing a deterministic, persistence-safe editing snapshot.
static CapabilityId editingCapabilityId()
Stable capability identity shared by every editable domain target.
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
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Scene capability that discovers and routes module-owned component payload editors.
static CapabilityId editorCapabilityId()
Stable capability id for editable scene component payloads.
Stable hierarchy capability implemented by document and runtime targets.
Definition SceneTarget.h:35
static CapabilityId editorCapabilityId()
Stable capability identity used instead of cross-module RTTI.
Definition SceneTarget.h:40
Capability implemented by document and live scene targets.
static editing::CapabilityId editingCapabilityId()
Editing capability id.
Result< DomainOperation > planRename(IEditableTarget &target, const ObjectId &object, const std::string &name) const
Query hierarchy capability and build a rename operation.
Result< DomainOperation > planDelete(IEditableTarget &target, const ObjectId &object) const
Query hierarchy capability and build a leaf deletion operation.
Result< DomainOperation > planReparent(IEditableTarget &target, const ObjectId &object, const ObjectId &parent) const
Query hierarchy capability and build a reparent operation.
Result< DomainOperation > plan(IEditableTarget &target, const CreateSceneObjectRequest &request) const
Query hierarchy capability and build a create operation.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
Shared in-memory scene target implementation for document/runtime adapters.
Definition SceneTarget.h:86
Result< SceneObjectSnapshot > sceneObject(const ObjectId &id) const override
Scene object.
TargetDescriptor describe() const override
Describe.
Result< DomainOperation > makeRename(const ObjectId &id, const std::string &name) const override
Make rename.
Result< DomainOperation > makeDelete(const ObjectId &id) const override
Make delete.
void * queryCapability(const CapabilityId &capability) override
Queries capability.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
SceneTargetBase(std::string id, std::string type)
Scene target base.
Result< SceneTransformValue > readTransform(const ObjectId &id) const override
Reads transform.
Result< DomainOperation > makeCreate(const CreateSceneObjectRequest &request) const override
Make create.
TargetId targetId() const override
Target id.
Definition SceneTarget.h:94
EditorValue snapshotValue() const override
Capture deterministic scene content for document persistence.
Result< DomainOperation > makeReparent(const ObjectId &id, const ObjectId &parent) const override
Make reparent.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
Result< DomainOperation > makeSetTransform(const ObjectId &id, const SceneTransformValue &transform) const override
Make set transform.
std::vector< ObjectId > sceneChildren(const ObjectId &parent) const override
Scene children.
Result< DomainOperation > plan(IEditableTarget &target, const ObjectId &object, const SceneTransformValue &transform) const
Query transform capability and build a transform operation.
const char * defaultValue
editing::ObjectId ObjectId
const EditorValue * field(const EditorValue &value, const char *name)
editing::Value EditorValue
DomainOperation public API.
PropertyDescriptor public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
std::vector< PropertyDescriptor > properties
SelectionItem public API.
SelectionSnapshot public API.
std::vector< SelectionItem > items
Stable discovery metadata exposed by every editable target.
Request used to create a scene object in either backend.
Definition SceneTarget.h:27
Immutable scene object snapshot returned across target boundaries.
Definition SceneTarget.h:19
Serializable transform exchanged across the scene editing boundary.