载入中...
搜索中...
未找到
SurfaceFluidTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <utility>
6
7namespace eve::fluids_editing {
8namespace {
9EditorDiagnostic diagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
11 std::move(message));
12}
13const EditorValue* field(const EditorValue& value, const std::string& key) {
14 const auto* object = value.getIf<EditorValue::Object>();
15 if (!object) return nullptr;
16 auto found = object->find(key);
17 return found == object->end() ? nullptr : &found->second;
18}
19EditorValue settingsValue(const SurfaceFluidSettings& s) {
20 return EditorValue::Object{{"gravity", EditorValue::Array{s.gravityX, s.gravityY, s.gravityZ}},
21 {"friction", s.friction},
22 {"maxSpeed", s.maxSpeed},
23 {"adhesionAcceleration", s.adhesionAcceleration},
24 {"maxCrossings", int64_t{s.maxCrossings}},
25 {"contactAngleDegrees", s.contactAngleDegrees},
26 {"mergeRadiusScale", s.mergeRadiusScale},
27 {"trailDeposition", s.trailDeposition},
28 {"airDrag", s.airDrag},
29 {"reattachDistance", s.reattachDistance},
30 {"diffusion", s.diffusion},
31 {"evaporation", s.evaporation},
32 {"maxWetness", s.maxWetness},
33 {"velocityStretch", s.velocityStretch},
34 {"maxAspectRatio", s.maxAspectRatio},
35 {"surfaceOffset", s.surfaceOffset},
36 {"dryRoughness", s.dryRoughness},
37 {"wetRoughness", s.wetRoughness},
38 {"drySpecular", s.drySpecular},
39 {"wetSpecular", s.wetSpecular},
40 {"wetDarkening", s.wetDarkening},
41 {"normalStrength", s.normalStrength}};
42}
43std::vector<EditorDiagnostic> validateSettings(const SurfaceFluidSettings& s) {
44 std::vector<EditorDiagnostic> out;
45 const double finite[]{s.gravityX,
46 s.gravityY,
47 s.gravityZ,
48 s.friction,
49 s.maxSpeed,
50 s.adhesionAcceleration,
51 s.contactAngleDegrees,
52 s.mergeRadiusScale,
53 s.trailDeposition,
54 s.airDrag,
55 s.reattachDistance,
56 s.diffusion,
57 s.evaporation,
58 s.maxWetness,
59 s.velocityStretch,
60 s.maxAspectRatio,
61 s.surfaceOffset,
62 s.dryRoughness,
63 s.wetRoughness,
64 s.drySpecular,
65 s.wetSpecular,
66 s.wetDarkening,
67 s.normalStrength};
68
69 if (std::any_of(std::begin(finite), std::end(finite), [](double v) { return !std::isfinite(v); }))
70 out.push_back(diagnostic("editor.surface-fluid.nonfinite", DiagnosticSeverity::Error,
71 "Surface fluid values must be finite"));
72
73 if (s.friction < 0 || s.maxSpeed <= 0 || s.adhesionAcceleration < 0 || s.maxCrossings < 1 ||
74 s.maxCrossings > 1024 || s.contactAngleDegrees <= 0 || s.contactAngleDegrees >= 180 ||
75 s.mergeRadiusScale <= 0 || s.trailDeposition < 0 || s.airDrag < 0 || s.reattachDistance < 0 ||
76 s.diffusion < 0 || s.evaporation < 0 || s.maxWetness <= 0 || s.velocityStretch < 0 || s.maxAspectRatio < 1 ||
77 s.surfaceOffset < 0 || s.dryRoughness < 0 || s.dryRoughness > 1 || s.wetRoughness < 0 || s.wetRoughness > 1 ||
78 s.drySpecular < 0 || s.drySpecular > 1 || s.wetSpecular < 0 || s.wetSpecular > 1 || s.wetDarkening < 0 ||
79 s.wetDarkening > 1 || s.normalStrength < 0 || s.normalStrength > 1)
80 out.push_back(diagnostic("editor.surface-fluid.range", DiagnosticSeverity::Error,
81 "Surface fluid values are outside safe runtime ranges"));
82 if (s.wetRoughness > s.dryRoughness)
83 out.push_back(diagnostic("editor.surface-fluid.roughness-order", DiagnosticSeverity::Warning,
84 "Wet roughness exceeds dry roughness"));
85 if (s.wetSpecular < s.drySpecular)
86 out.push_back(diagnostic("editor.surface-fluid.specular-order", DiagnosticSeverity::Warning,
87 "Wet specular is lower than dry specular"));
88 return out;
89}
90Result<SurfaceFluidSettings> parse(const EditorValue& value) {
91 SurfaceFluidSettings s;
92 const auto number = [&](const char* key, double& destination) {
93 const auto* entry = field(value, key);
94 const auto* v = entry ? entry->getIf<double>() : nullptr;
95 if (v) destination = *v;
96 return v != nullptr;
97 };
98 const auto* gravityValue = field(value, "gravity");
99 const auto* gravity = gravityValue ? gravityValue->getIf<EditorValue::Array>() : nullptr;
100 const auto* crossingsValue = field(value, "maxCrossings");
101 const auto* crossings = crossingsValue ? crossingsValue->getIf<int64_t>() : nullptr;
102 if (!gravity || gravity->size() != 3 || !crossings)
103 return eve::editing::failed<SurfaceFluidSettings>(EditorStatus::Rejected, RuleId("editor.surface-fluid.fields"),
104 "Surface fluid settings are incomplete");
105 double* gravityOut[]{&s.gravityX, &s.gravityY, &s.gravityZ};
106 for (int i = 0; i < 3; ++i) {
107 const auto* v = (*gravity)[i].getIf<double>();
108 if (!v)
109 return eve::editing::failed<SurfaceFluidSettings>(
110 EditorStatus::Rejected, RuleId("editor.surface-fluid.gravity"), "Gravity must be a Vec3");
111 *gravityOut[i] = *v;
112 }
113 s.maxCrossings = static_cast<int>(*crossings);
114 bool complete = true;
115 complete &= number("friction", s.friction);
116 complete &= number("maxSpeed", s.maxSpeed);
117 complete &= number("adhesionAcceleration", s.adhesionAcceleration);
118 complete &= number("contactAngleDegrees", s.contactAngleDegrees);
119 complete &= number("mergeRadiusScale", s.mergeRadiusScale);
120 complete &= number("trailDeposition", s.trailDeposition);
121 complete &= number("airDrag", s.airDrag);
122 complete &= number("reattachDistance", s.reattachDistance);
123 complete &= number("diffusion", s.diffusion);
124 complete &= number("evaporation", s.evaporation);
125 complete &= number("maxWetness", s.maxWetness);
126 complete &= number("velocityStretch", s.velocityStretch);
127 complete &= number("maxAspectRatio", s.maxAspectRatio);
128 complete &= number("surfaceOffset", s.surfaceOffset);
129 complete &= number("dryRoughness", s.dryRoughness);
130 complete &= number("wetRoughness", s.wetRoughness);
131 complete &= number("drySpecular", s.drySpecular);
132 complete &= number("wetSpecular", s.wetSpecular);
133 complete &= number("wetDarkening", s.wetDarkening);
134 complete &= number("normalStrength", s.normalStrength);
135 if (!complete)
136 return eve::editing::failed<SurfaceFluidSettings>(EditorStatus::Rejected, RuleId("editor.surface-fluid.fields"),
137 "Surface fluid settings are incomplete");
138 const auto diagnostics = validateSettings(s);
139 if (std::any_of(diagnostics.begin(), diagnostics.end(),
140 [](const auto& d) { return d.severity() == DiagnosticSeverity::Error; }))
141 return eve::editing::failed<SurfaceFluidSettings>(
142 EditorStatus::Rejected, RuleId("editor.surface-fluid.invalid"), "Surface fluid settings are invalid");
143 return eve::editing::applied<SurfaceFluidSettings>(s);
144}
146 double minimum = 0, double maximum = 0, bool ranged = false) {
149 d.displayNameKey = std::string("editor.surface-fluid.") + path;
150 d.category = category;
151 d.type = type;
152 d.flags = PropertyFlag::Runtime;
153 d.defaultValue = std::move(defaultValue);
154 if (ranged) {
155 d.numeric.minimum = minimum;
156 d.numeric.maximum = maximum;
157 }
158 return d;
159}
160} // namespace
161
162SurfaceFluidTarget::SurfaceFluidTarget(std::string id) : id_(std::move(id)) {}
164 return {TargetId(id_),
165 "surface-fluid",
167 false,
168 {CapabilityId("eve.editor.target.surface-fluid-properties")}};
169}
171 return c == CapabilityId("eve.editor.target.surface-fluid-properties") ? static_cast<IPropertyProvider*>(this)
172 : nullptr;
173}
174bool SurfaceFluidTarget::matches(const SelectionSnapshot& s) const {
175 return s.items.size() == 1 && s.items.front().target == TargetId(id_);
176}
178 if (!matches(s))
180 "Surface fluid selection mismatch",
181 "editor.surface-fluid.selection"));
183}
187 s.typeId = "fluids.surface";
188 s.properties = {
189 descriptor("gravity", PropertyType::Vec3, EditorValue::Array{d.gravityX, d.gravityY, d.gravityZ}, "droplet"),
190 descriptor("friction", PropertyType::Float, d.friction, "droplet", 0, 1000, true),
191 descriptor("maxSpeed", PropertyType::Float, d.maxSpeed, "droplet", 0.0001, 100000, true),
192 descriptor("adhesionAcceleration", PropertyType::Float, d.adhesionAcceleration, "droplet", 0, 100000, true),
193 descriptor("maxCrossings", PropertyType::Int, int64_t{d.maxCrossings}, "droplet", 1, 1024, true),
194 descriptor("contactAngleDegrees", PropertyType::Float, d.contactAngleDegrees, "droplet", 0.001, 179.999, true),
195 descriptor("mergeRadiusScale", PropertyType::Float, d.mergeRadiusScale, "droplet", 0.0001, 1000, true),
196 descriptor("trailDeposition", PropertyType::Float, d.trailDeposition, "droplet", 0, 1000, true),
197 descriptor("airDrag", PropertyType::Float, d.airDrag, "droplet", 0, 1000, true),
198 descriptor("reattachDistance", PropertyType::Float, d.reattachDistance, "droplet", 0, 1000, true),
199 descriptor("diffusion", PropertyType::Float, d.diffusion, "wetness", 0, 1000, true),
200 descriptor("evaporation", PropertyType::Float, d.evaporation, "wetness", 0, 1000, true),
201 descriptor("maxWetness", PropertyType::Float, d.maxWetness, "wetness", 0.0001, 1000, true),
202 descriptor("velocityStretch", PropertyType::Float, d.velocityStretch, "render", 0, 1000, true),
203 descriptor("maxAspectRatio", PropertyType::Float, d.maxAspectRatio, "render", 1, 1000, true),
204 descriptor("surfaceOffset", PropertyType::Float, d.surfaceOffset, "render", 0, 1000, true),
205 descriptor("dryRoughness", PropertyType::Float, d.dryRoughness, "material", 0, 1, true),
206 descriptor("wetRoughness", PropertyType::Float, d.wetRoughness, "material", 0, 1, true),
207 descriptor("drySpecular", PropertyType::Float, d.drySpecular, "material", 0, 1, true),
208 descriptor("wetSpecular", PropertyType::Float, d.wetSpecular, "material", 0, 1, true),
209 descriptor("wetDarkening", PropertyType::Float, d.wetDarkening, "material", 0, 1, true),
210 descriptor("normalStrength", PropertyType::Float, d.normalStrength, "material", 0, 1, true)};
211 return s;
212}
214 if (!matches(s) || !schema(s).find(p)) return {};
215 const auto* v = field(settingsValue(settings_), p.value());
216 return v ? PropertyReadResult{PropertyReadState::Value, *v, {}} : PropertyReadResult{};
217}
219 const EditorValue& v, PropertySetMode m) const {
220 if (m == PropertySetMode::Reset) return makeReset(s, p);
221 auto d = schema(s).find(p);
222 if (!matches(s) || !d || m != PropertySetMode::Absolute)
223 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.surface-fluid.set"),
224 "Surface fluid property requires a matching absolute edit");
225 auto checked = validatePropertyValue(*d, v);
226 if (!checked.ok()) return Result<DomainOperation>::failure(checked.status());
227 EditorValue candidate = settingsValue(settings_);
228 (*candidate.getIf<EditorValue::Object>())[p.value()] = v;
229 auto parsed = parse(candidate);
230 if (!parsed.ok()) return Result<DomainOperation>::failure(parsed.status());
232 op.type = "surface-fluid.settings.replace.v1";
233 op.inverseType = op.type;
234 op.target = TargetId(id_);
235 op.payload = settingsValue(parsed.value());
236 op.inverse = settingsValue(settings_);
237 op.hasInverse = true;
238 op.affectedProperties.push_back(p.value());
239 op.mergeKey = "surface-fluid:" + id_ + ":" + p.value();
240 return eve::editing::applied<DomainOperation>(std::move(op));
241}
243 auto d = schema(s).find(p);
244 if (!d)
245 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.surface-fluid.property"),
246 "Unknown surface fluid property");
247 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
248}
250 if (op.target != TargetId(id_) || op.type != "surface-fluid.settings.replace.v1")
251 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.surface-fluid.operation"),
252 "Surface fluid operation mismatch");
253 auto parsed = parse(op.payload);
254 if (!parsed.ok()) return Result<void>::failure(parsed.status());
255 settings_ = std::move(parsed.value());
256 bumpRevision();
257 widenDirty(0, 0);
258 return eve::editing::applied<void>();
259}
260std::vector<EditorDiagnostic> SurfaceFluidTarget::validate() const { return validateSettings(settings_); }
262 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"settings", settingsValue(settings_)}};
263}
265 const auto *v = field(snapshot, "schemaVersion"), *s = field(snapshot, "settings");
266 const auto* version = v ? v->getIf<int64_t>() : nullptr;
267 if (!version || *version != 1 || !s)
268 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.surface-fluid.snapshot"),
269 "Unsupported surface fluid snapshot");
270 auto parsed = parse(*s);
271 if (!parsed.ok()) return Result<void>::failure(parsed.status());
272 settings_ = std::move(parsed.value());
273 bumpRevision();
275 return eve::editing::applied<void>();
276}
277
278} // namespace eve::fluids_editing
double value
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
bool finite
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
double number
bool found
double gravity
const char * category
float m[16]
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.
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.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
SurfaceFluidTarget(std::string id)
Surface fluid target.
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.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
EditorValue snapshotValue() const
Capture schema-version-one settings.
TargetDescriptor describe() const override
Describe.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load and validate persisted settings.
std::vector< EditorDiagnostic > validate() const
Validate numerical safety and material response relationships.
const char * defaultValue
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)
ModelData parse(std::span< const uint8_t > bytes)
Parse.
DomainOperation public API.
std::vector< std::string > affectedProperties
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.
Renderer-neutral droplet motion, wet-film and wet-material authoring values.