载入中...
搜索中...
未找到
FluidTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <utility>
6
7namespace eve::fluids_editing {
8namespace {
9EditorDiagnostic fluidDiagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
11 std::move(message));
12}
13
14const EditorValue* field(const EditorValue& value, const char* key) {
15 const auto* object = value.getIf<EditorValue::Object>();
16 if (!object) return nullptr;
17 const auto found = object->find(key);
18 return found == object->end() ? nullptr : &found->second;
19}
20
21EditorValue settingsValue(const FluidSimulationSettings& s) {
22 return EditorValue::Object{{"maxParticles", int64_t{s.maxParticles}},
23 {"previewParticles", int64_t{s.previewParticles}},
24 {"particleRadius", s.particleRadius},
25 {"supportRadius", s.supportRadius},
26 {"restDensity", s.restDensity},
27 {"gravity", EditorValue::Array{s.gravityX, s.gravityY, s.gravityZ}},
28 {"viscosity", s.viscosity},
29 {"yieldStress", s.yieldStress},
30 {"cohesion", s.cohesion},
31 {"adhesion", s.adhesion},
32 {"damping", s.damping},
33 {"maximumVelocity", s.maximumVelocity},
34 {"iterations", int64_t{s.iterations}},
35 {"pbfIterations", int64_t{s.pbfIterations}}};
36}
37
38Result<FluidSimulationSettings> parseSettings(const EditorValue& value) {
39 const auto integer = [&](const char* key) {
40 const auto* entry = field(value, key);
41 return entry ? entry->getIf<int64_t>() : nullptr;
42 };
43 const auto number = [&](const char* key) {
44 const auto* entry = field(value, key);
45 return entry ? entry->getIf<double>() : nullptr;
46 };
47 const auto* max = integer("maxParticles");
48 const auto* preview = integer("previewParticles");
49 const auto* radius = number("particleRadius");
50 const auto* support = number("supportRadius");
51 const auto* density = number("restDensity");
52 const auto* gravityValue = field(value, "gravity");
53 const auto* gravity = gravityValue ? gravityValue->getIf<EditorValue::Array>() : nullptr;
54 const auto* viscosity = number("viscosity");
55 const auto* yield = number("yieldStress");
56 const auto* cohesion = number("cohesion");
57 const auto* adhesion = number("adhesion");
58 const auto* damping = number("damping");
59 const auto* velocity = number("maximumVelocity");
60 const auto* iterations = integer("iterations");
61 const auto* pbf = integer("pbfIterations");
62 if (!max || !preview || !radius || !support || !density || !gravity || gravity->size() != 3 || !viscosity ||
63 !yield || !cohesion || !adhesion || !damping || !velocity || !iterations || !pbf)
64 return eve::editing::failed<FluidSimulationSettings>(
65 EditorStatus::Rejected, RuleId("editor.fluid.invalid-settings"), "Fluid settings are incomplete");
66 double gravityComponents[3];
67 for (int i = 0; i < 3; ++i) {
68 const auto* component = (*gravity)[i].getIf<double>();
69 if (!component || !std::isfinite(*component))
70 return eve::editing::failed<FluidSimulationSettings>(
71 EditorStatus::Rejected, RuleId("editor.fluid.invalid-gravity"), "Fluid gravity must be finite");
72 gravityComponents[i] = *component;
73 }
74 const double values[]{*radius, *support, *density, *viscosity, *yield, *cohesion, *adhesion, *damping, *velocity};
75 if (std::any_of(std::begin(values), std::end(values), [](double component) { return !std::isfinite(component); }) ||
76 *max <= 0 || *max > 10000000 || *preview < 0 || *preview > *max || *radius <= 0.0 || *support <= 0.0 ||
77 *density <= 0.0 || *viscosity < 0.0 || *yield < 0.0 || *cohesion < 0.0 || *adhesion < 0.0 || *damping < 0.0 ||
78 *velocity <= 0.0 || *iterations <= 0 || *iterations > 1024 || *pbf <= 0 || *pbf > 1024)
79 return eve::editing::failed<FluidSimulationSettings>(EditorStatus::Rejected,
80 RuleId("editor.fluid.settings-range"),
81 "Fluid settings are outside safe solver ranges");
82 return eve::editing::applied<FluidSimulationSettings>(
83 {static_cast<int>(*max), static_cast<int>(*preview), *radius, *support, *density, gravityComponents[0],
84 gravityComponents[1], gravityComponents[2], *viscosity, *yield, *cohesion, *adhesion, *damping, *velocity,
85 static_cast<int>(*iterations), static_cast<int>(*pbf)});
86}
87
89 double maximum = 0.0, bool range = false) {
90 PropertyDescriptor result;
91 result.path = PropertyPath(path);
92 result.displayNameKey = std::string("editor.fluid.") + path;
93 result.category = "fluid";
94 result.type = type;
95 result.flags = PropertyFlag::Runtime;
96 result.defaultValue = std::move(value);
97 if (range) {
98 result.numeric.minimum = minimum;
99 result.numeric.maximum = maximum;
100 }
101 return result;
102}
103
104EditorValue setting(const FluidSimulationSettings& s, const std::string& path) {
105 const EditorValue values = settingsValue(s);
106 const auto* object = values.getIf<EditorValue::Object>();
107 return object->at(path);
108}
109
110} // namespace
111
114 return {TargetId(id_),
115 "fluid-simulation",
117 false,
118 {CapabilityId("eve.editor.target.fluid-properties")}};
119}
121 return capability == CapabilityId("eve.editor.target.fluid-properties") ? static_cast<IPropertyProvider*>(this)
122 : nullptr;
123}
124
126 if (operation.target != TargetId(id_) || operation.type != "fluid.settings.replace.v1")
127 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.fluid.operation-mismatch"),
128 "Fluid operation targets another document or type");
129 auto parsed = parseSettings(operation.payload);
130 if (!parsed.ok()) return Result<void>::failure(parsed.status());
131 settings_ = std::move(parsed.value());
132 bumpRevision();
133 widenDirty(0, 0);
134 return eve::editing::applied<void>();
135}
136
137bool FluidSimulationTarget::matches(const SelectionSnapshot& selection) const {
138 return selection.items.size() == 1 && selection.items.front().target == TargetId(id_);
139}
141 if (!matches(selection))
143 "Fluid selection does not match its target",
144 "editor.fluid.selection"));
146}
147
150 PropertySchema result;
151 result.typeId = "fluids.simulation";
152 result.properties = {
153 descriptor("maxParticles", PropertyType::Int, int64_t{d.maxParticles}, 1, 10000000, true),
154 descriptor("previewParticles", PropertyType::Int, int64_t{d.previewParticles}, 0, 10000000, true),
155 descriptor("particleRadius", PropertyType::Float, d.particleRadius, 0.000001, 1000, true),
156 descriptor("supportRadius", PropertyType::Float, d.supportRadius, 0.000001, 1000, true),
157 descriptor("restDensity", PropertyType::Float, d.restDensity, 0.000001, 1000000, true),
158 descriptor("gravity", PropertyType::Vec3, EditorValue::Array{d.gravityX, d.gravityY, d.gravityZ}),
159 descriptor("viscosity", PropertyType::Float, d.viscosity, 0, 1000000, true),
160 descriptor("yieldStress", PropertyType::Float, d.yieldStress, 0, 1000000, true),
161 descriptor("cohesion", PropertyType::Float, d.cohesion, 0, 1000000, true),
162 descriptor("adhesion", PropertyType::Float, d.adhesion, 0, 1000000, true),
163 descriptor("damping", PropertyType::Float, d.damping, 0, 1000000, true),
164 descriptor("maximumVelocity", PropertyType::Float, d.maximumVelocity, 0.000001, 1000000, true),
165 descriptor("iterations", PropertyType::Int, int64_t{d.iterations}, 1, 1024, true),
166 descriptor("pbfIterations", PropertyType::Int, int64_t{d.pbfIterations}, 1, 1024, true)};
167 return result;
168}
169
171 if (!matches(selection) || !schema(selection).find(path)) return {};
172 return {PropertyReadState::Value, setting(settings_, path.value()), {}};
173}
174
176 const PropertyPath& path, const EditorValue& value,
177 PropertySetMode mode) const {
178 if (mode == PropertySetMode::Reset) return makeReset(selection, path);
179 auto property = schema(selection).find(path);
180 if (!matches(selection) || !property || mode != PropertySetMode::Absolute)
181 return eve::editing::failed<DomainOperation>(
182 EditorStatus::Rejected, RuleId("editor.fluid.invalid-property-set"),
183 "Fluid property requires matching selection and absolute assignment");
184 auto valid = validatePropertyValue(*property, value);
185 if (!valid.ok()) return Result<DomainOperation>::failure(valid.status());
186 EditorValue candidateValue = settingsValue(settings_);
187 auto* object = candidateValue.getIf<EditorValue::Object>();
188 (*object)[path.value()] = value;
189 auto candidate = parseSettings(candidateValue);
190 if (!candidate.ok()) return Result<DomainOperation>::failure(candidate.status());
192 operation.type = "fluid.settings.replace.v1";
193 operation.inverseType = operation.type;
194 operation.target = TargetId(id_);
195 operation.payload = settingsValue(candidate.value());
196 operation.inverse = settingsValue(settings_);
197 operation.hasInverse = true;
198 operation.affectedProperties.push_back(path.value());
199 operation.mergeKey = "fluid:" + id_ + ":" + path.value();
200 return eve::editing::applied<DomainOperation>(std::move(operation));
201}
202
204 const PropertyPath& path) const {
205 auto property = schema(selection).find(path);
206 if (!property)
207 return eve::editing::failed<DomainOperation>(
208 EditorStatus::Unsupported, RuleId("editor.fluid.property-not-found"), "Fluid property is unknown");
209 return makeSet(selection, path, property->defaultValue, PropertySetMode::Absolute);
210}
211
212std::vector<EditorDiagnostic> FluidSimulationTarget::validate() const {
213 std::vector<EditorDiagnostic> result;
214 if (settings_.supportRadius < settings_.particleRadius * 2.0)
215 result.push_back(fluidDiagnostic("editor.fluid.support-radius-small", DiagnosticSeverity::Error,
216 "Fluid support radius should be at least twice particle radius"));
217 if (settings_.supportRadius > settings_.particleRadius * 8.0)
218 result.push_back(fluidDiagnostic("editor.fluid.support-radius-large", DiagnosticSeverity::Warning,
219 "Large support radius can make neighbor search prohibitively expensive"));
220 return result;
221}
222
224 std::uint64_t neighborBudget) const {
227 const std::uint64_t particles = static_cast<std::uint64_t>(settings_.previewParticles);
228 result.estimatedBytes = particles * 80ULL;
229 const double ratio = settings_.supportRadius / settings_.particleRadius;
230 result.estimatedNeighborChecks = static_cast<std::uint64_t>(
231 particles * std::min(512.0, ratio * ratio * ratio * 2.0) * settings_.iterations * settings_.pbfIterations);
232 result.diagnostics = validate();
233 if (result.estimatedBytes > byteBudget)
234 result.diagnostics.push_back(fluidDiagnostic("editor.fluid.memory-budget", DiagnosticSeverity::Error,
235 "Fluid preview exceeds its memory budget"));
236 if (result.estimatedNeighborChecks > neighborBudget)
237 result.diagnostics.push_back(fluidDiagnostic("editor.fluid.compute-budget", DiagnosticSeverity::Error,
238 "Fluid preview exceeds its estimated neighbor-work budget"));
239 const bool error = std::any_of(result.diagnostics.begin(), result.diagnostics.end(),
240 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; });
241 result.status = error ? EditorStatus::Rejected : EditorStatus::Applied;
242 return result;
243}
244
246 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"settings", settingsValue(settings_)}};
247}
249 const auto* versionValue = field(snapshot, "schemaVersion");
250 const auto* settings = field(snapshot, "settings");
251 const auto* version = versionValue ? versionValue->getIf<int64_t>() : nullptr;
252 if (!version || *version != 1 || !settings)
253 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.fluid.invalid-snapshot"),
254 "Fluid snapshot schema is unsupported");
255 auto parsed = parseSettings(*settings);
256 if (!parsed.ok()) return Result<void>::failure(parsed.status());
257 settings_ = std::move(parsed.value());
258 bumpRevision();
260 return eve::editing::applied<void>();
261}
262
263} // namespace eve::fluids_editing
ActionParameterOperation operation
double value
std::string descriptor
const std::string & s
std::map< std::string, Var > values
std::string message
float maximum[3]
float minimum[3]
std::uint32_t key
Range range
bool valid
std::string error
Definition Package.cpp:60
float radius
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
double number
bool found
double gravity
int iterations
Definition TreeMesh.cpp:311
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.
TargetDescriptor describe() const override
Describe.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load schema-version-one settings.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
FluidSimulationPreview previewBudget(std::uint64_t byteBudget=256ULL *1024ULL *1024ULL, std::uint64_t neighborBudget=100000000ULL) const
Estimate bounded memory and neighbor work for preview confirmation.
std::vector< EditorDiagnostic > validate() const
Validate solver ranges and cross-property kernel constraints.
FluidSimulationTarget(std::string id)
Fluid simulation target.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
FluidSimulationSettings settings() const
Return an immutable settings copy.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
EditorValue snapshotValue() const
Capture deterministic settings.
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
const EditorValue * field(const EditorValue &value, const char *name)
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
std::vector< PropertyDescriptor > properties
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
std::vector< SelectionItem > items
Stable discovery metadata exposed by every editable target.
Cost estimate used before allocating or running a fluid preview.
Definition FluidTarget.h:65
std::vector< EditorDiagnostic > diagnostics
Definition FluidTarget.h:70
Serializable parameters matching the CPU/GPU surface fluid solver.
Definition FluidTarget.h:47