载入中...
搜索中...
未找到
GpuAgentsDocument.cpp
浏览该文件的文档.
2
5
6#include <algorithm>
7#include <cmath>
8#include <utility>
9
11namespace {
12
13EditorDiagnostic diagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
15 std::move(message));
16}
17
18const EditorValue* field(const EditorValue& value, const std::string& key) {
19 const auto* object = value.getIf<EditorValue::Object>();
20 if (!object) return nullptr;
21 auto found = object->find(key);
22 return found == object->end() ? nullptr : &found->second;
23}
24
25EditorValue settingsValue(const GpuAgentsSettings& s) {
26 return EditorValue::Object{{"kind", s.kind},
27 {"maxAgents", s.maxAgents},
28 {"fixedDt", s.fixedDt},
29 {"agentRadius", s.agentRadius},
30 {"obstaclePredictTime", s.obstaclePredictTime},
31 {"seed", s.seed},
32 {"separationRadius", s.separationRadius},
33 {"cohesionRadius", s.cohesionRadius},
34 {"alignmentRadius", s.alignmentRadius},
35 {"separationWeight", s.separationWeight},
36 {"cohesionWeight", s.cohesionWeight},
37 {"alignmentWeight", s.alignmentWeight},
38 {"maxSpeed", s.maxSpeed},
39 {"moveSpeed", s.moveSpeed},
40 {"depositStrength", s.depositStrength},
41 {"trailDecayRate", s.trailDecayRate},
42 {"diffusionRate", s.diffusionRate},
43 {"trailFollow", s.trailFollow},
44 {"fieldResolution", s.fieldResolution},
45 {"worldSize", s.worldSize},
46 {"origin", EditorValue::Array{s.originX, s.originY, s.originZ}},
47 {"minSurfaceNormalZ", s.minSurfaceNormalZ},
48 {"lifeFieldSampler", s.lifeFieldSampler},
49 {"surfaceDataSampler", s.surfaceDataSampler}};
50}
51
52std::vector<EditorDiagnostic> validateSettings(const GpuAgentsSettings& s) {
53 std::vector<EditorDiagnostic> out;
54 if (s.kind != "Fish" && s.kind != "LifeNetwork" && s.kind != "Bird" && s.kind != "Petal") {
55 out.push_back(diagnostic("editor.gpuagents.kind", DiagnosticSeverity::Error,
56 "kind must be Fish, LifeNetwork, Bird, or Petal"));
57 }
58 if (s.maxAgents <= 0 || s.maxAgents > 100000) {
59 out.push_back(
60 diagnostic("editor.gpuagents.capacity", DiagnosticSeverity::Error, "maxAgents must be in (0, 100000]"));
61 }
62 if (!(s.fixedDt > 0.0) || s.fixedDt > 0.2) {
63 out.push_back(diagnostic("editor.gpuagents.fixedDt", DiagnosticSeverity::Error, "fixedDt must be in (0, 0.2]"));
64 }
65 if (!(s.agentRadius > 0.0) || !(s.worldSize > 0.0) || s.fieldResolution < 2) {
66 out.push_back(diagnostic("editor.gpuagents.range", DiagnosticSeverity::Error,
67 "agentRadius/worldSize/fieldResolution out of range"));
68 }
69 if (s.lifeFieldSampler.empty() || s.surfaceDataSampler.empty()) {
70 out.push_back(diagnostic("editor.gpuagents.samplers", DiagnosticSeverity::Error,
71 "material sampler names must be non-empty"));
72 }
73 return out;
74}
75
78 bool complete = true;
79 const auto number = [&](const char* key, double& destination) {
80 const auto* entry = field(value, key);
81 const auto* v = entry ? entry->getIf<double>() : nullptr;
82 if (v) destination = *v;
83 return v != nullptr;
84 };
85 const auto integer = [&](const char* key, std::int64_t& destination) {
86 const auto* entry = field(value, key);
87 const auto* v = entry ? entry->getIf<std::int64_t>() : nullptr;
88 if (v) destination = *v;
89 return v != nullptr;
90 };
91 const auto* kindValue = field(value, "kind");
92 const auto* kind = kindValue ? kindValue->getIf<std::string>() : nullptr;
93 if (kind)
94 s.kind = *kind;
95 else
96 complete = false;
97 complete &= integer("maxAgents", s.maxAgents);
98 complete &= number("fixedDt", s.fixedDt);
99 complete &= number("agentRadius", s.agentRadius);
100 complete &= number("obstaclePredictTime", s.obstaclePredictTime);
101 complete &= integer("seed", s.seed);
102 complete &= number("separationRadius", s.separationRadius);
103 complete &= number("cohesionRadius", s.cohesionRadius);
104 complete &= number("alignmentRadius", s.alignmentRadius);
105 complete &= number("separationWeight", s.separationWeight);
106 complete &= number("cohesionWeight", s.cohesionWeight);
107 complete &= number("alignmentWeight", s.alignmentWeight);
108 complete &= number("maxSpeed", s.maxSpeed);
109 complete &= number("moveSpeed", s.moveSpeed);
110 complete &= number("depositStrength", s.depositStrength);
111 complete &= number("trailDecayRate", s.trailDecayRate);
112 complete &= number("diffusionRate", s.diffusionRate);
113 complete &= number("trailFollow", s.trailFollow);
114 complete &= integer("fieldResolution", s.fieldResolution);
115 complete &= number("worldSize", s.worldSize);
116 complete &= number("minSurfaceNormalZ", s.minSurfaceNormalZ);
117 const auto* originValue = field(value, "origin");
118 const auto* origin = originValue ? originValue->getIf<EditorValue::Array>() : nullptr;
119 if (origin && origin->size() == 3) {
120 const auto* x = (*origin)[0].getIf<double>();
121 const auto* y = (*origin)[1].getIf<double>();
122 const auto* z = (*origin)[2].getIf<double>();
123 if (x && y && z) {
124 s.originX = *x;
125 s.originY = *y;
126 s.originZ = *z;
127 } else
128 complete = false;
129 } else
130 complete = false;
131 const auto* lifeSampler = field(value, "lifeFieldSampler");
132 const auto* lifeStr = lifeSampler ? lifeSampler->getIf<std::string>() : nullptr;
133 if (lifeStr)
134 s.lifeFieldSampler = *lifeStr;
135 else
136 complete = false;
137 const auto* surfSampler = field(value, "surfaceDataSampler");
138 const auto* surfStr = surfSampler ? surfSampler->getIf<std::string>() : nullptr;
139 if (surfStr)
140 s.surfaceDataSampler = *surfStr;
141 else
142 complete = false;
143
144 if (!complete) {
145 return eve::editing::failed<GpuAgentsSettings>(EditorStatus::Rejected, RuleId("editor.gpuagents.fields"),
146 "GPU Agents settings are incomplete");
147 }
148 const auto diagnostics = validateSettings(s);
149 if (std::any_of(diagnostics.begin(), diagnostics.end(),
150 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; })) {
151 return eve::editing::failed<GpuAgentsSettings>(EditorStatus::Rejected, RuleId("editor.gpuagents.invalid"),
152 "GPU Agents settings are invalid");
153 }
154 return eve::editing::applied<GpuAgentsSettings>(s);
155}
156
158 const char* category, double minimum = 0, double maximum = 0,
159 bool ranged = false) {
162 d.displayNameKey = std::string("editor.gpuagents.") + path;
163 d.category = category;
164 d.type = type;
166 d.defaultValue = std::move(defaultValue);
167 if (ranged) {
168 d.numeric.minimum = minimum;
169 d.numeric.maximum = maximum;
170 }
171 return d;
172}
173
174} // namespace
175
179 s.typeId = "gpuagents:effect-document";
180 s.version = 1;
181 auto kind = descriptor("kind", editing::PropertyType::String, d.kind, "effect");
182 kind.enumItems = {"Fish", "LifeNetwork", "Bird", "Petal"};
183 s.properties = {
184 std::move(kind),
185 descriptor("maxAgents", editing::PropertyType::Int, d.maxAgents, "effect", 1, 100000, true),
186 descriptor("fixedDt", editing::PropertyType::Float, d.fixedDt, "effect", 0.0001, 0.2, true),
187 descriptor("agentRadius", editing::PropertyType::Float, d.agentRadius, "effect", 0.001, 100, true),
188 descriptor("obstaclePredictTime", editing::PropertyType::Float, d.obstaclePredictTime, "effect", 0, 5, true),
189 descriptor("seed", editing::PropertyType::Int, d.seed, "effect", 0, 2147483647, true),
190 descriptor("separationRadius", editing::PropertyType::Float, d.separationRadius, "boids", 0.01, 100, true),
191 descriptor("cohesionRadius", editing::PropertyType::Float, d.cohesionRadius, "boids", 0.01, 100, true),
192 descriptor("alignmentRadius", editing::PropertyType::Float, d.alignmentRadius, "boids", 0.01, 100, true),
193 descriptor("separationWeight", editing::PropertyType::Float, d.separationWeight, "boids", 0, 20, true),
194 descriptor("cohesionWeight", editing::PropertyType::Float, d.cohesionWeight, "boids", 0, 20, true),
195 descriptor("alignmentWeight", editing::PropertyType::Float, d.alignmentWeight, "boids", 0, 20, true),
196 descriptor("maxSpeed", editing::PropertyType::Float, d.maxSpeed, "motion", 0.01, 100, true),
197 descriptor("moveSpeed", editing::PropertyType::Float, d.moveSpeed, "life", 0.01, 100, true),
198 descriptor("depositStrength", editing::PropertyType::Float, d.depositStrength, "life", 0, 10, true),
199 descriptor("trailDecayRate", editing::PropertyType::Float, d.trailDecayRate, "life", 0, 10, true),
200 descriptor("diffusionRate", editing::PropertyType::Float, d.diffusionRate, "life", 0, 1, true),
201 descriptor("trailFollow", editing::PropertyType::Float, d.trailFollow, "life", 0, 20, true),
202 descriptor("fieldResolution", editing::PropertyType::Int, d.fieldResolution, "life", 2, 1024, true),
203 descriptor("worldSize", editing::PropertyType::Float, d.worldSize, "life", 0.01, 10000, true),
204 descriptor("origin", editing::PropertyType::Vec3, EditorValue::Array{d.originX, d.originY, d.originZ}, "life"),
205 descriptor("minSurfaceNormalZ", editing::PropertyType::Float, d.minSurfaceNormalZ, "life", 0, 1, true),
206 descriptor("lifeFieldSampler", editing::PropertyType::String, d.lifeFieldSampler, "material"),
207 descriptor("surfaceDataSampler", editing::PropertyType::String, d.surfaceDataSampler, "material"),
208 };
209 return s;
210}
211
214 if (settings.kind == "LifeNetwork") {
215 profile = gpuagents::EffectProfile::makeLife(static_cast<int>(settings.maxAgents),
216 static_cast<int>(settings.fieldResolution));
217 profile.life.moveSpeed = static_cast<float>(settings.moveSpeed);
218 profile.life.depositStrength = static_cast<float>(settings.depositStrength);
219 profile.life.trailDecayRate = static_cast<float>(settings.trailDecayRate);
220 profile.life.diffusionRate = static_cast<float>(settings.diffusionRate);
221 profile.life.trailFollow = static_cast<float>(settings.trailFollow);
222 profile.life.worldSize = static_cast<float>(settings.worldSize);
223 profile.life.origin = {static_cast<float>(settings.originX), static_cast<float>(settings.originY),
224 static_cast<float>(settings.originZ)};
225 profile.life.minSurfaceNormalZ = static_cast<float>(settings.minSurfaceNormalZ);
226 } else if (settings.kind == "Bird") {
227 profile = gpuagents::EffectProfile::makeBird(static_cast<int>(settings.maxAgents));
228 profile.bird.separationRadius = static_cast<float>(settings.separationRadius);
229 profile.bird.cohesionRadius = static_cast<float>(settings.cohesionRadius);
230 profile.bird.alignmentRadius = static_cast<float>(settings.alignmentRadius);
231 profile.bird.separationWeight = static_cast<float>(settings.separationWeight);
232 profile.bird.cohesionWeight = static_cast<float>(settings.cohesionWeight);
233 profile.bird.alignmentWeight = static_cast<float>(settings.alignmentWeight);
234 profile.bird.maxSpeed = static_cast<float>(settings.maxSpeed);
235 } else if (settings.kind == "Petal") {
236 profile = gpuagents::EffectProfile::makePetal(static_cast<int>(settings.maxAgents));
237 } else {
238 profile = gpuagents::EffectProfile::makeFish(static_cast<int>(settings.maxAgents));
239 profile.fish.separationRadius = static_cast<float>(settings.separationRadius);
240 profile.fish.cohesionRadius = static_cast<float>(settings.cohesionRadius);
241 profile.fish.alignmentRadius = static_cast<float>(settings.alignmentRadius);
242 profile.fish.separationWeight = static_cast<float>(settings.separationWeight);
243 profile.fish.cohesionWeight = static_cast<float>(settings.cohesionWeight);
244 profile.fish.alignmentWeight = static_cast<float>(settings.alignmentWeight);
245 profile.fish.maxSpeed = static_cast<float>(settings.maxSpeed);
246 }
247 auto& base = profile.base();
248 base.fixedDt = static_cast<float>(settings.fixedDt);
249 base.agentRadius = static_cast<float>(settings.agentRadius);
250 base.obstaclePredictTime = static_cast<float>(settings.obstaclePredictTime);
251 base.seed = static_cast<std::uint32_t>(settings.seed);
252 return profile;
253}
254
256
258 return {TargetId(id_),
259 "gpuagents-effect",
261 false,
262 {CapabilityId("eve.editor.target.gpuagents-effect-properties")}};
263}
264
266 return c == CapabilityId("eve.editor.target.gpuagents-effect-properties") ? static_cast<IPropertyProvider*>(this)
267 : nullptr;
268}
269
270bool GpuAgentsDocumentTarget::matches(const SelectionSnapshot& s) const {
271 return s.items.size() == 1 && s.items.front().target == TargetId(id_);
272}
273
281
283
285 if (!matches(s) || !schema(s).find(p)) return {};
286 const auto* v = field(settingsValue(settings_), p.value());
288}
289
291 const EditorValue& v, PropertySetMode m) const {
292 if (m == PropertySetMode::Reset) return makeReset(s, p);
293 auto d = schema(s).find(p);
294 if (!matches(s) || !d || m != PropertySetMode::Absolute) {
295 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.gpuagents.set"),
296 "GPU Agents property requires a matching absolute edit");
297 }
298 auto checked = validatePropertyValue(*d, v);
299 if (!checked.ok()) return Result<DomainOperation>::failure(checked.status());
300 EditorValue candidate = settingsValue(settings_);
301 (*candidate.getIf<EditorValue::Object>())[p.value()] = v;
302 auto parsed = parse(candidate);
303 if (!parsed.ok()) return Result<DomainOperation>::failure(parsed.status());
305 op.type = "gpuagents.settings.replace.v1";
306 op.inverseType = op.type;
307 op.target = TargetId(id_);
308 op.payload = settingsValue(parsed.value());
309 op.inverse = settingsValue(settings_);
310 op.hasInverse = true;
311 return eve::editing::applied<DomainOperation>(std::move(op));
312}
313
315 const PropertyPath& p) const {
316 auto d = schema(s).find(p);
317 if (!matches(s) || !d) {
318 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.gpuagents.reset"),
319 "GPU Agents property reset requires a matching selection");
320 }
321 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
322}
323
325 if (operation.target != TargetId(id_) || operation.type != "gpuagents.settings.replace.v1") {
326 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.gpuagents.apply"),
327 "Unsupported GPU Agents domain operation");
328 }
329 auto parsed = parse(operation.payload);
330 if (!parsed.ok()) return Result<void>::failure(parsed.status());
331 settings_ = parsed.value();
332 bumpRevision();
333 return eve::editing::applied();
334}
335
336std::vector<EditorDiagnostic> GpuAgentsDocumentTarget::validate() const { return validateSettings(settings_); }
337
338EditorValue GpuAgentsDocumentTarget::snapshotValue() const { return settingsValue(settings_); }
339
341 auto parsed = parse(snapshot);
342 if (!parsed.ok()) return Result<void>::failure(parsed.status());
343 settings_ = parsed.value();
344 bumpRevision();
345 return eve::editing::applied();
346}
347
351 if (!backend) {
352 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.gpuagents.runtime"),
353 "EffectBackend is required");
354 }
355 const auto diagnostics = target.validate();
356 if (std::any_of(diagnostics.begin(), diagnostics.end(),
357 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; })) {
358 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.gpuagents.runtime-invalid"),
359 "Cannot apply invalid GPU Agents settings");
360 }
361 const auto profile = toEffectProfile(target.settings());
362 auto cfg = backend->configure(profile, target.targetId().value());
363 if (!cfg.ok()) return Result<void>::failure(cfg.status());
364 if (world && target.settings().kind == "LifeNetwork") {
365 world->surface().initFlat(
366 {static_cast<float>(target.settings().originX), static_cast<float>(target.settings().originY),
367 static_cast<float>(target.settings().originZ)},
368 static_cast<float>(target.settings().worldSize), static_cast<int>(target.settings().fieldResolution),
369 static_cast<float>(target.settings().originY));
370 }
371 return eve::editing::applied();
372}
373
374} // namespace eve::gpuagents_editing
LogicalId target
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string descriptor
const std::string & s
glm::vec4 p[6]
std::string message
float maximum[3]
float minimum[3]
std::uint32_t key
float v
std::int32_t c
TokenKind kind
World3D * world
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
V3 origin
Definition RoadBake.cpp:138
double number
bool found
TerrainThermalSettings settings
const char * category
float m[16]
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
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.
Binds an EffectProfile to a GpuAgentSimulation and optional renderer.
Result< void > configure(const EffectProfile &profile, std::string displayName={})
Configure from a profile and initialize empty capacity.
World-level registry for GPU agent simulations and environment semantics.
Reversible document for GPU Agents effect authoring.
std::vector< EditorDiagnostic > validate() const
Structured validation diagnostics.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
GpuAgentsDocumentTarget(std::string id)
Gpu agents document target.
TargetDescriptor describe() const override
Describe.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load and validate a persisted snapshot.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability. @ownership Borrowed; owned by this target. @lifetime Valid until...
EditorValue snapshotValue() const
Capture schema-version-one settings.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< void > apply(const GpuAgentsDocumentTarget &target, gpuagents::EffectBackend *backend, gpuagents::GpuAgentWorld *world) const
Configure backend from document and optionally re-init the world surface domain.
const char * defaultValue
PropertyType
PropertyType public API.
PropertySetMode
PropertySetMode public API.
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
PropertySchema gpuAgentsEffectSchema()
Build the UI-independent PropertySchema for gpuagents:effect-document v1.
editing::CapabilityId CapabilityId
editing::TargetId TargetId
gpuagents::EffectProfile toEffectProfile(const GpuAgentsSettings &settings)
Convert authoring settings into a runtime EffectProfile.
editing::PropertyPath PropertyPath
Severity
Stable severity of a diagnostic, independent of its display text.
Definition Diagnostic.h:20
DomainOperation public API.
PropertyDescriptor public API.
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.
Type-erased profile bundle owned by an EffectBackend.
static EffectProfile makePetal(int maxAgents=512)
Factory for a default petal profile.
const EffectProfileBase & base() const
Returns the shared base slice for the active kind.
static EffectProfile makeBird(int maxAgents=256)
Factory for a default bird profile.
static EffectProfile makeFish(int maxAgents=256)
Factory for a default fish profile.
static EffectProfile makeLife(int maxAgents=128, int resolution=64)
Factory for a default life-network profile.
Authoring values for one GPU Agents effect document.