20#include <simplesquirrel/simplesquirrel.hpp>
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;
64 : sim_(maxParticles,
params), preferGpu_(preferGpu) {}
72 delete shDensityLambda_;
99 delete shDensityLambda_;
113 shClear_ = shBuild_ = shDensityLambda_ = shDelta_ = shApply_ = shIntegrate_ = shAdvance_ =
nullptr;
114 bufPos_ = bufVel_ = bufHead_ = bufNext_ = bufDens_ = bufLambda_ = bufGrad_ = bufSdf_ =
nullptr;
115 stagePos_ = stageVel_ = stageDens_ =
nullptr;
128void FluidSimulator::stepSolver(
float dt,
int substeps) {
130 if (preferGpu_ && !gpuOk_) ensureGpu();
132 sim_.
step(dt, substeps);
136 const int iters = std::max(1, substeps);
137 const float sub = dt / float(iters);
142 for (
int it = 0; it < iters; ++it) {
143 setCommonConstants(shClear_, sub);
145 setCommonConstants(shBuild_, sub);
147 setCommonConstants(shIntegrate_, sub);
149 setCommonConstants(shAdvance_, sub);
151 for (
int k = 0; k < pbf; ++k) {
152 setCommonConstants(shClear_, sub);
154 setCommonConstants(shBuild_, sub);
156 setCommonConstants(shDensityLambda_, sub);
158 setCommonConstants(shDelta_, sub);
160 setCommonConstants(shApply_, sub);
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();
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]);
180 std::vector<float>& dens = sim_.
densities();
181 for (
int i = 0; i < sim_.
particleCount(); ++i) dens[
size_t(i)] = densF[size_t(i)];
186 auto valid = eve::physics::detail::validateSimulationStep(stepValue,
settings, observation_);
188 auto next = eve::physics::detail::advanceSimulationObservation(observation_, stepValue);
192 if (preferGpu_ && !gpuOk_) {
193 bool available =
false;
195 available = ensureGpu();
200 backendFallback_ =
true;
205 "fluids.simulationBackend", {{
"selected",
"cpu"}, {
"fallback",
"explicit"}})});
209 }
catch (
const std::exception&
error) {
212 "fluids.simulationBackend.step"));
216 "fluids.simulationBackend.step"));
218 observation_ = std::move(next).takeValue();
224 eve::physics::detail::validateSimulationObservation(
observation,
"fluids.simulationBackend.restoreObservation");
259bool FluidSimulator::ensureGpu() {
260 if (gpuOk_)
return true;
261 gpgpu_ = eve::gpgpu::Gpgpu::create();
262 if (!gpgpu_ || !gpgpu_->
isAvailable())
return false;
274 bufPos_ = gpgpu_->
newBuffer(
max * 4 *
int(
sizeof(
float)),
"storage");
275 bufVel_ = gpgpu_->
newBuffer(
max * 4 *
int(
sizeof(
float)),
"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");
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");
327void FluidSimulator::uploadSdf() {
328 if (!gpuOk_ || !bufSdf_ || !sdfDirty_)
return;
334void FluidSimulator::uploadParticles() {
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();
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;
349 seq_->
recordUpload(bufPos_, posF.data(), uint64_t(posF.size()) *
sizeof(
float));
350 seq_->
recordUpload(bufVel_, velF.data(), uint64_t(velF.size()) *
sizeof(
float));
353void FluidSimulator::downloadParticles() {
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));
361void FluidSimulator::setCommonConstants(gpgpu::ComputeShader*
shader,
float dt) {
363 const FluidParams&
p = sim_.
params();
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));
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));
405 auto sim = std::make_unique<FluidSimulator>(maxParticles,
FluidParams{},
true);
407 simulators_.push_back(std::move(sim));
417 auto r = std::make_unique<FluidSurfaceRenderer>(
params,
true);
419 renderers_.push_back(std::move(
r));
425void Fluids::expose(ssq::Table& table) {
426 auto cls = table.addClass(
name, Fluids::create,
false);
428 cls.addFunc(
"fluidRendererSettingsDefaults", [
vm = table.getHandle()](
Fluids*) {
429 return eve::script::projectStatusResult(vm, eve::Status::success(),
430 encodeFluidRendererSettings(FluidRendererSettings{}));
434 auto sim = table.addClass<FluidSimulator>(
435 "FluidSim", std::function<FluidSimulator*()>([]() -> FluidSimulator* {
return nullptr; }),
true);
451 auto surf = table.addClass<FluidSurfaceRenderer>(
452 "FluidSurface", std::function<FluidSurfaceRenderer*()>([]() -> FluidSurfaceRenderer* {
return nullptr; }),
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) {
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));
465 surf.addFunc(
"renderVolumeColorOnly",
466 [](FluidSurfaceRenderer* renderer, VolumeFluid* solver) { renderer->renderVolumeColorOnly(*solver); });
467 surf.addFunc(
"renderGasVolume", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluid* 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));
475 surf.addFunc(
"renderVolumeWithoutSurface", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
476 VolumeFluid* solver,
float absorption) {
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));
484 "renderConfiguredVolume", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluid* 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));
491 surf.addFunc(
"prepare", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer) {
492 return eve::script::projectResult(vm, renderer->prepare());
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));
498 surf.addFunc(
"renderVolumeColorToTexture", [
vm =
table.getHandle()](
499 FluidSurfaceRenderer* renderer, VolumeFluid* solver,
500 graphics::Graphics* graphics, graphics::Texture* texture) {
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));
507 surf.addFunc(
"compositeDiffuse", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer, VolumeFluidDiffuse* 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));
515 surf.addFunc(
"configureFoam",
516 [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool enabled,
int downsample) {
517 return eve::script::projectResult(vm, renderer->configureFoam(enabled, downsample));
519 surf.addFunc(
"compositeSceneRefraction", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
520 image::ImageData* scene,
float distortion,
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));
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));
538 surf.addFunc(
"configureRefractionEnabled", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool enabled) {
539 return eve::script::projectResult(vm, renderer->configureRefractionEnabled(enabled));
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())));
552 surf.addFunc(
"configureSurfaceBlend",
553 [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
int source,
int destination) {
554 return eve::script::projectResult(vm, renderer->configureSurfaceBlend(source, destination));
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));
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())));
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));
578 surf.addFunc(
"configureSurfaceEnabled", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool enabled) {
579 return eve::script::projectResult(vm, renderer->configureSurfaceEnabled(enabled));
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());
587 return eve::script::projectResult(vm, eve::Result<void>::failure(settings.status()));
588 return eve::script::projectResult(vm, renderer->configureRendererSettings(settings.value()));
590 surf.addFunc(
"rendererSettings", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer) {
591 return eve::script::projectStatusResult(vm, eve::Status::success(),
592 encodeFluidRendererSettings(renderer->rendererSettings()));
594 surf.addFunc(
"configureSurfaceBlurRadius", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
float radius) {
595 return eve::script::projectResult(vm, renderer->configureSurfaceBlurRadius(radius));
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));
603 surf.addFunc(
"configureReflection", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool enabled) {
604 return eve::script::projectResult(vm, renderer->configureReflection(enabled));
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}));
610 surf.addFunc(
"configureAnisotropy", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool enabled) {
613 surf.addFunc(
"configureSurfaceDownsample", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
int factor) {
616 surf.addFunc(
"configureThicknessDownsample", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
int factor) {
619 surf.addFunc(
"configureProjection", [
vm =
table.getHandle()](FluidSurfaceRenderer* renderer,
bool orthographic,
620 float verticalHalfSize) {
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());
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);
#define Module_IMPL(ModuleName, newExpr)
The single Squirrel projection for common Result, Status and Value.
TerrainThermalSettings settings
const UnitySourceAsset & source
static Diagnostic warning(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct a warning diagnostic.
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.
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Structured status and zero or more diagnostics for an operation.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
int maxParticles() const
Max particles.
void setSdf(const MeshSdf &sdf)
Replace the collision surface. Particles keep their state.
int spawnDrop(const glm::vec3 ¢er, 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).
eve::Result< void > restoreObservation(const eve::physics::SimulationObservation &observation) override
Restores tick/progress metadata after an owner-level restore.
void setParticleRadius(float radius)
Set particle radius (support radius is unchanged).
void setViscosity(float viscosity)
Set XSPH viscosity strength.
void setSdf(const MeshSdf &sdf)
Replace the collision surface (re-uploads the SDF on the GPU path).
void readPositions(std::vector< glm::vec3 > &out) const
Copy live particle positions out (CPU mirror; GPU path downloads).
eve::physics::SimulationObservation observation() const noexcept override
Returns completed tick/time observables.
FluidSimulator(int maxParticles, const FluidParams ¶ms, bool preferGpu)
Fluid simulator.
void setPbfIterations(int passes)
Set PBF density-constraint passes per substep.
bool usingGpu() const
Using gpu.
void step(float dt)
Advance the simulation by dt seconds.
const MeshSdf & sdf() const
Sdf.
void setSdfSphere(float cx, float cy, float cz, float radius, int res)
Convenience: bake a sphere SDF around the origin.
int spawnDrop(const glm::vec3 ¢er, float radius, int count)
Spawn a drop of particles near the surface.
void setDamping(float damping)
Set linear air damping.
void setAdhesion(float adhesion)
Set fluid-surface adhesion strength.
void setSupportRadius(float h)
Set SPH support radius.
int getParticleCount() const
Returns the particle count.
~FluidSimulator()
Fluid simulator.
void readDensities(std::vector< float > &out) const
Copy per-particle densities out (CPU mirror).
void setGravity(float x, float y, float z)
Set the gravity vector.
int getMaxParticles() const
Returns the max particles.
void setCohesion(float cohesion)
Set Akinci-style fluid-fluid cohesion strength.
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.
~Fluids() override
Fluids.
int getSimulatorCount() const
Returns the simulator count.
int getRendererCount() const
Returns the renderer count.
FluidSurfaceRenderer * newSurfaceRenderer(int width=160, int height=160)
Create a screen-space fluid renderer.
FluidSimulator * newSimulator(int maxParticles=8192)
Create a surface fluid simulator.
Uniform signed-distance voxel field over a box.
glm::ivec3 dims
Voxel resolution along each axis.
float cellSize
World-space size of one voxel.
glm::vec3 origin
World-space position of voxel (0,0,0).
std::vector< float > distances
Signed distances, dims.x * dims.y * dims.z floats.
static MeshSdf makeSphere(const glm::vec3 ¢er, float radius, const glm::ivec3 &dims)
Bake an analytic sphere into the field.
int voxelCount() const
Voxel count.
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...
bool isAvailable() const
True when the active Graphics backend can run compute (device initialized).
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Sequence * newSequence()
Create a Kompute-style command Sequence: record buffer transfers and compute dispatches into one comm...
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.
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
void begin()
Begins begin.
bool isAvailable() const
True when available.
std::vector< ParamSpec > params
GLSL compute kernels for the GPU surface-flow solver.
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()
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.
One deterministic fixed-step emitted by SimulationClock.
Duration delta
Fixed simulation duration for this step.
Tuning knobs of one fluid simulation.
float damping
Linear air damping applied each substep.
float supportRadius
SPH support radius h (kernel cutoff), typically 4x particleRadius.
float particleRadius
Resting particle radius in world units.
float adhesion
Fluid-surface adhesion strength (contact angle / sticking).
int pbfIterations
PBF density-constraint relaxation passes per substep.
float cohesion
Fluid-fluid cohesion strength (droplet formation).
int iterations
Solver substeps per call to step(dt).
float viscosity
XSPH viscosity strength (0 = inviscid).
glm::vec3 gravity
Gravity vector in world units / s^2.
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.