载入中...
搜索中...
未找到
VegetationFieldTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5
6namespace eve::graphics_editing {
7namespace {
8editing::Value vec3(glm::vec3 value) { return editing::Value::Array{value.x, value.y, value.z}; }
9
10bool readVec3(const editing::Value& value, glm::vec3& output) {
11 const auto* array = value.getIf<editing::Value::Array>();
12 if (!array || array->size() != 3) return false;
13 for (std::size_t index = 0; index < 3; ++index) {
14 const auto* number = (*array)[index].getIf<double>();
15 if (!number || !std::isfinite(*number)) return false;
16 output[static_cast<int>(index)] = static_cast<float>(*number);
17 }
18 return true;
19}
20
21const editing::Value* field(const editing::Value& value, const char* key) {
22 const auto* object = value.getIf<editing::Value::Object>();
23 if (!object) return nullptr;
24 const auto found = object->find(key);
25 return found == object->end() ? nullptr : &found->second;
26}
27
28editing::Value transformValue(const editing::StableId& id, const graphics::VegetationElement& element) {
30 {"id", id.value()}, {"center", vec3(element.center)}, {"extents", vec3(element.extents)}, {"yaw", element.yaw}};
31}
32
33editing::PropertyDescriptor property(std::string path, editing::PropertyType type, editing::Value defaultValue,
34 double minimum, double maximum, const char* units = "") {
35 editing::PropertyDescriptor result;
36 result.path = editing::PropertyPath(path);
37 result.displayNameKey = "editor.vegetation-field." + path;
38 result.descriptionKey = result.displayNameKey + ".description";
39 result.category = "vegetation-field-transform";
40 result.type = type;
41 result.flags = editing::PropertyFlag::Runtime;
42 result.defaultValue = std::move(defaultValue);
43 result.numeric.minimum = minimum;
44 result.numeric.maximum = maximum;
45 result.numeric.units = units;
46 return result;
47}
48} // namespace
49
50VegetationFieldTarget::VegetationFieldTarget(std::string id, graphics::VegetationField* field,
51 graphics::VegetationGlobals globals, std::vector<Entry> entries,
52 std::uint64_t fieldRevision)
53 : id_(std::move(id)),
54 field_(field),
55 globals_(globals),
56 entries_(std::move(entries)),
57 fieldRevision_(fieldRevision) {}
58
60 std::string id, graphics::VegetationField& fieldValue) {
61 if (id.empty())
62 return editing::failed<std::unique_ptr<VegetationFieldTarget>>(
63 editing::Status::Rejected, editing::RuleId("editor.vegetation-field.empty-id"),
64 "Vegetation field target ID is empty");
65 const auto revision = fieldValue.revision();
66 auto elements = fieldValue.snapshotElements();
67 if (!elements.ok()) return editing::Result<std::unique_ptr<VegetationFieldTarget>>::failure(elements.status());
68 std::vector<Entry> entries;
69 try {
70 entries.reserve(elements.value().size());
71 for (std::size_t index = 0; index < elements.value().size(); ++index)
72 entries.push_back(
73 {editing::StableId("element-" + std::to_string(index + 1)), std::move(elements.value()[index])});
74 return editing::applied<std::unique_ptr<VegetationFieldTarget>>(
75 std::unique_ptr<VegetationFieldTarget>(new VegetationFieldTarget(
76 std::move(id), &fieldValue, fieldValue.globalValues(), std::move(entries), revision)));
77 } catch (const std::bad_alloc&) {
78 return editing::failed<std::unique_ptr<VegetationFieldTarget>>(
79 editing::Status::Failed, editing::RuleId("editor.vegetation-field.allocation"),
80 "Vegetation field target allocation failed");
81 }
82}
83
84editing::TargetDescriptor VegetationFieldTarget::describe() const {
85 return {targetId(),
86 "vegetation-field",
87 revision_,
88 false,
89 {editing::CapabilityId("eve.editor.target.vegetation-field-properties")}};
90}
91
92void* VegetationFieldTarget::queryCapability(const editing::CapabilityId& capability) {
93 return capability == editing::CapabilityId("eve.editor.target.vegetation-field-properties")
94 ? static_cast<editing::IPropertyProvider*>(this)
95 : nullptr;
96}
97
98const VegetationFieldTarget::Entry* VegetationFieldTarget::selected(const editing::SelectionSnapshot& selection) const {
99 if (selection.items.size() != 1 || selection.items.front().target != targetId()) return nullptr;
100 const auto& item = selection.items.front().item;
101 const auto found =
102 std::find_if(entries_.begin(), entries_.end(), [&](const Entry& entry) { return entry.id == item; });
103 return found == entries_.end() ? nullptr : &*found;
104}
105
106VegetationFieldTarget::Entry* VegetationFieldTarget::selected(const std::string& id) {
107 const auto found =
108 std::find_if(entries_.begin(), entries_.end(), [&](const Entry& entry) { return entry.id.value() == id; });
109 return found == entries_.end() ? nullptr : &*found;
110}
111
112eve::Result<eve::Revision> VegetationFieldTarget::currentRevision(const editing::SelectionSnapshot& selection) const {
113 if (!selected(selection) || (field_ && field_->revision() != fieldRevision_))
115 eve::DiagnosticCode::Conflict, "Vegetation field selection or runtime revision is stale",
116 "editor.vegetation-field.selection"));
118}
119
120editing::PropertySchema VegetationFieldTarget::schema(const editing::SelectionSnapshot&) const {
122 result.typeId = "vegetation-field.element";
123 result.properties = {
124 property("element.center", editing::PropertyType::Vec3, vec3({0.f, 0.f, 0.f}), -1000000, 1000000, "m"),
125 property("element.extents", editing::PropertyType::Vec3, vec3({1.f, 1.f, 1.f}), 0.0001, 1000000, "m"),
126 property("element.yaw", editing::PropertyType::Float, 0.0, -1000000, 1000000, "rad")};
127 return result;
128}
129
130editing::PropertyReadResult VegetationFieldTarget::read(const editing::SelectionSnapshot& selection,
131 const editing::PropertyPath& path) const {
132 const Entry* entry = selected(selection);
133 if (!entry || !schema(selection).find(path)) return {};
134 if (path == editing::PropertyPath("element.center"))
135 return {editing::PropertyReadState::Value, vec3(entry->value.center), {}};
136 if (path == editing::PropertyPath("element.extents"))
137 return {editing::PropertyReadState::Value, vec3(entry->value.extents), {}};
138 return {editing::PropertyReadState::Value, entry->value.yaw, {}};
139}
140
141editing::Result<editing::DomainOperation> VegetationFieldTarget::replacement(const Entry& before, const Entry& after,
142 std::string propertyName) const {
144 operation.type = "vegetation-field.element.transform.v1";
145 operation.inverseType = operation.type;
146 operation.target = targetId();
147 operation.payload = transformValue(after.id, after.value);
148 operation.inverse = transformValue(before.id, before.value);
149 operation.hasInverse = true;
150 operation.affectedObjects.push_back({targetId(), after.id.value(), revision_});
151 if (!propertyName.empty()) operation.affectedProperties.push_back(propertyName);
152 operation.mergeKey = "vegetation-field:" + id_ + ":" + after.id.value() + ":transform";
153 return editing::applied<editing::DomainOperation>(std::move(operation));
154}
155
156editing::Result<editing::DomainOperation> VegetationFieldTarget::makeTransform(
157 const editing::SelectionSnapshot& selection, glm::vec3 center, glm::vec3 extents, float yaw) const {
158 const Entry* before = selected(selection);
159 if (!before || (field_ && field_->revision() != fieldRevision_))
160 return editing::failed<editing::DomainOperation>(editing::Status::Conflict,
161 editing::RuleId("editor.vegetation-field.stale"),
162 "Vegetation field transform targets stale state");
163 Entry after = *before;
164 after.value.center = center;
165 after.value.extents = extents;
166 after.value.yaw = yaw;
168 std::vector<graphics::VegetationElement> values;
169 values.reserve(entries_.size());
170 for (const Entry& entry : entries_) values.push_back(entry.id == after.id ? after.value : entry.value);
171 const auto valid = validator.replace(globals_, values);
173 return replacement(*before, after, {});
174}
175
176editing::Result<editing::DomainOperation> VegetationFieldTarget::makeSet(const editing::SelectionSnapshot& selection,
178 const editing::Value& value,
179 editing::PropertySetMode mode) const {
180 if (mode == editing::PropertySetMode::Reset) return makeReset(selection, path);
181 const Entry* before = selected(selection);
182 const auto descriptor = schema(selection).find(path);
183 if (!before || !descriptor || mode != editing::PropertySetMode::Absolute)
184 return editing::failed<editing::DomainOperation>(editing::Status::Rejected,
185 editing::RuleId("editor.vegetation-field.property"),
186 "Vegetation field property edit is invalid");
187 Entry after = *before;
188 if (path == editing::PropertyPath("element.center")) {
189 if (!readVec3(value, after.value.center))
190 return editing::failed<editing::DomainOperation>(editing::Status::Rejected,
191 editing::RuleId("editor.vegetation-field.center"),
192 "Vegetation field center must be a finite vec3");
193 } else if (path == editing::PropertyPath("element.extents")) {
194 if (!readVec3(value, after.value.extents))
195 return editing::failed<editing::DomainOperation>(editing::Status::Rejected,
196 editing::RuleId("editor.vegetation-field.extents"),
197 "Vegetation field extents must be a finite vec3");
198 } else {
199 const auto* number = value.getIf<double>();
200 if (!number || !std::isfinite(*number))
201 return editing::failed<editing::DomainOperation>(editing::Status::Rejected,
202 editing::RuleId("editor.vegetation-field.yaw"),
203 "Vegetation field yaw must be finite");
204 after.value.yaw = static_cast<float>(*number);
205 }
207 std::vector<graphics::VegetationElement> values;
208 values.reserve(entries_.size());
209 for (const Entry& entry : entries_) values.push_back(entry.id == after.id ? after.value : entry.value);
210 const auto valid = validator.replace(globals_, values);
212 return replacement(*before, after, path.value());
213}
214
215editing::Result<editing::DomainOperation> VegetationFieldTarget::makeReset(const editing::SelectionSnapshot& selection,
216 const editing::PropertyPath& path) const {
217 const auto descriptor = schema(selection).find(path);
218 if (!descriptor)
219 return editing::failed<editing::DomainOperation>(editing::Status::Unsupported,
220 editing::RuleId("editor.vegetation-field.property"),
221 "Unknown vegetation field property");
222 return makeSet(selection, path, descriptor->defaultValue, editing::PropertySetMode::Absolute);
223}
224
225editing::Result<void> VegetationFieldTarget::validateAndPublish(const std::vector<Entry>& entries) {
226 std::vector<graphics::VegetationElement> values;
227 try {
228 values.reserve(entries.size());
229 for (const Entry& entry : entries) values.push_back(entry.value);
230 } catch (const std::bad_alloc&) {
231 return editing::failed<void>(editing::Status::Failed, editing::RuleId("editor.vegetation-field.allocation"),
232 "Vegetation field publication allocation failed");
233 }
234 if (!field_) {
235 graphics::VegetationField validator;
236 const auto result = validator.replace(globals_, values);
237 return result.ok() ? editing::applied<void>() : editing::Result<void>::failure(result.status());
238 }
239 if (field_->revision() != fieldRevision_)
240 return editing::failed<void>(editing::Status::Conflict,
241 editing::RuleId("editor.vegetation-field.runtime-stale"),
242 "Vegetation field changed outside this editor target");
243 const auto result = field_->replace(globals_, values);
244 if (!result.ok()) return editing::Result<void>::failure(result.status());
245 fieldRevision_ = field_->revision();
246 return editing::applied<void>();
247}
248
249editing::Result<void> VegetationFieldTarget::applyDomainOperation(const editing::DomainOperation& operation) {
250 if (operation.target != targetId() || operation.type != "vegetation-field.element.transform.v1")
251 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("editor.vegetation-field.operation"),
252 "Vegetation field operation mismatch");
253 const auto* idValue = field(operation.payload, "id");
254 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
255 Entry* current = id ? selected(*id) : nullptr;
256 if (!current)
257 return editing::failed<void>(editing::Status::NotFound, editing::RuleId("editor.vegetation-field.element"),
258 "Vegetation field element was not found");
259 Entry replacementEntry = *current;
260 const auto* center = field(operation.payload, "center");
261 const auto* extents = field(operation.payload, "extents");
262 if (!center || !extents || !readVec3(*center, replacementEntry.value.center) ||
263 !readVec3(*extents, replacementEntry.value.extents))
264 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("editor.vegetation-field.transform"),
265 "Vegetation field transform payload is invalid");
266 const auto* yawValue = field(operation.payload, "yaw");
267 const auto* yaw = yawValue ? yawValue->getIf<double>() : nullptr;
268 if (!yaw || !std::isfinite(*yaw))
269 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("editor.vegetation-field.transform"),
270 "Vegetation field transform payload is invalid");
271 replacementEntry.value.yaw = static_cast<float>(*yaw);
272 auto candidate = entries_;
273 *std::find_if(candidate.begin(), candidate.end(), [&](const Entry& entry) { return entry.id == current->id; }) =
274 std::move(replacementEntry);
275 auto published = validateAndPublish(candidate);
276 if (!published.ok()) return published;
277 entries_ = std::move(candidate);
278 ++revision_;
279 dirty_.include(0, 0);
280 return editing::applied<void>();
281}
282
283std::unique_ptr<editing::IDomainOperationTarget> VegetationFieldTarget::cloneDomainState() const {
284 auto clone = std::unique_ptr<VegetationFieldTarget>(new VegetationFieldTarget(*this));
285 clone->field_ = nullptr;
286 return clone;
287}
288
289editing::Result<void> VegetationFieldTarget::commitDomainState(
290 std::unique_ptr<editing::IDomainOperationTarget> candidate) {
291 auto* typed = dynamic_cast<VegetationFieldTarget*>(candidate.get());
292 if (!typed || typed->id_ != id_ || !field_)
293 return editing::failed<void>(editing::Status::Conflict, editing::RuleId("editor.vegetation-field.candidate"),
294 "Vegetation field candidate does not match this target");
295 auto published = validateAndPublish(typed->entries_);
296 if (!published.ok()) return published;
297 globals_ = typed->globals_;
298 entries_ = std::move(typed->entries_);
299 revision_ = typed->revision_;
300 dirty_.include(typed->dirty_);
301 return editing::applied<void>();
302}
303
304std::vector<editing::StableId> VegetationFieldTarget::elementIds() const {
305 std::vector<editing::StableId> result;
306 result.reserve(entries_.size());
307 for (const Entry& entry : entries_) result.push_back(entry.id);
308 return result;
309}
310
311} // namespace eve::graphics_editing
ActionParameterOperation operation
double value
std::string output
std::string descriptor
std::unordered_map< std::string, QuestRuntime > entries
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
std::uint32_t key
wgpu::PopErrorScopeStatus status
bool valid
std::uint64_t revision
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::uint32_t targetId
double number
bool found
double current
std::string element
std::size_t elements
std::vector< UnitCandidate > units
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
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
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
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
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Backend-independent, owning vegetation influence field. Single writer; concurrent const evaluation is...
Result< std::vector< VegetationElement > > snapshotElements() const
Return an owning, priority-ordered element snapshot for revision-safe editor previews.
Result< void > replace(const VegetationGlobals &globals, std::span< const VegetationElement > elements)
Validate and atomically replace the entire field; failure preserves prior state.
VegetationGlobals globalValues() const
Return detached global defaults; concurrent reads require no writer. No callbacks.
uint64_t revision() const
Monotonic successful-publication revision for derived render snapshots.
Owning editor document whose runtime projection is one VegetationField. Element IDs remain stable whi...
const char * defaultValue
PropertyType
PropertyType public API.
StrongId< StableIdTag > StableId
Definition EditingIds.h:64
PropertySetMode
PropertySetMode public API.
const EditorValue * field(const EditorValue &value, const char *name)
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
std::vector< PropertyDescriptor > properties
SelectionSnapshot public API.
std::vector< SelectionItem > items
Stable discovery metadata exposed by every editable target.