载入中...
搜索中...
未找到
VolumeFluidTarget.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <cmath>
6#include <utility>
7
8namespace eve::fluids_editing {
9namespace {
10constexpr const char* kSchemaId = "eve.volume-fluid-authoring";
11constexpr std::int64_t kSchemaVersion = 1;
12constexpr std::uint64_t kEstimatedScratchBytesPerParticle = 2ULL * sizeof(fluids::VolumeFluidParticle) +
13 12ULL * sizeof(glm::vec3) + 2ULL * sizeof(glm::vec4) +
14 5ULL * sizeof(float) + 4ULL * sizeof(std::uint32_t);
15EditorDiagnostic targetDiagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
17 std::move(message));
18}
19const EditorValue* field(const EditorValue& value, const char* key) {
20 const auto* object = value.getIf<EditorValue::Object>();
21 if (!object) {
22 return nullptr;
23 }
24 const auto found = object->find(key);
25 return found == object->end() ? nullptr : &found->second;
26}
27EditorValue vec3(double x, double y, double z) { return EditorValue::Array{x, y, z}; }
28EditorValue settingsValue(const VolumeFluidAuthoringSettings& s) {
29 return EditorValue::Object{{"capacity", static_cast<std::int64_t>(s.capacity)},
30 {"previewParticles", static_cast<std::int64_t>(s.previewParticles)},
31 {"spacing", s.spacing},
32 {"gravity", vec3(s.gravityX, s.gravityY, s.gravityZ)},
33 {"minimum", vec3(s.minimumX, s.minimumY, s.minimumZ)},
34 {"maximum", vec3(s.maximumX, s.maximumY, s.maximumZ)},
35 {"iterations", static_cast<std::int64_t>(s.iterations)}};
36}
37bool readVec3(const EditorValue& value, const char* key, double& x, double& y, double& z) {
38 const auto* item = field(value, key);
39 const auto* values = item ? item->getIf<EditorValue::Array>() : nullptr;
40 if (!values || values->size() != 3) {
41 return false;
42 }
43 const auto* vx = (*values)[0].getIf<double>();
44 const auto* vy = (*values)[1].getIf<double>();
45 const auto* vz = (*values)[2].getIf<double>();
46 if (!vx || !vy || !vz || !std::isfinite(*vx) || !std::isfinite(*vy) || !std::isfinite(*vz)) {
47 return false;
48 }
49 x = *vx;
50 y = *vy;
51 z = *vz;
52 return true;
53}
54std::vector<EditorDiagnostic> validateSettings(const VolumeFluidAuthoringSettings& s) {
55 std::vector<EditorDiagnostic> out;
56 const double finite[]{s.spacing, s.gravityX, s.gravityY, s.gravityZ, s.minimumX,
57 s.minimumY, s.minimumZ, s.maximumX, s.maximumY, s.maximumZ};
58 if (std::any_of(std::begin(finite), std::end(finite), [](double v) { return !std::isfinite(v); })) {
59 out.push_back(targetDiagnostic("editor.volume-fluid.nonfinite", DiagnosticSeverity::Error,
60 "Volume fluid settings must be finite"));
61 }
62 if (s.capacity == 0 || s.capacity > 1000000 || s.previewParticles > s.capacity || s.spacing < 0.0001 ||
63 s.spacing > 100.0 || s.iterations == 0 || s.iterations > 20) {
64 out.push_back(targetDiagnostic("editor.volume-fluid.range", DiagnosticSeverity::Error,
65 "Volume fluid settings are outside runtime ranges"));
66 }
67 if (s.maximumX - s.minimumX < s.spacing || s.maximumY - s.minimumY < s.spacing ||
68 s.maximumZ - s.minimumZ < s.spacing) {
69 out.push_back(targetDiagnostic("editor.volume-fluid.bounds", DiagnosticSeverity::Error,
70 "Every container axis must fit at least one particle spacing"));
71 }
72 if (s.previewParticles > 4096) {
73 out.push_back(targetDiagnostic("editor.volume-fluid.preview-size", DiagnosticSeverity::Warning,
74 "Large CPU previews can exceed the interactive frame budget"));
75 }
76 return out;
77}
78Result<VolumeFluidAuthoringSettings> parseSettings(const EditorValue& value) {
79 const auto* object = value.getIf<EditorValue::Object>();
80 if (!object || object->size() != 7)
81 return eve::editing::failed<VolumeFluidAuthoringSettings>(EditorStatus::Rejected,
82 RuleId("editor.volume-fluid.fields"),
83 "Volume fluid settings require exactly seven fields");
84 const auto* cv = field(value, "capacity");
85 const auto* pv = field(value, "previewParticles");
86 const auto* sv = field(value, "spacing");
87 const auto* iv = field(value, "iterations");
88 const auto* capacity = cv ? cv->getIf<std::int64_t>() : nullptr;
89 const auto* preview = pv ? pv->getIf<std::int64_t>() : nullptr;
90 const auto* spacing = sv ? sv->getIf<double>() : nullptr;
91 const auto* iterations = iv ? iv->getIf<std::int64_t>() : nullptr;
92 if (!capacity || !preview || !spacing || !iterations || *capacity < 0 || *preview < 0 || *iterations < 0 ||
93 *capacity > 1000000 || *preview > 1000000 || *iterations > 20)
94 return eve::editing::failed<VolumeFluidAuthoringSettings>(
95 EditorStatus::Rejected, RuleId("editor.volume-fluid.types"), "Volume fluid scalar fields are invalid");
96 VolumeFluidAuthoringSettings s;
97 s.capacity = static_cast<std::uint32_t>(*capacity);
98 s.previewParticles = static_cast<std::uint32_t>(*preview);
99 s.spacing = *spacing;
100 s.iterations = static_cast<std::uint32_t>(*iterations);
101 if (!readVec3(value, "gravity", s.gravityX, s.gravityY, s.gravityZ) ||
102 !readVec3(value, "minimum", s.minimumX, s.minimumY, s.minimumZ) ||
103 !readVec3(value, "maximum", s.maximumX, s.maximumY, s.maximumZ))
104 return eve::editing::failed<VolumeFluidAuthoringSettings>(EditorStatus::Rejected,
105 RuleId("editor.volume-fluid.vectors"),
106 "Gravity and bounds must be finite Vec3 values");
107 const auto diagnostics = validateSettings(s);
108 if (std::any_of(diagnostics.begin(), diagnostics.end(),
109 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; })) {
110 return eve::editing::failed<VolumeFluidAuthoringSettings>(
111 EditorStatus::Rejected, RuleId("editor.volume-fluid.invalid"), "Volume fluid settings are invalid");
112 }
113 return eve::editing::applied<VolumeFluidAuthoringSettings>(s);
114}
116 double minimum = 0, double maximum = 0, bool ranged = false) {
119 d.displayNameKey = std::string("editor.volume-fluid.") + path;
120 d.category = category;
121 d.type = type;
122 d.flags = PropertyFlag::Runtime;
123 d.defaultValue = std::move(value);
124 if (ranged) {
125 d.numeric.minimum = minimum;
126 d.numeric.maximum = maximum;
127 }
128 return d;
129}
130EditorValue setting(const VolumeFluidAuthoringSettings& s, const std::string& path) {
131 return settingsValue(s).getIf<EditorValue::Object>()->at(path);
132}
133} // namespace
134
135VolumeFluidTarget::VolumeFluidTarget(std::string id) : id_(std::move(id)) {}
137 return {TargetId(id_),
138 "volume-fluid",
140 false,
141 {CapabilityId("eve.editor.target.volume-fluid-properties")}};
142}
144 return capability == CapabilityId("eve.editor.target.volume-fluid-properties")
145 ? static_cast<IPropertyProvider*>(this)
146 : nullptr;
147}
148bool VolumeFluidTarget::matches(const SelectionSnapshot& s) const {
149 return s.items.size() == 1 && s.items.front().target == TargetId(id_);
150}
152 if (!matches(s)) {
154 eve::DiagnosticCode::InvalidArgument, "Volume fluid selection mismatch", "editor.volume-fluid.selection"));
155 }
157}
160 PropertySchema result;
161 result.typeId = "fluids.volume";
162 result.properties = {
163 descriptor("capacity", PropertyType::Int, std::int64_t{d.capacity}, "solver", 1, 1000000, true),
164 descriptor("previewParticles", PropertyType::Int, std::int64_t{d.previewParticles}, "preview", 0, 1000000,
165 true),
166 descriptor("spacing", PropertyType::Float, d.spacing, "solver", 0.0001, 100.0, true),
167 descriptor("gravity", PropertyType::Vec3, vec3(d.gravityX, d.gravityY, d.gravityZ), "solver"),
168 descriptor("minimum", PropertyType::Vec3, vec3(d.minimumX, d.minimumY, d.minimumZ), "container"),
169 descriptor("maximum", PropertyType::Vec3, vec3(d.maximumX, d.maximumY, d.maximumZ), "container"),
170 descriptor("iterations", PropertyType::Int, std::int64_t{d.iterations}, "solver", 1, 20, true)};
171 return result;
172}
174 if (!matches(s) || !schema(s).find(p)) {
175 return {};
176 }
177 return {PropertyReadState::Value, setting(settings_, p.value()), {}};
178}
180 const EditorValue& value, PropertySetMode mode) const {
181 if (mode == PropertySetMode::Reset) {
182 return makeReset(s, p);
183 }
184 auto property = schema(s).find(p);
185 if (!matches(s) || !property || mode != PropertySetMode::Absolute) {
186 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.volume-fluid.set"),
187 "Volume fluid property requires a matching absolute edit");
188 }
189 auto valid = validatePropertyValue(*property, value);
190 if (!valid.ok()) {
192 }
193 EditorValue candidate = settingsValue(settings_);
194 (*candidate.getIf<EditorValue::Object>())[p.value()] = value;
195 auto parsed = parseSettings(candidate);
196 if (!parsed.ok()) {
197 return Result<DomainOperation>::failure(parsed.status());
198 }
200 op.type = "volume-fluid.settings.replace.v1";
201 op.inverseType = op.type;
202 op.target = TargetId(id_);
203 op.payload = settingsValue(parsed.value());
204 op.inverse = settingsValue(settings_);
205 op.hasInverse = true;
206 op.affectedProperties.push_back(p.value());
207 op.mergeKey = "volume-fluid:" + id_ + ":" + p.value();
208 return eve::editing::applied<DomainOperation>(std::move(op));
209}
211 auto property = schema(s).find(p);
212 if (!property) {
213 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.volume-fluid.property"),
214 "Unknown volume fluid property");
215 }
216 return makeSet(s, p, property->defaultValue, PropertySetMode::Absolute);
217}
219 if (op.target != TargetId(id_) || op.type != "volume-fluid.settings.replace.v1") {
220 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.volume-fluid.operation"),
221 "Volume fluid operation mismatch");
222 }
223 auto parsed = parseSettings(op.payload);
224 if (!parsed.ok()) {
225 return Result<void>::failure(parsed.status());
226 }
227 settings_ = parsed.value();
228 bumpRevision();
229 widenDirty(0, 0);
230 return eve::editing::applied<void>();
231}
232std::vector<EditorDiagnostic> VolumeFluidTarget::validate() const { return validateSettings(settings_); }
234 std::uint64_t visitBudget) const {
237 result.estimatedBytes = static_cast<std::uint64_t>(settings_.previewParticles) * kEstimatedScratchBytesPerParticle;
239 static_cast<std::uint64_t>(settings_.previewParticles) * 96ULL * settings_.iterations * 2ULL;
240 result.diagnostics = validate();
241 if (result.estimatedBytes > byteBudget) {
242 result.diagnostics.push_back(targetDiagnostic("editor.volume-fluid.memory-budget", DiagnosticSeverity::Error,
243 "Volume fluid preview exceeds its memory budget"));
244 }
245 if (result.estimatedConstraintVisits > visitBudget) {
246 result.diagnostics.push_back(targetDiagnostic("editor.volume-fluid.work-budget", DiagnosticSeverity::Error,
247 "Volume fluid preview exceeds its work budget"));
248 }
249 const bool error = std::any_of(result.diagnostics.begin(), result.diagnostics.end(),
250 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; });
251 result.status = error ? EditorStatus::Rejected : EditorStatus::Applied;
252 return result;
253}
255 return EditorValue::Object{
256 {"schemaId", kSchemaId}, {"schemaVersion", kSchemaVersion}, {"settings", settingsValue(settings_)}};
257}
259 const auto* object = snapshot.getIf<EditorValue::Object>();
260 const auto* sid = field(snapshot, "schemaId");
261 const auto* versionValue = field(snapshot, "schemaVersion");
262 const auto* settings = field(snapshot, "settings");
263 const auto* schemaId = sid ? sid->getIf<std::string>() : nullptr;
264 const auto* version = versionValue ? versionValue->getIf<std::int64_t>() : nullptr;
265 if (!object || object->size() != 3 || !schemaId || *schemaId != kSchemaId || !version ||
266 *version != kSchemaVersion || !settings)
267 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.volume-fluid.snapshot"),
268 "Unsupported or non-canonical volume fluid authoring snapshot");
269 auto parsed = parseSettings(*settings);
270 if (!parsed.ok()) {
271 return Result<void>::failure(parsed.status());
272 }
273 settings_ = parsed.value();
274 bumpRevision();
276 return eve::editing::applied<void>();
277}
279 fluids::VolumeFluid* simulation) const {
280 if (!simulation) {
281 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.volume-fluid.runtime-required"),
282 "Volume fluid publication requires a live simulation");
283 }
284 for (const auto& d : target.validate()) {
285 if (d.severity() == DiagnosticSeverity::Error) {
286 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.volume-fluid.runtime-invalid"),
287 d.message());
288 }
289 }
290 auto snapshot = simulation->snapshot();
291 const auto s = target.settings();
292 snapshot.settings.capacity = s.capacity;
293 snapshot.settings.spacing = static_cast<float>(s.spacing);
294 snapshot.settings.gravity = {static_cast<float>(s.gravityX), static_cast<float>(s.gravityY),
295 static_cast<float>(s.gravityZ)};
296 snapshot.settings.minimum = {static_cast<float>(s.minimumX), static_cast<float>(s.minimumY),
297 static_cast<float>(s.minimumZ)};
298 snapshot.settings.maximum = {static_cast<float>(s.maximumX), static_cast<float>(s.maximumY),
299 static_cast<float>(s.maximumZ)};
300 snapshot.settings.iterations = s.iterations;
301 auto restored = simulation->restore(snapshot);
302 if (!restored.ok()) {
303 return eve::editing::failed<void>(
304 EditorStatus::Conflict, RuleId("editor.volume-fluid.runtime-rejected"),
305 restored.error() ? restored.error()->message() : "Volume fluid runtime rejected authored settings");
306 }
307 return eve::editing::applied<void>();
308}
309} // namespace eve::fluids_editing
LogicalId target
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::map< std::string, Var > values
std::string message
float maximum[3]
float minimum[3]
std::uint32_t capacity
std::uint32_t key
float v
bool valid
bool finite
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
bool found
int spacing
Json object
int iterations
Definition TreeMesh.cpp:311
const char * category
std::size_t at
float vz
float vy
float vx
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.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
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
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
CPU position-based free-volume fluid with a bounded spatial grid. @ownership Owns all particle state;...
Result< void > restore(const VolumeFluidSnapshot &snapshot)
Validates a complete candidate before replacing state; failures preserve the current solver.
VolumeFluidSnapshot snapshot() const
Returns complete owning state; scratch grids and last-step contacts are derived and omitted.
Result< void > apply(const VolumeFluidTarget &target, fluids::VolumeFluid *simulation) const
Apply solver settings through the canonical snapshot restore transaction.
Reversible schema-driven document for native volume-fluid solver settings. @ownership Owns only autho...
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
EditorValue snapshotValue() const
Capture schema eve.volume-fluid-authoring version one.
VolumeFluidAuthoringPreview previewBudget(std::uint64_t byteBudget=256ULL *1024ULL *1024ULL, std::uint64_t visitBudget=100000000ULL) const
Estimate preview memory and constraint visits without allocation.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load a strict version-one authoring document.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
VolumeFluidTarget(std::string id)
Volume fluid target.
TargetDescriptor describe() const override
Describe.
VolumeFluidAuthoringSettings settings() const
Return an immutable copy of current authoring settings.
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.
std::vector< EditorDiagnostic > validate() const
Validate numeric ranges and container clearance.
void * queryCapability(const CapabilityId &capability) override
Query the property capability.
PropertySetMode
PropertySetMode public API.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::TargetId TargetId
Definition FluidTarget.h:42
editing::PropertyDescriptor PropertyDescriptor
Definition FluidTarget.h:30
editing::PropertyPath PropertyPath
Definition FluidTarget.h:32
editing::DiagnosticSeverity DiagnosticSeverity
Definition FluidTarget.h:19
editing::PropertyType PropertyType
Definition FluidTarget.h:37
editing::Value EditorValue
Definition FluidTarget.h:26
editing::RuleId RuleId
Definition FluidTarget.h:39
editing::CapabilityId CapabilityId
Definition FluidTarget.h:18
editing::Diagnostic EditorDiagnostic
Definition FluidTarget.h:22
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
Definition wgpu_types.h:20
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.
std::vector< PropertyDescriptor > properties
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.
unsigned capacity
Maximum live particles; admission fails atomically at capacity.
Definition VolumeFluid.h:84
Bounded preview allocation/work estimate before constructing a solver.
Serializable authoring parameters for the bounded volume-fluid solver.