载入中...
搜索中...
未找到
Fluids.cpp
浏览该文件的文档.
1#include "fluids/Fluids.h"
2
10#include "gpgpu/Gpgpu.h"
11#include "gpgpu/GpuBuffer.h"
12#include "gpgpu/Sequence.h"
13#include "image/ImageData.h"
14
15#include <algorithm>
16#include <cmath>
17#include <cstdint>
18#include <cstring>
19#include <exception>
20#include <simplesquirrel/simplesquirrel.hpp>
21#include <utility>
22
23namespace eve::fluids {
24namespace {
25
26constexpr int kPushCount = 0;
27constexpr int kPushDt = 1;
28constexpr int kPushRadius = 2;
29constexpr int kPushH = 3;
30constexpr int kPushRestDensity = 4;
31constexpr int kPushGravityX = 5;
32constexpr int kPushGravityY = 6;
33constexpr int kPushGravityZ = 7;
34constexpr int kPushViscosity = 8;
35constexpr int kPushYield = 9;
36constexpr int kPushCohesion = 10;
37constexpr int kPushAdhesion = 11;
38constexpr int kPushDamping = 12;
39constexpr int kPushMaxVelocity = 13;
40constexpr int kPushGridResX = 14;
41constexpr int kPushGridResY = 15;
42constexpr int kPushGridResZ = 16;
43constexpr int kPushGridOriginX = 17;
44constexpr int kPushGridOriginY = 18;
45constexpr int kPushGridOriginZ = 19;
46constexpr int kPushGridCell = 20;
47constexpr int kPushSdfResX = 21;
48constexpr int kPushSdfResY = 22;
49constexpr int kPushSdfResZ = 23;
50constexpr int kPushSdfOriginX = 24;
51constexpr int kPushSdfOriginY = 25;
52constexpr int kPushSdfOriginZ = 26;
53constexpr int kPushSdfCell = 27;
54constexpr int kPushIterations = 28;
55constexpr int kPushMode = 29;
56constexpr int kPushTime = 30;
57constexpr int kPushPbf = 31;
58
59int groupsFor(int count, int localSize = 64) { return (count + localSize - 1) / localSize; }
60
61} // namespace
62
63FluidSimulator::FluidSimulator(int maxParticles, const FluidParams& params, bool preferGpu)
64 : sim_(maxParticles, params), preferGpu_(preferGpu) {}
65
67 delete seq_;
68 delete shAdvance_;
69 delete shIntegrate_;
70 delete shApply_;
71 delete shDelta_;
72 delete shDensityLambda_;
73 delete shBuild_;
74 delete shClear_;
75 delete stageDens_;
76 delete stageVel_;
77 delete stagePos_;
78 delete bufSdf_;
79 delete bufGrad_;
80 delete bufLambda_;
81 delete bufDens_;
82 delete bufNext_;
83 delete bufHead_;
84 delete bufVel_;
85 delete bufPos_;
86}
87
89 sim_.setSdf(sdf);
90 grid_ = SimGrid::make(sdf, sim_.params().supportRadius);
91 sdfDirty_ = true;
92 if (gpuOk_) {
93 // Grid and SDF buffers depend on the field; rebuild them.
94 delete seq_;
95 delete shAdvance_;
96 delete shIntegrate_;
97 delete shApply_;
98 delete shDelta_;
99 delete shDensityLambda_;
100 delete shBuild_;
101 delete shClear_;
102 delete stageDens_;
103 delete stageVel_;
104 delete stagePos_;
105 delete bufSdf_;
106 delete bufGrad_;
107 delete bufLambda_;
108 delete bufDens_;
109 delete bufNext_;
110 delete bufHead_;
111 delete bufVel_;
112 delete bufPos_;
113 shClear_ = shBuild_ = shDensityLambda_ = shDelta_ = shApply_ = shIntegrate_ = shAdvance_ = nullptr;
114 bufPos_ = bufVel_ = bufHead_ = bufNext_ = bufDens_ = bufLambda_ = bufGrad_ = bufSdf_ = nullptr;
115 stagePos_ = stageVel_ = stageDens_ = nullptr;
116 seq_ = nullptr;
117 gpuOk_ = false;
118 ensureGpu();
119 }
120}
121
122int FluidSimulator::spawnDrop(const glm::vec3& center, float radius, int count) {
123 return sim_.spawnDrop(center, radius, count);
124}
125
126void FluidSimulator::step(float dt) { stepSolver(dt, std::max(1, sim_.params().iterations)); }
127
128void FluidSimulator::stepSolver(float dt, int substeps) {
129 if (sim_.particleCount() <= 0) return;
130 if (preferGpu_ && !gpuOk_) ensureGpu();
131 if (!gpuOk_) {
132 sim_.step(dt, substeps);
133 return;
134 }
135
136 const int iters = std::max(1, substeps);
137 const float sub = dt / float(iters);
138
139 seq_->begin();
140 uploadParticles();
141 const int pbf = std::max(0, sim_.params().pbfIterations);
142 for (int it = 0; it < iters; ++it) {
143 setCommonConstants(shClear_, sub);
144 seq_->recordDispatch(shClear_, groupsFor(grid_.cellCount()));
145 setCommonConstants(shBuild_, sub);
146 seq_->recordDispatch(shBuild_, groupsFor(sim_.maxParticles()));
147 setCommonConstants(shIntegrate_, sub);
148 seq_->recordDispatch(shIntegrate_, groupsFor(sim_.maxParticles()));
149 setCommonConstants(shAdvance_, sub);
150 seq_->recordDispatch(shAdvance_, groupsFor(sim_.maxParticles()));
151 for (int k = 0; k < pbf; ++k) {
152 setCommonConstants(shClear_, sub);
153 seq_->recordDispatch(shClear_, groupsFor(grid_.cellCount()));
154 setCommonConstants(shBuild_, sub);
155 seq_->recordDispatch(shBuild_, groupsFor(sim_.maxParticles()));
156 setCommonConstants(shDensityLambda_, sub);
157 seq_->recordDispatch(shDensityLambda_, groupsFor(sim_.maxParticles()));
158 setCommonConstants(shDelta_, sub);
159 seq_->recordDispatch(shDelta_, groupsFor(sim_.maxParticles()));
160 setCommonConstants(shApply_, sub);
161 seq_->recordDispatch(shApply_, groupsFor(sim_.maxParticles()));
162 }
163 }
164 downloadParticles();
165 seq_->submit();
166
167 // Update the CPU mirror so reads reflect GPU state.
168 std::vector<float> posF(size_t(sim_.maxParticles()) * 4u);
169 std::vector<float> velF(size_t(sim_.maxParticles()) * 4u);
170 std::vector<float> densF(size_t(sim_.maxParticles()), 0.f);
171 stagePos_->downloadBytes(posF.data(), uint64_t(posF.size()) * sizeof(float));
172 stageVel_->downloadBytes(velF.data(), uint64_t(velF.size()) * sizeof(float));
173 stageDens_->downloadBytes(densF.data(), uint64_t(densF.size()) * sizeof(float));
174 std::vector<FluidParticle>& cpu = sim_.particles();
175 for (int i = 0; i < sim_.particleCount(); ++i) {
176 const size_t b = size_t(i) * 4u;
177 cpu[size_t(i)].pos = glm::vec3(posF[b], posF[b + 1], posF[b + 2]);
178 cpu[size_t(i)].vel = glm::vec3(velF[b], velF[b + 1], velF[b + 2]);
179 }
180 std::vector<float>& dens = sim_.densities();
181 for (int i = 0; i < sim_.particleCount(); ++i) dens[size_t(i)] = densF[size_t(i)];
182}
183
186 auto valid = eve::physics::detail::validateSimulationStep(stepValue, settings, observation_);
187 if (!valid) return valid;
188 auto next = eve::physics::detail::advanceSimulationObservation(observation_, stepValue);
189 if (!next) return eve::Result<void>::failure(next.status());
190
191 try {
192 if (preferGpu_ && !gpuOk_) {
193 bool available = false;
194 try {
195 available = ensureGpu();
196 } catch (...) {
197 available = false;
198 }
199 if (!available) {
200 backendFallback_ = true;
201 backendSelectionStatus_ = eve::Status(
204 eve::DiagnosticCode::Unsupported, "Fluid GPGPU accelerator unavailable; CPU reference selected",
205 "fluids.simulationBackend", {{"selected", "cpu"}, {"fallback", "explicit"}})});
206 }
207 }
208 stepSolver(static_cast<float>(stepValue.delta.seconds()), settings.subStepCount);
209 } catch (const std::exception& error) {
211 eve::DiagnosticCode::Failed, std::string("Fluid simulation step failed: ") + error.what(),
212 "fluids.simulationBackend.step"));
213 } catch (...) {
215 eve::DiagnosticCode::Failed, "Fluid simulation step failed with an unknown exception",
216 "fluids.simulationBackend.step"));
217 }
218 observation_ = std::move(next).takeValue();
220}
221
223 auto valid =
224 eve::physics::detail::validateSimulationObservation(observation, "fluids.simulationBackend.restoreObservation");
225 if (!valid) return valid;
226 observation_ = observation;
228}
229
230void FluidSimulator::readPositions(std::vector<glm::vec3>& out) const {
231 out.clear();
232 out.reserve(size_t(sim_.particleCount()));
233 for (int i = 0; i < sim_.particleCount(); ++i) out.push_back(sim_.particles()[size_t(i)].pos);
234}
235
236void FluidSimulator::readDensities(std::vector<float>& out) const {
237 out.clear();
238 out.reserve(size_t(sim_.particleCount()));
239 for (int i = 0; i < sim_.particleCount(); ++i) out.push_back(sim_.densities()[size_t(i)]);
240}
241
242void FluidSimulator::setGravity(float x, float y, float z) { sim_.params().gravity = glm::vec3(x, y, z); }
243
244void FluidSimulator::setViscosity(float viscosity) { sim_.params().viscosity = viscosity; }
245void FluidSimulator::setCohesion(float cohesion) { sim_.params().cohesion = cohesion; }
246void FluidSimulator::setAdhesion(float adhesion) { sim_.params().adhesion = adhesion; }
247void FluidSimulator::setPbfIterations(int passes) { sim_.params().pbfIterations = std::max(0, passes); }
248void FluidSimulator::setDamping(float damping) { sim_.params().damping = damping; }
249void FluidSimulator::setParticleRadius(float radius) { sim_.params().particleRadius = std::max(1e-4f, radius); }
251 sim_.params().supportRadius = std::max(1e-4f, h);
252 grid_ = SimGrid::make(sim_.sdf(), sim_.params().supportRadius);
253}
254
255void FluidSimulator::setSdfSphere(float cx, float cy, float cz, float radius, int res) {
256 setSdf(MeshSdf::makeSphere(glm::vec3(cx, cy, cz), radius, glm::ivec3(res, res, res)));
257}
258
259bool FluidSimulator::ensureGpu() {
260 if (gpuOk_) return true;
261 gpgpu_ = eve::gpgpu::Gpgpu::create();
262 if (!gpgpu_ || !gpgpu_->isAvailable()) return false;
263 if (sim_.sdf().voxelCount() <= 0) return false;
264
265 const int max = sim_.maxParticles();
266 try {
267 shClear_ = gpgpu_->newShader(kFluidClearGrid);
268 shBuild_ = gpgpu_->newShader(kFluidBuildGrid);
269 shDensityLambda_ = gpgpu_->newShader(kFluidDensityLambda);
270 shDelta_ = gpgpu_->newShader(kFluidComputeDelta);
271 shApply_ = gpgpu_->newShader(kFluidApplyDelta);
272 shIntegrate_ = gpgpu_->newShader(kFluidIntegrate);
273 shAdvance_ = gpgpu_->newShader(kFluidApplyDelta);
274 bufPos_ = gpgpu_->newBuffer(max * 4 * int(sizeof(float)), "storage");
275 bufVel_ = gpgpu_->newBuffer(max * 4 * int(sizeof(float)), "storage");
276 bufHead_ = gpgpu_->newBuffer(grid_.cellCount() * int(sizeof(int)), "storage");
277 bufNext_ = gpgpu_->newBuffer(max * int(sizeof(int)), "storage");
278 bufDens_ = gpgpu_->newBuffer(max * int(sizeof(float)), "storage");
279 bufLambda_ = gpgpu_->newBuffer(max * int(sizeof(float)), "storage");
280 bufGrad_ = gpgpu_->newBuffer(max * 4 * int(sizeof(float)), "storage");
281 bufSdf_ = gpgpu_->newBuffer(sim_.sdf().voxelCount() * int(sizeof(float)), "storage");
282 stagePos_ = gpgpu_->newBuffer(max * 4 * int(sizeof(float)), "staging");
283 stageVel_ = gpgpu_->newBuffer(max * 4 * int(sizeof(float)), "staging");
284 stageDens_ = gpgpu_->newBuffer(max * int(sizeof(float)), "staging");
285 seq_ = gpgpu_->newSequence();
286 } catch (...) {
287 return false;
288 }
289 if (!seq_ || !seq_->isAvailable()) return false;
290
291 gpuOk_ = true; // uploadSdf() guards on gpuOk_; set it before uploading.
292 uploadSdf();
293 shClear_->bindBuffer(2, bufHead_);
294 shBuild_->bindBuffer(0, bufPos_);
295 shBuild_->bindBuffer(2, bufHead_);
296 shBuild_->bindBuffer(3, bufNext_);
297 shDensityLambda_->bindBuffer(0, bufPos_);
298 shDensityLambda_->bindBuffer(2, bufHead_);
299 shDensityLambda_->bindBuffer(3, bufNext_);
300 shDensityLambda_->bindBuffer(4, bufDens_);
301 shDensityLambda_->bindBuffer(6, bufLambda_);
302 shDensityLambda_->bindBuffer(7, bufGrad_);
303 shDelta_->bindBuffer(0, bufPos_);
304 shDelta_->bindBuffer(2, bufHead_);
305 shDelta_->bindBuffer(3, bufNext_);
306 shDelta_->bindBuffer(6, bufLambda_);
307 shDelta_->bindBuffer(7, bufGrad_);
308 shApply_->bindBuffer(0, bufPos_);
309 shApply_->bindBuffer(1, bufVel_);
310 shApply_->bindBuffer(2, bufHead_);
311 shApply_->bindBuffer(3, bufNext_);
312 shApply_->bindBuffer(7, bufGrad_);
313 shApply_->bindBuffer(5, bufSdf_);
314 shIntegrate_->bindBuffer(0, bufPos_);
315 shIntegrate_->bindBuffer(1, bufVel_);
316 shIntegrate_->bindBuffer(2, bufHead_);
317 shIntegrate_->bindBuffer(3, bufNext_);
318 shIntegrate_->bindBuffer(5, bufSdf_);
319 shIntegrate_->bindBuffer(7, bufGrad_);
320 shAdvance_->bindBuffer(0, bufPos_);
321 shAdvance_->bindBuffer(1, bufVel_);
322 shAdvance_->bindBuffer(5, bufSdf_);
323 shAdvance_->bindBuffer(7, bufGrad_);
324 return true;
325}
326
327void FluidSimulator::uploadSdf() {
328 if (!gpuOk_ || !bufSdf_ || !sdfDirty_) return;
329 const std::vector<float>& dist = sim_.sdf().distances;
330 bufSdf_->writeFloat32s(dist.data(), int(dist.size()));
331 sdfDirty_ = false;
332}
333
334void FluidSimulator::uploadParticles() {
335 if (!gpuOk_) return;
336 const int max = sim_.maxParticles();
337 std::vector<float> posF(size_t(max) * 4u, 0.f);
338 std::vector<float> velF(size_t(max) * 4u, 0.f);
339 const auto& particles = sim_.particles();
340 for (int i = 0; i < sim_.particleCount(); ++i) {
341 const size_t b = size_t(i) * 4u;
342 posF[b] = particles[size_t(i)].pos.x;
343 posF[b + 1] = particles[size_t(i)].pos.y;
344 posF[b + 2] = particles[size_t(i)].pos.z;
345 velF[b] = particles[size_t(i)].vel.x;
346 velF[b + 1] = particles[size_t(i)].vel.y;
347 velF[b + 2] = particles[size_t(i)].vel.z;
348 }
349 seq_->recordUpload(bufPos_, posF.data(), uint64_t(posF.size()) * sizeof(float));
350 seq_->recordUpload(bufVel_, velF.data(), uint64_t(velF.size()) * sizeof(float));
351}
352
353void FluidSimulator::downloadParticles() {
354 if (!gpuOk_) return;
355 const int max = sim_.maxParticles();
356 seq_->recordDownload(bufPos_, stagePos_, uint64_t(size_t(max) * 4u) * sizeof(float));
357 seq_->recordDownload(bufVel_, stageVel_, uint64_t(size_t(max) * 4u) * sizeof(float));
358 seq_->recordDownload(bufDens_, stageDens_, uint64_t(size_t(max)) * sizeof(float));
359}
360
361void FluidSimulator::setCommonConstants(gpgpu::ComputeShader* shader, float dt) {
362 if (!shader) return;
363 const FluidParams& p = sim_.params();
364 shader->setFloat(kPushCount, float(sim_.particleCount()));
365 shader->setFloat(kPushDt, dt);
366 shader->setFloat(kPushRadius, p.particleRadius);
367 shader->setFloat(kPushH, p.supportRadius);
368 shader->setFloat(kPushRestDensity, p.restDensity);
369 shader->setFloat(kPushGravityX, p.gravity.x);
370 shader->setFloat(kPushGravityY, p.gravity.y);
371 shader->setFloat(kPushGravityZ, p.gravity.z);
372 shader->setFloat(kPushViscosity, p.viscosity);
373 shader->setFloat(kPushYield, p.yieldStress);
374 shader->setFloat(kPushCohesion, p.cohesion);
375 shader->setFloat(kPushAdhesion, p.adhesion);
376 shader->setFloat(kPushDamping, p.damping);
377 shader->setFloat(kPushMaxVelocity, p.maxVelocity);
378 shader->setFloat(kPushGridResX, float(grid_.dims.x));
379 shader->setFloat(kPushGridResY, float(grid_.dims.y));
380 shader->setFloat(kPushGridResZ, float(grid_.dims.z));
381 shader->setFloat(kPushGridOriginX, grid_.origin.x);
382 shader->setFloat(kPushGridOriginY, grid_.origin.y);
383 shader->setFloat(kPushGridOriginZ, grid_.origin.z);
384 shader->setFloat(kPushGridCell, grid_.cellSize);
385 shader->setFloat(kPushSdfResX, float(sim_.sdf().dims.x));
386 shader->setFloat(kPushSdfResY, float(sim_.sdf().dims.y));
387 shader->setFloat(kPushSdfResZ, float(sim_.sdf().dims.z));
388 shader->setFloat(kPushSdfOriginX, sim_.sdf().origin.x);
389 shader->setFloat(kPushSdfOriginY, sim_.sdf().origin.y);
390 shader->setFloat(kPushSdfOriginZ, sim_.sdf().origin.z);
391 shader->setFloat(kPushSdfCell, sim_.sdf().cellSize);
392 shader->setFloat(kPushIterations, float(p.iterations));
393 shader->setFloat(kPushMode, 0.f);
394 if (shader == shAdvance_) shader->setFloat(kPushMode, 1.f);
395 shader->setFloat(kPushTime, 0.f);
396 shader->setFloat(kPushPbf, float(p.pbfIterations));
397}
398
399Fluids::Fluids() = default;
400Fluids::~Fluids() = default;
401
403
405 auto sim = std::make_unique<FluidSimulator>(maxParticles, FluidParams{}, true);
406 FluidSimulator* raw = sim.get();
407 simulators_.push_back(std::move(sim));
408 return raw;
409}
410
411int Fluids::getSimulatorCount() const { return int(simulators_.size()); }
412
415 params.width = std::clamp(width, 8, 1024);
416 params.height = std::clamp(height, 8, 1024);
417 auto r = std::make_unique<FluidSurfaceRenderer>(params, true);
418 FluidSurfaceRenderer* raw = r.get();
419 renderers_.push_back(std::move(r));
420 return raw;
421}
422
423int Fluids::getRendererCount() const { return int(renderers_.size()); }
424
425void Fluids::expose(ssq::Table& table) {
426 auto cls = table.addClass(name, Fluids::create, false);
427 expose(cls);
428 cls.addFunc("fluidRendererSettingsDefaults", [vm = table.getHandle()](Fluids*) {
429 return eve::script::projectStatusResult(vm, eve::Status::success(),
430 encodeFluidRendererSettings(FluidRendererSettings{}));
431 });
433
434 auto sim = table.addClass<FluidSimulator>(
435 "FluidSim", std::function<FluidSimulator*()>([]() -> FluidSimulator* { return nullptr; }), true);
436 sim.addFunc("setSdfSphere", &FluidSimulator::setSdfSphere);
437 sim.addFunc("setGravity", &FluidSimulator::setGravity);
438 sim.addFunc("setViscosity", &FluidSimulator::setViscosity);
439 sim.addFunc("setCohesion", &FluidSimulator::setCohesion);
440 sim.addFunc("setAdhesion", &FluidSimulator::setAdhesion);
441 sim.addFunc("setPbfIterations", &FluidSimulator::setPbfIterations);
442 sim.addFunc("setDamping", &FluidSimulator::setDamping);
443 sim.addFunc("setParticleRadius", &FluidSimulator::setParticleRadius);
444 sim.addFunc("setSupportRadius", &FluidSimulator::setSupportRadius);
445 sim.addFunc("spawnDrop", &FluidSimulator::spawnDrop);
446 sim.addFunc("step", static_cast<void (FluidSimulator::*)(float)>(&FluidSimulator::step));
447 sim.addFunc("getParticleCount", &FluidSimulator::getParticleCount);
448 sim.addFunc("getMaxParticles", &FluidSimulator::getMaxParticles);
449 sim.addFunc("usingGpu", &FluidSimulator::usingGpu);
450
451 auto surf = table.addClass<FluidSurfaceRenderer>(
452 "FluidSurface", std::function<FluidSurfaceRenderer*()>([]() -> FluidSurfaceRenderer* { return nullptr; }),
453 true);
454 surf.addFunc("render", &FluidSurfaceRenderer::renderFrom);
455 surf.addFunc("renderVolume",
456 [](FluidSurfaceRenderer* renderer, VolumeFluid* solver) { renderer->renderVolume(*solver); });
457 surf.addFunc("renderVolumeInterpolated", [vm = table.getHandle()](FluidSurfaceRenderer* renderer,
458 VolumeFluid* solver, float alpha) {
459 if (!solver)
460 return eve::script::projectResult(
461 vm, eve::Result<void>::failure(eve::Diagnostic::error(
462 eve::DiagnosticCode::InvalidArgument, "Missing volume fluid", "fluids.surface.interpolation")));
463 return eve::script::projectResult(vm, renderer->renderVolumeInterpolated(*solver, alpha));
464 });
465 surf.addFunc("renderVolumeColorOnly",
466 [](FluidSurfaceRenderer* renderer, VolumeFluid* solver) { renderer->renderVolumeColorOnly(*solver); });
467 surf.addFunc("renderGasVolume", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluid* solver,
468 float absorption) {
469 if (!solver)
470 return eve::script::projectResult(
471 vm, eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument,
472 "Missing volume fluid", "fluids.surface.gas")));
473 return eve::script::projectResult(vm, renderer->renderGasVolume(*solver, absorption));
474 });
475 surf.addFunc("renderVolumeWithoutSurface", [vm = table.getHandle()](FluidSurfaceRenderer* renderer,
476 VolumeFluid* solver, float absorption) {
477 if (!solver)
478 return eve::script::projectResult(
479 vm, eve::Result<void>::failure(eve::Diagnostic::error(
480 eve::DiagnosticCode::InvalidArgument, "Missing volume fluid", "fluids.surface.volume-cloud")));
481 return eve::script::projectResult(vm, renderer->renderVolumeWithoutSurface(*solver, absorption));
482 });
483 surf.addFunc(
484 "renderConfiguredVolume", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluid* solver) {
485 if (!solver)
486 return eve::script::projectResult(vm, eve::Result<void>::failure(eve::Diagnostic::error(
487 eve::DiagnosticCode::InvalidArgument, "Missing volume fluid",
488 "fluids.surface.configured-volume")));
489 return eve::script::projectResult(vm, renderer->renderConfiguredVolume(*solver));
490 });
491 surf.addFunc("prepare", [vm = table.getHandle()](FluidSurfaceRenderer* renderer) {
492 return eve::script::projectResult(vm, renderer->prepare());
493 });
494 surf.addFunc("copyToTexture", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, graphics::Graphics* graphics,
495 graphics::Texture* texture) {
496 return eve::script::projectResult(vm, renderer->copyToTexture(graphics, texture));
497 });
498 surf.addFunc("renderVolumeColorToTexture", [vm = table.getHandle()](
499 FluidSurfaceRenderer* renderer, VolumeFluid* solver,
500 graphics::Graphics* graphics, graphics::Texture* texture) {
501 if (!solver)
502 return eve::script::projectResult(vm, eve::Result<void>::failure(eve::Diagnostic::error(
503 eve::DiagnosticCode::InvalidArgument, "Missing volume fluid",
504 "fluids.surface.renderVolumeColorToTexture")));
505 return eve::script::projectResult(vm, renderer->renderVolumeColorToTexture(*solver, graphics, texture));
506 });
507 surf.addFunc("compositeDiffuse", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluidDiffuse* pool,
508 float radius, float opacity, float fadeSeconds) {
509 if (!pool)
510 return eve::script::projectResult(
511 vm, eve::Result<void>::failure(eve::Diagnostic::error(
512 eve::DiagnosticCode::InvalidArgument, "Missing diffuse pool", "fluids.surface.diffuse")));
513 return eve::script::projectResult(vm, renderer->compositeDiffuse(*pool, radius, opacity, fadeSeconds));
514 });
515 surf.addFunc("configureFoam",
516 [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool enabled, int downsample) {
517 return eve::script::projectResult(vm, renderer->configureFoam(enabled, downsample));
518 });
519 surf.addFunc("compositeSceneRefraction", [vm = table.getHandle()](FluidSurfaceRenderer* renderer,
520 image::ImageData* scene, float distortion,
521 float absorption) {
522 if (!scene || scene->getFormat() != "RGBA8" || scene->getWidth() != renderer->getWidth() ||
523 scene->getHeight() != renderer->getHeight())
524 return eve::script::projectResult(
525 vm, eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument,
526 "Refraction requires matching RGBA8 scene image",
527 "fluids.surface.refraction")));
528 return eve::script::projectResult(
529 vm, renderer->compositeSceneRefraction(
530 std::span<const uint8_t>(static_cast<const uint8_t*>(scene->getData()), scene->getSize()),
531 distortion, absorption));
532 });
533 surf.addFunc("configureRefraction", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, float transparency,
534 float absorption, float coefficient, int downsample) {
535 return eve::script::projectResult(
536 vm, renderer->configureRefraction(transparency, absorption, coefficient, downsample));
537 });
538 surf.addFunc("configureRefractionEnabled", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool enabled) {
539 return eve::script::projectResult(vm, renderer->configureRefractionEnabled(enabled));
540 });
541 surf.addFunc("compositeConfiguredSceneRefraction", [vm = table.getHandle()](FluidSurfaceRenderer* renderer,
542 image::ImageData* scene) {
543 if (!scene || scene->getFormat() != "RGBA8" || scene->getWidth() != renderer->getWidth() ||
544 scene->getHeight() != renderer->getHeight())
545 return eve::script::projectResult(vm, eve::Result<void>::failure(eve::Diagnostic::error(
546 eve::DiagnosticCode::InvalidArgument,
547 "Configured refraction requires matching RGBA8 scene image",
548 "fluids.surface.configuredRefraction")));
549 return eve::script::projectResult(vm, renderer->compositeConfiguredSceneRefraction(std::span<const uint8_t>(
550 static_cast<const uint8_t*>(scene->getData()), scene->getSize())));
551 });
552 surf.addFunc("configureSurfaceBlend",
553 [vm = table.getHandle()](FluidSurfaceRenderer* renderer, int source, int destination) {
554 return eve::script::projectResult(vm, renderer->configureSurfaceBlend(source, destination));
555 });
556 surf.addFunc("configureParticleBlend", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, int source,
557 int destination, bool depthWrite) {
558 return eve::script::projectResult(vm, renderer->configureParticleBlend(source, destination, depthWrite));
559 });
560 surf.addFunc("compositeConfiguredSurfaceBlend", [vm = table.getHandle()](FluidSurfaceRenderer* renderer,
561 image::ImageData* scene) {
562 if (!scene || scene->getFormat() != "RGBA8" || scene->getWidth() != renderer->getWidth() ||
563 scene->getHeight() != renderer->getHeight())
564 return eve::script::projectResult(
565 vm, eve::Result<void>::failure(eve::Diagnostic::error(
566 eve::DiagnosticCode::InvalidArgument,
567 "Configured surface blend requires matching RGBA8 scene image", "fluids.surface.blend")));
568 return eve::script::projectResult(vm, renderer->compositeConfiguredSurfaceBlend(std::span<const uint8_t>(
569 static_cast<const uint8_t*>(scene->getData()), scene->getSize())));
570 });
571 surf.addFunc("setMode", &FluidSurfaceRenderer::setMode);
572 surf.addFunc(
573 "configureSurface", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, float thicknessScale,
574 float thicknessCutoff, float depthFalloff, int smoothIterations) {
575 return eve::script::projectResult(
576 vm, renderer->configureSurface(thicknessScale, thicknessCutoff, depthFalloff, smoothIterations));
577 });
578 surf.addFunc("configureSurfaceEnabled", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool enabled) {
579 return eve::script::projectResult(vm, renderer->configureSurfaceEnabled(enabled));
580 });
581 surf.addFunc("configureRendererSettings",
582 [vm = table.getHandle()](FluidSurfaceRenderer* renderer, ssq::Object object) {
583 auto value = eve::script::valueFromSquirrel(object);
584 if (!value) return eve::script::projectResult(vm, eve::Result<void>::failure(value.status()));
585 auto settings = decodeFluidRendererSettings(value.value());
586 if (!settings)
587 return eve::script::projectResult(vm, eve::Result<void>::failure(settings.status()));
588 return eve::script::projectResult(vm, renderer->configureRendererSettings(settings.value()));
589 });
590 surf.addFunc("rendererSettings", [vm = table.getHandle()](FluidSurfaceRenderer* renderer) {
591 return eve::script::projectStatusResult(vm, eve::Status::success(),
592 encodeFluidRendererSettings(renderer->rendererSettings()));
593 });
594 surf.addFunc("configureSurfaceBlurRadius", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, float radius) {
595 return eve::script::projectResult(vm, renderer->configureSurfaceBlurRadius(radius));
596 });
597 surf.addFunc("configureMaterial", [vm = table.getHandle()](
598 FluidSurfaceRenderer* renderer, bool lighting, float smoothness,
599 float metalness, float ambientMultiplier, float reflection, float opacity) {
600 return eve::script::projectResult(
601 vm, renderer->configureMaterial(lighting, smoothness, metalness, ambientMultiplier, reflection, opacity));
602 });
603 surf.addFunc("configureReflection", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool enabled) {
604 return eve::script::projectResult(vm, renderer->configureReflection(enabled));
605 });
606 surf.addFunc("configureColors", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, float br, float bg,
607 float bb, float rr, float rg, float rb) {
608 return eve::script::projectResult(vm, renderer->configureColors({br, bg, bb}, {rr, rg, rb}));
609 });
610 surf.addFunc("configureAnisotropy", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool enabled) {
611 return eve::script::projectResult(vm, renderer->configureAnisotropy(enabled));
612 });
613 surf.addFunc("configureSurfaceDownsample", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, int factor) {
614 return eve::script::projectResult(vm, renderer->configureSurfaceDownsample(factor));
615 });
616 surf.addFunc("configureThicknessDownsample", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, int factor) {
617 return eve::script::projectResult(vm, renderer->configureThicknessDownsample(factor));
618 });
619 surf.addFunc("configureProjection", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, bool orthographic,
620 float verticalHalfSize) {
621 return eve::script::projectResult(vm, renderer->configureProjection(orthographic, verticalHalfSize));
622 });
623 surf.addFunc("setCamera", &FluidSurfaceRenderer::setCamera);
624 surf.addFunc("setCameraXYZ",
625 [](FluidSurfaceRenderer* renderer, float ex, float ey, float ez, float tx, float ty, float tz,
626 float fov) { renderer->setCamera({ex, ey, ez}, {tx, ty, tz}, {0.f, 1.f, 0.f}, fov); });
627 surf.addFunc("getWidth", &FluidSurfaceRenderer::getWidth);
628 surf.addFunc("getHeight", &FluidSurfaceRenderer::getHeight);
629 surf.addFunc("usingGpu", &FluidSurfaceRenderer::usingGpu);
630 surf.addFunc("writePpm", &FluidSurfaceRenderer::writePpm);
631 surf.addFunc("copyToImage", [vm = table.getHandle()](FluidSurfaceRenderer* renderer, image::ImageData* image) {
632 if (!image || image->getFormat() != "RGBA8" || image->getWidth() != renderer->getWidth() ||
633 image->getHeight() != renderer->getHeight() || image->getSize() != renderer->color().size())
636 "Fluid output requires matching RGBA8 image",
637 "fluids.copyToImage")));
638 std::memcpy(image->getData(), renderer->color().data(), renderer->color().size());
640 });
641}
642
643void Fluids::expose(ssq::Class& cls) {
645 cls.addFunc("getName", &Fluids::getName);
646 cls.addFunc("newSimulator", &Fluids::newSimulator);
647 cls.addFunc("getSimulatorCount", &Fluids::getSimulatorCount);
648 cls.addFunc("newSurfaceRenderer", &Fluids::newSurfaceRenderer);
649 cls.addFunc("getRendererCount", &Fluids::getRendererCount);
650}
651
652} // namespace eve::fluids
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
glm::vec4 p[6]
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
vk::UniqueImage image
double r
int h
std::uint32_t height
std::uint32_t width
std::string name
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::string error
Definition Package.cpp:60
float radius
Shader * shader
Lighting3DPack lighting
std::uint32_t count
The single Squirrel projection for common Result, Status and Value.
TerrainThermalSettings settings
float opacity
const UnitySourceAsset & source
static Diagnostic warning(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct a warning diagnostic.
Definition Diagnostic.h:134
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
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
int maxParticles() const
Max particles.
void setSdf(const MeshSdf &sdf)
Replace the collision surface. Particles keep their state.
int spawnDrop(const glm::vec3 &center, float radius, int count)
Spawn a roughly spherical drop of particles near the surface.
FluidParams & params()
Params.
int particleCount() const
Particle count.
const std::vector< FluidParticle > & particles() const
Particles.
const MeshSdf & sdf() const
Sdf.
const std::vector< float > & densities() const
Densities.
void step(float dt)
Advance the simulation by dt seconds (params.iterations substeps).
GPU-backed surface fluid simulator (falls back to the CPU solver).
Definition Fluids.h:44
eve::Result< void > restoreObservation(const eve::physics::SimulationObservation &observation) override
Restores tick/progress metadata after an owner-level restore.
Definition Fluids.cpp:222
void setParticleRadius(float radius)
Set particle radius (support radius is unchanged).
Definition Fluids.cpp:249
void setViscosity(float viscosity)
Set XSPH viscosity strength.
Definition Fluids.cpp:244
void setSdf(const MeshSdf &sdf)
Replace the collision surface (re-uploads the SDF on the GPU path).
Definition Fluids.cpp:88
void readPositions(std::vector< glm::vec3 > &out) const
Copy live particle positions out (CPU mirror; GPU path downloads).
Definition Fluids.cpp:230
eve::physics::SimulationObservation observation() const noexcept override
Returns completed tick/time observables.
Definition Fluids.h:82
FluidSimulator(int maxParticles, const FluidParams &params, bool preferGpu)
Fluid simulator.
Definition Fluids.cpp:63
void setPbfIterations(int passes)
Set PBF density-constraint passes per substep.
Definition Fluids.cpp:247
bool usingGpu() const
Using gpu.
Definition Fluids.h:113
void step(float dt)
Advance the simulation by dt seconds.
Definition Fluids.cpp:126
const MeshSdf & sdf() const
Sdf.
Definition Fluids.h:64
void setSdfSphere(float cx, float cy, float cz, float radius, int res)
Convenience: bake a sphere SDF around the origin.
Definition Fluids.cpp:255
int spawnDrop(const glm::vec3 &center, float radius, int count)
Spawn a drop of particles near the surface.
Definition Fluids.cpp:122
void setDamping(float damping)
Set linear air damping.
Definition Fluids.cpp:248
void setAdhesion(float adhesion)
Set fluid-surface adhesion strength.
Definition Fluids.cpp:246
void setSupportRadius(float h)
Set SPH support radius.
Definition Fluids.cpp:250
int getParticleCount() const
Returns the particle count.
Definition Fluids.h:105
~FluidSimulator()
Fluid simulator.
Definition Fluids.cpp:66
void readDensities(std::vector< float > &out) const
Copy per-particle densities out (CPU mirror).
Definition Fluids.cpp:236
void setGravity(float x, float y, float z)
Set the gravity vector.
Definition Fluids.cpp:242
int getMaxParticles() const
Returns the max particles.
Definition Fluids.h:109
void setCohesion(float cohesion)
Set Akinci-style fluid-fluid cohesion strength.
Definition Fluids.cpp:245
Buffer-level screen-space fluid renderer.
void setMode(int mode)
Switch shading: 0 water, 1 mud.
void renderFrom(FluidSimulator *sim)
Script-friendly wrapper: render(*sim) with a null check.
Fluids module — factory + script binding.
Definition Fluids.h:203
~Fluids() override
Fluids.
int getSimulatorCount() const
Returns the simulator count.
Definition Fluids.cpp:411
int getRendererCount() const
Returns the renderer count.
Definition Fluids.cpp:423
FluidSurfaceRenderer * newSurfaceRenderer(int width=160, int height=160)
Create a screen-space fluid renderer.
Definition Fluids.cpp:413
FluidSimulator * newSimulator(int maxParticles=8192)
Create a surface fluid simulator.
Definition Fluids.cpp:404
Uniform signed-distance voxel field over a box.
Definition FluidSdf.h:28
glm::ivec3 dims
Voxel resolution along each axis.
Definition FluidSdf.h:35
float cellSize
World-space size of one voxel.
Definition FluidSdf.h:33
glm::vec3 origin
World-space position of voxel (0,0,0).
Definition FluidSdf.h:31
std::vector< float > distances
Signed distances, dims.x * dims.y * dims.z floats.
Definition FluidSdf.h:37
static MeshSdf makeSphere(const glm::vec3 &center, float radius, const glm::ivec3 &dims)
Bake an analytic sphere into the field.
Definition FluidSdf.cpp:130
int voxelCount() const
Voxel count.
Definition FluidSdf.cpp:87
virtual void bindBuffer(int binding, GpuBuffer *buffer)=0
Bind a storage buffer to set=0 binding. binding in [0, kMaxBindings).
ComputeShader * newShader(const std::string &source)
Compatibility-only raw-owning shader factory (Vulkan: GLSL; WebGPU: WGSL). Vulkan delegates to the ch...
Definition Gpgpu.cpp:324
bool isAvailable() const
True when the active Graphics backend can run compute (device initialized).
Definition Gpgpu.cpp:284
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Definition Gpgpu.cpp:372
Sequence * newSequence()
Create a Kompute-style command Sequence: record buffer transfers and compute dispatches into one comm...
Definition Gpgpu.cpp:384
virtual void writeFloat32s(const float *data, int count, int startIndex=0)=0
Bulk float upload/download (one transfer). startIndex is in floats.
virtual void downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset=0) const =0
Downloads bytes.
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
Record upload.
Definition Sequence.cpp:77
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
Definition Sequence.cpp:82
void submit()
Submit.
Definition Sequence.cpp:92
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
Definition Sequence.cpp:87
void begin()
Begins begin.
Definition Sequence.cpp:75
bool isAvailable() const
True when available.
Definition Sequence.cpp:66
std::vector< ParamSpec > params
GLSL compute kernels for the GPU surface-flow solver.
Definition FluidTarget.h:12
const char * kFluidClearGrid
Zero the linked-list cell heads.
const char * kFluidComputeDelta
Accumulate PBF position deltas into the (reused) grad buffer.
const char * kFluidApplyDelta
Apply PBF deltas and re-project onto the SDF surface.
void exposeVolumeFluidType(ssq::Table &table)
Registers the script-owned volume-fluid class on the VM thread.
const char * kFluidDensityLambda
Density, gradient sum and PBF lambda in one pass (mirror computeDensitiesAndGrads + computeLambdas).
const char * kFluidIntegrate
Viscosity + cohesion + adhesion + gravity + integration + SDF projection.
const char * kFluidBuildGrid
Insert every particle into its grid cell (linked list via atomicExchange).
void exposeVolumeFluidFactory(ssq::Class &cls)
Registers volume-fluid factories on the existing Fluids class.
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
int groupsFor(int count)
Groups for.
bool enabled
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Duration delta
Fixed simulation duration for this step.
Definition Time.h:162
Tuning knobs of one fluid simulation.
Definition FluidMath.h:26
float damping
Linear air damping applied each substep.
Definition FluidMath.h:44
float supportRadius
SPH support radius h (kernel cutoff), typically 4x particleRadius.
Definition FluidMath.h:30
float particleRadius
Resting particle radius in world units.
Definition FluidMath.h:28
float adhesion
Fluid-surface adhesion strength (contact angle / sticking).
Definition FluidMath.h:42
int pbfIterations
PBF density-constraint relaxation passes per substep.
Definition FluidMath.h:50
float cohesion
Fluid-fluid cohesion strength (droplet formation).
Definition FluidMath.h:40
int iterations
Solver substeps per call to step(dt).
Definition FluidMath.h:48
float viscosity
XSPH viscosity strength (0 = inviscid).
Definition FluidMath.h:36
glm::vec3 gravity
Gravity vector in world units / s^2.
Definition FluidMath.h:34
Camera + reconstruction tuning for the SSF pipeline.
static SimGrid make(const MeshSdf &sdf, float cellSize)
Build a grid covering the SDF domain plus one cell of padding.
float cellSize
Cell size in world units (== SPH support radius).
int cellCount() const
Cell count.
glm::vec3 origin
World position of cell (0,0,0).
glm::ivec3 dims
Cell counts per axis.
Observable backend progress shared by CPU and accelerator providers.
Validated solver policy for one simulation step.