From 92f5f09a9d0c1f8bd1c0f62978d438a67430f7e0 Mon Sep 17 00:00:00 2001 From: Nghia Truong Date: Tue, 12 Nov 2019 18:53:51 -0700 Subject: [PATCH] Add FluidSimulation2D example --- CMakeLists.txt | 1 + src/CMakeLists.txt | 4 + src/fluidsimulation2d/CMakeLists.txt | 87 +++ .../DataStructures/Array2X.h | 185 +++++++ .../DataStructures/MathHelpers.h | 104 ++++ .../DataStructures/PCGSolver.h | 276 ++++++++++ .../DataStructures/SDFObject.h | 114 ++++ .../DataStructures/SparseMatrix.h | 120 ++++ .../DrawableObjects/FlatShadeObject2D.h | 67 +++ .../DrawableObjects/ParticleGroup2D.cpp | 77 +++ .../DrawableObjects/ParticleGroup2D.h | 75 +++ .../DrawableObjects/WireframeObject2D.h | 65 +++ .../FluidSimulation2DExample.cpp | 480 ++++++++++++++++ .../FluidSolver/ApicSolver2D.cpp | 520 ++++++++++++++++++ .../FluidSolver/ApicSolver2D.h | 81 +++ .../FluidSolver/SolverData.h | 192 +++++++ .../Shaders/ParticleSphereShader2D.cpp | 98 ++++ .../Shaders/ParticleSphereShader2D.frag | 39 ++ .../Shaders/ParticleSphereShader2D.h | 67 +++ .../Shaders/ParticleSphereShader2D.vert | 70 +++ .../SourceSansPro-Regular.ttf | Bin 0 -> 269108 bytes src/fluidsimulation2d/resources.conf | 12 + 22 files changed, 2734 insertions(+) create mode 100644 src/fluidsimulation2d/CMakeLists.txt create mode 100644 src/fluidsimulation2d/DataStructures/Array2X.h create mode 100644 src/fluidsimulation2d/DataStructures/MathHelpers.h create mode 100644 src/fluidsimulation2d/DataStructures/PCGSolver.h create mode 100644 src/fluidsimulation2d/DataStructures/SDFObject.h create mode 100644 src/fluidsimulation2d/DataStructures/SparseMatrix.h create mode 100644 src/fluidsimulation2d/DrawableObjects/FlatShadeObject2D.h create mode 100644 src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.cpp create mode 100644 src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.h create mode 100644 src/fluidsimulation2d/DrawableObjects/WireframeObject2D.h create mode 100644 src/fluidsimulation2d/FluidSimulation2DExample.cpp create mode 100644 src/fluidsimulation2d/FluidSolver/ApicSolver2D.cpp create mode 100644 src/fluidsimulation2d/FluidSolver/ApicSolver2D.h create mode 100644 src/fluidsimulation2d/FluidSolver/SolverData.h create mode 100644 src/fluidsimulation2d/Shaders/ParticleSphereShader2D.cpp create mode 100644 src/fluidsimulation2d/Shaders/ParticleSphereShader2D.frag create mode 100644 src/fluidsimulation2d/Shaders/ParticleSphereShader2D.h create mode 100644 src/fluidsimulation2d/Shaders/ParticleSphereShader2D.vert create mode 100644 src/fluidsimulation2d/SourceSansPro-Regular.ttf create mode 100644 src/fluidsimulation2d/resources.conf diff --git a/CMakeLists.txt b/CMakeLists.txt index cf54de96e..5739ec1f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(WITH_BOX2D_EXAMPLE "Build Box2D integration example" OFF) option(WITH_BULLET_EXAMPLE "Build Bullet integration example (requires the BulletIntegration library)" OFF) cmake_dependent_option(WITH_CUBEMAP_EXAMPLE "Build CubeMap example (requires some JPEG importer plugin)" OFF "NOT MAGNUM_TARGET_GLES" OFF) option(WITH_DART_EXAMPLE "Build DART integration example (requires the DartIntegration library)" OFF) +option(WITH_FLUIDSIMULATION2D_EXAMPLE "Build 2D Fluid Simulation example (requires the ImGui integration)" OFF) option(WITH_FLUIDSIMULATION3D_EXAMPLE "Build 3D Fluid Simulation example (requires the ImGui integration)" OFF) option(WITH_IMGUI_EXAMPLE "Build ImGui example" OFF) option(WITH_LEAPMOTION_EXAMPLE "Build LeapMotion example" OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a98cc148..7d7165605 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,10 @@ if(WITH_DART_EXAMPLE) add_subdirectory(dart) endif() +if(WITH_FLUIDSIMULATION2D_EXAMPLE) + add_subdirectory(fluidsimulation2d) +endif() + if(WITH_FLUIDSIMULATION3D_EXAMPLE) add_subdirectory(fluidsimulation3d) endif() diff --git a/src/fluidsimulation2d/CMakeLists.txt b/src/fluidsimulation2d/CMakeLists.txt new file mode 100644 index 000000000..fe2d3a629 --- /dev/null +++ b/src/fluidsimulation2d/CMakeLists.txt @@ -0,0 +1,87 @@ +# +# This file is part of Magnum. +# +# Original authors — credit is appreciated but not required: +# +# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — +# Vladimír Vondruš +# 2019 — Nghia Truong +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or distribute +# this software, either in source code form or as a compiled binary, for any +# purpose, commercial or non-commercial, and by any means. +# +# In jurisdictions that recognize copyright laws, the author or authors of +# this software dedicate any and all copyright interest in the software to +# the public domain. We make this dedication for the benefit of the public +# at large and to the detriment of our heirs and successors. We intend this +# dedication to be an overt act of relinquishment in perpetuity of all +# present and future rights to this software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +cmake_minimum_required(VERSION 3.4) +project(magnum-fluidsimulation2d) + +# Add module path in case this is project root +if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../modules/" ${CMAKE_MODULE_PATH}) +endif() + +find_package(Corrade REQUIRED Main) +find_package(Magnum REQUIRED + GL + MeshTools + Primitives + SceneGraph + Shaders + Sdl2Application) +find_package(MagnumIntegration REQUIRED ImGui) + +set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) + +corrade_add_resource(FluidSimulation2D_RESOURCES resources.conf) + +add_executable(magnum-fluidsimulation2d WIN32 + FluidSimulation2DExample.cpp + DataStructures/Array2X.h + DataStructures/MathHelpers.h + DataStructures/PCGSolver.h + DataStructures/SDFObject.h + DataStructures/SparseMatrix.h + DrawableObjects/FlatShadeObject2D.h + DrawableObjects/ParticleGroup2D.h + DrawableObjects/ParticleGroup2D.cpp + DrawableObjects/WireframeObject2D.h + FluidSolver/SolverData.h + FluidSolver/ApicSolver2D.h + FluidSolver/ApicSolver2D.cpp + Shaders/ParticleSphereShader2D.h + Shaders/ParticleSphereShader2D.cpp + ${FluidSimulation2D_RESOURCES}) +target_link_libraries(magnum-fluidsimulation2d PRIVATE + Corrade::Main + Magnum::Application + Magnum::GL + Magnum::Magnum + Magnum::MeshTools + Magnum::Primitives + Magnum::SceneGraph + Magnum::Shaders + MagnumIntegration::ImGui) +target_include_directories(magnum-fluidsimulation2d PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}) + +install(TARGETS magnum-fluidsimulation2d DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) + +# Make the executable a default target to build & run in Visual Studio +set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT magnum-fluidsimulation2d) diff --git a/src/fluidsimulation2d/DataStructures/Array2X.h b/src/fluidsimulation2d/DataStructures/Array2X.h new file mode 100644 index 000000000..81aeda264 --- /dev/null +++ b/src/fluidsimulation2d/DataStructures/Array2X.h @@ -0,0 +1,185 @@ +#ifndef Magnum_Examples_FluidSimulation2D_Array2X_h +#define Magnum_Examples_FluidSimulation2D_Array2X_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "MathHelpers.h" + +namespace Magnum { namespace Examples { +template +class Array2X { +public: + Array2X() = default; + Array2X(std::size_t nx, std::size_t ny) : _size{nx, ny}, _data(nx * ny) {} + Array2X(std::size_t nx, std::size_t ny, const T& value) : _size{nx, ny}, _data(nx * ny, value) {} + + Array2X& operator=(const Array2X& other) { + if(other.size_x() != size_x() || other.size_y() != size_y()) { + Fatal{} << "Copy array with different size!"; + } + std::memcpy(data(), other.data(), count() * sizeof(T)); + return *this; + } + + /******************************* Accessors *******************************/ + template + const T& operator()(IntType i, IntType j) const { + assert(i >= 0 + && j >= 0 + && static_cast(i) < _size[0] + && static_cast(j) < _size[1]); + return _data[i + _size[0] * j]; + } + + template + T& operator()(IntType i, IntType j) { + assert(i >= 0 + && j >= 0 + && static_cast(i) < _size[0] + && static_cast(j) < _size[1]); + return _data[i + _size[0] * j]; + } + + template + const T& operator()(const Math::Vector2& coord) const { return (*this)(coord[0], coord[1]); } + + template + T& operator()(const Math::Vector2& coord) { return (*this)(coord[0], coord[1]); } + + const T* data() const { return _data.data(); } + T* data() { return _data.data(); } + + std::size_t size_x() const { return _size[0]; } + std::size_t size_y() const { return _size[1]; } + std::size_t count() const { return _data.size(); } + + /******************************* Modifiers *******************************/ + void assign(const T& value) { _data.assign(_data.size(), value); } + void set_zero() { _data.assign(_data.size(), T(0)); } + + template + void resize(IntType nx, IntType ny) { + _size[0] = static_cast(nx); + _size[1] = static_cast(ny); + _data.resize(_size[0] * _size[1]); + } + + template + void resize(IntType nx, IntType ny, const T& value) { + _size = { static_cast(nx), static_cast(ny) }; + _data.resize(_size[0] * _size[1], value); + } + + void swapContent(Array2X& other) { + /* Only allow to swap content of array having the same sizes */ + if(other.size_x() != size_x() || other.size_y() != size_y()) { + Fatal{} << "Swap content of arrays having different sizes!"; + } + _data.swap(other._data); + } + + /*************************** Data manimupations ***************************/ + template + void loop1D(Function&& func) const { + for(std::size_t i = 0, iend = count(); i < iend; ++i) { + func(i); + } + } + + template + void loop2D(Function&& func) const { + for(std::size_t j = 0; j < size_y(); ++j) { + for(std::size_t i = 0; i < size_x(); ++i) { + func(i, j); + } + } + } + + T interpolateValue(const Math::Vector2& point) const { + Int i, j; + T fx, fy; + barycentric(point[0], i, fx, 0, Int(size_x())); + barycentric(point[1], j, fy, 0, Int(size_y())); + T v00 = (*this)(i, j); + T v10 = (*this)(i + 1, j); + T v01 = (*this)(i, j + 1); + T v11 = (*this)(i + 1, j + 1); + return bilerp(v00, v10, v01, v11, fx, fy); + } + + Math::Vector2 affineInterpolateValue(const Math::Vector2& point) const { + Int i, j; + T fx, fy; + barycentric(point[0], i, fx, 0, Int(size_x())); + barycentric(point[1], j, fy, 0, Int(size_y())); + T v00 = (*this)(i, j); + T v10 = (*this)(i + 1, j); + T v01 = (*this)(i, j + 1); + T v11 = (*this)(i + 1, j + 1); + return bilerpGradient(v00, v10, v01, v11, fx, fy); + } + + Math::Vector2 interpolateGradient(const Math::Vector2& point) const { + Int i, j; + T fx, fy; + barycentric(point[0], i, fx, 0, Int(size_x())); + barycentric(point[1], j, fy, 0, Int(size_y())); + T v00 = (*this)(i, j); + T v01 = (*this)(i, j + 1); + T v10 = (*this)(i + 1, j); + T v11 = (*this)(i + 1, j + 1); + T ddy0 = (v01 - v00); + T ddy1 = (v11 - v10); + T ddx0 = (v10 - v00); + T ddx1 = (v11 - v01); + + Math::Vector2 grad( + Math::lerp(ddx0, ddx1, fy), + Math::lerp(ddy0, ddy1, fx) + ); + const auto magSqr = grad.dot(); + if(magSqr > T(1e-20)) { + grad /= std::sqrt(magSqr); + } + return grad; + } + +private: + std::size_t _size[2] { 0, 0 }; + std::vector _data; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DataStructures/MathHelpers.h b/src/fluidsimulation2d/DataStructures/MathHelpers.h new file mode 100644 index 000000000..557031a9a --- /dev/null +++ b/src/fluidsimulation2d/DataStructures/MathHelpers.h @@ -0,0 +1,104 @@ +#ifndef Magnum_Examples_FluidSimulation2D_MathHelpers_h +#define Magnum_Examples_FluidSimulation2D_MathHelpers_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +namespace Magnum { namespace Examples { +template +inline T fractionInside(T phi_left, T phi_right) { + if(phi_left < 0 && phi_right < 0) { + return T(1); + } + if(phi_left < 0 && phi_right >= 0) { + return phi_left / (phi_left - phi_right); + } + if(phi_left >= 0 && phi_right < 0) { + return phi_right / (phi_right - phi_left); + } else { + return T(0); + } +} + +template +inline void barycentric(T x, int& i, T& f, int i_low, int i_high) { + T s = std::floor(x); + i = static_cast(s); + if(i < i_low) { + i = i_low; + f = 0; + } else if(i > i_high - 2) { + i = i_high - 2; + f = 1; + } else { + f = T(x - s); + } +} + +template +inline T bilerp(const T& v00, const T& v10, + const T& v01, const T& v11, + T fx, T fy) { + return Math::lerp(Math::lerp(v00, v10, fx), + Math::lerp(v01, v11, fx), + fy); +} + +template +inline Math::Vector2 bilerpGradient(const T& v00, const T& v10, + const T& v01, const T& v11, + T fx, T fy) { + const Math::Vector2 f00(fy - T(1), fx - T(1)); + const Math::Vector2 f10(T(1) - fy, -fx); + const Math::Vector2 f01(-fy, T(1) - fx); + const Math::Vector2 f11(fy, fx); + return f00 * v00 + f10 * v10 + f01 * v01 + f11 * v11; +} + +template +inline T smoothKernel(T r2, T h2) { + const auto t = T(1) - r2 / h2; + const auto t_exp3 = t * t * t; + return Math::max(t_exp3, T(0)); +} + +template +inline T linearKernel(const Math::Vector2& d, T hInv) { + const auto tx = T(1) - std::abs(d.x() * hInv); + const auto ty = T(1) - std::abs(d.y() * hInv); + return Math::max(tx * ty, T(0)); +} +} } + +#endif diff --git a/src/fluidsimulation2d/DataStructures/PCGSolver.h b/src/fluidsimulation2d/DataStructures/PCGSolver.h new file mode 100644 index 000000000..ad6ae7d18 --- /dev/null +++ b/src/fluidsimulation2d/DataStructures/PCGSolver.h @@ -0,0 +1,276 @@ +#ifndef Magnum_Examples_FluidSimulation2D_PCGSolver_h +#define Magnum_Examples_FluidSimulation2D_PCGSolver_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "SparseMatrix.h" + +namespace Magnum { namespace Examples { +template +class PCGSolver { +public: + PCGSolver(T toleranceFactor_ = T(1e-10), uint32_t maxIterations_ = 1000) : + _toleranceFactor{toleranceFactor_}, _maxIterations{maxIterations_} {} + + bool solve(SparseMatrix& matrix, const std::vector& rhs, std::vector& result) { + uint32_t rows = matrix.size; + if(rows == 0) { + return false; + } + if(_m.size() != rows) { + _m.resize(rows); + _s.resize(rows); + _z.resize(rows); + _r.resize(rows); + } + + _r = rhs; + _lastResidual = maxAbs(_r); + if(!(_lastResidual > 0)) { + _lastIterationCount = 0; + return true; + } + + formPreconditioner(matrix); + applyPreconditioner(_r, _z); + T rho = dotProduct(_z, _r); + if(!(rho > 0) || rho != rho) { + _lastIterationCount = 0; + return false; + } + + _s = _z; + matrix.compressData(); /* must prepare compact data for Matrix * Vector operation */ + + const T tolerance = _toleranceFactor * _lastResidual; + uint32_t iter { 0 }; + for(; iter < _maxIterations; ++iter) { + matrix.multiply(_s, _z); + const T alpha = rho / dotProduct(_s, _z); + addScaled(alpha, _s, result); + addScaled(-alpha, _z, _r); + _lastResidual = maxAbs(_r); + if(_lastResidual < tolerance) { + _lastIterationCount = iter + 1; + return true; + } + applyPreconditioner(_r, _z); + const T rho_new = dotProduct(_z, _r); + const T beta = rho_new / rho; + addScaled(beta, _s, _z); + _s.swap(_z); + rho = rho_new; + } + + /* Failed to converge */ + _lastIterationCount = _maxIterations; + return false; + } + + /* API to query last solve */ + uint32_t lastIterationCount() const { return _lastIterationCount; } + T lastResidual() const { return _lastResidual; } + +private: + void formPreconditioner(const SparseMatrix& matrix) { + static constexpr T s_modification_parameter = T(0.97); + + const auto size = matrix.size; + _precond.reset(size); + + /* Copy data from the matrix */ + for(uint32_t i = 0; i < size; ++i) { + _precond.colStartIdx[i] = static_cast(_precond.colIndices.size()); + const auto& indices = matrix.rowIndices[i]; + const auto& values = matrix.rowValues[i]; + for(uint32_t j = 0, jend = indices.size(); j < jend; ++j) { + if(indices[j] > i) { + _precond.colIndices.push_back(indices[j]); + _precond.colValues.push_back(values[j]); + } else if(indices[j] == i) { + _precond.invdiag[i] = values[j]; + } + } + } + _precond.colStartIdx[size] = static_cast(_precond.colIndices.size()); + + for(uint32_t k = 0; k < size; ++k) { + auto invdiag = _precond.invdiag[k]; + if(invdiag == T(0)) { + continue; /* null row/column */ + } + invdiag = T(1) / std::sqrt(invdiag); + _precond.invdiag[k] = invdiag; + + const auto pStart = _precond.colStartIdx[k]; + const auto pEnd = _precond.colStartIdx[k + 1]; + for(uint32_t p = pStart; p < pEnd; ++p) { + _precond.colValues[p] *= invdiag; + } + + /* Process the lower elements of column k */ + for(uint32_t p = pStart; p < pEnd; ++p) { + const auto j = _precond.colIndices[p]; + const auto multiplier = _precond.colValues[p]; + T missing = 0; + uint32_t a = pStart; + + uint32_t b = 0; + while(a < pEnd && _precond.colIndices[a] < j) { + while(b < matrix.rowIndices[j].size()) { + if(matrix.rowIndices[j][b] < _precond.colIndices[a]) { + ++b; + } else if(matrix.rowIndices[j][b] == _precond.colIndices[a]) { + break; + } else { + missing += _precond.colValues[a]; + break; + } + } + ++a; + } + + invdiag = _precond.invdiag[j]; + if(a < pEnd && _precond.colIndices[a] == j) { + invdiag -= multiplier * _precond.colValues[a]; + } + ++a; + + b = _precond.colStartIdx[j]; + const auto jEnd = _precond.colStartIdx[j + 1]; + while(a < pEnd && b < jEnd) { + if(_precond.colIndices[b] < _precond.colIndices[a]) { + ++b; + } else if(_precond.colIndices[b] == _precond.colIndices[a]) { + _precond.colValues[b] -= multiplier * _precond.colValues[a]; + ++a; + ++b; + } else { + missing += _precond.colValues[a++]; + } + } + + while(a < pEnd) { + missing += _precond.colValues[a++]; + } + + _precond.invdiag[j] = invdiag - s_modification_parameter * multiplier * missing; + } + } + } + + void applyPreconditioner(const std::vector& rhs, std::vector& result) const { + const uint32_t rows = _precond.rows; + + /* Solve L * result = rhs */ + result = rhs; + for(uint32_t i = 0; i < rows; ++i) { + const auto colStart = _precond.colStartIdx[i]; + const auto colEnd = _precond.colStartIdx[i + 1]; + const auto tmp = result[i] * _precond.invdiag[i]; + result[i] = tmp; + for(uint32_t j = colStart; j < colEnd; ++j) { + result[_precond.colIndices[j]] -= _precond.colValues[j] * tmp; + } + } + + /* solve L^T * result = result */ + uint32_t i = rows; + do { + --i; + const auto colStart = _precond.colStartIdx[i]; + const auto colEnd = _precond.colStartIdx[i + 1]; + auto tmp = result[i]; + for(uint32_t j = colStart; j < colEnd; ++j) { + tmp -= _precond.colValues[j] * result[_precond.colIndices[j]]; + } + tmp *= _precond.invdiag[i]; + result[i] = tmp; + } while (i != 0); + } + + T dotProduct(const std::vector& x, const std::vector& y) const { + T sum = 0; + for(std::size_t i = 0; i < x.size(); ++i) { + sum += x[i] * y[i]; + } + return sum; + } + + T maxAbs(const std::vector& x) const { + T maxVal = 0; + for(std::size_t i = 0; i < x.size(); ++i) { + const auto absVal = std::abs(x[i]); + if(absVal > maxVal) { + maxVal = absVal; + } + } + return maxVal; + } + + void addScaled(T alpha, const std::vector& x, std::vector& y) const { + for(std::size_t i = 0; i < x.size(); ++i) { + y[i] += alpha * x[i]; + } + } + + /* Solver parameters */ + const uint32_t _maxIterations; + const T _toleranceFactor; + + /* Preconditioner - triangular matrix */ + struct { + uint32_t rows; + std::vector invdiag; /* inverse of diagonal elements */ + std::vector colValues; /* values below the diagonal, listed column by column */ + std::vector colIndices; /* a list of all row indices, for each column in turn */ + std::vector colStartIdx; /* where each column begins in rowindex (plus an extra entry at the end, of #nonzeros) */ + + void reset(uint32_t rows_) { + rows = rows_; + invdiag.assign(rows, T(0)); /* important: must set zero */ + colValues.resize(0); + colIndices.resize(0); + colStartIdx.resize(rows + 1); + } + } _precond; + + /* Solver temporary variables */ + std::vector _m, _z, _s, _r; + + /* Status of last solve */ + uint32_t _lastIterationCount { 0 }; + T _lastResidual { 0 }; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DataStructures/SDFObject.h b/src/fluidsimulation2d/DataStructures/SDFObject.h new file mode 100644 index 000000000..1414308f8 --- /dev/null +++ b/src/fluidsimulation2d/DataStructures/SDFObject.h @@ -0,0 +1,114 @@ +#ifndef Magnum_Examples_FluidSimulation2D_SDFObject_h +#define Magnum_Examples_FluidSimulation2D_SDFObject_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +namespace Magnum { namespace Examples { +struct SDFObject { + enum class ObjectType { + /* Basic primitives */ + Circle = 0, + Box, + + /* Boolean operations */ + Intersection, + Subtraction, + Union + }; + + SDFObject() : center{0, 0}, radii{1}, type{ObjectType::Circle}, negativeInside{true} {} + + SDFObject(const Vector2& center_, Float radius_, ObjectType type_, bool negativeInside_ = true) : + SDFObject(center_, Vector2{ radius_, 0 }, type_, negativeInside_) {} + + SDFObject(const Vector2& center_, const Vector2& radii_, ObjectType type_, bool negativeInside_ = true) : + center{center_}, radii{radii_}, type{type_}, negativeInside{negativeInside_} { + if(type != ObjectType::Circle + && type != ObjectType::Box) { + Fatal{} << "Invalid object type"; + } + } + + SDFObject(SDFObject* obj1_, SDFObject* obj2_, ObjectType type_) : + type{type_}, obj1{obj1_}, obj2{obj2_} { + if(type != ObjectType::Intersection + && type != ObjectType::Subtraction + && type != ObjectType::Union) { + Fatal{} << "Invalid boolean operation"; + } + } + + Float signedDistance(const Vector2& pos) const { + switch(type) { + case ObjectType::Circle: { + const auto dist = (pos - center).length() - radii[0]; + return negativeInside ? dist : -dist; + } + case ObjectType::Box: { + const auto dx = std::abs(pos[0] - center[0]) - radii[0]; + const auto dy = std::abs(pos[1] - center[1]) - radii[1]; + Float dist { 0 }; + if(dx < 0 && dy < 0) { + dist = Math::max(dx, dy); + } else { + const Float dax = Math::max(dx, 0.0f); + const Float day = Math::max(dy, 0.0f); + dist = std::sqrt(dax * dax + day * day); + } + return negativeInside ? dist : -dist; + } + + case ObjectType::Intersection: + return Math::max(obj1->signedDistance(pos), obj2->signedDistance(pos)); + case ObjectType::Subtraction: + return Math::max(obj1->signedDistance(pos), -obj2->signedDistance(pos)); + case ObjectType::Union: + return Math::min(obj1->signedDistance(pos), obj2->signedDistance(pos)); + } + return 0; + } + + Vector2 center; + Vector2 radii; + ObjectType type; + bool negativeInside { true }; + + Containers::Pointer obj1 { nullptr }; + Containers::Pointer obj2 { nullptr }; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DataStructures/SparseMatrix.h b/src/fluidsimulation2d/DataStructures/SparseMatrix.h new file mode 100644 index 000000000..9643bc6b2 --- /dev/null +++ b/src/fluidsimulation2d/DataStructures/SparseMatrix.h @@ -0,0 +1,120 @@ +#ifndef Magnum_Examples_FluidSimulation2D_SparseMatrix_h +#define Magnum_Examples_FluidSimulation2D_SparseMatrix_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +namespace Magnum { namespace Examples { +template +struct SparseMatrix { + explicit SparseMatrix(uint32_t size_ = 0) { resize(size_); } + + void resize(std::size_t size_) { + size = size_; + rowIndices.resize(size); + rowValues.resize(size); + rowStartIdx.resize(size + 1); + } + + void clear() { + for(auto& indices : rowIndices) { + indices.resize(0); + } + for(auto& vals : rowValues) { + vals.resize(0); + } + } + + template + void addToElement(IntType i_, IntType j_, U inc_val_) { + const auto i = static_cast(i_); + const auto j = static_cast(j_); + const auto val = static_cast(inc_val_); + auto& currentRowIndices = rowIndices[i]; + + auto iter = std::lower_bound(currentRowIndices.begin(), currentRowIndices.end(), j); + if((iter != currentRowIndices.end()) && (*iter == j)) { + const auto k = std::distance(currentRowIndices.begin(), iter); + rowValues[i][k] += val; + } else { + auto insert_sorted_vector = + [](std::vector& vec, uint32_t item) { + return vec.insert(std::upper_bound(vec.begin(), vec.end(), item), item); + }; + iter = insert_sorted_vector(currentRowIndices, j); + const auto k = std::distance(currentRowIndices.begin(), iter); + rowValues[i].insert(rowValues[i].begin() + k, val); + } + } + + void compressData() { + rowStartIdx[0] = 0; + for(uint32_t i = 0; i < size; ++i) { + rowStartIdx[i + 1] = rowStartIdx[i] + static_cast(rowIndices[i].size()); + } + + auto copyData = [](auto& dst, const auto& src) { + dst.resize(0); + for(const auto& vec : src) { + dst.insert(dst.end(), vec.begin(), vec.end()); + } + }; + + copyData(compactIndices, rowIndices); + copyData(compactValues, rowValues); + } + + void multiply(const std::vector& x, std::vector& result) const { + result.resize(size); + for(uint32_t i = 0; i < size; ++i) { + T tmp = 0; + for(uint32_t j = rowStartIdx[i], jend = rowStartIdx[i + 1]; j < jend; ++j) { + tmp += compactValues[j] * x[compactIndices[j]]; + } + result[i] = tmp; + } + } + + uint32_t size; + + /* Sparse matrix data */ + std::vector> rowIndices; + std::vector> rowValues; + + /* Compact data */ + std::vector compactValues; + std::vector compactIndices; + std::vector rowStartIdx; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DrawableObjects/FlatShadeObject2D.h b/src/fluidsimulation2d/DrawableObjects/FlatShadeObject2D.h new file mode 100644 index 000000000..b8d5d8b4b --- /dev/null +++ b/src/fluidsimulation2d/DrawableObjects/FlatShadeObject2D.h @@ -0,0 +1,67 @@ +#ifndef Magnum_Examples_DrawableObjects_FlatShadeObject2D_h +#define Magnum_Examples_DrawableObjects_FlatShadeObject2D_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +namespace Magnum { namespace Examples { +using Object2D = SceneGraph::Object; + +class FlatShadeObject2D : public SceneGraph::Drawable2D { +public: + explicit FlatShadeObject2D(Object2D& object, Shaders::Flat2D& shader, const Color3& color, GL::Mesh& mesh, SceneGraph::DrawableGroup2D* const drawables) : + SceneGraph::Drawable2D{object, drawables}, _shader(shader), _color(color), _mesh(mesh) {} + + void draw(const Matrix3& transformation, SceneGraph::Camera2D& camera) override { + if(_bEnabled) { + _shader.setColor(_color) + .setTransformationProjectionMatrix(camera.projectionMatrix() * transformation); + _mesh.draw(_shader); + } + } + + FlatShadeObject2D& setColor(const Color3& color) { _color = color; return *this; } + FlatShadeObject2D& setEnabled(bool bEnabled) { _bEnabled = bEnabled; return *this; } + +private: + Shaders::Flat2D& _shader; + Color3 _color; + GL::Mesh& _mesh; + bool _bEnabled { true }; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.cpp b/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.cpp new file mode 100644 index 000000000..8072dcf3a --- /dev/null +++ b/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.cpp @@ -0,0 +1,77 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "DrawableObjects/ParticleGroup2D.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Magnum { namespace Examples { +using namespace Math::Literals; + +ParticleGroup2D::ParticleGroup2D(const std::vector& points, float particleRadius) : + _points{points}, + _particleRadius{particleRadius}, + _meshParticles{GL::MeshPrimitive::Points} { + _meshParticles.addVertexBuffer(_bufferParticles, 0, Shaders::Generic2D::Position{}); + _particleShader.reset(new ParticleSphereShader2D); +} + +ParticleGroup2D& ParticleGroup2D::draw(Containers::Pointer& camera, Int screenHeight, Int projectionHeight) { + if(_points.empty()) { return *this; } + + if(_dirty) { + Containers::ArrayView data(reinterpret_cast(&_points[0]), _points.size() * 2); + _bufferParticles.setData(data); + _meshParticles.setCount(static_cast(_points.size())); + _dirty = false; + } + + (*_particleShader) + /* particle data */ + .setNumParticles(static_cast(_points.size())) + .setParticleRadius(_particleRadius) + /* sphere render data */ + .setColorMode(_colorMode) + .setColor(_color) + /* view/prj matrices and size */ + .setViewProjectionMatrix(camera->projectionMatrix() * camera->cameraMatrix()) + .setScreenHeight(screenHeight) + .setDomainHeight(projectionHeight); + + _meshParticles.draw(*_particleShader); + return *this; +} +} } diff --git a/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.h b/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.h new file mode 100644 index 000000000..0c427a6e6 --- /dev/null +++ b/src/fluidsimulation2d/DrawableObjects/ParticleGroup2D.h @@ -0,0 +1,75 @@ +#ifndef Magnum_Examples_FluidSimulation2D_Particle2DGroup_h +#define Magnum_Examples_FluidSimulation2D_Particle2DGroup_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "Shaders/ParticleSphereShader2D.h" + +namespace Magnum { namespace Examples { +class ParticleGroup2D { +public: + explicit ParticleGroup2D(const std::vector& points, float particleRadius); + + ParticleGroup2D& draw(Containers::Pointer& camera, Int screenHeight, Int projectionHeight); + + bool isDirty() const { return _dirty; } + ParticleGroup2D& setDirty() { _dirty = true; return *this; } + + Float particleRadius() const { return _particleRadius; } + ParticleGroup2D& setParticleRadius(Float radius) { _particleRadius = radius; return *this; } + + ParticleSphereShader2D::ColorMode colorMode() const { return _colorMode; } + ParticleGroup2D& setColorMode(ParticleSphereShader2D::ColorMode colorMode) { _colorMode = colorMode; return *this; } + + const Color3& color() const { return _color; } + ParticleGroup2D& setColor(const Color3& color) { _color = color; return *this; } + +private: + const std::vector& _points; + bool _dirty { false }; + + Float _particleRadius { 1.0f }; + ParticleSphereShader2D::ColorMode _colorMode { ParticleSphereShader2D::ColorMode::RampColorById }; + Color3 _color{ 0.1f }; + + GL::Buffer _bufferParticles; + GL::Mesh _meshParticles; + Containers::Pointer _particleShader; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/DrawableObjects/WireframeObject2D.h b/src/fluidsimulation2d/DrawableObjects/WireframeObject2D.h new file mode 100644 index 000000000..1362bfbf1 --- /dev/null +++ b/src/fluidsimulation2d/DrawableObjects/WireframeObject2D.h @@ -0,0 +1,65 @@ +#ifndef Magnum_Examples_DrawableObjects_WireframeObject2D_h +#define Magnum_Examples_DrawableObjects_WireframeObject2D_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "DrawableObjects/FlatShadeObject2D.h" + +namespace Magnum { namespace Examples { +using Scene2D = SceneGraph::Scene; + +class WireframeObject2D { +public: + explicit WireframeObject2D(Scene2D* const scene, SceneGraph::DrawableGroup2D* const drawableGroup, GL::Mesh&& mesh) : + _mesh(std::move(mesh)) { + _obj2D.reset(new Object2D{ scene }); + _flatShader = Shaders::Flat2D{}; + _drawableObj.reset(new FlatShadeObject2D{ *_obj2D, _flatShader, Color3{ 1.0f }, _mesh, drawableGroup }); + } + + WireframeObject2D& setColor(const Color3& color) { _drawableObj->setColor(color); return *this; } + WireframeObject2D& setTransformation(const Matrix3& matrix) { _obj2D->setTransformation(matrix); return *this; } + WireframeObject2D& setEnabled(bool bEnabled) { _drawableObj->setEnabled(bEnabled); return *this; } + +protected: + GL::Mesh _mesh{ NoCreate }; + Shaders::Flat2D _flatShader{ NoCreate }; + Containers::Pointer _obj2D; + Containers::Pointer _drawableObj; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/FluidSimulation2DExample.cpp b/src/fluidsimulation2d/FluidSimulation2DExample.cpp new file mode 100644 index 000000000..996bbb8be --- /dev/null +++ b/src/fluidsimulation2d/FluidSimulation2DExample.cpp @@ -0,0 +1,480 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DrawableObjects/ParticleGroup2D.h" +#include "DrawableObjects/WireframeObject2D.h" +#include "FluidSolver/ApicSolver2D.h" + +namespace Magnum { namespace Examples { +class FluidSimulation2DExample : public Platform::Application { +public: + explicit FluidSimulation2DExample(const Arguments& arguments); + virtual ~FluidSimulation2DExample() = default; + +protected: + void viewportEvent(ViewportEvent& event) override; + void keyPressEvent(KeyEvent& event) override; + void keyReleaseEvent(KeyEvent& event) override; + void mousePressEvent(MouseEvent& event) override; + void mouseReleaseEvent(MouseEvent& event) override; + void mouseMoveEvent(MouseMoveEvent& event) override; + void mouseScrollEvent(MouseScrollEvent& event) override; + void textInputEvent(TextInputEvent& event) override; + void drawEvent() override; + + /* Fluid simulation helper functions */ + void resetSimulation(); + Vector2 windowPos2WorldPos(const Vector2i& winPos); + + /* Window control */ + void showMenu(); + bool _showMenu = true; + ImGuiIntegration::Context _imGuiContext{ NoCreate }; + + /* Scene and drawable group must be constructed before camera and other + drawble objects */ + Containers::Pointer _scene; + Containers::Pointer _drawableGroup; + + /* Camera helpers */ + Containers::Pointer _objCamera; + Containers::Pointer _camera; + + /* Fluid simulation system */ + Containers::Pointer _fluidSolver; + Containers::Pointer _drawableParticles; + Containers::Pointer _drawableBoundary; + Float _speed { 2.0f }; + Float _evolvedTime { 0 }; + Int _numEmission { 0 }; + bool _bAutoEmitParticles { true }; + bool _pausedSimulation { false }; + + /* Mouse-Fluid interaction */ + Containers::Pointer _drawablePointer; + Timeline _timeline; + Vector2 _lastMousePressedWorldPos; + Float _mouseInteractionRadius { 5 }; + Float _mouseInteractionMagnitude { 5 }; + bool _bMouseInteraction { true }; +}; + +namespace { +static constexpr Float GridCellLength = 1.0f; /* length of 1 grid cell */ +static constexpr Int NumGridCellX = 100; /* number of cells in x dimension */ +static constexpr Int NumGridCellY = 100; /* number of cells in y dimension */ +static constexpr Vector2 GridStart { -50, -50 }; /* lower corner of the grid */ +static constexpr Int radiusCircleBoundary = 45; /* radius of the boundary circle */ + +/* Viewport will display this window */ +static constexpr Float ProjectionScale = 1.05f; +static constexpr Int DomainDisplayW = Int(NumGridCellX * GridCellLength * ProjectionScale); +static constexpr Int DomainDisplayH = Int(NumGridCellY * GridCellLength * ProjectionScale); + +Vector2 gridCenter() { + return Vector2(NumGridCellX, NumGridCellY) * GridCellLength * 0.5f + GridStart; +} +} + +FluidSimulation2DExample::FluidSimulation2DExample(const Arguments& arguments) : Platform::Application{arguments, NoCreate} { + /* Setup window */ + { + const Vector2 dpiScaling = this->dpiScaling({}); + Configuration conf; + conf.setTitle("Magnum 2D Fluid Simulation Example") + .setSize(conf.size(), dpiScaling) + .setWindowFlags(Configuration::WindowFlag::Resizable); + GLConfiguration glConf; + glConf.setSampleCount(dpiScaling.max() < 2.0f ? 8 : 2); + if(!tryCreate(conf, glConf)) { + create(conf, glConf.setSampleCount(0)); + } + } + + /* Setup ImGui, load a better font */ + { + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + + ImFontConfig fontConfig; + fontConfig.FontDataOwnedByAtlas = false; + const Vector2 size = Vector2{ windowSize() } / dpiScaling(); + Utility::Resource rs{ "data" }; + Containers::ArrayView font = rs.getRaw("SourceSansPro-Regular.ttf"); + ImGui::GetIO().Fonts->AddFontFromMemoryTTF( + const_cast(font.data()), Int(font.size()), 16.0f * framebufferSize().x() / size.x(), &fontConfig); + + _imGuiContext = ImGuiIntegration::Context(*ImGui::GetCurrentContext(), + Vector2{ windowSize() } / dpiScaling(), windowSize(), framebufferSize()); + + /* Setup proper blending to be used by ImGui */ + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::SourceAlpha, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + } + + /* Setup scene objects and camera */ + { + /* Setup scene objects */ + _scene.reset(new Scene2D{}); + _drawableGroup.reset(new SceneGraph::DrawableGroup2D{}); + + /* Configure camera */ + _objCamera.reset(new Object2D{ _scene.get() }); + _objCamera->setTransformation(Matrix3::translation(gridCenter())); + + _camera.reset(new SceneGraph::Camera2D{ *_objCamera }); + _camera->setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend) + .setProjectionMatrix(Matrix3::projection(Vector2{ DomainDisplayW, DomainDisplayH })) + .setViewport(GL::defaultFramebuffer.viewport().size()); + } + + /* Setup fluid solver */ + { + SceneObjects* sceneObjs = new SceneObjects; + sceneObjs->emitterT0 = SDFObject(gridCenter() + Vector2(10, 10), 30, SDFObject::ObjectType::Circle); + sceneObjs->emitter = SDFObject(gridCenter() + Vector2(15, 20), 15, SDFObject::ObjectType::Circle); + sceneObjs->boundary = SDFObject(gridCenter(), radiusCircleBoundary, SDFObject::ObjectType::Circle, false); + _fluidSolver.reset(new ApicSolver2D{ GridStart, GridCellLength, NumGridCellX, NumGridCellY, sceneObjs }); + + /* Drawable particles */ + _drawableParticles.reset(new ParticleGroup2D{ _fluidSolver->particlePositions(), _fluidSolver->particleRadius() }); + _drawableParticles->setColor(Color3(85.0f / 255, 200.0f / 255, 245.0f / 255)); + + /* Drawable boundary*/ + _drawableBoundary.reset(new WireframeObject2D(_scene.get(), _drawableGroup.get(), + MeshTools::compile(Primitives::circle2DWireframe(128)))); + _drawableBoundary->setTransformation(Matrix3::scaling(Vector2{ radiusCircleBoundary + _fluidSolver->particleRadius() })); + _drawableBoundary->setColor(Color3(1, 1, 1)); + + /* Visualize mouse pointer for mouse-fluid interaction */ + _drawablePointer.reset(new WireframeObject2D(_scene.get(), _drawableGroup.get(), + MeshTools::compile(Primitives::circle2DWireframe(32)))); + _drawablePointer->setColor(Color3(0, 1, 0)); + _drawablePointer->setEnabled(false); + } + + /* Enable depth test, render particles as sprites */ + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::ProgramPointSize); + + /* Start the timer, loop at 60 Hz max */ + setSwapInterval(1); + setMinimalLoopPeriod(16); + _timeline.start(); +} + +void FluidSimulation2DExample::drawEvent() { + GL::defaultFramebuffer.clear(GL::FramebufferClear::Color | GL::FramebufferClear::Depth); + _imGuiContext.newFrame(); + + /* Enable text input, if needed */ + if(ImGui::GetIO().WantTextInput && !isTextInputActive()) { + startTextInput(); + } else if(!ImGui::GetIO().WantTextInput && isTextInputActive()) { + stopTextInput(); + } + + /* Draw objects */ + { + /* Trigger drawable object to update the particles to the GPU */ + _drawableParticles->setDirty(); + _drawableParticles->draw(_camera, GL::defaultFramebuffer.viewport().size().y(), DomainDisplayH); + + /* Draw other objects (boundary mesh, pointer mesh) */ + _camera->draw(*_drawableGroup); + } + + if(!_pausedSimulation) { + static constexpr Float frameTime = 1.0f / 60.0f; + if(_evolvedTime > 1.0f) { /* pause for a while before starting simulation */ + _fluidSolver->advanceFrame(frameTime * _speed); + } + _evolvedTime += frameTime; + + /* Emit particles automatically */ + if(_bAutoEmitParticles && _evolvedTime > 10.0f) { + static Float lastTime { _evolvedTime }; + if(_evolvedTime - lastTime > 1.5f /* emit every 1.5 second */ + && _numEmission < 5) { /* emit 5 times */ + _fluidSolver->emitParticles(); + lastTime = _evolvedTime; + ++_numEmission; + } + } + } + + /* Menu for parameters */ + if(_showMenu) { showMenu(); } + + /* Update application cursor */ + _imGuiContext.updateApplicationCursor(*this); + + /* Render ImGui window */ + { + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); + GL::Renderer::disable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::ScissorTest); + + _imGuiContext.drawFrame(); + + GL::Renderer::disable(GL::Renderer::Feature::ScissorTest); + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + GL::Renderer::disable(GL::Renderer::Feature::Blending); + } + + swapBuffers(); + + /* Run next frame immediately */ + redraw(); +} + +void FluidSimulation2DExample::viewportEvent(ViewportEvent& event) { + /* Resize the main framebuffer */ + GL::defaultFramebuffer.setViewport({ {}, event.framebufferSize() }); + + /* Relayout ImGui */ + _imGuiContext.relayout(Vector2{ event.windowSize() } / event.dpiScaling(), event.windowSize(), event.framebufferSize()); + + /* Recompute the camera's projection matrix */ + _camera->setViewport(event.framebufferSize()); +} + +void FluidSimulation2DExample::keyPressEvent(Platform::Sdl2Application::KeyEvent& event) { + switch(event.key()) { + case KeyEvent::Key::E: + _fluidSolver->emitParticles(); + break; + case KeyEvent::Key::H: + _showMenu ^= true; + event.setAccepted(true); + break; + case KeyEvent::Key::R: + resetSimulation(); + event.setAccepted(true); + break; + case KeyEvent::Key::Space: + _pausedSimulation ^= true; + event.setAccepted(true); + break; + default: + if(_imGuiContext.handleKeyPressEvent(event)) { + event.setAccepted(true); + } + } +} + +void FluidSimulation2DExample::keyReleaseEvent(KeyEvent& event) { + if(_imGuiContext.handleKeyReleaseEvent(event)) { + event.setAccepted(true); + return; + } +} + +void FluidSimulation2DExample::mousePressEvent(MouseEvent& event) { + if(_imGuiContext.handleMousePressEvent(event)) { + event.setAccepted(true); + return; + } + _lastMousePressedWorldPos = windowPos2WorldPos(event.position()); + if(_bMouseInteraction) { + _timeline.nextFrame(); + _drawablePointer->setEnabled(true); + _drawablePointer->setTransformation(Matrix3::translation(_lastMousePressedWorldPos) * + Matrix3::scaling(Vector2{ _mouseInteractionRadius })); + event.setAccepted(); + } +} + +void FluidSimulation2DExample::mouseReleaseEvent(MouseEvent& event) { + if(_imGuiContext.handleMouseReleaseEvent(event)) { + event.setAccepted(true); + } + if(_bMouseInteraction) { + _drawablePointer->setEnabled(false); + event.setAccepted(); + } +} + +void FluidSimulation2DExample::mouseMoveEvent(MouseMoveEvent& event) { + if(_imGuiContext.handleMouseMoveEvent(event)) { + event.setAccepted(true); + return; + } + if(!event.buttons()) { return; } + + const Vector2 currentPos = windowPos2WorldPos(event.position()); + if(_bMouseInteraction) { + _timeline.nextFrame(); + const auto dt = _timeline.previousFrameDuration(); + if(dt > 1e-4f) { + _fluidSolver->addRepulsiveVelocity(_lastMousePressedWorldPos, currentPos, dt, + _mouseInteractionRadius, _mouseInteractionMagnitude * 0.01f); + _drawablePointer->setTransformation(Matrix3::translation(currentPos) * + Matrix3::scaling(Vector2{ _mouseInteractionRadius })); + event.setAccepted(); + } + _lastMousePressedWorldPos = currentPos; + } +} + +void FluidSimulation2DExample::mouseScrollEvent(MouseScrollEvent& event) { + const Float delta = event.offset().y(); + if(Math::abs(delta) < 1.0e-2f) { + return; + } + if(_imGuiContext.handleMouseScrollEvent(event)) { + /* Prevent scrolling the page */ + event.setAccepted(); + return; + } +} + +void FluidSimulation2DExample::textInputEvent(TextInputEvent& event) { + if(_imGuiContext.handleTextInputEvent(event)) { + event.setAccepted(true); + } +} + +void FluidSimulation2DExample::showMenu() { + ImGui::SetNextWindowPos({ 10.0f, 10.0f }, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowBgAlpha(0.5f); + ImGui::Begin("Options", nullptr); + + /* General information */ + ImGui::Text("Hide/show menu: H"); + ImGui::Text("Num. particles: %d", Int(_fluidSolver->numParticles())); + ImGui::Text("Rendering: %3.2f FPS", Double(ImGui::GetIO().Framerate)); + ImGui::Spacing(); + + /* Rendering parameters */ + if(ImGui::TreeNode("Particle Rendering")) { + ImGui::PushID("Particle Rendering"); + { + constexpr const char* items[] = { "Uniform", "Ramp by ID" }; + static Int colorMode = 1; + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + if(ImGui::Combo("Color Mode", &colorMode, items, 2)) { + _drawableParticles->setColorMode(ParticleSphereShader2D::ColorMode(colorMode)); + } + ImGui::PopItemWidth(); + if(colorMode == 0) { /* Uniform color */ + static Color3 color = _drawableParticles->color(); + if(ImGui::ColorEdit3("Color", color.data())) { + _drawableParticles->setColor(color); + } + } + } + ImGui::PopID(); + ImGui::TreePop(); + } + ImGui::Spacing(); + ImGui::Separator(); + + /* Simulation parameters */ + if(ImGui::TreeNodeEx("Simulation", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::PushID("Simulation"); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f); + ImGui::InputFloat("Speed", &_speed); + ImGui::Checkbox("Auto emit particles 5 times", &_bAutoEmitParticles); + ImGui::PopItemWidth(); + ImGui::BeginGroup(); + ImGui::Checkbox("Mouse interaction", &_bMouseInteraction); + if(_bMouseInteraction) { + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::SliderFloat("Radius", &_mouseInteractionRadius, 1.0f, 10.0f); + ImGui::SliderFloat("Magnitude", &_mouseInteractionMagnitude, 1.0f, 10.0f); + ImGui::PopItemWidth(); + } + ImGui::EndGroup(); + ImGui::PopID(); + ImGui::TreePop(); + } + ImGui::Spacing(); + ImGui::Separator(); + + /* Reset */ + ImGui::Spacing(); + if(ImGui::Button("Emit Particles")) { + _fluidSolver->emitParticles(); + } + ImGui::SameLine(); + if(ImGui::Button(_pausedSimulation ? "Play Sim" : "Pause Sim")) { + _pausedSimulation ^= true; + } + ImGui::SameLine(); + if(ImGui::Button("Reset Sim")) { + resetSimulation(); + } + ImGui::End(); +} + +void FluidSimulation2DExample::resetSimulation() { + _fluidSolver->reset(); + _pausedSimulation = false; + _evolvedTime = 0; + _numEmission = 0; +} + +Vector2 FluidSimulation2DExample::windowPos2WorldPos(const Vector2i& winPos) { + /* Compute inverted model view projection matrix */ + const Matrix3 invViewProjMat = (_camera->projectionMatrix() * _camera->cameraMatrix()).inverted(); + + /* Compute the world coordinate from window coordinate */ + const Vector2i flippedPos = Vector2i(winPos.x(), GL::defaultFramebuffer.viewport().size().y() - winPos.y()); + const Vector2 ndcPos = Vector2(flippedPos) / Vector2(GL::defaultFramebuffer.viewport().size()) * Vector2(2) - Vector2(1); + const Vector3 worldPos = invViewProjMat * Vector3(ndcPos, 1); + return Vector2(worldPos.x() / worldPos.z(), worldPos.y() / worldPos.z()); +} +} } + +MAGNUM_APPLICATION_MAIN(Magnum::Examples::FluidSimulation2DExample) diff --git a/src/fluidsimulation2d/FluidSolver/ApicSolver2D.cpp b/src/fluidsimulation2d/FluidSolver/ApicSolver2D.cpp new file mode 100644 index 000000000..f5e7cd716 --- /dev/null +++ b/src/fluidsimulation2d/FluidSolver/ApicSolver2D.cpp @@ -0,0 +1,520 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "FluidSolver/ApicSolver2D.h" + +#include + +namespace Magnum { namespace Examples { +ApicSolver2D::ApicSolver2D(const Vector2& origin, Float cellSize, Int Ni, Int Nj, SceneObjects* sceneObjs) : + _objects{sceneObjs}, + _particles{cellSize}, + _grid{origin, cellSize, Ni, Nj} { + if(Ni < 1 || Nj < 1) { + Fatal{} << "Invalid grid resolution"; + } + if(!sceneObjs) { + Fatal{} << "Invalid scene object"; + } + + /* Initialize data */ + initBoundary(); + generateParticles(_objects->emitterT0, 0); +} + +/* This function should be called again every time the boundary changes */ +void ApicSolver2D::initBoundary() { + _grid.boundarySDF.loop2D( + [&](std::size_t i, std::size_t j) { + _grid.boundarySDF(i, j) = _objects->boundary.signedDistance(_grid.getWorldPos(i, j)); + }); + + /* Initialize the fluid cell weights from boundary signed distance field */ + _grid.u_weights.loop2D( + [&](std::size_t i, std::size_t j) { + _grid.u_weights(i, j) = Float(1) - fractionInside(_grid.boundarySDF(i, j + 1), _grid.boundarySDF(i, j)); + _grid.u_weights(i, j) = Math::clamp(_grid.u_weights(i, j), Float(0), Float(1)); + }); + _grid.v_weights.loop2D( + [&](std::size_t i, std::size_t j) { + _grid.v_weights(i, j) = Float(1) - fractionInside(_grid.boundarySDF(i + 1, j), _grid.boundarySDF(i, j)); + _grid.v_weights(i, j) = Math::clamp(_grid.v_weights(i, j), Float(0), Float(1)); + }); +} + +void ApicSolver2D::generateParticles(const SDFObject& sdfObj, Float initialVelocity_y) { + using Distribution = std::uniform_real_distribution; + const Float rndScale = _particles.particleRadius * 0.5f; + std::mt19937 gen((std::random_device{})()); + Distribution distr(-rndScale, rndScale); + + /* Generate new particles */ + std::vector newParticles; + _grid.fluidSDF.loop2D( + [&](std::size_t i, std::size_t j) { + const Vector2 cellCenter = _grid.getWorldPos(i + 0.5f, j + 0.5f); + for(Int k = 0; k < 2; ++k) { + const Vector2 ppos = cellCenter + Vector2(distr(gen), distr(gen)); + if(sdfObj.signedDistance(ppos) < 0) { + newParticles.push_back(ppos); + } + } + }); + + /* Insert into the system */ + _particles.addParticles(newParticles, initialVelocity_y); +} + +void ApicSolver2D::addRepulsiveVelocity(const Vector2& p0, const Vector2& p1, Float dt, Float radius, Float magnitude) { + Vector2 movingVel = p1 - p0; + const auto movingDist = movingVel.length(); + if(movingDist < _particles.particleRadius) { + return; + } + movingVel /= dt; + + Vector2 from, to; + for(std::size_t i = 0; i < 2; ++i) { + from[i] = Math::min(p0[i], p1[i]); + to[i] = Math::max(p0[i], p1[i]); + } + + const Int span = Int(radius / _grid.cellSize); + const Vector2i fromCell = _grid.getCellIdx(from) - Vector2i(span); + const Vector2i toCell = _grid.getCellIdx(to) + Vector2i(span); + const Vector2 p01 = p1 - p0; + const Float p01DistSqr = p01.dot(); + + const auto distToSegment = + [&](const Vector2& pos) { + const Float t = Math::max(0.0f, Math::min(1.0f, Math::dot(pos - p0, p01) / p01DistSqr)); + const Vector2 prj = p0 + t * p01; + return (pos - prj).length(); + }; + + for(Int j = fromCell.y(); j <= toCell.y(); ++j) { + for(Int i = fromCell.x(); i <= toCell.x(); ++i) { + if(!_grid.isValidCellIdx(i, j)) { + continue; + } + + const auto& particleIdxs = _grid.cellParticles(i, j); + for(auto p : particleIdxs) { + const auto dist = distToSegment(_particles.positions[p]); + const auto t = dist / radius; + if(t < 1.0f) { + const auto w = Math::lerp(0.0f, magnitude, t); + _particles.velocities[p] += movingVel * w; + } + } + } + } +} + +void ApicSolver2D::advanceFrame(Float frameDuration) { + Float frameTime = 0; + + while(frameTime < frameDuration) { + auto substep = timestepCFL(); + const auto remainingTime = frameDuration - frameTime; + if(frameTime + substep > frameDuration) { + substep = remainingTime; + } else if(frameTime + Float(1.5) * substep > frameDuration) { + substep = remainingTime * Float(0.5); + } + frameTime += substep; + + /* Advect particles */ + moveParticles(substep); + + /* Particles => grid */ + collectParticlesToCells(); + particleVelocity2Grid(); + + /* Update grid velocity */ + extrapolate(_grid.u, _grid.u_tmp, _grid.u_valid, _grid.u_old_valid); + extrapolate(_grid.v, _grid.v_tmp, _grid.v_valid, _grid.v_old_valid); + addGravity(substep); + computeFluidSDF(); + solvePressures(substep); + + /* Enforce boundary condition */ + constrainVelocity(); + + /* Grid => particles */ + relaxParticlePositions(substep); + gridVelocity2Particle(); + } +} + +Float ApicSolver2D::timestepCFL() const { + Float maxVel = 0; + _grid.u.loop1D([&](std::size_t i) { maxVel = Math::max(maxVel, std::abs(_grid.u.data()[i])); }); + _grid.v.loop1D([&](std::size_t i) { maxVel = Math::max(maxVel, std::abs(_grid.v.data()[i])); }); + return maxVel > 0 ? _grid.cellSize / maxVel * 3.0f : 1.0f; +} + +void ApicSolver2D::moveParticles(Float dt) { + _particles.loopAll( + [&](uint32_t p) { + const Vector2 newPos = _particles.positions[p] + _particles.velocities[p] * dt; + _particles.positions[p] = _grid.constrainBoundary(newPos); + }); +} + +void ApicSolver2D::collectParticlesToCells() { + _grid.cellParticles.loop1D([&](std::size_t i) { _grid.cellParticles.data()[i].resize(0); }); + _particles.loopAll([&](uint32_t p) { + const Vector2 ppos = _particles.positions[p]; + const Vector2i gridCoord = _grid.getValidCellIdx(ppos); + _grid.cellParticles(gridCoord).push_back(p); + }); +} + +void ApicSolver2D::particleVelocity2Grid() { + _grid.u.loop2D( + [&](std::size_t i, std::size_t j) { + Float sum_w = 0.0f; + Float sum_u = 0.0f; + const Vector2 nodePos = _grid.getWorldPos(i, j + 0.5f); + _grid.loopNeigborParticles(static_cast(i), static_cast(j), -1, 0, -1, 1, [&](uint32_t p) { + const Vector2 xpg = nodePos - _particles.positions[p]; + const auto w = linearKernel(xpg, _grid.invCellSize); + if(w > 0) { + sum_w += w; + sum_u += w * (_particles.velocities[p].x() + + Math::dot(_particles.affineMat[p][0], xpg)); + } + }); + _grid.u(i, j) = sum_w > 0 ? sum_u / sum_w : 0.0f; + _grid.u_valid(i, j) = sum_w > 0 ? 1 : 0; + }); + + _grid.v.loop2D( + [&](std::size_t i, std::size_t j) { + Float sum_w = 0.0; + Float sum_v = 0.0; + const Vector2 nodePos = _grid.getWorldPos(i + 0.5f, j); + _grid.loopNeigborParticles(static_cast(i), static_cast(j), -1, 1, -1, 0, [&](uint32_t p) { + const Vector2 xpg = nodePos - _particles.positions[p]; + const auto w = linearKernel(xpg, _grid.invCellSize); + if(w > 0) { + sum_w += w; + sum_v += w * (_particles.velocities[p].y() + + Math::dot(_particles.affineMat[p][1], xpg)); + } + }); + _grid.v(i, j) = sum_w > 0 ? sum_v / sum_w : 0.0f; + _grid.v_valid(i, j) = sum_w > 0 ? 1 : 0; + }); +} + +void ApicSolver2D::extrapolate(Array2X& grid, Array2X& tmp_grid, Array2X& valid, Array2X& old_valid) const { + tmp_grid = grid; + old_valid = valid; + + Array2X* pgrids[] = { &grid, &tmp_grid }; + Array2X* pvalids[] = { &valid, &old_valid }; + + for(int layers = 0; layers < 1; ++layers) { + auto pgrid_src = pgrids[layers & 1]; + auto pgrid_tgt = pgrids[!(layers & 1)]; + + auto pvalid_src = pvalids[layers & 1]; + auto pvalid_tgt = pvalids[!(layers & 1)]; + + grid.loop2D([&](std::size_t i, std::size_t j) { + if(i == 0 || i == grid.size_x() - 1 || + j == 0 || j == grid.size_y() - 1) { + return; + } + + Float sum = 0; + Int count = 0; + + if(!(*pvalid_src)(i, j)) { + const std::size_t rows[] = { i + 1, i - 1, i, i }; + const std::size_t cols[] = { j, j, j + 1, j - 1 }; + + for(std::size_t cell = 0; cell < 4; ++cell) { + if((*pvalid_src)(rows[cell], cols[cell])) { + sum += (*pgrid_src)(rows[cell], cols[cell]); + ++count; + } + } + + if(count > 0) { + (*pgrid_tgt)(i, j) = sum / static_cast(count); + (*pvalid_tgt)(i, j) = 1; + } + } + }); + + (*pgrid_src).swapContent(*pgrid_tgt); + (*pvalid_src).swapContent(*pvalid_tgt); + } +} + +void ApicSolver2D::addGravity(Float dt) { + _grid.v.loop2D( + [&](std::size_t i, std::size_t j) { + if(_grid.v_valid(i, j)) { + _grid.v(i, j) -= 9.81f * dt; /* gravity */ + } + }); +} + +void ApicSolver2D::computeFluidSDF() { + _grid.fluidSDF.assign(3 * _grid.cellSize); + + _particles.loopAll( + [&](uint32_t p) { + const Vector2 ppos = _particles.positions[p]; + const Vector2i gridPos = Vector2i(_grid.getGridPos(ppos) - Vector2(0.5)); + + for(int j = gridPos.y() - 2; j <= gridPos.y() + 2; ++j) { + for(int i = gridPos.x() - 2; i <= gridPos.x() + 2; ++i) { + if(!_grid.isValidCellIdx(i, j)) { + continue; + } + const Vector2 cellCenter = _grid.getWorldPos(i + 0.5f, j + 0.5f); + const Float sdfVal = (cellCenter - ppos).length() - _particles.particleRadius; + if(_grid.fluidSDF(i, j) > sdfVal) { + _grid.fluidSDF(i, j) = sdfVal; + } + } + } + }); + + _grid.fluidSDF.loop2D( + [&](std::size_t i, std::size_t j) { + const Vector2 cellCenter = _grid.getWorldPos(i + 0.5f, j + 0.5f); + const Float sdfVal = _objects->boundary.signedDistance(cellCenter); + if(_grid.fluidSDF(i, j) > sdfVal) { + _grid.fluidSDF(i, j) = sdfVal; + } + }); +} + +void ApicSolver2D::solvePressures(Float dt) { + const auto Ni = static_cast(_grid.Ni); + const auto Nj = static_cast(_grid.Nj); + const auto numCells = Ni * Nj; + + _pressureSolver.resize(numCells); + _pressureSolver.clear(); + + for(std::size_t j = 1; j < Nj - 1; ++j) { + for(std::size_t i = 1; i < Ni - 1; ++i) { + const auto row = i + Ni * j; + double rhsVal = 0; + const auto centerSDF = _grid.fluidSDF(i, j); + + if(centerSDF >= 0) { + _pressureSolver.rhs[row] = rhsVal; + continue; + } + + const Float cellsWeights[] = { + _grid.u_weights(i + 1, j), + _grid.u_weights(i, j), + _grid.v_weights(i, j + 1), + _grid.v_weights(i, j) + }; + const Float cellsSDF[] = { + _grid.fluidSDF(i + 1, j), + _grid.fluidSDF(i - 1, j), + _grid.fluidSDF(i, j + 1), + _grid.fluidSDF(i, j - 1) + }; + const Float cellsVel[] = { + -_grid.u(i + 1, j), /* minus velocity */ + _grid.u(i, j), + -_grid.v(i, j + 1), /* minus velocity */ + _grid.v(i, j) + }; + const std::size_t cols[] = { + row + 1, + row - 1, + row + Ni, + row - Ni + }; + + /* Fill-in matrix */ + for(std::size_t cell = 0; cell < 4; ++cell) { + rhsVal += static_cast(cellsWeights[cell] * cellsVel[cell]); + const auto term = cellsWeights[cell] * dt; + if(cellsSDF[cell] < 0) { + _pressureSolver.matrix.addToElement(row, row, term); + _pressureSolver.matrix.addToElement(row, cols[cell], -term); + } else { + const auto theta = Math::max(0.01f, fractionInside(centerSDF, cellsSDF[cell])); + _pressureSolver.matrix.addToElement(row, row, term / theta); + } + } + + /* Write rhs */ + _pressureSolver.rhs[row] = rhsVal; + } + } + + _pressureSolver.solve(); /* now solve the linear system for cells' pressure */ + + _grid.u.loop2D( + [&](std::size_t i, std::size_t j) { + /* Edges of the domain, or entirely in solid */ + if(i == 0 || i == _grid.u.size_x() - 1 || !(_grid.u_weights(i, j) > 0)) { + _grid.u(i, j) = 0; + return; + } + + const auto centerSDF = _grid.fluidSDF(i, j); + const auto leftSDF = _grid.fluidSDF(i - 1, j); + if(centerSDF < 0 || leftSDF < 0) { + Float theta = 1; + if(_grid.fluidSDF(i, j) >= 0 || leftSDF >= 0) { + theta = Math::max(0.01f, fractionInside(leftSDF, centerSDF)); + } + const auto row = i + j * Ni; + const auto pressure = static_cast(_pressureSolver.solution[row] - _pressureSolver.solution[row - 1]); + _grid.u(i, j) -= pressure * (dt / theta); + } + }); + + _grid.v.loop2D( + [&](std::size_t i, std::size_t j) { + /* Edges of the domain, or entirely in solid */ + if(j == 0 || j == _grid.v.size_y() - 1 || !(_grid.v_weights(i, j) > 0)) { + _grid.v(i, j) = 0; + return; + } + + const auto centerSDF = _grid.fluidSDF(i, j); + const auto bottomSDF = _grid.fluidSDF(i, j - 1); + if(centerSDF < 0 || bottomSDF < 0) { + Float theta = 1; + if(centerSDF >= 0 || bottomSDF >= 0) { + theta = Math::max(0.01f, fractionInside(bottomSDF, centerSDF)); + } + const auto row = i + j * Ni; + const auto pressure = static_cast(_pressureSolver.solution[row] - _pressureSolver.solution[row - Ni]); + _grid.v(i, j) -= pressure * (dt / theta); + } + }); +} + +void ApicSolver2D::constrainVelocity() { + _grid.u_tmp = _grid.u; + _grid.v_tmp = _grid.v; + + _grid.u.loop2D( + [&](std::size_t i, std::size_t j) { + if(_grid.u_weights(i, j) > 0) { /* not entirely in solid */ + return; + } + const Vector2 gridPos = Vector2(i, j + 0.5f); + const Vector2 normal = _grid.boundarySDF.interpolateGradient(gridPos); + Vector2 vel = _grid.velocityFromGridPos(gridPos); + Float perp_component = Math::dot(vel, normal); + vel -= perp_component * normal; + _grid.u_tmp(i, j) = vel[0]; + }); + + _grid.v.loop2D( + [&](std::size_t i, std::size_t j) { + if(_grid.v_weights(i, j) > 0) { /* not entirely in solid */ + return; + } + const Vector2 gridPos = Vector2(i + 0.5f, j); + const Vector2 normal = _grid.boundarySDF.interpolateGradient(gridPos); + Vector2 vel = _grid.velocityFromGridPos(gridPos); + Float perp_component = Math::dot(vel, normal); + vel -= perp_component * normal; + _grid.v_tmp(i, j) = vel[1]; + }); + + /* Only swap u_tmp and v_tmp after constraining both u and v */ + _grid.u.swapContent(_grid.u_tmp); + _grid.v.swapContent(_grid.v_tmp); +} + +void ApicSolver2D::relaxParticlePositions(Float dt) { + const Float restDist = _grid.cellSize / std::sqrt(2.0f) * 1.1f; + const Float restDistSqr = restDist * restDist; + const Float overlappedSqr = restDistSqr * 0.0001f; + const Float jitterMag = restDist / dt / 128.0f * 0.01f; + static constexpr Float stiffness = 5.0f; + + _particles.loopAll( + [&](uint32_t p) { + const Vector2 ppos = _particles.positions[p]; + const Vector2i gridCoord = _grid.getValidCellIdx(ppos); + Vector2 spring = Vector2(0); + _grid.loopNeigborParticles( + gridCoord.x(), gridCoord.y(), -1, 1, -1, 1, [&](uint32_t q) { + if(p == q) { + return; + } + const Vector2 xpq = ppos - _particles.positions[q]; + const auto distSqr = xpq.dot(); + const auto w = stiffness * smoothKernel(distSqr, restDistSqr); + if(distSqr > overlappedSqr) { + spring += xpq * (w / std::sqrt(distSqr) * restDist); + } else { + spring.x() += ((rand() & 255) - 128) * jitterMag; + spring.y() += ((rand() & 255) - 128) * jitterMag; + } + }); + + const Vector2 newPos = ppos + dt * spring; + _particles.tmp[p] = _grid.constrainBoundary(newPos); + }); + + _particles.positions.swap(_particles.tmp); +} + +void ApicSolver2D::gridVelocity2Particle() { + auto& u = _grid.u; + auto& v = _grid.v; + const auto dxInv = _grid.invCellSize; + + _particles.loopAll( + [&](uint32_t p) { + const Vector2 gridPos = _grid.getGridPos(_particles.positions[p]); + const Vector2 px = gridPos - Vector2(0, 0.5); + const Vector2 py = gridPos - Vector2(0.5, 0); + + _particles.velocities[p] = Vector2(u.interpolateValue(px), + v.interpolateValue(py)); + _particles.affineMat[p] = Matrix2x2(u.affineInterpolateValue(px) * dxInv, + v.affineInterpolateValue(py) * dxInv); + }); +} +} } diff --git a/src/fluidsimulation2d/FluidSolver/ApicSolver2D.h b/src/fluidsimulation2d/FluidSolver/ApicSolver2D.h new file mode 100644 index 000000000..87b174110 --- /dev/null +++ b/src/fluidsimulation2d/FluidSolver/ApicSolver2D.h @@ -0,0 +1,81 @@ +#ifndef Magnum_Examples_FluidSimulation2D_ApicSolver2D_h +#define Magnum_Examples_FluidSimulation2D_ApicSolver2D_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "FluidSolver/SolverData.h" + +namespace Magnum { namespace Examples { +/* + * 2D Affine Particle-in-Cell fluid solver + */ +class ApicSolver2D { +public: + ApicSolver2D(const Vector2& origin, Float cellSize, Int Ni, Int Nj, SceneObjects* sceneObjs); + + /* Manipulation */ + void reset() { _particles.reset(); _particles.addParticles(_particles.positionsT0, 0); } + void emitParticles() { generateParticles(_objects->emitter, 10); } + void addRepulsiveVelocity(const Vector2& p0, const Vector2& p1, Float dt, Float radius, Float magnitude); + void advanceFrame(Float frameDuration); + + /* Properties */ + UnsignedInt numParticles() const { return _particles.size(); } + Float particleRadius() const { return _particles.particleRadius; } + const std::vector& particlePositions() const { return _particles.positions; } + +private: + /* Initialization */ + void initBoundary(); + void generateParticles(const SDFObject& sdfObj, Float initialVelocity_y); + + /* Simulation */ + Float timestepCFL() const; + void moveParticles(Float dt); + void collectParticlesToCells(); + void particleVelocity2Grid(); + void extrapolate(Array2X& grid, Array2X& tmp_grid, Array2X& valid, Array2X& old_valid) const; + void addGravity(Float dt); + void computeFluidSDF(); + void solvePressures(Float dt); + void constrainVelocity(); + void relaxParticlePositions(Float dt); + void gridVelocity2Particle(); + + Containers::Pointer _objects; + ParticleData _particles; + GridData _grid; + LinearSystemSolver _pressureSolver; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/FluidSolver/SolverData.h b/src/fluidsimulation2d/FluidSolver/SolverData.h new file mode 100644 index 000000000..32dcee1ba --- /dev/null +++ b/src/fluidsimulation2d/FluidSolver/SolverData.h @@ -0,0 +1,192 @@ +#ifndef Magnum_Examples_FluidSimulation2D_SolverData_h +#define Magnum_Examples_FluidSimulation2D_SolverData_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include "DataStructures/Array2X.h" +#include "DataStructures/SDFObject.h" +#include "DataStructures/PCGSolver.h" + +namespace Magnum { namespace Examples { +struct SceneObjects { + SDFObject emitterT0; /* emitter that is called once upon initialization */ + SDFObject emitter; /* emitter that is called by user when requested */ + SDFObject boundary; /* solid boundary */ +}; + +struct ParticleData { + explicit ParticleData(Float cellSize) : particleRadius{cellSize* 0.5f} {} + + uint32_t size() const { return static_cast(positions.size()); } + + void addParticles(const std::vector& newParticles, Float initialVelocity_y) { + if(positionsT0.size() == 0) { + positionsT0 = newParticles; + } + positions.insert(positions.end(), newParticles.begin(), newParticles.end()); + velocities.resize(size(), Vector2(0, -initialVelocity_y)); + affineMat.resize(size(), Matrix2x2(0)); + tmp.resize(size(), Vector2(0)); + } + + void reset() { + positions.resize(0); + velocities.resize(0); + affineMat.resize(0); + tmp.resize(0); + } + + template + void loopAll(Function&& func) const { + for(uint32_t p = 0, pend = size(); p < pend; ++p) { + func(p); + } + } + + const Float particleRadius; + std::vector positionsT0; + std::vector positions; + std::vector velocities; + std::vector affineMat; + std::vector tmp; +}; + +struct GridData { + GridData(const Vector2& origin_, Float cellSize_, Int Ni_, Int Nj_) : + origin{origin_}, Ni{Ni_}, Nj{Nj_}, + cellSize{cellSize_}, + invCellSize{Float(1.0) / cellSize} { + u.resize(Ni + 1, Nj); + v.resize(Ni, Nj + 1); + u_tmp.resize(Ni + 1, Nj); + v_tmp.resize(Ni, Nj + 1); + u_weights.resize(Ni + 1, Nj); + v_weights.resize(Ni, Nj + 1); + u_valid.resize(Ni + 1, Nj); + v_valid.resize(Ni, Nj + 1); + u_old_valid.resize(Ni + 1, Nj); + v_old_valid.resize(Ni, Nj + 1); + + fluidSDF.resize(Ni, Nj); + boundarySDF.resize(Ni + 1, Nj + 1); + cellParticles.resize(Ni, Nj); + } + + Vector2 getGridPos(const Vector2& worldPos) const { return (worldPos - origin) * invCellSize; } + Vector2 getWorldPos(Float grid_x, Float grid_y) const { return Vector2(grid_x, grid_y) * cellSize + origin; } + + bool isValidCellIdx(int x, int y) const { return x >= 0 && x < Ni && y >= 0 && y < Nj; } + Vector2i getCellIdx(const Vector2& worldPos) const { return Vector2i(getGridPos(worldPos)); } + Vector2i getValidCellIdx(const Vector2& worldPos) const { + auto tmp = getCellIdx(worldPos); + tmp.x() = Math::max(0, Math::min(Ni - 1, tmp.x())); + tmp.y() = Math::max(0, Math::min(Nj - 1, tmp.y())); + return tmp; + } + + Vector2 velocityFromGridPos(const Vector2& gridPos) const { + const Vector2 px = Vector2(gridPos[0], gridPos[1] - 0.5f); + const Vector2 py = Vector2(gridPos[0] - 0.5f, gridPos[1]); + return Vector2(u.interpolateValue(px), + v.interpolateValue(py)); + } + + Vector2 constrainBoundary(const Vector2& worldPos) const { + const Vector2 gridPos = getGridPos(worldPos); + const auto sdfVal = boundarySDF.interpolateValue(gridPos); + if(sdfVal < 0) { + const auto normal = boundarySDF.interpolateGradient(gridPos); + return worldPos - sdfVal * normal; + } else { + return worldPos; + } + } + + template + void loopNeigborParticles(int i, int j, int il, int ih, int jl, int jh, Function&& func) const { + for(int sj = j + jl; sj <= j + jh; ++sj) { + for(int si = i + il; si <= i + ih; ++si) { + if(si < 0 || si > Ni - 1 || sj < 0 || sj > Nj - 1) { continue; } + const auto& neighbors = cellParticles(si, sj); + for(auto p : neighbors) { + func(p); + } + } + } + } + + /* Grid spatial information */ + const Vector2 origin; + const Int Ni, Nj; + const Float cellSize; + const Float invCellSize; + + /* Nodes and cells' data */ + Array2X u, u_tmp, u_weights; + Array2X v, v_tmp, v_weights; + Array2X u_valid, v_valid, u_old_valid, v_old_valid; + Array2X boundarySDF; + Array2X fluidSDF; + + Array2X> cellParticles; +}; + +struct LinearSystemSolver { + void resize(std::size_t newSize) { + rhs.resize(newSize); + solution.resize(newSize); + matrix.resize(newSize); + } + + void clear() { + matrix.clear(); + solution.assign(solution.size(), 0); + } + + void solve() { + bool bSuccess = pcgSolver.solve(matrix, rhs, solution); + if(!bSuccess) { + Error{} << "Pressure solve failed!"; + } + } + + /* Use double for linear system (the solver converges slower if using float number) */ + using pcg_real = Double; + PCGSolver pcgSolver; + SparseMatrix matrix; + std::vector rhs; + std::vector solution; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.cpp b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.cpp new file mode 100644 index 000000000..250cc0cd1 --- /dev/null +++ b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.cpp @@ -0,0 +1,98 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "Shaders/ParticleSphereShader2D.h" + +#include +#include +#include +#include +#include +#include + +namespace Magnum { namespace Examples { +ParticleSphereShader2D::ParticleSphereShader2D() { + Utility::Resource rs("data"); + + GL::Shader vertShader{ GL::Version::GL330, GL::Shader::Type::Vertex }; + GL::Shader fragShader{ GL::Version::GL330, GL::Shader::Type::Fragment }; + vertShader.addSource(rs.get("ParticleSphereShader2D.vert")); + fragShader.addSource(rs.get("ParticleSphereShader2D.frag")); + + CORRADE_INTERNAL_ASSERT(GL::Shader::compile({ vertShader, fragShader })); + attachShaders({ vertShader, fragShader }); + CORRADE_INTERNAL_ASSERT(link()); + + _uNumParticles = uniformLocation("numParticles"); + _uParticleRadius = uniformLocation("particleRadius"); + + _uColorMode = uniformLocation("colorMode"); + _uColor = uniformLocation("uniformColor"); + + _uViewProjectionMatrix = uniformLocation("viewProjectionMatrix"); + _uScreenHeight = uniformLocation("screenHeight"); + _uDomainHeight = uniformLocation("domainHeight"); +} + +ParticleSphereShader2D& ParticleSphereShader2D::setNumParticles(Int numParticles) { + setUniform(_uNumParticles, numParticles); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setParticleRadius(Float radius) { + setUniform(_uParticleRadius, radius); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setColorMode(Int colorMode) { + setUniform(_uColorMode, colorMode); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setColor(const Color3& color) { + setUniform(_uColor, color); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setViewProjectionMatrix(const Matrix3& matrix) { + setUniform(_uViewProjectionMatrix, matrix); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setScreenHeight(Int height) { + setUniform(_uScreenHeight, height); + return *this; +} + +ParticleSphereShader2D& ParticleSphereShader2D::setDomainHeight(Int height) { + setUniform(_uDomainHeight, height); + return *this; +} +} } diff --git a/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.frag b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.frag new file mode 100644 index 000000000..647da07c8 --- /dev/null +++ b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.frag @@ -0,0 +1,39 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +flat in vec3 color; +layout(location = 0) out lowp vec4 fragmentColor; + +void main() { + vec2 point = gl_PointCoord.xy*vec2(2.0, -2.0) + vec2(-1.0, 1.0); + float mag = dot(point, point); + if(mag > 1.0) discard; /* outside the circle */ + fragmentColor = vec4(color, 1.0); +} diff --git a/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.h b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.h new file mode 100644 index 000000000..ee251681c --- /dev/null +++ b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.h @@ -0,0 +1,67 @@ +#ifndef Magnum_Examples_FluidSimulation2D_ParticleSphereFlatShader_h +#define Magnum_Examples_FluidSimulation2D_ParticleSphereFlatShader_h +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +namespace Magnum { namespace Examples { +class ParticleSphereShader2D : public GL::AbstractShaderProgram { +public: + enum ColorMode { + UniformDiffuseColor = 0, + RampColorById + }; + + explicit ParticleSphereShader2D(); + + ParticleSphereShader2D& setNumParticles(Int numParticles); + ParticleSphereShader2D& setParticleRadius(Float radius); + + ParticleSphereShader2D& setColorMode(Int colorMode); + ParticleSphereShader2D& setColor(const Color3& color); + + ParticleSphereShader2D& setViewport(const Vector2i& viewport); + ParticleSphereShader2D& setViewProjectionMatrix(const Matrix3& matrix); + ParticleSphereShader2D& setScreenHeight(Int height); + ParticleSphereShader2D& setDomainHeight(Int height); + +private: + Int _uNumParticles, + _uParticleRadius, + _uColorMode, + _uColor, + _uViewProjectionMatrix, + _uScreenHeight, + _uDomainHeight; +}; +} } + +#endif diff --git a/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.vert b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.vert new file mode 100644 index 000000000..d6bc1cc29 --- /dev/null +++ b/src/fluidsimulation2d/Shaders/ParticleSphereShader2D.vert @@ -0,0 +1,70 @@ +/* + This file is part of Magnum. + + Original authors — credit is appreciated but not required: + + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 — + Vladimír Vondruš + 2019 — Nghia Truong + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or distribute + this software, either in source code form or as a compiled binary, for any + purpose, commercial or non-commercial, and by any means. + + In jurisdictions that recognize copyright laws, the author or authors of + this software dedicate any and all copyright interest in the software to + the public domain. We make this dedication for the benefit of the public + at large and to the detriment of our heirs and successors. We intend this + dedication to be an overt act of relinquishment in perpetuity of all + present and future rights to this software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +uniform highp mat3 viewProjectionMatrix; +uniform int numParticles; +uniform int colorMode; +uniform int screenHeight; +uniform int domainHeight; +uniform float particleRadius; +uniform vec3 uniformColor; + + +layout(location = 0) in highp vec2 position; +flat out vec3 color; + +const vec3 colorRamp[] = vec3[] ( + vec3(1.0, 0.0, 0.0), + vec3(1.0, 0.5, 0.0), + vec3(1.0, 1.0, 0.0), + vec3(1.0, 0.0, 1.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 1.0, 1.0), + vec3(0.0, 0.0, 1.0) +); + +vec3 generateVertexColor() { + if(colorMode == 1 ) { /* ramp color by particle id */ + float segmentSize = float(numParticles)/6.0f; + float segment = floor(float(gl_VertexID)/segmentSize); + float t = (float(gl_VertexID) - segmentSize*segment)/segmentSize; + vec3 startVal = colorRamp[int(segment)]; + vec3 endVal = colorRamp[int(segment) + 1]; + return mix(startVal, endVal, t); + } else { /* uniform diffuse color */ + return uniformColor; + } +} + +void main() { + color = generateVertexColor(); + gl_PointSize = particleRadius * float(screenHeight) / float(domainHeight); + gl_Position = mat4(viewProjectionMatrix) * vec4(position, 0, 1.0); +} diff --git a/src/fluidsimulation2d/SourceSansPro-Regular.ttf b/src/fluidsimulation2d/SourceSansPro-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b422bf43226f3c5f8a7821b5bda8e8effae586b2 GIT binary patch literal 269108 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 Kife4pfAxO|Kpl1f literal 0 HcmV?d00001 diff --git a/src/fluidsimulation2d/resources.conf b/src/fluidsimulation2d/resources.conf new file mode 100644 index 000000000..37053ec31 --- /dev/null +++ b/src/fluidsimulation2d/resources.conf @@ -0,0 +1,12 @@ +group=data + +[file] +filename=Shaders/ParticleSphereShader2D.vert +alias=ParticleSphereShader2D.vert + +[file] +filename=Shaders/ParticleSphereShader2D.frag +alias=ParticleSphereShader2D.frag + +[file] +filename=SourceSansPro-Regular.ttf