├── Projects ├── CMakeLists.txt ├── Simulations │ ├── CMakeLists.txt │ ├── Scenes │ │ ├── RisingSmoke │ │ │ ├── CMakeLists.txt │ │ │ └── RisingSmoke.cpp │ │ ├── ViscousLiquid │ │ │ ├── CMakeLists.txt │ │ │ └── ViscousLiquid.cpp │ │ ├── LevelSetLiquid │ │ │ ├── CMakeLists.txt │ │ │ └── LevelSetLiquid.cpp │ │ ├── CMakeLists.txt │ │ └── MultiMaterialLiquid │ │ │ ├── CMakeLists.txt │ │ │ ├── MultiMaterialBubbles.cpp │ │ │ └── MultiMaterialLayers.cpp │ └── Library │ │ ├── CMakeLists.txt │ │ ├── EulerianSmokeSimulator.h │ │ ├── EulerianLiquidSimulator.h │ │ ├── MultiMaterialLiquidSimulator.h │ │ ├── MultiMaterialPressureProjection.h │ │ └── MultiMaterialLiquidSimulator.cpp └── Tests │ ├── TestGeometricMGPoissonSolver │ ├── Utilities │ │ └── CMakeLists.txt │ ├── TestOneLevel │ │ ├── CMakeLists.txt │ │ └── TestOneLevel.cpp │ ├── TestSymmetry │ │ └── CMakeLists.txt │ ├── TestSmoother │ │ ├── CMakeLists.txt │ │ └── TestSmoother.cpp │ ├── TestGeometricCG │ │ └── CMakeLists.txt │ ├── TestMGTransfers │ │ └── CMakeLists.txt │ └── CMakeLists.txt │ ├── TestLevelSet │ ├── CMakeLists.txt │ └── TestLevelSet.cpp │ ├── TestScalarGrid │ ├── CMakeLists.txt │ └── TestScalarGrid.cpp │ ├── TestParticleSurfacing │ ├── CMakeLists.txt │ └── TestParticleSurfacing.cpp │ └── CMakeLists.txt ├── .gitmodules ├── Library ├── CMakeLists.txt ├── Utilities │ ├── CMakeLists.txt │ ├── Integrator.h │ ├── Transform.h │ ├── Timer.h │ ├── UniformGrid.h │ ├── GridUtilities.h │ └── SparseUniformGrid.h ├── RenderTools │ ├── CMakeLists.txt │ └── Renderer.h ├── SurfaceTrackers │ ├── CMakeLists.txt │ ├── Predicates.h │ ├── InitialGeometry.h │ ├── FluidParticles.h │ ├── EdgeMesh.h │ ├── InitialGeometry.cpp │ ├── EdgeMesh.cpp │ └── LevelSet.h └── SimTools │ ├── CMakeLists.txt │ ├── ViscositySolver.h │ ├── ComputeWeights.h │ ├── Noise.h │ ├── GeometricMultigridPoissonSolver.h │ ├── FieldAdvector.h │ ├── PressureProjection.h │ ├── GeometricPressureProjection.h │ ├── ConjugateGradientSolver.h │ ├── GeometricConjugateGradientSolver.h │ ├── Noise.cpp │ ├── ComputeWeights.cpp │ ├── ExtrapolateField.h │ ├── TestVelocityFields.h │ └── GeometricMultigridOperators.h ├── UnitTests ├── CMakeLists.txt ├── GridUtilitiesTests.cpp ├── UniformGridTests.cpp └── AnalyticalPoissonSolverTests.cpp ├── cmake ├── FindFreeglut.cmake └── FindEIGEN3.cmake ├── CMakeLists.txt ├── README.md ├── .clang-format ├── .gitattributes └── .gitignore /Projects/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Simulations) 2 | add_subdirectory(Tests) -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "googletest"] 2 | path = googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /Projects/Simulations/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(REGULAR_FOLDER RegularGridSimulations) 2 | 3 | add_subdirectory(Library) 4 | add_subdirectory(Scenes) -------------------------------------------------------------------------------- /Library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(RenderTools SimTools SurfaceTrackers Utilities) 2 | 3 | set(SOURCE_FOLDER BaseLibraries) 4 | 5 | add_subdirectory(RenderTools) 6 | add_subdirectory(SimTools) 7 | add_subdirectory(SurfaceTrackers) 8 | add_subdirectory(Utilities) -------------------------------------------------------------------------------- /Library/Utilities/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(Utilities INTERFACE) 2 | 3 | target_include_directories(Utilities INTERFACE 4 | $ 5 | $) 6 | 7 | target_link_libraries(Utilities 8 | INTERFACE 9 | RenderTools) -------------------------------------------------------------------------------- /Library/RenderTools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(RenderTools Renderer.cpp) 2 | 3 | target_include_directories(RenderTools PUBLIC 4 | $ 5 | $) 6 | 7 | set_target_properties(RenderTools PROPERTIES FOLDER ${SOURCE_FOLDER}) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/Utilities/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(GeometricMGUtilities INTERFACE) 2 | 3 | target_include_directories(GeometricMGUtilities INTERFACE 4 | $ 5 | $) 6 | 7 | target_link_libraries(GeometricMGUtilities 8 | INTERFACE 9 | SimTools 10 | Utilities) -------------------------------------------------------------------------------- /Projects/Tests/TestLevelSet/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestLevelSet TestLevelSet.cpp) 2 | 3 | target_link_libraries(TestLevelSet 4 | PRIVATE 5 | RenderTools 6 | SimTools 7 | SurfaceTrackers 8 | Utilities) 9 | 10 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 11 | 12 | install(TARGETS TestLevelSet RUNTIME DESTINATION ${REL}) 13 | 14 | set_target_properties(TestLevelSet PROPERTIES FOLDER ${TEST_FOLDER}) -------------------------------------------------------------------------------- /Projects/Tests/TestScalarGrid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestScalarGrid TestScalarGrid.cpp) 2 | 3 | target_link_libraries(TestScalarGrid PRIVATE 4 | RenderTools 5 | SimTools 6 | SurfaceTrackers 7 | Utilities) 8 | 9 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 10 | 11 | install(TARGETS TestScalarGrid RUNTIME DESTINATION ${REL}) 12 | 13 | set_target_properties(TestScalarGrid PROPERTIES FOLDER ${TEST_FOLDER}) -------------------------------------------------------------------------------- /Projects/Tests/TestParticleSurfacing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestParticleSurfacing TestParticleSurfacing.cpp) 2 | 3 | target_link_libraries(TestParticleSurfacing PRIVATE 4 | RenderTools 5 | SimTools 6 | SurfaceTrackers 7 | Utilities) 8 | 9 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 10 | 11 | install(TARGETS TestParticleSurfacing RUNTIME DESTINATION ${REL}) 12 | 13 | set_target_properties(TestParticleSurfacing PROPERTIES FOLDER ${TEST_FOLDER}) -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/RisingSmoke/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(RisingSmoke RisingSmoke.cpp) 2 | 3 | target_link_libraries(RisingSmoke 4 | PRIVATE 5 | RegularGridSimLibrary 6 | RenderTools 7 | SimTools 8 | SurfaceTrackers 9 | Utilities) 10 | 11 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 12 | 13 | install(TARGETS RisingSmoke RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(RisingSmoke PROPERTIES FOLDER ${REGULAR_FOLDER}) 16 | -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/ViscousLiquid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ViscousLiquid ViscousLiquid.cpp) 2 | 3 | target_link_libraries(ViscousLiquid 4 | PRIVATE 5 | RegularGridSimLibrary 6 | RenderTools 7 | SimTools 8 | SurfaceTrackers 9 | Utilities) 10 | 11 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 12 | 13 | install(TARGETS ViscousLiquid RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(ViscousLiquid PROPERTIES FOLDER ${REGULAR_FOLDER}) 16 | -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/LevelSetLiquid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(LevelSetLiquid LevelSetLiquid.cpp) 2 | 3 | target_link_libraries(LevelSetLiquid 4 | PRIVATE 5 | RegularGridSimLibrary 6 | RenderTools 7 | SimTools 8 | SurfaceTrackers 9 | Utilities) 10 | 11 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 12 | 13 | install(TARGETS LevelSetLiquid RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(LevelSetLiquid PROPERTIES FOLDER ${REGULAR_FOLDER}) 16 | -------------------------------------------------------------------------------- /Library/SurfaceTrackers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(SurfaceTrackers 2 | EdgeMesh.cpp 3 | FluidParticles.cpp 4 | InitialGeometry.cpp 5 | LevelSet.cpp 6 | Predicates.cpp) 7 | 8 | target_link_libraries(SurfaceTrackers 9 | PRIVATE 10 | RenderTools 11 | SimTools 12 | Utilities) 13 | 14 | target_include_directories(SurfaceTrackers PUBLIC 15 | $ 16 | $) 17 | 18 | set_target_properties(SurfaceTrackers PROPERTIES FOLDER ${SOURCE_FOLDER}) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestOneLevel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestOneLevel TestOneLevel.cpp) 2 | 3 | target_link_libraries(TestOneLevel 4 | PRIVATE 5 | GeometricMGUtilities 6 | RenderTools 7 | SurfaceTrackers 8 | SimTools 9 | Utilities) 10 | 11 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | install(TARGETS TestOneLevel RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(TestOneLevel PROPERTIES FOLDER ${TEST_FOLDER}/${GEOMETRICMG_FOLDER}/TestProjects) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestSymmetry/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestSymmetry TestSymmetry.cpp) 2 | 3 | target_link_libraries(TestSymmetry 4 | PRIVATE 5 | GeometricMGUtilities 6 | RenderTools 7 | SurfaceTrackers 8 | SimTools 9 | Utilities) 10 | 11 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | install(TARGETS TestSymmetry RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(TestSymmetry PROPERTIES FOLDER ${TEST_FOLDER}/${GEOMETRICMG_FOLDER}/TestProjects) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestSmoother/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestMGSmoother TestSmoother.cpp) 2 | 3 | target_link_libraries(TestMGSmoother 4 | PRIVATE 5 | GeometricMGUtilities 6 | RenderTools 7 | SurfaceTrackers 8 | SimTools 9 | Utilities) 10 | 11 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | install(TARGETS TestMGSmoother RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(TestMGSmoother PROPERTIES FOLDER ${TEST_FOLDER}/${GEOMETRICMG_FOLDER}/TestProjects) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestGeometricCG/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestGeometricCG TestGeometricCG.cpp) 2 | 3 | target_link_libraries(TestGeometricCG 4 | PRIVATE 5 | GeometricMGUtilities 6 | RenderTools 7 | SurfaceTrackers 8 | SimTools 9 | Utilities) 10 | 11 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | install(TARGETS TestGeometricCG RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(TestGeometricCG PROPERTIES FOLDER ${TEST_FOLDER}/${GEOMETRICMG_FOLDER}/TestProjects) -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestMGTransfers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestMGTransfers TestMGTransfers.cpp) 2 | 3 | target_link_libraries(TestMGTransfers 4 | PRIVATE 5 | GeometricMGUtilities 6 | RenderTools 7 | SurfaceTrackers 8 | SimTools 9 | Utilities) 10 | 11 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | install(TARGETS TestMGTransfers RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(TestMGTransfers PROPERTIES FOLDER ${TEST_FOLDER}/${GEOMETRICMG_FOLDER}/TestProjects) -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | macro(SUBDIRLIST result curdir) 2 | file(GLOB children RELATIVE ${curdir} ${curdir}/*) 3 | set(dirlist "") 4 | foreach(child ${children}) 5 | if(IS_DIRECTORY ${curdir}/${child}) 6 | list(APPEND dirlist ${child}) 7 | endif() 8 | endforeach() 9 | set(${result} ${dirlist}) 10 | endmacro() 11 | 12 | SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) 13 | 14 | FOREACH(subdir ${SUBDIRS}) 15 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/CMakeLists.txt" ) 16 | ADD_SUBDIRECTORY(${subdir}) 17 | endif() 18 | ENDFOREACH() -------------------------------------------------------------------------------- /Projects/Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TEST_FOLDER TestProjects) 2 | 3 | macro(SUBDIRLIST result curdir) 4 | file(GLOB children RELATIVE ${curdir} ${curdir}/*) 5 | set(dirlist "") 6 | foreach(child ${children}) 7 | if(IS_DIRECTORY ${curdir}/${child}) 8 | list(APPEND dirlist ${child}) 9 | endif() 10 | endforeach() 11 | set(${result} ${dirlist}) 12 | endmacro() 13 | 14 | SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) 15 | 16 | FOREACH(subdir ${SUBDIRS}) 17 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/CMakeLists.txt" ) 18 | ADD_SUBDIRECTORY(${subdir}) 19 | endif() 20 | ENDFOREACH() 21 | -------------------------------------------------------------------------------- /Library/SimTools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(SimTools 2 | ComputeWeights.cpp 3 | GeometricMultigridOperators.cpp 4 | GeometricMultigridPoissonSolver.cpp 5 | GeometricPressureProjection.cpp 6 | Noise.cpp 7 | PressureProjection.cpp 8 | ViscositySolver.cpp) 9 | 10 | target_link_libraries(SimTools 11 | PRIVATE 12 | SurfaceTrackers 13 | Utilities) 14 | 15 | target_include_directories(SimTools PUBLIC 16 | $ 17 | $) 18 | 19 | set_target_properties(SimTools PROPERTIES FOLDER ${SOURCE_FOLDER}) 20 | -------------------------------------------------------------------------------- /Projects/Simulations/Library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(RegularGridSimLibrary EulerianLiquidSimulator.cpp EulerianSmokeSimulator.cpp MultiMaterialLiquidSimulator.cpp MultiMaterialPressureProjection.cpp) 2 | 3 | target_link_libraries(RegularGridSimLibrary 4 | PRIVATE 5 | RenderTools 6 | SimTools 7 | SurfaceTrackers 8 | Utilities) 9 | 10 | target_include_directories(RegularGridSimLibrary PUBLIC 11 | $ 12 | $) 13 | 14 | set_target_properties(RegularGridSimLibrary PROPERTIES FOLDER ${REGULAR_FOLDER}/Library) 15 | -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(GEOMETRICMG_FOLDER GeometricMG) 2 | 3 | macro(SUBDIRLIST result curdir) 4 | file(GLOB children RELATIVE ${curdir} ${curdir}/*) 5 | set(dirlist "") 6 | foreach(child ${children}) 7 | if(IS_DIRECTORY ${curdir}/${child}) 8 | list(APPEND dirlist ${child}) 9 | endif() 10 | endforeach() 11 | set(${result} ${dirlist}) 12 | endmacro() 13 | 14 | SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) 15 | 16 | FOREACH(subdir ${SUBDIRS}) 17 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/CMakeLists.txt" ) 18 | ADD_SUBDIRECTORY(${subdir}) 19 | endif() 20 | ENDFOREACH() 21 | -------------------------------------------------------------------------------- /Library/SurfaceTrackers/Predicates.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_PREDICATES_H 2 | #define FLUIDSIM2D_PREDICATES_H 3 | 4 | #include "GridUtilities.h" 5 | 6 | namespace FluidSim2D 7 | { 8 | 9 | using REAL = double; 10 | using Vec2R = Vec2t; 11 | 12 | REAL exactinit(); // call this before anything else 13 | 14 | REAL orient2d(const REAL *pa, 15 | const REAL *pb, 16 | const REAL *pc); 17 | 18 | enum class IntersectionLabels { YES, ON, NO }; 19 | 20 | // Check that a mesh edge crosses over a grid edge. 21 | // Rotate grid edge to x-axis to perform check. 22 | IntersectionLabels exactEdgeIntersect(Vec2R q, Vec2R r, Vec2R s, Axis axis); 23 | 24 | } 25 | #endif -------------------------------------------------------------------------------- /Library/SurfaceTrackers/InitialGeometry.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_INITIAL_GEOMETRY_H 2 | #define FLUIDSIM2D_INITIAL_GEOMETRY_H 3 | 4 | #include "EdgeMesh.h" 5 | #include "Utilities.h" 6 | 7 | /////////////////////////////////// 8 | // 9 | // InitialGeometry.h 10 | // Ryan Goldade 2017 11 | // 12 | // List of initial surface configurations 13 | // to speed up scene creation. 14 | // 15 | //////////////////////////////////// 16 | 17 | namespace FluidSim2D 18 | { 19 | 20 | EdgeMesh makeCircleMesh(const Vec2d& center = Vec2d::Zero(), double radius = 1, double divisions = 10); 21 | EdgeMesh makeSquareMesh(const Vec2d& center = Vec2d::Zero(), const Vec2d& scale = Vec2d::Ones()); 22 | EdgeMesh makeDiamondMesh(const Vec2d& center = Vec2d::Zero(), const Vec2d& scale = Vec2d::Ones()); 23 | EdgeMesh makeNotchedDiskMesh(); 24 | EdgeMesh makeVortexMesh(); 25 | 26 | } 27 | 28 | #endif -------------------------------------------------------------------------------- /Library/SimTools/ViscositySolver.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_VISCOSITY_SOLVER_H 2 | #define FLUIDSIM2D_VISCOSITY_SOLVER_H 3 | 4 | #include "LevelSet.h" 5 | #include "ScalarGrid.h" 6 | #include "Utilities.h" 7 | #include "VectorGrid.h" 8 | 9 | /////////////////////////////////// 10 | // 11 | // ViscositySolver.h/cpp 12 | // Ryan Goldade 2017 13 | // 14 | // Variational viscosity solver. 15 | // Uses ghost fluid weights for moving 16 | // collisions. Uses volume control 17 | // weights for the various tensor and 18 | // velocity sample positions. 19 | // 20 | //////////////////////////////////// 21 | 22 | namespace FluidSim2D 23 | { 24 | 25 | void ViscositySolver(double dt, 26 | const LevelSet& surface, 27 | VectorGrid& velocity, 28 | const LevelSet& solidSurface, 29 | const VectorGrid& solidVelocity, 30 | const ScalarGrid& viscosity); 31 | } 32 | 33 | #endif -------------------------------------------------------------------------------- /Library/SimTools/ComputeWeights.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_COMPUTE_WEIGHTS_H 2 | #define FLUIDSIM2D_COMPUTE_WEIGHTS_H 3 | 4 | #include "LevelSet.h" 5 | #include "ScalarGrid.h" 6 | #include "Utilities.h" 7 | #include "VectorGrid.h" 8 | 9 | /////////////////////////////////// 10 | // 11 | // ComputeWeights.h/cpp 12 | // Ryan Goldade 2017 13 | // 14 | // Useful collection of tools to compute 15 | // control volume weights for use in both 16 | // pressure projection and viscosity solves. 17 | // 18 | //////////////////////////////////// 19 | 20 | namespace FluidSim2D 21 | { 22 | 23 | VectorGrid computeGhostFluidWeights(const LevelSet& surface); 24 | 25 | VectorGrid computeCutCellWeights(const LevelSet& surface, bool invert = false); 26 | 27 | ScalarGrid computeSupersampledAreas(const LevelSet& surface, ScalarGridSettings::SampleType sampleType, int samples); 28 | 29 | VectorGrid computeSupersampledFaceAreas(const LevelSet& surface, int samples); 30 | 31 | } 32 | 33 | #endif -------------------------------------------------------------------------------- /UnitTests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(MSVC) 2 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 3 | endif() 4 | 5 | add_subdirectory("${PROJECT_SOURCE_DIR}/googletest" googletest) 6 | 7 | mark_as_advanced( 8 | BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS 9 | gmock_build_tests gtest_build_samples gtest_build_tests 10 | gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols 11 | ) 12 | 13 | macro(package_add_test TESTNAME) 14 | add_executable(${TESTNAME} ${ARGN}) 15 | target_link_libraries(${TESTNAME} PRIVATE gmock gtest gtest_main SimTools SurfaceTrackers Utilities) 16 | set_target_properties(${TESTNAME} PROPERTIES FOLDER UnitTests) 17 | endmacro() 18 | 19 | package_add_test(2DFluidTests AnalyticalPoissonSolverTests.cpp AnalyticalViscosityTests.cpp ComputeWeightsTests.cpp EdgeMeshTests.cpp GeometricCGTests.cpp GeometricMultigridTests.cpp GridUtilitiesTests.cpp LevelSetTests.cpp ScalarGridTests.cpp SparseUniformGridTests.cpp UniformGridTests.cpp VectorGridTests.cpp) -------------------------------------------------------------------------------- /cmake/FindFreeglut.cmake: -------------------------------------------------------------------------------- 1 | # Try to find the FREEGLUT library 2 | # 3 | # FREEGLUT_INCLUDE_DIR 4 | # FREEGLUT_LIBRARY 5 | # FREEGLUT_FOUND 6 | 7 | FIND_PATH( 8 | FREEGLUT_INCLUDE_DIR GL/freeglut.h 9 | ${CMAKE_INCLUDE_PATH} 10 | $ENV{include} 11 | ${OPENGL_INCLUDE_DIR} 12 | /usr/include 13 | /usr/local/include 14 | ) 15 | FIND_LIBRARY( 16 | FREEGLUT_LIBRARY 17 | NAMES freeglut_static freeglut glut 18 | PATH 19 | ${CMAKE_LIBRARY_PATH} 20 | $ENV{lib} 21 | /usr/lib 22 | /usr/local/lib 23 | ) 24 | 25 | IF (FREEGLUT_INCLUDE_DIR AND FREEGLUT_LIBRARY) 26 | SET(FREEGLUT_FOUND TRUE) 27 | ENDIF (FREEGLUT_INCLUDE_DIR AND FREEGLUT_LIBRARY) 28 | 29 | IF (FREEGLUT_FOUND) 30 | IF (NOT FREEGLUT_FIND_QUIETLY) 31 | MESSAGE(STATUS "Found FREEGLUT: ${FREEGLUT_LIBRARY}") 32 | ENDIF (NOT FREEGLUT_FIND_QUIETLY) 33 | ELSE (FREEGLUT_FOUND) 34 | IF (FREEGLUT_FIND_REQUIRED) 35 | MESSAGE(FATAL_ERROR "Could not find FREEGLUT") 36 | ENDIF (FREEGLUT_FIND_REQUIRED) 37 | ENDIF (FREEGLUT_FOUND) -------------------------------------------------------------------------------- /Library/Utilities/Integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_INTEGRATOR_H 2 | #define FLUIDSIM2D_INTEGRATOR_H 3 | 4 | #include "Utilities.h" 5 | 6 | /////////////////////////////////// 7 | // 8 | // Integrator.h 9 | // Ryan Goldade 2016 10 | // 11 | // Integration functions to assist 12 | // with Lagrangian advection. 13 | // 14 | //////////////////////////////////// 15 | 16 | namespace FluidSim2D 17 | { 18 | 19 | enum class IntegrationOrder { FORWARDEULER, RK3 }; 20 | 21 | template 22 | T Integrator(double h, const T& x, const Function& f, IntegrationOrder order) 23 | { 24 | T value; 25 | 26 | switch (order) 27 | { 28 | case IntegrationOrder::FORWARDEULER: 29 | value = x + h * f(0, x); 30 | break; 31 | case IntegrationOrder::RK3: 32 | { 33 | T k1 = h * f(0., x); 34 | T k2 = h * f(h / 2., x + k1 / 2.); 35 | T k3 = h * f(h, x - k1 + k2); 36 | 37 | value = x + (1. / 6.) * (k1 + 4. * k2 + k3); 38 | break; 39 | } 40 | default: 41 | assert(false); 42 | } 43 | 44 | return value; 45 | } 46 | 47 | } 48 | 49 | #endif -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/MultiMaterialLiquid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(MultiMaterialBubbles MultiMaterialBubbles.cpp) 2 | 3 | target_link_libraries(MultiMaterialBubbles 4 | PRIVATE 5 | RegularGridSimLibrary 6 | RenderTools 7 | SimTools 8 | SurfaceTrackers 9 | Utilities) 10 | 11 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 12 | 13 | install(TARGETS MultiMaterialBubbles RUNTIME DESTINATION ${REL}) 14 | 15 | set_target_properties(MultiMaterialBubbles PROPERTIES FOLDER ${REGULAR_FOLDER}) 16 | 17 | add_executable(MultiMaterialLayers MultiMaterialLayers.cpp) 18 | 19 | target_link_libraries(MultiMaterialLayers 20 | PRIVATE 21 | RegularGridSimLibrary 22 | RenderTools 23 | SimTools 24 | SurfaceTrackers 25 | Utilities) 26 | 27 | file( RELATIVE_PATH REL ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) 28 | 29 | install(TARGETS MultiMaterialLayers RUNTIME DESTINATION ${REL}) 30 | 31 | set_target_properties(MultiMaterialLayers PROPERTIES FOLDER ${REGULAR_FOLDER}) -------------------------------------------------------------------------------- /Library/SimTools/Noise.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_NOISE_H 2 | #define FLUIDSIM2D_NOISE_H 3 | 4 | #include "Utilities.h" 5 | 6 | /////////////////////////////////// 7 | // 8 | // Borrowed and modified from Robert Bridson's 9 | // public code. 10 | // 11 | /////////////////////////////////// 12 | 13 | namespace FluidSim2D 14 | { 15 | 16 | struct Noise3 17 | { 18 | Noise3(unsigned seed = 171717); 19 | virtual ~Noise3() {}; 20 | 21 | void reinitialize(unsigned seed); 22 | double operator()(double x, double y, double z) const; 23 | double operator()(const Vec3d& x) const { return (*this)(x[0], x[1], x[2]); } 24 | 25 | protected: 26 | 27 | static constexpr unsigned n = 128; 28 | Vec3d myBasis[n]; 29 | int myPerm[n]; 30 | 31 | unsigned hashIndex(int i, int j, int k) const 32 | { 33 | return myPerm[(myPerm[(myPerm[i%n] + j) % n] + k) % n]; 34 | } 35 | }; 36 | 37 | // FlowNoise classes - time varying versions of some of the above 38 | struct FlowNoise3 : public Noise3 39 | { 40 | FlowNoise3(unsigned seed = 171717, double spinVariation = 0.2); 41 | void setTime(double time); // period of repetition is approximately 1 42 | 43 | protected: 44 | 45 | Vec3d myOriginalBasis[n]; 46 | double mySpinRate[n]; 47 | Vec3d mySpinAxis[n]; 48 | }; 49 | 50 | } 51 | #endif -------------------------------------------------------------------------------- /Library/Utilities/Transform.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_TRANSFORM_H 2 | #define FLUIDSIM2D_TRANSFORM_H 3 | 4 | #include "Utilities.h" 5 | 6 | /////////////////////////////////// 7 | // 8 | // Transform.h 9 | // Ryan Goldade 2017 10 | // 11 | // Simple transform container to simplify 12 | // implementation for various transforms 13 | // between grids, etc. 14 | // 15 | //////////////////////////////////// 16 | 17 | namespace FluidSim2D 18 | { 19 | 20 | class Transform 21 | { 22 | public: 23 | Transform(double dx = 1, const Vec2d& offset = Vec2d::Zero()) 24 | : myDx(dx) 25 | , myOffset(offset) 26 | {} 27 | 28 | FORCE_INLINE Vec2d indexToWorld(const Vec2d& indexPoint) const 29 | { 30 | return indexPoint * myDx + myOffset; 31 | } 32 | 33 | FORCE_INLINE Vec2d worldToIndex(const Vec2d& worldPoint) const 34 | { 35 | return (worldPoint - myOffset) / myDx; 36 | } 37 | 38 | FORCE_INLINE double dx() const { return myDx; } 39 | FORCE_INLINE Vec2d offset() const { return myOffset; } 40 | 41 | FORCE_INLINE bool operator==(const Transform& rhs) const 42 | { 43 | if (myDx != rhs.myDx) return false; 44 | if (myOffset != rhs.myOffset) return false; 45 | return true; 46 | } 47 | 48 | FORCE_INLINE bool operator!=(const Transform& rhs) const 49 | { 50 | return !(*this == rhs); 51 | } 52 | 53 | private: 54 | double myDx; 55 | Vec2d myOffset; 56 | }; 57 | 58 | } 59 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | set (CMAKE_CXX_STANDARD 17) 3 | project(2DFluid) 4 | 5 | set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) 6 | 7 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 8 | SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 9 | 10 | find_package(OpenGL REQUIRED) 11 | 12 | find_package(OpenMP) 13 | if (OPENMP_FOUND) 14 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 15 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 16 | set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") 17 | endif() 18 | 19 | if(MSVC) 20 | find_package(FREEGLUT REQUIRED) 21 | else() 22 | find_package(GLUT REQUIRED) 23 | endif() 24 | 25 | if(MSVC) 26 | include_directories( ${OPENGL_INCLUDE_DIR} ${FREEGLUT_INCLUDE_DIR} ) 27 | link_libraries(${OPENGL_LIBRARY} ${FREEGLUT_LIBRARY} ) 28 | else() 29 | include_directories( ${OPENGL_INCLUDE_DIRS} ${GLUT_INCLUDE_DIRS} ) 30 | link_libraries(${OPENGL_LIBRARIES} ${GLUT_LIBRARIES} ) 31 | endif() 32 | 33 | find_package(EIGEN3 REQUIRED) 34 | if (EIGEN3_FOUND) 35 | include_directories(${EIGEN3_INCLUDE_DIR}) 36 | endif() 37 | 38 | find_package(TBB REQUIRED) 39 | if (TBB_FOUND) 40 | include_directories(${TBB_INCLUDE_DIRS}) 41 | link_libraries(${TBB_LIBRARIES} ${TBB_LIBRARIES_DEBUG}) 42 | endif() 43 | 44 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 45 | 46 | add_subdirectory(Library) 47 | add_subdirectory(Projects) 48 | 49 | enable_testing() 50 | add_subdirectory(UnitTests) 51 | -------------------------------------------------------------------------------- /Library/SimTools/GeometricMultigridPoissonSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_GEOMETRIC_MULTIGRID_POISSONSOLVER_H 2 | #define FLUIDSIM2D_GEOMETRIC_MULTIGRID_POISSONSOLVER_H 3 | 4 | #include "Eigen/Sparse" 5 | 6 | #include "GeometricMultigridOperators.h" 7 | #include "UniformGrid.h" 8 | #include "Utilities.h" 9 | #include "VectorGrid.h" 10 | 11 | namespace FluidSim2D 12 | { 13 | 14 | class GeometricMultigridPoissonSolver 15 | { 16 | using MGCellLabels = GeometricMultigridOperators::CellLabels; 17 | 18 | public: 19 | GeometricMultigridPoissonSolver() : myMGLevels(0) {}; 20 | GeometricMultigridPoissonSolver(const UniformGrid& initialDomainLabels, 21 | const VectorGrid& boundaryWeights, 22 | int mgLevels, 23 | double dx); 24 | 25 | void applyMGVCycle(UniformGrid& solutionVector, 26 | const UniformGrid& rhsVector, 27 | bool useInitialGuess = false); 28 | 29 | private: 30 | 31 | std::vector> myDomainLabels; 32 | std::vector> mySolutionGrids, myRHSGrids, myResidualGrids; 33 | 34 | std::vector myBoundaryCells; 35 | 36 | int myMGLevels; 37 | 38 | VectorGrid myFineBoundaryWeights; 39 | 40 | std::vector myDx; 41 | 42 | int myBoundarySmootherIterations; 43 | int myBoundarySmootherWidth; 44 | 45 | UniformGrid myDirectSolverIndices; 46 | 47 | Eigen::SparseMatrix mySparseMatrix; 48 | Eigen::SimplicialCholesky> myCoarseSolver; 49 | }; 50 | 51 | } 52 | 53 | #endif -------------------------------------------------------------------------------- /Library/Utilities/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_TIMER_H 2 | #define FLUIDSIM2D_TIMER_H 3 | 4 | #ifdef _MSC_VER 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | #include "Utilities.h" 11 | 12 | /////////////////////////////////// 13 | // 14 | // Timer.h 15 | // Ryan Goldade 2017 16 | // 17 | // 18 | //////////////////////////////////// 19 | 20 | namespace FluidSim2D 21 | { 22 | 23 | class Timer 24 | { 25 | public: 26 | Timer() 27 | { 28 | #ifdef _MSC_VER 29 | QueryPerformanceFrequency(&Frequency); 30 | QueryPerformanceCounter(&StartingTime); 31 | #else 32 | struct timezone tz; 33 | gettimeofday(&myStart, &tz); 34 | #endif 35 | } 36 | 37 | float stop() 38 | { 39 | #ifdef _MSC_VER 40 | QueryPerformanceCounter(&EndingTime); 41 | ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; 42 | 43 | return float(ElapsedMicroseconds.QuadPart) / float(Frequency.QuadPart); 44 | #else 45 | 46 | struct timezone tz; 47 | gettimeofday(&myEnd, &tz); 48 | 49 | return (myEnd.tv_sec + myEnd.tv_usec * 1E-6) - (myStart.tv_sec + myStart.tv_usec * 1E-6); 50 | #endif 51 | } 52 | 53 | void reset() 54 | { 55 | #ifdef _MSC_VER 56 | QueryPerformanceFrequency(&Frequency); 57 | QueryPerformanceCounter(&StartingTime); 58 | #else 59 | struct timezone tz; 60 | gettimeofday(&myStart, &tz); 61 | #endif 62 | } 63 | 64 | private: 65 | 66 | #ifdef _MSC_VER 67 | LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds; 68 | LARGE_INTEGER Frequency; 69 | #else 70 | struct timeval myStart, myEnd; 71 | #endif 72 | }; 73 | 74 | } 75 | #endif -------------------------------------------------------------------------------- /Library/SimTools/FieldAdvector.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_FIELD_ADVECTOR_H 2 | #define FLUIDSIM2D_FIELD_ADVECTOR_H 3 | 4 | #include "tbb/blocked_range.h" 5 | #include "tbb/parallel_for.h" 6 | 7 | #include "Integrator.h" 8 | #include "ScalarGrid.h" 9 | #include "Utilities.h" 10 | #include "VectorGrid.h" 11 | 12 | /////////////////////////////////// 13 | // 14 | // FieldAdvector.h/cpp 15 | // Ryan Goldade 2017 16 | // 17 | // A versatile advection class to handle 18 | // forward advection and semi-Lagrangian 19 | // backtracing. 20 | // 21 | //////////////////////////////////// 22 | 23 | namespace FluidSim2D 24 | { 25 | 26 | enum class InterpolationOrder { LINEAR, CUBIC }; 27 | 28 | template 29 | void advectField(double dt, Field& destinationField, const Field& sourceField, const VelocityField& velocity, IntegrationOrder order, InterpolationOrder interpOrder) 30 | { 31 | assert(&destinationField != &sourceField); 32 | 33 | tbb::parallel_for(tbb::blocked_range(0, sourceField.voxelCount()), [&](const tbb::blocked_range& range) 34 | { 35 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 36 | { 37 | Vec2i cell = sourceField.unflatten(cellIndex); 38 | 39 | Vec2d worldPoint = sourceField.indexToWorld(cell.cast()); 40 | worldPoint = Integrator(-dt, worldPoint, velocity, order); 41 | 42 | switch (interpOrder) 43 | { 44 | case InterpolationOrder::LINEAR: 45 | destinationField(cell) = sourceField.biLerp(worldPoint); 46 | break; 47 | case InterpolationOrder::CUBIC: 48 | destinationField(cell) = sourceField.biCubicInterp(worldPoint, false, true); 49 | break; 50 | default: 51 | assert(false); 52 | break; 53 | } 54 | } 55 | }); 56 | } 57 | 58 | } 59 | 60 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2dFluid 2 | This is a 2D fluid simulation project based largley on the material found in Robert Bridson's textbook "Fluid Simulation for Computer Graphics, 2nd Edition". The code contains implementations for the staggered-grid Eulerian pressure projection and viscosity solve, as well as surface tracking methods for level sets, explicit simplicial meshes and fluid particles (markers, PIC, FLIP). 3 | 4 | To build the project in Linux, create a new folder in the root directly (usually "build"), type cmake .. then make. The binaries to run the simulations should compile in the ./bin folder. The project requires Eigen, tbb, and glut to be installed and findable. 5 | 6 | For VS, use the cmake-gui to generate the solution file. Make sure to include the path to your Eigen3, freeglut, and tbb folders. 7 | 8 | ## Legal stuff: 9 | 10 | Copyright 2018 Ryan Goldade 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | 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 OR COPYRIGHT HOLDERS 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. 17 | -------------------------------------------------------------------------------- /Library/SimTools/PressureProjection.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_PRESSURE_PROJECTION_H 2 | #define FLUIDSIM2D_PRESSURE_PROJECTION_H 3 | 4 | #include "LevelSet.h" 5 | #include "Renderer.h" 6 | #include "ScalarGrid.h" 7 | #include "Utilities.h" 8 | #include "VectorGrid.h" 9 | 10 | /////////////////////////////////// 11 | // 12 | // PressureProjection.h/cpp 13 | // Ryan Goldade 2017 14 | // 15 | // Variational pressure solve. Allows 16 | // for moving solids. 17 | // 18 | //////////////////////////////////// 19 | 20 | namespace FluidSim2D 21 | { 22 | 23 | class PressureProjection 24 | { 25 | 26 | public: 27 | // For variational solve, surface should be extrapolated into the solid boundary 28 | PressureProjection(const LevelSet& surface, 29 | const VectorGrid& cutCellWeights, 30 | const VectorGrid& ghostFluidWeights, 31 | const VectorGrid& solidVelocity); 32 | 33 | void project(VectorGrid& velocity); 34 | 35 | void setInitialGuess(const ScalarGrid& initialGuessPressure) 36 | { 37 | assert(mySurface.isGridMatched(initialGuessPressure)); 38 | myUseInitialGuessPressure = true; 39 | myInitialGuessPressure = &initialGuessPressure; 40 | } 41 | 42 | void disableInitialGuess() 43 | { 44 | myUseInitialGuessPressure = false; 45 | } 46 | 47 | ScalarGrid getPressureGrid() 48 | { 49 | return myPressure; 50 | } 51 | 52 | const VectorGrid& getValidFaces() 53 | { 54 | return myValidFaces; 55 | } 56 | 57 | void drawPressure(Renderer& renderer) const; 58 | 59 | private: 60 | 61 | const VectorGrid& mySolidVelocity; 62 | const VectorGrid& myGhostFluidWeights; 63 | const VectorGrid& myCutCellWeights; 64 | 65 | // Store flags for solved faces 66 | VectorGrid myValidFaces; 67 | 68 | const LevelSet& mySurface; 69 | 70 | ScalarGrid myPressure; 71 | 72 | const ScalarGrid* myInitialGuessPressure; 73 | bool myUseInitialGuessPressure; 74 | }; 75 | 76 | } 77 | 78 | #endif -------------------------------------------------------------------------------- /Library/SimTools/GeometricPressureProjection.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_GEOMETRIC_PRESSURE_PROJECTION_H 2 | #define FLUIDSIM2D_GEOMETRIC_PRESSURE_PROJECTION_H 3 | 4 | #include "GeometricConjugateGradientSolver.h" 5 | #include "LevelSet.h" 6 | #include "Renderer.h" 7 | #include "ScalarGrid.h" 8 | #include "Utilities.h" 9 | #include "VectorGrid.h" 10 | 11 | /////////////////////////////////// 12 | // 13 | // GeometricPressureProjection.h/cpp 14 | // Ryan Goldade 2019 15 | // 16 | //////////////////////////////////// 17 | 18 | namespace FluidSim2D 19 | { 20 | 21 | class GeometricPressureProjection 22 | { 23 | using StoreReal = double; 24 | using SolveReal = double; 25 | 26 | public: 27 | GeometricPressureProjection(const LevelSet& surface, 28 | const VectorGrid& cutCellWeights, 29 | const VectorGrid& ghostFluidWeights, 30 | const VectorGrid& solidVelocity); 31 | 32 | void project(VectorGrid& velocity, bool useMGPreconditioner); 33 | 34 | void setInitialGuess(const ScalarGrid& initialGuessPressure) 35 | { 36 | assert(mySurface.isGridMatched(initialGuessPressure)); 37 | myUseInitialGuessPressure = true; 38 | myInitialGuessPressure = &initialGuessPressure; 39 | } 40 | 41 | void disableInitialGuess() 42 | { 43 | myUseInitialGuessPressure = false; 44 | } 45 | 46 | ScalarGrid getPressureGrid() 47 | { 48 | return myPressure; 49 | } 50 | 51 | const VectorGrid& getValidFaces() 52 | { 53 | return myValidFaces; 54 | } 55 | 56 | void drawPressure(Renderer& renderer) const; 57 | 58 | private: 59 | 60 | const VectorGrid& mySolidVelocity; 61 | const VectorGrid& myGhostFluidWeights; 62 | const VectorGrid& myCutCellWeights; 63 | 64 | // Store flags for solved faces 65 | VectorGrid myValidFaces; 66 | 67 | const LevelSet& mySurface; 68 | 69 | ScalarGrid myPressure; 70 | 71 | const ScalarGrid *myInitialGuessPressure; 72 | bool myUseInitialGuessPressure; 73 | }; 74 | 75 | } 76 | 77 | #endif -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | DerivePointerAlignment: false 4 | PointerAlignment: Left 5 | ColumnLimit: 240 6 | IndentWidth: 4 7 | AccessModifierOffset: -4 8 | AlignAfterOpenBracket: Align 9 | 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | 15 | 16 | AllowShortBlocksOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: false 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterReturnType: None 22 | 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: true 25 | 26 | BinPackArguments: true 27 | BinPackParameters: true 28 | 29 | BraceWrapping: 30 | AfterClass: true # false 31 | AfterControlStatement: true # false 32 | AfterEnum: true # false 33 | AfterFunction: true # false 34 | AfterNamespace: true # false 35 | AfterObjCDeclaration: true # false 36 | AfterStruct: true # false 37 | AfterUnion: true # false 38 | AfterExternBlock: true # false 39 | BeforeCatch: true # false 40 | BeforeElse: true # false 41 | IndentBraces: true # false 42 | SplitEmptyFunction: true 43 | SplitEmptyRecord: true 44 | SplitEmptyNamespace: true 45 | 46 | BreakBeforeBinaryOperators: None 47 | BreakBeforeBraces: Allman 48 | BreakBeforeInheritanceComma: false 49 | BreakBeforeTernaryOperators: true 50 | BreakConstructorInitializers: BeforeComma 51 | BreakStringLiterals: true 52 | CompactNamespaces: false 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 54 | ConstructorInitializerIndentWidth: 4 55 | ContinuationIndentWidth: 4 56 | Cpp11BracedListStyle: true 57 | DerivePointerAlignment: false 58 | DisableFormat: false 59 | ExperimentalAutoDetectBinPacking: false 60 | FixNamespaceComments: true 61 | IncludeBlocks: Preserve 62 | IndentCaseLabels: true 63 | IndentPPDirectives: None 64 | IndentWrappedFunctionNames: false 65 | KeepEmptyLinesAtTheStartOfBlocks: false 66 | MaxEmptyLinesToKeep: 1 67 | NamespaceIndentation: None 68 | PointerAlignment: Left 69 | ReflowComments: true 70 | SortIncludes: true 71 | SortUsingDeclarations: true 72 | SpaceAfterCStyleCast: false 73 | SpaceAfterTemplateKeyword: false 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 1 78 | SpacesInAngles: false 79 | SpacesInParentheses: false 80 | SpacesInSquareBrackets: false 81 | Standard: Cpp11 82 | TabWidth: 8 83 | UseTab: Never -------------------------------------------------------------------------------- /Projects/Tests/TestParticleSurfacing/TestParticleSurfacing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "EdgeMesh.h" 4 | #include "FluidParticles.h" 5 | #include "InitialGeometry.h" 6 | #include "LevelSet.h" 7 | #include "Renderer.h" 8 | #include "TestVelocityFields.h" 9 | #include "Transform.h" 10 | #include "Utilities.h" 11 | 12 | using namespace FluidSim2D; 13 | 14 | static std::unique_ptr particles; 15 | static std::unique_ptr renderer; 16 | 17 | static bool isDisplayDirty = true; 18 | 19 | static Transform xform; 20 | static Vec2i gridSize; 21 | 22 | void display() 23 | { 24 | if (isDisplayDirty) 25 | { 26 | renderer->clear(); 27 | 28 | LevelSet sphereUnionSurface = particles->surfaceParticles(xform, gridSize, 5); 29 | 30 | sphereUnionSurface.drawGrid(*renderer, true); 31 | sphereUnionSurface.drawSupersampledValues(*renderer, .5, 5, 3); 32 | sphereUnionSurface.drawDCSurface(*renderer, Vec3d(1., 0., 0.), 2.); 33 | sphereUnionSurface.drawNormals(*renderer, Vec3d(.5, .5, .5), .1); 34 | 35 | particles->drawPoints(*renderer, Vec3d(1., 0., 0.), 5.); 36 | 37 | glutPostRedisplay(); 38 | } 39 | } 40 | 41 | void keyboard(unsigned char key, int, int) 42 | { 43 | if (key == 'n') 44 | { 45 | LevelSet reseedSurface = particles->surfaceParticles(xform, gridSize, 5); 46 | particles->reseed(reseedSurface); 47 | } 48 | } 49 | 50 | int main(int argc, char** argv) 51 | { 52 | EdgeMesh initialMesh = makeCircleMesh(); 53 | 54 | EdgeMesh tempMesh = makeCircleMesh(Vec2d(.5, .5), 1., 10); 55 | assert(tempMesh.unitTestMesh()); 56 | initialMesh.insertMesh(tempMesh); 57 | 58 | tempMesh = makeCircleMesh(Vec2d(.05, .05), .5, 10); 59 | assert(tempMesh.unitTestMesh()); 60 | initialMesh.insertMesh(tempMesh); 61 | 62 | assert(initialMesh.unitTestMesh()); 63 | 64 | double dx = .125; 65 | Vec2d topRightCorner(2.25, 2.25); 66 | Vec2d bottomLeftCorner(-1.5, -1.5); 67 | gridSize = ((topRightCorner - bottomLeftCorner) / dx).cast(); 68 | xform = Transform(dx, bottomLeftCorner); 69 | Vec2d center = .5 * (topRightCorner + bottomLeftCorner); 70 | 71 | renderer = std::make_unique("Particle Surfacing Test", Vec2i(1000, 1000), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 72 | 73 | LevelSet initialSurface(xform, gridSize, 5); 74 | initialSurface.initFromMesh(initialMesh, false); 75 | 76 | particles = std::make_unique(dx * .75, 8, 2); 77 | particles->init(initialSurface); 78 | 79 | std::function displayFunc = display; 80 | renderer->setUserDisplay(displayFunc); 81 | 82 | std::function keyboardFunc = keyboard; 83 | renderer->setUserKeyboard(keyboardFunc); 84 | 85 | renderer->run(); 86 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Projects/Simulations/Library/EulerianSmokeSimulator.h: -------------------------------------------------------------------------------- 1 | #ifndef EULERIAN_SMOKE_SIMULATOR_H 2 | #define EULERIAN_SMOKE_SIMULATOR_H 3 | 4 | #include "Integrator.h" 5 | #include "LevelSet.h" 6 | #include "ScalarGrid.h" 7 | #include "Transform.h" 8 | #include "Utilities.h" 9 | #include "VectorGrid.h" 10 | 11 | /////////////////////////////////// 12 | // 13 | // EulerianSmokeSimulator.h/cpp 14 | // Ryan Goldade 2016 15 | // 16 | // Wrapper class around the staggered MAC grid smoke simulator 17 | // (which stores face-aligned velocities and pressure). 18 | // Handles velocity, surface, viscosity field advection, 19 | // pressure projection, viscosity and velocity extrapolation. 20 | // 21 | //////////////////////////////////// 22 | 23 | namespace FluidSim2D 24 | { 25 | 26 | class EulerianSmokeSimulator 27 | { 28 | public: 29 | EulerianSmokeSimulator(const Transform& xform, Vec2i size, double ambientTemperature = 300) 30 | : myXform(xform), myAmbientTemperature(ambientTemperature) 31 | { 32 | myVelocity = VectorGrid(myXform, size, VectorGridSettings::SampleType::STAGGERED); 33 | mySolidVelocity = VectorGrid(myXform, size, 0, VectorGridSettings::SampleType::STAGGERED); 34 | 35 | mySolidSurface = LevelSet(myXform, size, 5); 36 | 37 | mySmokeDensity = ScalarGrid(myXform, size, 0); 38 | mySmokeTemperature = ScalarGrid(myXform, size, myAmbientTemperature); 39 | 40 | myOldPressure = ScalarGrid(myXform, size, 0); 41 | } 42 | 43 | void setSolidSurface(const LevelSet& solidSurface); 44 | void setSolidVelocity(const VectorGrid& solidVelocity); 45 | 46 | void setFluidVelocity(const VectorGrid& velocity); 47 | void setSmokeSource(const ScalarGrid& density, const ScalarGrid& temperature); 48 | 49 | void advectFluidMaterial(double dt, InterpolationOrder order); 50 | void advectFluidVelocity(double dt, InterpolationOrder order); 51 | void advectOldPressure(double dt, InterpolationOrder order); 52 | 53 | // Perform pressure project, viscosity solver, extrapolation, surface and velocity advection 54 | void runTimestep(double dt); 55 | 56 | // Useful for CFL 57 | double maxVelocityMagnitude() { return myVelocity.maxMagnitude(); } 58 | 59 | // Rendering tools 60 | void drawGrid(Renderer& renderer) const; 61 | void drawFluidDensity(Renderer& renderer, double maxDensity); 62 | void drawFluidVelocity(Renderer& renderer, double length) const; 63 | 64 | void drawSolidSurface(Renderer& renderer); 65 | void drawSolidVelocity(Renderer& renderer, double length) const; 66 | 67 | private: 68 | 69 | // Simulation containers 70 | VectorGrid myVelocity, mySolidVelocity; 71 | LevelSet mySolidSurface; 72 | ScalarGrid mySmokeDensity, mySmokeTemperature; 73 | 74 | double myAmbientTemperature; 75 | 76 | Transform myXform; 77 | 78 | ScalarGrid myOldPressure; 79 | }; 80 | 81 | } 82 | #endif -------------------------------------------------------------------------------- /Projects/Tests/TestLevelSet/TestLevelSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "EdgeMesh.h" 4 | #include "InitialGeometry.h" 5 | #include "LevelSet.h" 6 | #include "Renderer.h" 7 | #include "TestVelocityFields.h" 8 | #include "Transform.h" 9 | #include "Utilities.h" 10 | 11 | using namespace FluidSim2D; 12 | 13 | static std::unique_ptr renderer; 14 | static std::unique_ptr surface; 15 | static std::unique_ptr velocityField; 16 | 17 | static constexpr double dt = 1./24.; 18 | 19 | static bool runSimulation = false; 20 | static bool runSingleTimestep = false; 21 | static bool isDisplayDirty = true; 22 | 23 | void keyboard(unsigned char key, int, int) 24 | { 25 | if (key == ' ') 26 | runSimulation = !runSimulation; 27 | else if (key == 'n') 28 | runSingleTimestep = true; 29 | } 30 | 31 | void display() 32 | { 33 | if (runSimulation || runSingleTimestep) 34 | { 35 | renderer->clear(); 36 | 37 | EdgeMesh surfaceMesh = surface->buildDCMesh(); 38 | 39 | surfaceMesh.advectMesh(dt, *velocityField, IntegrationOrder::RK3); 40 | surface->initFromMesh(surfaceMesh, true); 41 | 42 | isDisplayDirty = true; 43 | } 44 | 45 | runSingleTimestep = false; 46 | 47 | if (isDisplayDirty) 48 | { 49 | surface->drawGrid(*renderer, true); 50 | surface->drawDCSurface(*renderer, Vec3d(1., 0., 0.), 2.); 51 | surface->drawNormals(*renderer, Vec3d(.5, .5, .5), .1); 52 | 53 | glutPostRedisplay(); 54 | } 55 | } 56 | 57 | int main(int argc, char** argv) 58 | { 59 | EdgeMesh initialMesh = makeCircleMesh(); 60 | 61 | EdgeMesh testMesh2 = makeCircleMesh(Vec2d(.5, .5), 1., 10.); 62 | EdgeMesh testMesh3 = makeCircleMesh(Vec2d(.05, .05), .5, 10.); 63 | 64 | assert(initialMesh.unitTestMesh()); 65 | assert(testMesh2.unitTestMesh()); 66 | assert(testMesh3.unitTestMesh()); 67 | 68 | initialMesh.insertMesh(testMesh2); 69 | initialMesh.insertMesh(testMesh3); 70 | 71 | assert(initialMesh.unitTestMesh()); 72 | 73 | AlignedBox2d bbox; 74 | 75 | for (int vertexIndex = 0; vertexIndex < initialMesh.vertexCount(); ++vertexIndex) 76 | { 77 | bbox.extend(initialMesh.vertex(vertexIndex)); 78 | } 79 | 80 | Vec2d boundingBoxSize = bbox.max() - bbox.min(); 81 | 82 | double dx = .05; 83 | 84 | Vec2d origin = bbox.min() - boundingBoxSize; 85 | Vec2i size = (3. * boundingBoxSize / dx).cast(); 86 | 87 | Transform xform(dx, origin); 88 | surface = std::make_unique(xform, size, 5); 89 | surface->initFromMesh(initialMesh, true /* resize grid */); 90 | 91 | renderer = std::make_unique("Levelset Test", Vec2i(1000), origin, double(size[1]) * dx, &argc, argv); 92 | 93 | velocityField = std::make_unique(); 94 | 95 | std::function displayFunc = display; 96 | renderer->setUserDisplay(displayFunc); 97 | 98 | std::function keyboardFunc = keyboard; 99 | renderer->setUserKeyboard(keyboardFunc); 100 | 101 | renderer->run(); 102 | } 103 | -------------------------------------------------------------------------------- /cmake/FindEIGEN3.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Eigen3 lib 2 | # 3 | # This module supports requiring a minimum version, e.g. you can do 4 | # find_package(Eigen3 3.1.2) 5 | # to require version 3.1.2 or newer of Eigen3. 6 | # 7 | # Once done this will define 8 | # 9 | # EIGEN3_FOUND - system has eigen lib with correct version 10 | # EIGEN3_INCLUDE_DIR - the eigen include directory 11 | # EIGEN3_VERSION - eigen version 12 | 13 | # Copyright (c) 2006, 2007 Montel Laurent, 14 | # Copyright (c) 2008, 2009 Gael Guennebaud, 15 | # Copyright (c) 2009 Benoit Jacob 16 | # Redistribution and use is allowed according to the terms of the 2-clause BSD license. 17 | 18 | if(NOT Eigen3_FIND_VERSION) 19 | if(NOT Eigen3_FIND_VERSION_MAJOR) 20 | set(Eigen3_FIND_VERSION_MAJOR 2) 21 | endif(NOT Eigen3_FIND_VERSION_MAJOR) 22 | if(NOT Eigen3_FIND_VERSION_MINOR) 23 | set(Eigen3_FIND_VERSION_MINOR 91) 24 | endif(NOT Eigen3_FIND_VERSION_MINOR) 25 | if(NOT Eigen3_FIND_VERSION_PATCH) 26 | set(Eigen3_FIND_VERSION_PATCH 0) 27 | endif(NOT Eigen3_FIND_VERSION_PATCH) 28 | 29 | set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") 30 | endif(NOT Eigen3_FIND_VERSION) 31 | 32 | macro(_eigen3_check_version) 33 | file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) 34 | 35 | string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") 36 | set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") 37 | string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") 38 | set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") 39 | string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") 40 | set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") 41 | 42 | set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) 43 | if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 44 | set(EIGEN3_VERSION_OK FALSE) 45 | else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 46 | set(EIGEN3_VERSION_OK TRUE) 47 | endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 48 | 49 | if(NOT EIGEN3_VERSION_OK) 50 | 51 | message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " 52 | "but at least version ${Eigen3_FIND_VERSION} is required") 53 | endif(NOT EIGEN3_VERSION_OK) 54 | endmacro(_eigen3_check_version) 55 | 56 | if (EIGEN3_INCLUDE_DIR) 57 | 58 | # in cache already 59 | _eigen3_check_version() 60 | set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) 61 | 62 | else (EIGEN3_INCLUDE_DIR) 63 | 64 | find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library 65 | PATHS 66 | ${CMAKE_INSTALL_PREFIX}/include 67 | ${KDE4_INCLUDE_DIR} 68 | PATH_SUFFIXES eigen3 eigen 69 | ) 70 | 71 | if(EIGEN3_INCLUDE_DIR) 72 | _eigen3_check_version() 73 | endif(EIGEN3_INCLUDE_DIR) 74 | 75 | include(FindPackageHandleStandardArgs) 76 | find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) 77 | 78 | mark_as_advanced(EIGEN3_INCLUDE_DIR) 79 | 80 | endif(EIGEN3_INCLUDE_DIR) 81 | 82 | -------------------------------------------------------------------------------- /Library/RenderTools/Renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_RENDERER_H 2 | #define FLUIDSIM2D_RENDERER_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __APPLE__ 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #include "Utilities.h" 14 | 15 | /////////////////////////////////// 16 | // 17 | // Renderer.h 18 | // Ryan Goldade 2016 19 | // 20 | // Render machine to handle adding 21 | // primitives from various different 22 | // sources and having a central place 23 | // to run the render loop. 24 | // 25 | //////////////////////////////////// 26 | 27 | namespace FluidSim2D 28 | { 29 | 30 | class Renderer 31 | { 32 | public: 33 | Renderer(const char *title, Vec2i windowSize, Vec2d screenOrigin, 34 | double screenHeight, int *argc, char **argv); 35 | 36 | void display(); 37 | void mouse(int button, int state, int x, int y); 38 | void drag(int x, int y); 39 | void keyboard(unsigned char key, int x, int y); 40 | void reshape(int w, int h); 41 | 42 | void setUserMouseClick(const std::function& mouseClickFunction); 43 | void setUserKeyboard(const std::function& keyboardFunction); 44 | void setUserMouseDrag(const std::function& mouseDragFunction); 45 | void setUserDisplay(const std::function& displayFunction); 46 | 47 | void addPoint(const Vec2d& point, const Vec3d& colour = Vec3d(0, 0, 0), double size = 1.); 48 | void addPoints(const VecVec2d& points, const Vec3d& colour = Vec3d(0), double size = 1.); 49 | 50 | void addLine(const Vec2d& startingPoint, const Vec2d& endingPoint, const Vec3d& colour = Vec3d(0), double lineWidth = 1); 51 | void addLines(const VecVec2d& startingPoints, const VecVec2d& endingPoints, const Vec3d& colour, double lineWidth = 1); 52 | 53 | void addTriFaces(const VecVec2d& vertices, const VecVec3i& faces, const VecVec3d& faceColours); 54 | void addQuadFaces(const VecVec2d& vertices, const VecVec4i& faces, const VecVec3d& faceColours); 55 | 56 | void drawPrimitives() const; 57 | 58 | void printImage(const std::string& filename) const; 59 | 60 | void clear(); 61 | void run(); 62 | 63 | private: 64 | 65 | std::vector myPoints; 66 | VecVec3d myPointColours; 67 | std::vector myPointSizes; 68 | 69 | std::vector myLineStartingPoints; 70 | std::vector myLineEndingPoints; 71 | VecVec3d myLineColours; 72 | std::vector myLineWidths; 73 | 74 | std::vector myTriVertices; 75 | std::vector myTriFaces; 76 | std::vector myTriFaceColours; 77 | 78 | std::vector myQuadVertices; 79 | std::vector myQuadFaces; 80 | std::vector myQuadFaceColours; 81 | 82 | // width, height 83 | Vec2i myWindowSize; 84 | 85 | Vec2d myCurrentScreenOrigin; 86 | double myCurrentScreenHeight; 87 | 88 | Vec2d myDefaultScreenOrigin; 89 | double myDefaultScreenHeight; 90 | 91 | // Mouse specific state 92 | Vec2i myMousePosition; 93 | bool myMouseMoved; 94 | 95 | enum class MouseAction { INACTIVE, PAN, ZOOM_IN, ZOOM_OUT }; 96 | MouseAction myMouseAction; 97 | 98 | // User specific extensions for each glut callback 99 | std::function myUserKeyboardFunction; 100 | std::function myUserMouseClickFunction; 101 | std::function myUserMouseDragFunction; 102 | std::function myUserDisplayFunction; 103 | }; 104 | 105 | } 106 | 107 | #endif -------------------------------------------------------------------------------- /Projects/Tests/TestScalarGrid/TestScalarGrid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "LevelSet.h" 4 | #include "Renderer.h" 5 | #include "ScalarGrid.h" 6 | #include "Transform.h" 7 | #include "Utilities.h" 8 | #include "VectorGrid.h" 9 | 10 | using namespace FluidSim2D; 11 | 12 | static std::unique_ptr gRenderer; 13 | 14 | static bool gDoScalarTest = false; 15 | static bool gDoVectorTest = false; 16 | static bool gDoLevelSetTest = true; 17 | 18 | int main(int argc, char** argv) 19 | { 20 | double dx = .5; 21 | Vec2d topRightCorner(20, 20); 22 | Vec2d bottomLeftCorner(-20, -20); 23 | Vec2i size = ((topRightCorner - bottomLeftCorner) / dx).cast(); 24 | Transform xform(dx, bottomLeftCorner); 25 | Vec2d center = .5f * (topRightCorner + bottomLeftCorner); 26 | 27 | gRenderer = std::make_unique("Scalar Grid Test", Vec2i(1000, 1000), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 28 | 29 | // Test scalar grid 30 | if (gDoScalarTest) 31 | { 32 | ScalarGrid testGrid(xform, size, ScalarGridSettings::SampleType::CENTER, ScalarGridSettings::BorderType::CLAMP); 33 | 34 | // Make sure flatten and unflatten are working 35 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 36 | { 37 | int flatIndex = testGrid.flatten(cell); 38 | Vec2i testCell = testGrid.unflatten(flatIndex); 39 | 40 | assert(cell == testCell); 41 | }); 42 | 43 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 44 | { 45 | Vec2d worldPosition = testGrid.indexToWorld(cell.cast()); 46 | testGrid(cell) = (worldPosition - center).norm(); 47 | }); 48 | 49 | testGrid.drawGrid(*gRenderer); 50 | testGrid.drawSamplePoints(*gRenderer, Vec3d(1, 0, 0), 5); 51 | testGrid.drawSupersampledValues(*gRenderer, .5, 3, 5); 52 | } 53 | // Test vector grid. TODO: move to vector grid test.. this is a scalar grid test after all. 54 | else if (gDoVectorTest) 55 | { 56 | VectorGrid testVectorGrid(xform, size, VectorGridSettings::SampleType::STAGGERED, ScalarGridSettings::BorderType::CLAMP); 57 | 58 | for (int axis : {0, 1}) 59 | { 60 | forEachVoxelRange(Vec2i::Zero(), testVectorGrid.size(axis), [&](const Vec2i& cell) 61 | { 62 | Vec2d offset(0); offset[axis] += .5; 63 | Vec2d worldPosition0 = testVectorGrid.indexToWorld(cell.cast() - offset, axis); 64 | Vec2d worldPosition1 = testVectorGrid.indexToWorld(cell.cast() + offset, axis); 65 | 66 | double gradient = ((worldPosition1 - center).norm() - (worldPosition0 - center).norm()) / dx; 67 | testVectorGrid(cell, axis) = gradient; 68 | }); 69 | } 70 | 71 | testVectorGrid.drawGrid(*gRenderer); 72 | testVectorGrid.drawSamplePoints(*gRenderer, Vec3d(1, 0, 0), Vec3d(0, 1, 0), Vec2d(5, 5)); 73 | testVectorGrid.drawSamplePointVectors(*gRenderer, Vec3d::Zero(), .5); 74 | } 75 | else if (gDoLevelSetTest) 76 | { 77 | LevelSet testLevelSet(xform, size, 5); 78 | 79 | double radius = .3 * (topRightCorner - center).norm(); 80 | forEachVoxelRange(Vec2i::Zero(), testLevelSet.size(), [&](const Vec2i& cell) 81 | { 82 | Vec2d worldPosition = testLevelSet.indexToWorld(cell.cast()); 83 | testLevelSet(cell) = (worldPosition - center).norm() - radius; 84 | }); 85 | 86 | testLevelSet.reinit(true); 87 | testLevelSet.drawGrid(*gRenderer, true); 88 | testLevelSet.drawSupersampledValues(*gRenderer, .25, 3, 5); 89 | testLevelSet.drawNormals(*gRenderer, Vec3d::Zero(), .5); 90 | 91 | testLevelSet.drawSurface(*gRenderer, Vec3d(1, 0, 0)); 92 | } 93 | 94 | gRenderer->run(); 95 | } 96 | -------------------------------------------------------------------------------- /Library/SimTools/ConjugateGradientSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef CONJUGATE_GRADIENT_SOLVER_H 2 | #define CONJUGATE_GRADIENT_SOLVER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | // A custom CG solver derived from Eigen's implementation 9 | 10 | namespace FluidSim2D 11 | { 12 | 13 | template 18 | void solveConjugateGradient(Vector& solution, 19 | const Vector& rhs, 20 | const MatrixVectorMultiplyFunctor& matrixVectorMultiplyFunctor, 21 | const PreconditionerFunctor& preconditionerFunctor, 22 | const ResidualPrinterFunctor& residualPrinterFunctor, 23 | const SolveReal tolerance, 24 | const int maxIterations, 25 | const bool doProjectNullSpace = false) 26 | { 27 | SolveReal rhsNorm2 = rhs.squaredNorm(); 28 | if (rhsNorm2 == 0) 29 | { 30 | solution.setZero(); 31 | std::cout << "RHS is zero. Nothing to solve" << std::endl; 32 | return; 33 | } 34 | 35 | Vector residual = rhs - matrixVectorMultiplyFunctor(solution); //initial residual 36 | 37 | if (doProjectNullSpace) 38 | residual.array() -= residual.sum() / SolveReal(residual.rows()); 39 | 40 | SolveReal threshold = tolerance * tolerance * rhsNorm2; 41 | SolveReal residualNorm2 = residual.squaredNorm(); 42 | 43 | if (residualNorm2 < threshold) 44 | { 45 | std::cout << "Residual already below error: " << std::sqrt(residualNorm2 / rhsNorm2) << std::endl; 46 | return; 47 | } 48 | 49 | Vector z(rhs.rows()), tmp(rhs.rows()); 50 | 51 | // 52 | // Build initial search direction from preconditioner 53 | // 54 | 55 | Vector p = preconditionerFunctor(residual); 56 | 57 | if (doProjectNullSpace) 58 | p.array() -= p.sum() / SolveReal(p.rows()); 59 | 60 | SolveReal absNew = residual.dot(p); 61 | 62 | int iteration = 0; 63 | 64 | while (iteration < maxIterations) 65 | { 66 | tmp.noalias() = matrixVectorMultiplyFunctor(p); 67 | 68 | SolveReal alpha = absNew / p.dot(tmp); 69 | solution += alpha * p; 70 | residual -= alpha * tmp; 71 | 72 | if (doProjectNullSpace) 73 | residual.array() -= residual.sum() / SolveReal(residual.rows()); 74 | 75 | residualPrinterFunctor(residual, iteration); 76 | 77 | residualNorm2 = residual.squaredNorm(); 78 | if (residualNorm2 < threshold) 79 | break; 80 | else std::cout << " Residual: " << std::sqrt(residualNorm2 / rhs.squaredNorm()) << std::endl; 81 | 82 | // Start with the diagonal preconditioner 83 | z.noalias() = preconditionerFunctor(residual); 84 | 85 | SolveReal absOld = absNew; 86 | absNew = residual.dot(z); // update the absolute value of r 87 | SolveReal beta = absNew / absOld; // calculate the Gram-Schmidt value used to create the new search direction 88 | p = z + beta * p; // update search direction 89 | 90 | if (doProjectNullSpace) 91 | p.array() -= p.sum() / SolveReal(p.rows()); 92 | 93 | ++iteration; 94 | } 95 | 96 | std::cout << "Iterations: " << iteration << std::endl; 97 | std::cout << "Relative L2 Error: " << std::sqrt(residualNorm2 / rhs.squaredNorm()) << std::endl; 98 | 99 | residual = rhs - matrixVectorMultiplyFunctor(solution); 100 | residualNorm2 = residual.squaredNorm(); 101 | std::cout << "Re-computed Relative L2 Error: " << std::sqrt(residualNorm2 / rhs.squaredNorm()) << std::endl; 102 | 103 | std::cout << "L-infinity Error: " << residual.template lpNorm() << std::endl; 104 | } 105 | 106 | } 107 | 108 | #endif -------------------------------------------------------------------------------- /Library/SurfaceTrackers/FluidParticles.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_FLUID_PARTICLES_H 2 | #define FLUIDSIM2D_FLUID_PARTICLES_H 3 | 4 | #include 5 | 6 | #include "LevelSet.h" 7 | #include "Renderer.h" 8 | #include "Utilities.h" 9 | 10 | /////////////////////////////////// 11 | // 12 | // FluidParticles.h/cpp 13 | // Ryan Goldade 2016 14 | // 15 | // 2d particle surface tracker with 16 | // foward advection, resampling, 17 | // rendering. 18 | // 19 | //////////////////////////////////// 20 | 21 | namespace FluidSim2D 22 | { 23 | 24 | class FluidParticles 25 | { 26 | public: 27 | FluidParticles() : myParticleRadius(0) {} 28 | 29 | // The particle radius is used to construct a surface around the particles. 30 | // This is also used during reseeding so we don't put particles too close to the surface. 31 | // The count is the target number of particles to see into a particle grid cell. The\ 32 | // oversample is how many more particles should be created at cells near the supplied 33 | // surface. 34 | FluidParticles(double particleRadius, int countPerCell, double oversample = 1., bool trackVelocity = false) 35 | : myParticleRadius(particleRadius) 36 | , myParticleDensity(countPerCell) 37 | , myOversampleRate(oversample) 38 | , myTrackVelocity(trackVelocity) 39 | { 40 | assert(countPerCell >= 0); 41 | } 42 | 43 | // The initialize step seeds particles inside of the given surface. 44 | // The particles are seeded in each associated voxel of the LevelSet 45 | // grid. If a voxel is completely contained inside the surface, "count" 46 | // many particles will be seeded. Otherwise some fraction of the amount 47 | // will be generated. 48 | void init(const LevelSet& surface); 49 | 50 | void setVelocity(const VectorGrid& vel); 51 | void applyVelocity(VectorGrid& vel); 52 | void incrementVelocity(VectorGrid& vel); 53 | void blendVelocity(const VectorGrid& vel_old, 54 | const VectorGrid& vel_new, 55 | double blend); 56 | 57 | LevelSet surfaceParticles(const Transform& xform, const Vec2i& size, int narrowBand) const; 58 | void drawPoints(Renderer& renderer, const Vec3d& colour = Vec3d(1, 0, 0), double pointSize = 1) const; 59 | void drawVelocity(Renderer& renderer, const Vec3d& colour = Vec3d(0, 0, 1), double length = .25) const; 60 | 61 | // Seed particles into areas of the surface that are under represented 62 | // and delete particles in areas that are over represented. If the particle 63 | // count is below the min multiplier, then the cell will be seeded up to the count. 64 | // If the particle count is above the max multiplier, then the cell will emptied down 65 | // to the count. 66 | void reseed(const LevelSet& surface, double minDensity = 1., double maxDensity = 1., const VectorGrid* velocity = nullptr, double seed = 0); 67 | 68 | // Push particles inside collision objects back to the surface 69 | void bumpParticles(const LevelSet& solidSurface); 70 | 71 | int particleCount() const { return int(myParticles.size()); } 72 | 73 | Vec2d getPosition(int particleIndex) const 74 | { 75 | assert(particleIndex >= 0 && particleIndex < myParticles.size()); 76 | return myParticles[particleIndex]; 77 | } 78 | 79 | const VecVec2d& getPositions() const { return myParticles; } 80 | 81 | void advect(double dt, const VectorGrid& velocity, const IntegrationOrder order); 82 | 83 | protected: 84 | VecVec2d myParticles, myNewParticles; 85 | VecVec2d myVelocity; 86 | bool myTrackVelocity; 87 | double myParticleRadius; 88 | 89 | int myParticleDensity; 90 | double myOversampleRate; 91 | }; 92 | 93 | } 94 | 95 | #endif -------------------------------------------------------------------------------- /Library/SurfaceTrackers/EdgeMesh.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_EDGE_MESH_H 2 | #define FLUIDSIM2D_EDGE_MESH_H 3 | 4 | #include 5 | #include 6 | 7 | #include "Integrator.h" 8 | #include "Renderer.h" 9 | #include "Utilities.h" 10 | 11 | /////////////////////////////////// 12 | // 13 | // EdgeMesh.h/cpp 14 | // Ryan Goldade 2016 15 | // 16 | // 2-d mesh container with edge and 17 | // vertex accessors. 18 | // 19 | //////////////////////////////////// 20 | 21 | namespace FluidSim2D 22 | { 23 | 24 | class EdgeMesh 25 | { 26 | 27 | public: 28 | // Vanilla constructor leaves initialization up to the caller 29 | EdgeMesh() 30 | {} 31 | 32 | // Initialize mesh container with edges and the associated vertices. 33 | // Input mesh should be water-tight with no dangling edges 34 | // (i.e. no vertex has a valence less than 2). 35 | EdgeMesh(const VecVec2i& edges, const VecVec2d& vertices); 36 | 37 | void reinitialize(const VecVec2i& edges, const VecVec2d& vertices); 38 | 39 | // Add more mesh pieces to an already existing mesh (although the existing mesh could 40 | // empty). The incoming mesh edges point to vertices (and vice versa) from 0 to edge count - 1 locally. They need 41 | // to be offset by the edge/vertex size in the existing mesh. 42 | void insertMesh(const EdgeMesh& mesh); 43 | 44 | const VecVec2i& edges() const; 45 | const VecVec2d& vertices() const; 46 | 47 | FORCE_INLINE const Vec2i& edge(int index) const 48 | { 49 | return myEdges[index]; 50 | } 51 | 52 | FORCE_INLINE const Vec2d& vertex(int index) const 53 | { 54 | return myVertices[index]; 55 | } 56 | 57 | FORCE_INLINE void setVertex(int index, const Vec2d& vertex) 58 | { 59 | myVertices[index] = vertex; 60 | } 61 | 62 | void clear(); 63 | 64 | FORCE_INLINE int edgeCount() const 65 | { 66 | return int(myEdges.size()); 67 | } 68 | 69 | FORCE_INLINE int vertexCount() const 70 | { 71 | return int(myVertices.size()); 72 | } 73 | 74 | FORCE_INLINE Vec2d scaledNormal(int edgeIndex) const 75 | { 76 | const Vec2i& edge = myEdges[edgeIndex]; 77 | Vec2d tangent = myVertices[edge[1]] - myVertices[edge[0]]; 78 | 79 | return Vec2d(-tangent[1], tangent[0]); 80 | } 81 | 82 | FORCE_INLINE Vec2d normal(int edgeIndex) const 83 | { 84 | Vec2d normal = scaledNormal(edgeIndex); 85 | double norm = normal.norm(); 86 | 87 | if (norm > 0) 88 | { 89 | return normal / norm; 90 | } 91 | 92 | return Vec2d::Zero(); 93 | } 94 | 95 | AlignedBox2d boundingBox() const; 96 | 97 | //Reverse winding order 98 | void reverse(); 99 | 100 | void scale(double s); 101 | 102 | void translate(const Vec2d& t); 103 | 104 | // Test for degenerate edge (i.e. an edge with zero length) 105 | bool isEdgeDegenerate(int edgeIndex) const 106 | { 107 | const Vec2i& edge = myEdges[edgeIndex]; 108 | return myVertices[edge[0]] == myVertices[edge[1]]; 109 | } 110 | 111 | void drawMesh(Renderer& renderer, 112 | Vec3d edgeColour = Vec3d::Zero(), 113 | double edgeWidth = 1, 114 | bool doRenderEdgeNormals = false, 115 | bool doRenderVertices = false, 116 | Vec3d vertexColour = Vec3d::Zero()) const; 117 | 118 | template 119 | void advectMesh(double dt, const VelocityField& velocity, const IntegrationOrder order); 120 | 121 | bool unitTestMesh() const; 122 | 123 | private: 124 | 125 | void initialize(const VecVec2i& edges, const VecVec2d& vertices); 126 | 127 | VecVec2i myEdges; 128 | VecVec2d myVertices; 129 | }; 130 | 131 | template 132 | void EdgeMesh::advectMesh(double dt, const VelocityField& velocity, const IntegrationOrder order) 133 | { 134 | for (Vec2d& vertex : myVertices) 135 | { 136 | vertex = Integrator(dt, vertex, velocity, order); 137 | } 138 | } 139 | 140 | } 141 | 142 | #endif -------------------------------------------------------------------------------- /Projects/Simulations/Library/EulerianLiquidSimulator.h: -------------------------------------------------------------------------------- 1 | #ifndef EULERIAN_LIQUID_SIMULATOR_H 2 | #define EULERIAN_LIQUID_SIMULATOR_H 3 | 4 | #include "Integrator.h" 5 | #include "LevelSet.h" 6 | #include "ScalarGrid.h" 7 | #include "Transform.h" 8 | #include "Utilities.h" 9 | #include "VectorGrid.h" 10 | 11 | /////////////////////////////////// 12 | // 13 | // EulerianLiquidSimulator.h/cpp 14 | // Ryan Goldade 2016 15 | // 16 | // Wrapper class around the staggered MAC grid liquid simulator 17 | // (which stores face-aligned velocities and pressure). 18 | // Handles velocity, surface, viscosity field advection, 19 | // pressure projection, viscosity and velocity extrapolation. 20 | // 21 | //////////////////////////////////// 22 | 23 | namespace FluidSim2D 24 | { 25 | 26 | class EulerianLiquidSimulator 27 | { 28 | public: 29 | EulerianLiquidSimulator(const Transform& xform, Vec2i size, double cfl = 5.) 30 | : myXform(xform) 31 | , myDoSolveViscosity(false) 32 | , myCFL(cfl) 33 | { 34 | myLiquidVelocity = VectorGrid(myXform, size, 0, VectorGridSettings::SampleType::STAGGERED); 35 | mySolidVelocity = VectorGrid(myXform, size, 0, VectorGridSettings::SampleType::STAGGERED); 36 | 37 | myLiquidSurface = LevelSet(myXform, size, myCFL); 38 | mySolidSurface = LevelSet(myXform, size, myCFL); 39 | 40 | myOldPressure = ScalarGrid(myXform, size, 0); 41 | } 42 | 43 | void setSolidSurface(const LevelSet& solidSurface); 44 | void setSolidVelocity(const VectorGrid& solidVelocity); 45 | 46 | void setLiquidSurface(const LevelSet& liquidSurface); 47 | void setLiquidVelocity(const VectorGrid& liquidVelocity); 48 | 49 | void setViscosity(const ScalarGrid& viscosityGrid) 50 | { 51 | assert(myLiquidSurface.isGridMatched(viscosityGrid)); 52 | myViscosity = viscosityGrid; 53 | myDoSolveViscosity = true; 54 | } 55 | 56 | void setViscosity(double constantViscosity = 1.) 57 | { 58 | myViscosity = ScalarGrid(myLiquidSurface.xform(), myLiquidSurface.size(), constantViscosity); 59 | myDoSolveViscosity = true; 60 | } 61 | 62 | void unionLiquidSurface(const LevelSet& addedLiquidSurface); 63 | 64 | template 65 | void addForce(double dt, const ForceSampler& force); 66 | 67 | void addForce(double dt, const Vec2d& force); 68 | 69 | void advectOldPressure(const double dt); 70 | void advectLiquidSurface(double dt, IntegrationOrder integrator = IntegrationOrder::FORWARDEULER); 71 | void advectViscosity(double dt, IntegrationOrder integrator = IntegrationOrder::FORWARDEULER, InterpolationOrder interpolator = InterpolationOrder::LINEAR); 72 | void advectLiquidVelocity(double dt, IntegrationOrder integrator = IntegrationOrder::RK3, InterpolationOrder interpolator = InterpolationOrder::LINEAR); 73 | 74 | // Perform pressure project, viscosity solver, extrapolation, surface and velocity advection 75 | void runTimestep(double dt); 76 | 77 | // Useful for CFL 78 | double maxVelocityMagnitude() { return myLiquidVelocity.maxMagnitude(); } 79 | 80 | // Rendering tools 81 | void drawGrid(Renderer& renderer) const; 82 | 83 | void drawVolumetricSurface(Renderer& renderer) const; 84 | 85 | void drawLiquidSurface(Renderer& renderer); 86 | void drawLiquidVelocity(Renderer& renderer, double length) const; 87 | 88 | void drawSolidSurface(Renderer& renderer); 89 | void drawSolidVelocity(Renderer& renderer, double length) const; 90 | 91 | private: 92 | 93 | // Simulation containers 94 | VectorGrid myLiquidVelocity, mySolidVelocity; 95 | LevelSet myLiquidSurface, mySolidSurface; 96 | ScalarGrid myViscosity; 97 | 98 | Transform myXform; 99 | 100 | bool myDoSolveViscosity; 101 | double myCFL; 102 | 103 | ScalarGrid myOldPressure; 104 | }; 105 | 106 | } 107 | 108 | #endif -------------------------------------------------------------------------------- /Projects/Simulations/Library/MultiMaterialLiquidSimulator.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTI_MATERIAL_LIQUID_SIMULATOR_H 2 | #define MULTI_MATERIAL_LIQUID_SIMULATOR_H 3 | 4 | #include "ExtrapolateField.h" 5 | #include "FieldAdvector.h" 6 | #include "Integrator.h" 7 | #include "LevelSet.h" 8 | #include "ScalarGrid.h" 9 | #include "Transform.h" 10 | #include "Utilities.h" 11 | #include "VectorGrid.h" 12 | 13 | /////////////////////////////////// 14 | // 15 | // MultiMaterialLiquidSimulator.h/cpp 16 | // Ryan Goldade 2018 17 | // 18 | // A purely Eulerian implementation of 19 | // the multi-FLIP style multi-material 20 | // fluid simulator. 21 | // 22 | //////////////////////////////////// 23 | 24 | namespace FluidSim2D 25 | { 26 | 27 | class MultiMaterialLiquidSimulator 28 | { 29 | public: 30 | MultiMaterialLiquidSimulator(const Transform& xform, Vec2i size, int materials, double narrowBand = 5.) 31 | : myXform(xform) 32 | , myGridSize(size) 33 | , myMaterialCount(materials) 34 | { 35 | assert(myMaterialCount > 1); 36 | myFluidVelocities.resize(myMaterialCount); 37 | myFluidSurfaces.resize(myMaterialCount); 38 | myFluidDensities.resize(myMaterialCount); 39 | 40 | for (int i = 0; i < myMaterialCount; ++i) 41 | myFluidVelocities[i] = VectorGrid(xform, size, 0, VectorGridSettings::SampleType::STAGGERED); 42 | 43 | for (int i = 0; i < myMaterialCount; ++i) 44 | myFluidSurfaces[i] = LevelSet(xform, size, narrowBand); 45 | 46 | mySolidSurface = LevelSet(myXform, size, narrowBand); 47 | 48 | myOldPressure = ScalarGrid(myXform, size, 0); 49 | } 50 | 51 | template 52 | void addForce(double dt, int material, const ForceSampler& force); 53 | 54 | void addForce(double dt, int material, const Vec2d& force); 55 | 56 | void advectFluidSurfaces(double dt, IntegrationOrder integrator = IntegrationOrder::FORWARDEULER); 57 | void advectFluidVelocities(double dt, IntegrationOrder integrator = IntegrationOrder::RK3, InterpolationOrder interpolator = InterpolationOrder::LINEAR); 58 | 59 | // Perform pressure project, viscosity solver, extrapolation, surface and velocity advection 60 | void runTimestep(double dt); 61 | 62 | // Useful for CFL 63 | double maxVelocityMagnitude() const 64 | { 65 | double maxVelocity = 0; 66 | for (int i = 0; i < myMaterialCount; ++i) 67 | maxVelocity = std::max(maxVelocity, myFluidVelocities[i].maxMagnitude()); 68 | return maxVelocity; 69 | } 70 | 71 | void setSolidSurface(const LevelSet& solidSurface); 72 | 73 | void setMaterial(const LevelSet& surface, double density, int material) 74 | { 75 | assert(material < myMaterialCount); 76 | 77 | assert(surface.isGridMatched(myFluidSurfaces[material])); 78 | myFluidSurfaces[material] = surface; 79 | myFluidDensities[material] = density; 80 | } 81 | 82 | void setMaterial(const LevelSet& surface, const VectorGrid& velocity, 83 | double density, int material) 84 | { 85 | assert(material < myMaterialCount); 86 | 87 | assert(surface.isGridMatched(myFluidSurfaces[material])); 88 | myFluidSurfaces[material] = surface; 89 | 90 | assert(velocity.isGridMatched(myFluidVelocities[material])); 91 | myFluidVelocities[material] = velocity; 92 | 93 | myFluidDensities[material] = density; 94 | } 95 | 96 | void drawMaterialSurface(Renderer& renderer, int material); 97 | void drawMaterialVelocity(Renderer& renderer, double length, int material) const; 98 | void drawSolidSurface(Renderer& renderer); 99 | 100 | private: 101 | 102 | std::vector> myFluidVelocities; 103 | std::vector myFluidSurfaces; 104 | std::vector myFluidDensities; 105 | 106 | LevelSet mySolidSurface; 107 | 108 | Vec2i myGridSize; 109 | Transform myXform; 110 | int myMaterialCount; 111 | 112 | ScalarGrid myOldPressure; 113 | }; 114 | 115 | } 116 | 117 | #endif -------------------------------------------------------------------------------- /Library/SimTools/GeometricConjugateGradientSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_GEOMETRIC_CONJUGATE_GRADIENT_SOLVER_H 2 | #define FLUIDSIM2D_GEOMETRIC_CONJUGATE_GRADIENT_SOLVER_H 3 | 4 | #include "GeometricMultigridOperators.h" 5 | #include "UniformGrid.h" 6 | #include "Utilities.h" 7 | #include "VectorGrid.h" 8 | 9 | namespace FluidSim2D 10 | { 11 | 12 | template 18 | void solveGeometricConjugateGradient(UniformGrid& solutionGrid, 19 | const UniformGrid& rhsGrid, 20 | const MatrixVectorMultiplyFunctor& matrixVectorMultiplyFunctor, 21 | const PreconditionerFunctor& preconditionerFunctor, 22 | const DotProductFunctor& dotProductFunctor, 23 | const SquaredL2NormFunctor& squaredNormFunctor, 24 | const AddToVectorFunctor& addToVectorFunctor, 25 | const AddScaledVectorFunctor& addScaledVectorFunctor, 26 | const double tolerance, 27 | const int maxIterations, 28 | bool verbose = false) 29 | { 30 | assert(solutionGrid.size() == rhsGrid.size()); 31 | 32 | double rhsNorm2 = squaredNormFunctor(rhsGrid); 33 | if (rhsNorm2 == 0) 34 | { 35 | if (verbose) 36 | std::cout << "RHS is zero. Nothing to solve" << std::endl; 37 | return; 38 | } 39 | 40 | // Build initial residual vector using an initial guess 41 | UniformGrid residualGrid(solutionGrid.size(), 0); 42 | 43 | matrixVectorMultiplyFunctor(residualGrid, solutionGrid); 44 | addScaledVectorFunctor(residualGrid, rhsGrid, residualGrid, -1); 45 | 46 | double residualNorm2 = squaredNormFunctor(residualGrid); 47 | double threshold = double(tolerance) * double(tolerance) * rhsNorm2; 48 | 49 | if (residualNorm2 < threshold) 50 | { 51 | if (verbose) 52 | std::cout << "Residual already below error: " << std::sqrt(residualNorm2 / rhsNorm2) << std::endl; 53 | return; 54 | } 55 | 56 | // Apply preconditioner for initial search direction 57 | UniformGrid pGrid(solutionGrid.size(), 0); 58 | 59 | preconditionerFunctor(pGrid, residualGrid); 60 | 61 | double absNew; 62 | absNew = dotProductFunctor(pGrid, residualGrid); 63 | 64 | UniformGrid zGrid(solutionGrid.size(), 0); 65 | UniformGrid tempGrid(solutionGrid.size(), 0); 66 | 67 | int iteration = 0; 68 | for (; iteration < maxIterations; ++iteration) 69 | { 70 | if (verbose) 71 | std::cout << " Iteration: " << iteration << std::endl; 72 | 73 | // Matrix-vector multiplication 74 | matrixVectorMultiplyFunctor(tempGrid, pGrid); 75 | 76 | double alpha; 77 | alpha = absNew / dotProductFunctor(pGrid, tempGrid); 78 | 79 | // Update solution 80 | addToVectorFunctor(solutionGrid, pGrid, alpha); 81 | 82 | // Update residual 83 | addToVectorFunctor(residualGrid, tempGrid, -alpha); 84 | 85 | residualNorm2 = squaredNormFunctor(residualGrid); 86 | 87 | if (verbose) 88 | std::cout << " Relative error: " << std::sqrt(residualNorm2 / rhsNorm2) << std::endl; 89 | 90 | if (residualNorm2 < threshold) 91 | break; 92 | 93 | preconditionerFunctor(zGrid, residualGrid); 94 | 95 | double absOld = absNew; 96 | double beta; 97 | 98 | absNew = dotProductFunctor(zGrid, residualGrid); 99 | beta = absNew / absOld; 100 | 101 | addScaledVectorFunctor(pGrid, zGrid, pGrid, beta); 102 | } 103 | 104 | if (verbose) 105 | std::cout << "Iterations: " << iteration << std::endl; 106 | 107 | double error = std::sqrt(residualNorm2 / rhsNorm2); 108 | 109 | if (verbose) 110 | std::cout << "Drifted relative L2 Error: " << error << std::endl; 111 | 112 | // Recompute residual 113 | matrixVectorMultiplyFunctor(residualGrid, solutionGrid); 114 | addScaledVectorFunctor(residualGrid, rhsGrid, residualGrid, -1); 115 | error = std::sqrt(squaredNormFunctor(residualGrid) / rhsNorm2); 116 | 117 | if (verbose) 118 | std::cout << "Recomputed relative L2 Error: " << error << std::endl; 119 | } 120 | 121 | } 122 | #endif -------------------------------------------------------------------------------- /Library/SimTools/Noise.cpp: -------------------------------------------------------------------------------- 1 | #include "Noise.h" 2 | 3 | namespace FluidSim2D 4 | { 5 | 6 | template 7 | VecXt sampleSphere(unsigned &seed) 8 | { 9 | VecXt v; 10 | double m2; 11 | do 12 | { 13 | for (int i = 0; i < N; ++i) 14 | v[i] = randhashd(seed++, -1, 1); 15 | 16 | m2 = v.norm(); 17 | } 18 | while (m2 > 1 || m2 == 0); 19 | 20 | return v / std::sqrt(m2); 21 | } 22 | 23 | //============================================================================ 24 | 25 | Noise3::Noise3(unsigned seed) 26 | { 27 | for (int i = 0; i < n; ++i) 28 | { 29 | myBasis[i] = sampleSphere<3>(seed); 30 | myPerm[i] = i; 31 | } 32 | reinitialize(seed); 33 | } 34 | 35 | void Noise3::reinitialize(unsigned seed) 36 | { 37 | for (int i = 1; i < n; ++i) 38 | { 39 | int j = randhash(seed++) % (i + 1); 40 | std::swap(myPerm[i], myPerm[j]); 41 | } 42 | } 43 | 44 | double Noise3::operator()(double x, double y, double z) const 45 | { 46 | double floorx = std::floor(x), floory = std::floor(y), floorz = std::floor(z); 47 | 48 | int i = (int)floorx, j = (int)floory, k = (int)floorz; 49 | const Vec3d& n000 = myBasis[hashIndex(i, j, k)]; 50 | const Vec3d& n100 = myBasis[hashIndex(i + 1, j, k)]; 51 | const Vec3d& n010 = myBasis[hashIndex(i, j + 1, k)]; 52 | const Vec3d& n110 = myBasis[hashIndex(i + 1, j + 1, k)]; 53 | const Vec3d& n001 = myBasis[hashIndex(i, j, k + 1)]; 54 | const Vec3d& n101 = myBasis[hashIndex(i + 1, j, k + 1)]; 55 | const Vec3d& n011 = myBasis[hashIndex(i, j + 1, k + 1)]; 56 | const Vec3d& n111 = myBasis[hashIndex(i + 1, j + 1, k + 1)]; 57 | 58 | double fx = x - floorx, fy = y - floory, fz = z - floorz; 59 | 60 | double sx = fx * fx * fx * (10 - fx * (15 - fx * 6)); 61 | double sy = fy * fy * fy * (10 - fy * (15 - fy * 6)); 62 | double sz = fz * fz * fz * (10 - fz * (15 - fz * 6)); 63 | 64 | return trilerp(fx * n000[0] + fy * n000[1] + fz * n000[2], 65 | (fx - 1) * n100[0] + fy * n100[1] + fz * n100[2], 66 | fx * n010[0] + (fy - 1) * n010[1] + fz * n010[2], 67 | (fx - 1) * n110[0] + (fy - 1) * n110[1] + fz * n110[2], 68 | fx * n001[0] + fy * n001[1] + (fz - 1) * n001[2], 69 | (fx - 1) * n101[0] + fy * n101[1] + (fz - 1) * n101[2], 70 | fx * n011[0] + (fy - 1) * n011[1] + (fz - 1) * n011[2], 71 | (fx - 1) * n111[0] + (fy - 1) * n111[1] + (fz - 1) * n111[2], 72 | sx, sy, sz); 73 | } 74 | 75 | //============================================================================ 76 | 77 | FlowNoise3::FlowNoise3(unsigned seed, double spinVariation) 78 | : Noise3(seed) 79 | { 80 | seed += 8 * n; // probably avoids overlap with sequence used in initializing superclass Noise3 81 | for (int i = 0; i < n; ++i) 82 | { 83 | myOriginalBasis[i] = myBasis[i]; 84 | mySpinAxis[i] = sampleSphere<3>(seed); 85 | mySpinRate[i] = 2.0 * PI * randhashd(seed++, 0.1 - 0.5 * spinVariation, 0.1 + 0.5 * spinVariation); 86 | } 87 | } 88 | 89 | void FlowNoise3::setTime(double t) 90 | { 91 | for (int i = 0; i < n; ++i) 92 | { 93 | double theta = mySpinRate[i] * t; 94 | double c = std::cos(theta), s = std::sin(theta); 95 | // form rotation matrix 96 | 97 | double R00 = c + (1 - c) * std::pow(mySpinAxis[i][0], 2); 98 | double R01 = (1 - c) * mySpinAxis[i][0] * mySpinAxis[i][1] - s * mySpinAxis[i][2]; 99 | double R02 = (1 - c) * mySpinAxis[i][0] * mySpinAxis[i][2] + s * mySpinAxis[i][1]; 100 | 101 | double R10 = (1 - c) * mySpinAxis[i][0] * mySpinAxis[i][1] + s * mySpinAxis[i][2]; 102 | double R11 = c + (1 - c) * std::pow(mySpinAxis[i][1], 2); 103 | double R12 = (1 - c) * mySpinAxis[i][1] * mySpinAxis[i][2] - s * mySpinAxis[i][0]; 104 | 105 | double R20 = (1 - c) * mySpinAxis[i][0] * mySpinAxis[i][2] - s * mySpinAxis[i][1]; 106 | double R21 = (1 - c) * mySpinAxis[i][1] * mySpinAxis[i][2] + s * mySpinAxis[i][0]; 107 | double R22 = c + (1 - c) * std::pow(mySpinAxis[i][2], 2); 108 | 109 | myBasis[i][0] = R00 * myOriginalBasis[i][0] + R01 * myOriginalBasis[i][1] + R02 * myOriginalBasis[i][2]; 110 | myBasis[i][1] = R10 * myOriginalBasis[i][0] + R11 * myOriginalBasis[i][1] + R12 * myOriginalBasis[i][2]; 111 | myBasis[i][2] = R20 * myOriginalBasis[i][0] + R21 * myOriginalBasis[i][1] + R22 * myOriginalBasis[i][2]; 112 | } 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /Library/Utilities/UniformGrid.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_UNIFORM_GRID_H 2 | #define FLUIDSIM2D_UNIFORM_GRID_H 3 | 4 | #include 5 | #include 6 | 7 | #include "GridUtilities.h" 8 | #include "Utilities.h" 9 | 10 | /////////////////////////////////// 11 | // 12 | // UniformGrid.h/cpp 13 | // Ryan Goldade 2016 14 | // 15 | // Uniform grid class that stores templated values at grid centers 16 | // Any positioned-based storage here must be accounted for by the caller. 17 | // 18 | //////////////////////////////////// 19 | 20 | namespace FluidSim2D 21 | { 22 | 23 | template 24 | class UniformGrid 25 | { 26 | public: 27 | 28 | UniformGrid() : mySize(Vec2i::Zero()) {} 29 | 30 | UniformGrid(const Vec2i& size) : mySize(size) 31 | { 32 | for (int axis : {0, 1}) 33 | assert(size[axis] > 0); 34 | 35 | myGrid.setZero(mySize[0] * mySize[1]); 36 | } 37 | 38 | UniformGrid(const Vec2i& size, const T& value) : mySize(size) 39 | { 40 | for (int axis : {0, 1}) 41 | assert(size[axis] > 0); 42 | 43 | myGrid.resize(mySize[0] * mySize[1]); 44 | myGrid.array() = value; 45 | } 46 | 47 | // Accessor is y-major because the inside loop for most processes is naturally y. Should give better cache coherence. 48 | // Clamping should only occur for interpolation. Direct index access that's outside of the grid should 49 | // be a sign of an error. 50 | T& operator()(int i, int j) { return (*this)(Vec2i(i, j)); } 51 | 52 | T& operator()(const Vec2i& coord) 53 | { 54 | for (int axis : {0, 1}) 55 | assert(coord[axis] >= 0 && coord[axis] < mySize[axis]); 56 | 57 | return myGrid[flatten(coord)]; 58 | } 59 | 60 | const T& operator()(int i, int j) const { return (*this)(Vec2i(i, j)); } 61 | 62 | const T& operator()(const Vec2i& coord) const 63 | { 64 | for (int axis : {0, 1}) 65 | assert(coord[axis] >= 0 && coord[axis] < mySize[axis]); 66 | 67 | return myGrid[flatten(coord)]; 68 | } 69 | 70 | void clear() 71 | { 72 | mySize.setZero(); 73 | myGrid.resize(0); 74 | } 75 | 76 | bool empty() const { return myGrid.rows() == 0; } 77 | 78 | void resize(const Vec2i& size) 79 | { 80 | for (int axis : {0, 1}) 81 | assert(size[axis] >= 0); 82 | 83 | mySize = size; 84 | myGrid.resize(mySize[0] * mySize[1]); 85 | } 86 | 87 | void resize(const Vec2i& size, const T& value) 88 | { 89 | for (int axis : {0, 1}) 90 | assert(size[axis] >= 0); 91 | 92 | resize(size); 93 | myGrid.array() = value; 94 | } 95 | 96 | void reset(const T& value) 97 | { 98 | myGrid.array() = value; 99 | } 100 | 101 | const Vec2i& size() const { return mySize; } 102 | 103 | int voxelCount() const { return mySize[0] * mySize[1]; } 104 | 105 | int flatten(const Vec2i& coord) const 106 | { 107 | return coord[1] + mySize[1] * coord[0]; 108 | } 109 | 110 | Vec2i unflatten(int index) const 111 | { 112 | assert(index >= 0 && index < voxelCount()); 113 | 114 | return Vec2i(index / mySize[1], index % mySize[1]); 115 | } 116 | 117 | void printAsOBJ(const std::string& filename) const; 118 | 119 | protected: 120 | 121 | //Grid center container 122 | VectorXt myGrid; 123 | Vec2i mySize; 124 | }; 125 | 126 | template 127 | void UniformGrid::printAsOBJ(const std::string& filename) const 128 | { 129 | std::ofstream writer(filename + std::string(".obj")); 130 | 131 | // Print the grid as a heightfield in the y-axis. 132 | if (writer) 133 | { 134 | // Print vertices first. 135 | int vertexCount = 0; 136 | forEachVoxelRange(Vec2i::Zero(), this->mySize, [&](const Vec2i& cell) 137 | { 138 | Vec2d position = cell.cast(); 139 | 140 | int flatIndex = this->flatten(cell); 141 | assert(this->unflatten(flatIndex) == cell); 142 | 143 | writer << "v " << position[0] << " " << myGrid[flatIndex] << " " << position[1] << "#" << vertexCount << "\n"; 144 | 145 | assert(flatIndex == vertexCount); 146 | ++vertexCount; 147 | }); 148 | 149 | assert(vertexCount == this->mySize[0] * this->mySize[1]); 150 | // Print quad faces 151 | forEachVoxelRange(Vec2i::Ones(), this->mySize, [&](const Vec2i& node) 152 | { 153 | writer << "f"; 154 | for (int cellIndex = 0; cellIndex < 4; ++cellIndex) 155 | { 156 | Vec2i cell = nodeToCellCCW(node, cellIndex); 157 | 158 | int quadVertexIndex = this->flatten(Vec2i(cell)); 159 | 160 | assert(this->unflatten(quadVertexIndex) == cell); 161 | 162 | writer << " " << quadVertexIndex + 1; 163 | } 164 | writer << "\n"; 165 | 166 | }); 167 | } 168 | else 169 | std::cerr << "Failed to write to file: " << filename << std::endl; 170 | } 171 | 172 | } 173 | 174 | #endif -------------------------------------------------------------------------------- /Library/SurfaceTrackers/InitialGeometry.cpp: -------------------------------------------------------------------------------- 1 | #include "InitialGeometry.h" 2 | 3 | namespace FluidSim2D 4 | { 5 | 6 | // This convention follows from normals are "left" turns 7 | EdgeMesh makeCircleMesh(const Vec2d& center, double radius, double divisions) 8 | { 9 | VecVec2d verts; 10 | VecVec2i edges; 11 | 12 | double startAngle = 0; 13 | double endAngle = 2. * PI; 14 | 15 | double angleResolution = (endAngle - startAngle) / divisions; 16 | 17 | verts.emplace_back(radius * cos(endAngle) + center[0], radius * sin(endAngle) + center[1]); 18 | 19 | // Loop in CW order, allowing for the "left" turn to be outside circle 20 | for (double theta = endAngle - angleResolution; theta >= startAngle + angleResolution; theta -= angleResolution) 21 | { 22 | verts.emplace_back(radius * cos(theta) + center[0], radius * sin(theta) + center[1]); 23 | 24 | int vertIndex = int(verts.size()); 25 | 26 | edges.emplace_back(vertIndex - 2, vertIndex - 1); 27 | } 28 | 29 | //Close mesh 30 | edges.emplace_back(verts.size() - 1, 0); 31 | 32 | return EdgeMesh(edges, verts); 33 | } 34 | 35 | EdgeMesh makeSquareMesh(const Vec2d& center, const Vec2d& scale) 36 | { 37 | VecVec2d verts; 38 | VecVec2i edges; 39 | 40 | verts.emplace_back(1.0 * scale[0], -1.0 * scale[1]); 41 | verts.emplace_back(-1.0 * scale[0], -1.0 * scale[1]); 42 | verts.emplace_back(-1.0 * scale[0], 1.0 * scale[1]); 43 | verts.emplace_back(1.0 * scale[0], 1.0 * scale[1]); 44 | 45 | edges.emplace_back(0, 1); 46 | edges.emplace_back(1, 2); 47 | edges.emplace_back(2, 3); 48 | edges.emplace_back(3, 0); 49 | 50 | EdgeMesh mesh = EdgeMesh(edges, verts); 51 | 52 | mesh.translate(center); 53 | 54 | return mesh; 55 | } 56 | 57 | EdgeMesh makeDiamondMesh(const Vec2d& center, const Vec2d& scale) 58 | { 59 | VecVec2d verts; 60 | VecVec2i edges; 61 | 62 | verts.emplace_back(1.0 * scale[0], 0); 63 | verts.emplace_back(0, -1.0 * scale[1]); 64 | verts.emplace_back(-1.0 * scale[0], 0); 65 | verts.emplace_back(0, 1.0 * scale[1]); 66 | 67 | edges.emplace_back(0, 1); 68 | edges.emplace_back(1, 2); 69 | edges.emplace_back(2, 3); 70 | edges.emplace_back(3, 0); 71 | 72 | EdgeMesh mesh = EdgeMesh(edges, verts); 73 | 74 | mesh.translate(center); 75 | 76 | return mesh; 77 | } 78 | 79 | // Initial conditions for testing level set methods. 80 | 81 | EdgeMesh makeNotchedDiskMesh() 82 | { 83 | VecVec2d verts; 84 | VecVec2i edges; 85 | 86 | //Make circle portion 87 | double startAngle = -PI / 2. + acos(1. - std::pow(2.5, 2) / (2. * std::pow(15., 2))); 88 | double endAngle = 3. * PI / 2. - acos(1. - std::pow(2.5, 2) / (2 * std::pow(15., 2))); 89 | 90 | double angleResolution = (endAngle - startAngle) / 100.; 91 | 92 | Vec2d center(50, 75); 93 | double radius = 15; 94 | 95 | Vec2d startPoint(radius * cos(startAngle) + center[0], 96 | radius * sin(startAngle) + center[1]); 97 | 98 | verts.push_back(startPoint); 99 | 100 | for (double theta = startAngle; theta < endAngle; theta += angleResolution) 101 | { 102 | Vec2d nextPoint(radius * cos(theta + angleResolution) + center[0], 103 | radius * sin(theta + angleResolution) + center[1]); 104 | 105 | verts.push_back(nextPoint); 106 | 107 | int vertIndex = int(verts.size()); 108 | 109 | edges.emplace_back(vertIndex - 1, vertIndex - 2); 110 | 111 | startPoint = nextPoint; 112 | } 113 | 114 | //Make gap 115 | Vec2d gapPoint = startPoint + Vec2d(0, 25); 116 | 117 | verts.push_back(gapPoint); 118 | int vertIndex = int(verts.size()); 119 | edges.emplace_back(vertIndex - 1, vertIndex - 2); 120 | 121 | Vec2d nextGapPoint = gapPoint + Vec2d(5, 0); 122 | 123 | verts.push_back(nextGapPoint); 124 | vertIndex = int(verts.size()); 125 | edges.emplace_back(vertIndex - 1, vertIndex - 2); 126 | 127 | //Close mesh 128 | edges.emplace_back(0, vertIndex - 1); 129 | 130 | return EdgeMesh(edges, verts); 131 | } 132 | 133 | EdgeMesh makeVortexMesh() 134 | { 135 | VecVec2d verts; 136 | VecVec2i edges; 137 | 138 | //Make circle 139 | double startAngle = 0; 140 | double endAngle = 2. * PI; 141 | 142 | double angleResolution = (endAngle - startAngle) / 100.0; 143 | 144 | Vec2d center(0.50, 0.75); 145 | double radius = 0.15; 146 | 147 | Vec2d startPoint(radius * cos(startAngle) + center[0], 148 | radius * sin(startAngle) + center[1]); 149 | 150 | verts.push_back(startPoint); 151 | 152 | for (double theta = startAngle; theta < endAngle; theta += angleResolution) 153 | { 154 | Vec2d nextPoint(radius * cos(theta + angleResolution) + center[0], 155 | radius * sin(theta + angleResolution) + center[1]); 156 | 157 | verts.push_back(nextPoint); 158 | 159 | int vertIndex = int(verts.size()); 160 | 161 | edges.emplace_back(vertIndex - 1, vertIndex - 2); 162 | 163 | startPoint = nextPoint; 164 | } 165 | 166 | //Close mesh 167 | edges.emplace_back(verts.size() - 1, 0); 168 | 169 | return EdgeMesh(edges, verts); 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /Projects/Simulations/Library/MultiMaterialPressureProjection.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTI_MATERIAL_PRESSURE_PROJECTION_H 2 | #define MULTI_MATERIAL_PRESSURE_PROJECTION_H 3 | 4 | #include 5 | 6 | #include "ComputeWeights.h" 7 | #include "GeometricMultigridOperators.h" 8 | #include "LevelSet.h" 9 | #include "Renderer.h" 10 | #include "ScalarGrid.h" 11 | #include "Utilities.h" 12 | #include "VectorGrid.h" 13 | 14 | /////////////////////////////////// 15 | // 16 | // MultiMaterialPressureProjection.h/cpp 17 | // Ryan Goldade 2017 18 | // 19 | //////////////////////////////////// 20 | 21 | namespace FluidSim2D 22 | { 23 | 24 | class MultiMaterialPressureProjection 25 | { 26 | using MGCellLabels = GeometricMultigridOperators::CellLabels; 27 | using SolveReal = double; 28 | using Vector = Eigen::VectorXd; 29 | 30 | const int UNSOLVED_CELL = -1; 31 | 32 | public: 33 | MultiMaterialPressureProjection(const std::vector& surfaces, 34 | const std::vector& densities, 35 | const LevelSet& solidSurface); 36 | 37 | const VectorGrid& getValidFaces(int material) 38 | { 39 | assert(material < myMaterialCount); 40 | return myValidMaterialFaces[material]; 41 | } 42 | 43 | void project(std::vector>& velocities); 44 | 45 | void setInitialGuess(const ScalarGrid& initialGuessPressure) 46 | { 47 | assert(myFluidSurfaces[0].isGridMatched(initialGuessPressure)); 48 | myUseInitialGuessPressure = true; 49 | myInitialGuessPressure = &initialGuessPressure; 50 | } 51 | 52 | void disableInitialGuess() 53 | { 54 | myUseInitialGuessPressure = false; 55 | } 56 | 57 | ScalarGrid getPressureGrid() 58 | { 59 | return myPressure; 60 | } 61 | 62 | void drawPressure(Renderer& renderer) const; 63 | 64 | void printPressure(const std::string& filename) const 65 | { 66 | myPressure.printAsOBJ(filename + ".obj"); 67 | } 68 | 69 | private: 70 | 71 | // 72 | // Helper functions for multigrid preconditioner 73 | // 74 | 75 | void copyToPreconditionerGrids(std::vector>& mgSourceGrid, 76 | UniformGrid& smootherSourceGrid, 77 | const std::vector>& mgDomainCellLabels, 78 | const UniformGrid& materialCellLabels, 79 | const UniformGrid& solvableCellIndices, 80 | const Vector& sourceVector, 81 | const VecVec2i& mgExpandedOffset) const; 82 | 83 | void applyBoundarySmoothing(std::vector& tempDestinationVector, 84 | const VecVec2i& boundarySmootherCells, 85 | const std::vector>& mgDomainCellLabels, 86 | const UniformGrid& materialCellLabels, 87 | const UniformGrid& smootherDestinationGrid, 88 | const UniformGrid& smootherSourceGrid, 89 | const VecVec2i& mgExpandedOffset) const; 90 | 91 | void updateDestinationGrid(UniformGrid& smootherDestinationGrid, 92 | const UniformGrid& materialCellLabels, 93 | const VecVec2i& boundarySmootherCells, 94 | const std::vector& tempDestinationVector) const; 95 | 96 | void applyDirichletToMG(std::vector>& mgSourceGrid, 97 | std::vector>& mgDestinationGrid, 98 | const UniformGrid& smootherDestinationGrid, 99 | const std::vector>& mgDomainCellLabels, 100 | const UniformGrid& materialCellLabels, 101 | const UniformGrid& solvableCellIndices, 102 | const Vector& sourceVector, 103 | const VecVec2i& boundarySmootherCells, 104 | const VecVec2i& mgExpandedOffset) const; 105 | 106 | void updateSmootherGrid(UniformGrid& smootherDestinationGrid, 107 | const std::vector>& mgDestinationGrid, 108 | const UniformGrid& materialCellLabels, 109 | const std::vector>& mgDomainCellLabels, 110 | const VecVec2i& mgExpandedOffset) const; 111 | 112 | void copyMGSolutionToVector(Vector& destinationVector, 113 | const UniformGrid& materialCellLabels, 114 | const UniformGrid& solvableCellIndices, 115 | const std::vector>& mgDomainCellLabels, 116 | const std::vector>& mgDestinationGrid, 117 | const VecVec2i& mgExpandedOffset) const; 118 | 119 | void copyBoundarySolutionToVector(Vector& destinationVector, 120 | const UniformGrid& materialCellLabels, 121 | const UniformGrid& solvableCellIndices, 122 | const UniformGrid& smootherDestinationGrid, 123 | const VecVec2i& boundarySmootherCells) const; 124 | 125 | ScalarGrid myPressure; 126 | 127 | const std::vector& myFluidSurfaces; 128 | const std::vector& myFluidDensities; 129 | 130 | VectorGrid mySolidCutCellWeights; 131 | std::vector> myMaterialCutCellWeights; 132 | std::vector> myValidMaterialFaces; 133 | 134 | const LevelSet& mySolidSurface; 135 | const int myMaterialCount; 136 | 137 | const ScalarGrid* myInitialGuessPressure; 138 | bool myUseInitialGuessPressure; 139 | }; 140 | 141 | } 142 | #endif -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestOneLevel/TestOneLevel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "tbb/blocked_range.h" 9 | #include "tbb/parallel_reduce.h" 10 | #include "tbb/parallel_for.h" 11 | 12 | #include "GeometricMultigridOperators.h" 13 | #include "GeometricMultigridPoissonSolver.h" 14 | #include "InitialMultigridTestDomains.h" 15 | #include "Renderer.h" 16 | #include "ScalarGrid.h" 17 | #include "Transform.h" 18 | #include "UniformGrid.h" 19 | #include "Utilities.h" 20 | 21 | using namespace FluidSim2D; 22 | 23 | std::unique_ptr gRenderer; 24 | 25 | static constexpr int gGridSize = 512; 26 | static constexpr bool gUseComplexDomain = true; 27 | static constexpr bool gUseSolidSphere = true; 28 | 29 | int main(int argc, char** argv) 30 | { 31 | using namespace GeometricMultigridOperators; 32 | 33 | UniformGrid domainCellLabels; 34 | VectorGrid boundaryWeights; 35 | 36 | int mgLevels; 37 | { 38 | UniformGrid baseDomainCellLabels; 39 | VectorGrid baseBoundaryWeights; 40 | 41 | // Complex domain set up 42 | if (gUseComplexDomain) 43 | buildComplexDomain(baseDomainCellLabels, 44 | baseBoundaryWeights, 45 | gGridSize, 46 | gUseSolidSphere); 47 | // Simple domain set up 48 | else 49 | buildSimpleDomain(baseDomainCellLabels, 50 | baseBoundaryWeights, 51 | gGridSize, 52 | 1 /*dirichlet band*/); 53 | 54 | // Build expanded domain 55 | std::pair mgSettings = buildExpandedDomain(domainCellLabels, boundaryWeights, baseDomainCellLabels, baseBoundaryWeights); 56 | 57 | mgLevels = mgSettings.second; 58 | } 59 | 60 | double dx = boundaryWeights.dx(); 61 | 62 | UniformGrid rhsGrid(domainCellLabels.size(), 0); 63 | 64 | UniformGrid solutionGrid(domainCellLabels.size(), 0); 65 | 66 | tbb::parallel_for(tbb::blocked_range(0, domainCellLabels.voxelCount()), [&](const tbb::blocked_range& range) 67 | { 68 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 69 | { 70 | Vec2i cell = domainCellLabels.unflatten(cellIndex); 71 | 72 | if (domainCellLabels(cell) == CellLabels::INTERIOR_CELL || 73 | domainCellLabels(cell) == CellLabels::BOUNDARY_CELL) 74 | { 75 | Vec2d point(dx * cell.cast()); 76 | solutionGrid(cell) = 4. * (std::sin(2 * PI * point[0]) * std::sin(2 * PI * point[1]) + 77 | std::sin(4 * PI * point[0]) * std::sin(4 * PI * point[1])); 78 | } 79 | } 80 | }); 81 | 82 | auto l1Norm = [&](const UniformGrid &grid) 83 | { 84 | assert(grid.size() == domainCellLabels.size()); 85 | 86 | double accumulatedValue = tbb::parallel_deterministic_reduce(tbb::blocked_range(0, domainCellLabels.voxelCount()), double(0), 87 | [&](const tbb::blocked_range& range, double accumulatedValue) -> double 88 | { 89 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 90 | { 91 | Vec2i cell = domainCellLabels.unflatten(cellIndex); 92 | 93 | if (domainCellLabels(cell) == CellLabels::INTERIOR_CELL || 94 | domainCellLabels(cell) == CellLabels::BOUNDARY_CELL) 95 | { 96 | accumulatedValue += fabs(grid(cell)); 97 | } 98 | } 99 | 100 | return accumulatedValue; 101 | }, 102 | [](double a, double b) -> double 103 | { 104 | return a + b; 105 | }); 106 | 107 | return accumulatedValue; 108 | }; 109 | 110 | // Print initial guess 111 | solutionGrid.printAsOBJ("initialGuess"); 112 | 113 | std::cout << "L-1 initial: " << l1Norm(solutionGrid) << std::endl; 114 | 115 | // Pre-build multigrid preconditioner 116 | GeometricMultigridPoissonSolver mgSolver(domainCellLabels, boundaryWeights, mgLevels, dx); 117 | 118 | for (int iteration = 0; iteration < 50; ++iteration) 119 | { 120 | mgSolver.applyMGVCycle(solutionGrid, rhsGrid, true /* use initial guess */); 121 | 122 | std::cout << "L-1 v-cycle " << iteration << ": " << l1Norm(solutionGrid) << std::endl; 123 | 124 | // Print corrected solution after one v-cycle 125 | //solutionGrid.printAsOBJ("solutionGrid" + std::to_string(iteration)); 126 | } 127 | 128 | // Print domain labels to make sure they are set up correctly 129 | int pixelHeight = 1080; 130 | int pixelWidth = pixelHeight; 131 | gRenderer = std::make_unique("One level correction test", Vec2i(pixelWidth, pixelHeight), Vec2d::Zero(), 1, &argc, argv); 132 | 133 | ScalarGrid tempGrid(Transform(dx, Vec2d::Zero()), domainCellLabels.size()); 134 | 135 | tbb::parallel_for(tbb::blocked_range(0, tempGrid.voxelCount()), [&](const tbb::blocked_range& range) 136 | { 137 | for (int flatIndex = range.begin(); flatIndex != range.end(); ++flatIndex) 138 | { 139 | Vec2i cell = tempGrid.unflatten(flatIndex); 140 | 141 | tempGrid(cell) = double(domainCellLabels(cell)); 142 | } 143 | }); 144 | 145 | tempGrid.drawVolumetric(*gRenderer, Vec3d::Zero(), Vec3d::Ones(), double(CellLabels::INTERIOR_CELL), double(CellLabels::BOUNDARY_CELL)); 146 | 147 | gRenderer->run(); 148 | } -------------------------------------------------------------------------------- /Library/Utilities/GridUtilities.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_GRID_UTILITIES_H 2 | #define FLUIDSIM2D_GRID_UTILITIES_H 3 | 4 | #include 5 | 6 | #include "Utilities.h" 7 | 8 | // The marching squares template uses a binary encoding of 9 | // inside/outside cell nodes to provide a set of 10 | // grid edges + isosurface intersection to build mesh edges from. 11 | // 12 | // 3 --- 2 --- 2 13 | // | | 14 | // 3 1 15 | // | | 16 | // 0 --- 0 --- 1 17 | // 18 | 19 | namespace FluidSim2D 20 | { 21 | 22 | template 23 | RealType lengthFraction(RealType phi0, RealType phi1) 24 | { 25 | RealType theta = 0.; 26 | 27 | if (phi0 <= 0) 28 | { 29 | if (phi1 <= 0) 30 | theta = 1.; 31 | else // if (phi1 > 0) 32 | theta = phi0 / (phi0 - phi1); 33 | } 34 | else if (phi0 > 0 && phi1 <= 0) 35 | theta = phi1 / (phi1 - phi0); 36 | 37 | assert(theta >= 0 && theta <= 1); 38 | 39 | return theta; 40 | } 41 | 42 | enum class Axis { XAXIS, YAXIS }; 43 | 44 | // Helper fuctions to map between geometry components in the grid. 45 | 46 | FORCE_INLINE Vec2i cellToCell(const Vec2i& cell, int axis, int direction) 47 | { 48 | Vec2i adjacentCell(cell); 49 | 50 | if (direction == 0) 51 | --adjacentCell[axis]; 52 | else 53 | { 54 | assert(direction == 1); 55 | ++adjacentCell[axis]; 56 | } 57 | 58 | return adjacentCell; 59 | } 60 | 61 | FORCE_INLINE Vec2i cellToFace(const Vec2i& cell, int axis, int direction) 62 | { 63 | Vec2i face(cell); 64 | 65 | if (direction == 1) 66 | ++face[axis]; 67 | else assert(direction == 0); 68 | 69 | return face; 70 | } 71 | 72 | // Map cell to face using the same winding order as cellToNodeCCW 73 | FORCE_INLINE Vec3i cellToFaceCCW(const Vec2i &cell, int index) 74 | { 75 | int axis = index % 2 == 0 ? 1 : 0; 76 | Vec3i face(cell[0], cell[1], axis); 77 | 78 | assert(index < 4); 79 | 80 | if (index == 1 || index == 2) 81 | ++face[axis]; 82 | 83 | return face; 84 | } 85 | 86 | FORCE_INLINE Vec2i cellToNode(const Vec2i& cell, int nodeIndex) 87 | { 88 | assert(nodeIndex >= 0 && nodeIndex < 4); 89 | 90 | Vec2i node(cell); 91 | 92 | for (int axis : {0, 1}) 93 | { 94 | if (nodeIndex & (1 << axis)) 95 | ++node[axis]; 96 | } 97 | 98 | return node; 99 | } 100 | 101 | // Map cell to nodes CCW from bottom-left 102 | FORCE_INLINE Vec2i cellToNodeCCW(const Vec2i& cell, int nodeIndex) 103 | { 104 | const Vec2i cellToNodeOffsets[4] = { Vec2i::Zero(), Vec2i(1, 0), Vec2i::Ones(), Vec2i(0, 1) }; 105 | 106 | assert(nodeIndex >= 0 && nodeIndex < 4); 107 | 108 | Vec2i node(cell); 109 | node += cellToNodeOffsets[nodeIndex]; 110 | 111 | return node; 112 | } 113 | 114 | FORCE_INLINE Vec2i faceToCell(const Vec2i& face, int axis, int direction) 115 | { 116 | Vec2i cell(face); 117 | 118 | if (direction == 0) 119 | --cell[axis]; 120 | else assert(direction == 1); 121 | 122 | return cell; 123 | } 124 | 125 | // An x-aligned face really means that the face normal is in the 126 | // x-direction so the face nodes must be y-direction offsets. 127 | FORCE_INLINE Vec2i faceToNode(const Vec2i& face, int faceAxis, int direction) 128 | { 129 | assert(faceAxis >= 0 && faceAxis < 2); 130 | 131 | Vec2i node(face); 132 | 133 | if (direction == 1) 134 | ++node[(faceAxis + 1) % 2]; 135 | else assert(direction == 0); 136 | 137 | return node; 138 | } 139 | 140 | // Offset node index in the axis direction. 141 | FORCE_INLINE Vec2i nodeToFace(const Vec2i& node, int offsetAxis, int direction) 142 | { 143 | Vec2i face(node); 144 | 145 | if (direction == 0) 146 | --face[offsetAxis]; 147 | else assert(direction == 1); 148 | 149 | return face; 150 | } 151 | 152 | FORCE_INLINE Vec2i nodeToCellCCW(const Vec2i& node, int cellIndex) 153 | { 154 | const Vec2i nodeToCellOffsets[4] = { Vec2i::Zero(), Vec2i(-1,0), Vec2i(-1,-1), Vec2i(0,-1) }; 155 | 156 | assert(cellIndex >= 0 && cellIndex < 4); 157 | 158 | Vec2i cell(node); 159 | cell += nodeToCellOffsets[cellIndex]; 160 | 161 | return cell; 162 | } 163 | 164 | FORCE_INLINE Vec2i nodeToCell(const Vec2i& node, int cellIndex) 165 | { 166 | assert(cellIndex >= 0 && cellIndex < 4); 167 | 168 | Vec2i cell(node); 169 | for (int axis : {0, 1}) 170 | { 171 | if (!(cellIndex & (1 << axis))) 172 | --cell[axis]; 173 | } 174 | 175 | return cell; 176 | } 177 | 178 | const std::vector colour_swatch = { Vec3d(1, 0, 0), 179 | Vec3d(0, 1, 0), 180 | Vec3d(0, 0, 1), 181 | Vec3d(1, 1, 0), 182 | Vec3d(1, 0, 1), 183 | Vec3d(0, 1, 1) }; 184 | 185 | template 186 | void forEachVoxelRange(const Vec2i& start, const Vec2i& end, const Function& f) 187 | { 188 | Vec2i cell; 189 | for (cell[0] = start[0]; cell[0] < end[0]; ++cell[0]) 190 | for (cell[1] = start[1]; cell[1] < end[1]; ++cell[1]) 191 | f(cell); 192 | } 193 | 194 | template 195 | void forEachVoxelRangeReverse(const Vec2i& start, const Vec2i& end, const Function& f) 196 | { 197 | Vec2i cell; 198 | for (cell[0] = end[0] - 1; cell[0] >= start[0]; --cell[0]) 199 | for (cell[1] = end[1] - 1; cell[1] >= start[1]; --cell[1]) 200 | f(cell); 201 | } 202 | 203 | } 204 | 205 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | 4 | CMakeFiles 5 | CMakeCache.txt 6 | 7 | Projects/* 8 | !Projects/Simulations 9 | !Projects/Simulations/* 10 | !Projects/Tests 11 | !Projects/Tests/* 12 | !Projects/CMakeLists.txt 13 | 14 | build*/* 15 | 16 | ## Ignore Visual Studio temporary files, build results, and 17 | ## files generated by popular Visual Studio add-ons. 18 | 19 | # User-specific files 20 | *.suo 21 | *.user 22 | *.userosscache 23 | *.sln.docstates 24 | 25 | # User-specific files (MonoDevelop/Xamarin Studio) 26 | *.userprefs 27 | 28 | # Build results 29 | [Dd]ebug/ 30 | [Dd]ebugPublic/ 31 | [Rr]elease/ 32 | [Rr]eleases/ 33 | [Xx]64/ 34 | [Xx]86/ 35 | [Bb]uild/ 36 | bld/ 37 | [Bb]in/ 38 | [Oo]bj/ 39 | 40 | # Visual Studio 2015 cache/options directory 41 | .vs/ 42 | # Uncomment if you have tasks that create the project's static files in wwwroot 43 | #wwwroot/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUNIT 50 | *.VisualState.xml 51 | TestResult.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # DNX 59 | project.lock.json 60 | artifacts/ 61 | 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.pch 69 | *.pdb 70 | *.pgc 71 | *.pgd 72 | *.rsp 73 | *.sbr 74 | *.tlb 75 | *.tli 76 | *.tlh 77 | *.tmp 78 | *.tmp_proj 79 | *.log 80 | *.vspscc 81 | *.vssscc 82 | .builds 83 | *.pidb 84 | *.svclog 85 | *.scc 86 | 87 | # Chutzpah Test files 88 | _Chutzpah* 89 | 90 | # Visual C++ cache files 91 | ipch/ 92 | *.aps 93 | *.ncb 94 | *.opendb 95 | *.opensdf 96 | *.sdf 97 | *.cachefile 98 | *.VC.db 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # TFS 2012 Local Workspace 107 | $tf/ 108 | 109 | # Guidance Automation Toolkit 110 | *.gpState 111 | 112 | # ReSharper is a .NET coding add-in 113 | _ReSharper*/ 114 | *.[Rr]e[Ss]harper 115 | *.DotSettings.user 116 | 117 | # JustCode is a .NET coding add-in 118 | .JustCode 119 | 120 | # TeamCity is a build add-in 121 | _TeamCity* 122 | 123 | # DotCover is a Code Coverage Tool 124 | *.dotCover 125 | 126 | # NCrunch 127 | _NCrunch_* 128 | .*crunch*.local.xml 129 | nCrunchTemp_* 130 | 131 | # MightyMoose 132 | *.mm.* 133 | AutoTest.Net/ 134 | 135 | # Web workbench (sass) 136 | .sass-cache/ 137 | 138 | # Installshield output folder 139 | [Ee]xpress/ 140 | 141 | # DocProject is a documentation generator add-in 142 | DocProject/buildhelp/ 143 | DocProject/Help/*.HxT 144 | DocProject/Help/*.HxC 145 | DocProject/Help/*.hhc 146 | DocProject/Help/*.hhk 147 | DocProject/Help/*.hhp 148 | DocProject/Help/Html2 149 | DocProject/Help/html 150 | 151 | # Click-Once directory 152 | publish/ 153 | 154 | # Publish Web Output 155 | *.[Pp]ublish.xml 156 | *.azurePubxml 157 | 158 | # TODO: Un-comment the next line if you do not want to checkin 159 | # your web deploy settings because they may include unencrypted 160 | # passwords 161 | #*.pubxml 162 | *.publishproj 163 | 164 | # NuGet Packages 165 | *.nupkg 166 | # The packages folder can be ignored because of Package Restore 167 | **/packages/* 168 | # except build/, which is used as an MSBuild target. 169 | !**/packages/build/ 170 | # Uncomment if necessary however generally it will be regenerated when needed 171 | #!**/packages/repositories.config 172 | # NuGet v3's project.json files produces more ignoreable files 173 | *.nuget.props 174 | *.nuget.targets 175 | 176 | # Microsoft Azure Build Output 177 | csx/ 178 | *.build.csdef 179 | 180 | # Microsoft Azure Emulator 181 | ecf/ 182 | rcf/ 183 | 184 | # Windows Store app package directory 185 | AppPackages/ 186 | BundleArtifacts/ 187 | 188 | # Visual Studio cache files 189 | # files ending in .cache can be ignored 190 | *.[Cc]ache 191 | # but keep track of directories ending in .cache 192 | !*.[Cc]ache/ 193 | 194 | # Others 195 | ClientBin/ 196 | [Ss]tyle[Cc]op.* 197 | ~$* 198 | *~ 199 | *.dbmdl 200 | *.dbproj.schemaview 201 | *.pfx 202 | *.publishsettings 203 | node_modules/ 204 | orleans.codegen.cs 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # LightSwitch generated files 250 | GeneratedArtifacts/ 251 | ModelManifest.xml 252 | 253 | # Paket dependency manager 254 | .paket/paket.exe 255 | 256 | 257 | # Mac 258 | .DS_Store 259 | -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/LevelSetLiquid/LevelSetLiquid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "EdgeMesh.h" 5 | #include "EulerianLiquidSimulator.h" 6 | #include "InitialGeometry.h" 7 | #include "LevelSet.h" 8 | #include "Renderer.h" 9 | #include "Transform.h" 10 | #include "Utilities.h" 11 | 12 | using namespace FluidSim2D; 13 | 14 | static std::unique_ptr renderer; 15 | static std::unique_ptr simulator; 16 | 17 | static int frameCount = 0; 18 | static bool runSimulation = false; 19 | static bool runSingleTimestep = false; 20 | static bool isDisplayDirty = true; 21 | 22 | static bool drawLiquidVelocities = false; 23 | 24 | static LevelSet seedSurface; 25 | 26 | static bool printFrame = false; 27 | 28 | static double seedTime = 0; 29 | static constexpr double seedPeriod = 2; 30 | static constexpr double dt = 1. / 60.; 31 | 32 | static constexpr double cfl = 5; 33 | 34 | static Transform xform; 35 | 36 | static LevelSet testLevelSet; 37 | 38 | void display() 39 | { 40 | if (runSimulation || runSingleTimestep) 41 | { 42 | double frameTime = 0.; 43 | std::cout << "\nStart of frame: " << frameCount << ". Timestep: " << dt << std::endl; 44 | 45 | while (frameTime < dt) 46 | { 47 | // Set CFL condition 48 | double speed = simulator->maxVelocityMagnitude(); 49 | 50 | double localDt = dt - frameTime; 51 | assert(localDt >= 0); 52 | 53 | if (speed > 1E-6) 54 | { 55 | double cflDt = cfl * xform.dx() / speed; 56 | if (localDt > cflDt) 57 | { 58 | localDt = cflDt; 59 | std::cout << "\n Throttling frame with substep: " << localDt << "\n" << std::endl; 60 | } 61 | } 62 | 63 | if (localDt <= 0) 64 | break; 65 | 66 | if (seedTime > seedPeriod) 67 | { 68 | simulator->unionLiquidSurface(seedSurface); 69 | seedTime = 0; 70 | } 71 | 72 | simulator->addForce(localDt, Vec2d(0., -9.8)); 73 | 74 | simulator->runTimestep(localDt); 75 | 76 | seedTime += localDt; 77 | 78 | // Store accumulated substep times 79 | frameTime += localDt; 80 | } 81 | std::cout << "\n\nEnd of frame: " << frameCount << "\n" << std::endl; 82 | ++frameCount; 83 | 84 | runSingleTimestep = false; 85 | isDisplayDirty = true; 86 | } 87 | if (isDisplayDirty) 88 | { 89 | renderer->clear(); 90 | 91 | simulator->drawVolumetricSurface(*renderer); 92 | simulator->drawLiquidSurface(*renderer); 93 | simulator->drawSolidSurface(*renderer); 94 | 95 | if (drawLiquidVelocities) 96 | simulator->drawLiquidVelocity(*renderer, .5); 97 | 98 | isDisplayDirty = false; 99 | 100 | if (printFrame) 101 | { 102 | std::string frameCountString = std::to_string(frameCount); 103 | std::string renderFilename = "freeSurfaceLiquidSimulation_" + std::string(4 - frameCountString.length(), '0') + frameCountString; 104 | renderer->printImage(renderFilename); 105 | } 106 | 107 | glutPostRedisplay(); 108 | } 109 | } 110 | 111 | void keyboard(unsigned char key, int, int) 112 | { 113 | if (key == ' ') 114 | runSimulation = !runSimulation; 115 | else if (key == 'n') 116 | runSingleTimestep = true; 117 | else if (key == 'p') 118 | { 119 | printFrame = !printFrame; 120 | isDisplayDirty = true; 121 | } 122 | else if (key == 'v') 123 | { 124 | drawLiquidVelocities = !drawLiquidVelocities; 125 | isDisplayDirty = true; 126 | } 127 | } 128 | 129 | int main(int argc, char** argv) 130 | { 131 | // Scene settings 132 | double dx = .0125; 133 | Vec2d topRightCorner(2.5, 2.5); 134 | Vec2d bottomLeftCorner(-2.5, -2.5); 135 | Vec2i gridSize = ((topRightCorner - bottomLeftCorner).array() / dx).matrix().cast(); 136 | xform = Transform(dx, bottomLeftCorner); 137 | Vec2d center = .5 * (topRightCorner + bottomLeftCorner); 138 | 139 | renderer = std::make_unique("Levelset Liquid Simulator", Vec2i(1000, 1000), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 140 | 141 | EdgeMesh liquidMesh = makeCircleMesh(center - Vec2d(0,.65), 1, 100); 142 | assert(liquidMesh.unitTestMesh()); 143 | 144 | EdgeMesh solidMesh = makeCircleMesh(center, 2, 100); 145 | solidMesh.reverse(); 146 | assert(solidMesh.unitTestMesh()); 147 | 148 | LevelSet liquidSurface(xform, gridSize, 5); 149 | liquidSurface.initFromMesh(liquidMesh, false); 150 | 151 | testLevelSet = liquidSurface; 152 | 153 | LevelSet solidSurface(xform, gridSize, 5); 154 | solidSurface.setBackgroundNegative(); 155 | solidSurface.initFromMesh(solidMesh, false); 156 | 157 | Vec2d seedCenter = xform.offset() + xform.dx() * (gridSize.array() / 2).matrix().cast() + Vec2d(.8, .8); 158 | EdgeMesh seedMesh = makeSquareMesh(seedCenter, Vec2d(.5, .5)); 159 | 160 | seedSurface = LevelSet(xform, gridSize, 5); 161 | seedSurface.initFromMesh(seedMesh, false); 162 | 163 | // Set up simulation 164 | 165 | simulator = std::make_unique(xform, gridSize, 5); 166 | simulator->setLiquidSurface(liquidSurface); 167 | simulator->setSolidSurface(solidSurface); 168 | 169 | std::function displayFunc = display; 170 | renderer->setUserDisplay(displayFunc); 171 | 172 | std::function keyboardFunc = keyboard; 173 | renderer->setUserKeyboard(keyboardFunc); 174 | 175 | renderer->run(); 176 | } 177 | -------------------------------------------------------------------------------- /Projects/Tests/TestGeometricMGPoissonSolver/TestSmoother/TestSmoother.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "GeometricMultigridOperators.h" 7 | #include "InitialMultigridTestDomains.h" 8 | #include "Renderer.h" 9 | #include "ScalarGrid.h" 10 | #include "Transform.h" 11 | #include "UniformGrid.h" 12 | #include "Utilities.h" 13 | 14 | using namespace FluidSim2D; 15 | 16 | std::unique_ptr gRenderer; 17 | 18 | static constexpr int gGridSize = 256; 19 | static constexpr bool gUseComplexDomain = true; 20 | static constexpr bool gUseSolidSphere = true; 21 | 22 | static constexpr bool gUseRandomGuess = true; 23 | 24 | static constexpr int gMaxIterations = 1000; 25 | static constexpr double gDeltaAmplitude = 1000; 26 | 27 | int main(int argc, char** argv) 28 | { 29 | using namespace GeometricMultigridOperators; 30 | 31 | UniformGrid domainCellLabels; 32 | VectorGrid boundaryWeights; 33 | 34 | int mgLevels; 35 | Vec2i exteriorOffset; 36 | { 37 | UniformGrid baseDomainCellLabels; 38 | VectorGrid baseBoundaryWeights; 39 | 40 | // Complex domain set up 41 | if (gUseComplexDomain) 42 | buildComplexDomain(baseDomainCellLabels, 43 | baseBoundaryWeights, 44 | gGridSize, 45 | gUseSolidSphere); 46 | // Simple domain set up 47 | else 48 | buildSimpleDomain(baseDomainCellLabels, 49 | baseBoundaryWeights, 50 | gGridSize, 51 | 1 /*dirichlet band*/); 52 | 53 | // Build expanded domain 54 | std::pair mgSettings = buildExpandedDomain(domainCellLabels, boundaryWeights, baseDomainCellLabels, baseBoundaryWeights); 55 | 56 | exteriorOffset = mgSettings.first; 57 | mgLevels = mgSettings.second; 58 | } 59 | 60 | double dx = boundaryWeights.dx(); 61 | 62 | UniformGrid rhsGrid(domainCellLabels.size(), 0); 63 | UniformGrid solutionGrid(domainCellLabels.size(), 0); 64 | UniformGrid residualGrid(domainCellLabels.size(), 0); 65 | 66 | if (gUseRandomGuess) 67 | { 68 | tbb::parallel_for(tbb::blocked_range(0, domainCellLabels.voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 69 | { 70 | std::default_random_engine generator; 71 | std::uniform_real_distribution distribution(0, 1); 72 | 73 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 74 | { 75 | Vec2i cell = domainCellLabels.unflatten(cellIndex); 76 | 77 | if (domainCellLabels(cell) == CellLabels::INTERIOR_CELL || 78 | domainCellLabels(cell) == CellLabels::BOUNDARY_CELL) 79 | { 80 | solutionGrid(cell) = distribution(generator); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | // Set delta function 87 | double deltaPercent = .1; 88 | Vec2i deltaPoint = (deltaPercent * Vec2d(gGridSize, gGridSize)).cast() + exteriorOffset; 89 | 90 | forEachVoxelRange(deltaPoint - Vec2i::Ones(), deltaPoint + Vec2i(2, 2), [&](const Vec2i& cell) 91 | { 92 | if (domainCellLabels(cell) == CellLabels::INTERIOR_CELL || 93 | domainCellLabels(cell) == CellLabels::BOUNDARY_CELL) 94 | rhsGrid(cell) = gDeltaAmplitude; 95 | }); 96 | 97 | double oldLInfinityError = std::numeric_limits::max(); 98 | double oldL2Error = oldLInfinityError; 99 | 100 | VecVec2i boundaryCells = buildBoundaryCells(domainCellLabels, 3); 101 | 102 | std::cout.precision(10); 103 | 104 | for (int iteration = 0; iteration < gMaxIterations; ++iteration) 105 | { 106 | boundaryJacobiPoissonSmoother(solutionGrid, rhsGrid, domainCellLabels, boundaryCells, dx, &boundaryWeights); 107 | 108 | interiorJacobiPoissonSmoother(solutionGrid, rhsGrid, domainCellLabels, dx, &boundaryWeights); 109 | 110 | boundaryJacobiPoissonSmoother(solutionGrid, rhsGrid, domainCellLabels, boundaryCells, dx, &boundaryWeights); 111 | 112 | computePoissonResidual(residualGrid, solutionGrid, rhsGrid, domainCellLabels, dx); 113 | 114 | double lInfinityError = lInfinityNorm(residualGrid, domainCellLabels); 115 | double l2Error = squaredl2Norm(residualGrid, domainCellLabels); 116 | 117 | if (oldLInfinityError < lInfinityError) 118 | std::cout << "L-Infinity error didn't decrease" << std::endl; 119 | if (oldL2Error < l2Error) 120 | std::cout << "L-2 error didn't decrease" << std::endl; 121 | 122 | oldLInfinityError = lInfinityError; 123 | oldL2Error = l2Error; 124 | std::cout << "Iteration: " << iteration << ". L-infinity error: " << lInfinityError << ". L-2 error: " << std::sqrt(l2Error) << std::endl; 125 | } 126 | 127 | // Print domain labels to make sure they are set up correctly 128 | int pixelHeight = 1080; 129 | int pixelWidth = pixelHeight; 130 | gRenderer = std::make_unique("Smoother Test", Vec2i(pixelWidth, pixelHeight), Vec2d::Zero(), 1, &argc, argv); 131 | 132 | ScalarGrid tempGrid(Transform(dx, Vec2d::Zero()), domainCellLabels.size()); 133 | 134 | tbb::parallel_for(tbb::blocked_range(0, tempGrid.voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 135 | { 136 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 137 | { 138 | Vec2i cell = tempGrid.unflatten(cellIndex); 139 | 140 | tempGrid(cell) = double(domainCellLabels(cell)); 141 | } 142 | }); 143 | 144 | tempGrid.drawVolumetric(*gRenderer, Vec3d::Zero(), Vec3d::Ones(), double(CellLabels::INTERIOR_CELL), double(CellLabels::BOUNDARY_CELL)); 145 | 146 | gRenderer->run(); 147 | } -------------------------------------------------------------------------------- /UnitTests/GridUtilitiesTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "GridUtilities.h" 7 | #include "Utilities.h" 8 | 9 | using namespace FluidSim2D; 10 | 11 | TEST(GRID_UTILITIES_TESTS, LENGTH_FRACTION_HALF_TEST) 12 | { 13 | { 14 | double theta = lengthFraction(5., -5.); 15 | EXPECT_TRUE(isNearlyEqual(theta, .5)); 16 | } 17 | 18 | { 19 | double theta = lengthFraction(-5., 5.); 20 | EXPECT_TRUE(isNearlyEqual(theta, .5)); 21 | } 22 | 23 | { 24 | double theta = lengthFraction(1., -1.); 25 | EXPECT_TRUE(isNearlyEqual(theta, .5)); 26 | } 27 | 28 | { 29 | double theta = lengthFraction(-1., 1.); 30 | EXPECT_TRUE(isNearlyEqual(theta, .5)); 31 | } 32 | } 33 | 34 | TEST(GRID_UTILITIES_TESTS, LENGTH_FRACTION_ZERO_TEST) 35 | { 36 | { 37 | double theta = lengthFraction(0., 1.); 38 | EXPECT_TRUE(isNearlyEqual(theta, 0.)); 39 | } 40 | 41 | { 42 | double theta = lengthFraction(1., 0.); 43 | EXPECT_TRUE(isNearlyEqual(theta, 0.)); 44 | } 45 | 46 | { 47 | double theta = lengthFraction(5., 1.); 48 | EXPECT_TRUE(isNearlyEqual(theta, 0.)); 49 | } 50 | } 51 | 52 | TEST(GRID_UTILITIES_TESTS, LENGTH_FRACTION_ONE_TEST) 53 | { 54 | { 55 | double theta = lengthFraction(-1., 0.); 56 | EXPECT_TRUE(isNearlyEqual(theta, 1.)); 57 | } 58 | 59 | { 60 | double theta = lengthFraction(0., -1.); 61 | EXPECT_TRUE(isNearlyEqual(theta, 1.)); 62 | } 63 | 64 | { 65 | double theta = lengthFraction(-1., -5.); 66 | EXPECT_TRUE(isNearlyEqual(theta, 1.)); 67 | } 68 | } 69 | 70 | TEST(GRID_UTILITIES_TESTS, CELL_TO_CELL_TEST) 71 | { 72 | int testSize = 1000; 73 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 74 | { 75 | Vec2i cell = (10000. * Vec2d::Random()).cast(); 76 | 77 | for (int axis : {0, 1}) 78 | for (int direction : {0, 1}) 79 | { 80 | Vec2i adjacentCell = cellToCell(cell, axis, direction); 81 | Vec2i returnCell = cellToCell(adjacentCell, axis, (direction + 1) % 2); 82 | 83 | EXPECT_TRUE(cell == returnCell); 84 | } 85 | } 86 | } 87 | 88 | TEST(GRID_UTILITIES_TESTS, CELL_TO_FACE_TEST) 89 | { 90 | int testSize = 1000; 91 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 92 | { 93 | Vec2i cell = (10000. * Vec2d::Random()).cast(); 94 | 95 | for (int axis : {0, 1}) 96 | for (int direction : {0, 1}) 97 | { 98 | Vec2i adjacentFace = cellToFace(cell, axis, direction); 99 | Vec2i returnCell = faceToCell(adjacentFace, axis, (direction + 1) % 2); 100 | 101 | EXPECT_TRUE(cell == returnCell); 102 | } 103 | } 104 | } 105 | 106 | TEST(GRID_UTILITIES_TESTS, CELL_TO_FACE_CCW_TEST) 107 | { 108 | int testSize = 1000; 109 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 110 | { 111 | Vec2i cell = (10000. * Vec2d::Random()).cast(); 112 | 113 | for (int faceIndex = 0; faceIndex < 4; ++faceIndex) 114 | { 115 | Vec3i adjacentFace = cellToFaceCCW(cell, faceIndex); 116 | Vec2i face(adjacentFace[0], adjacentFace[1]); 117 | int axis = adjacentFace[2]; 118 | 119 | std::array returnCells = { faceToCell(face, axis, 0), faceToCell(face, axis, 1) }; 120 | 121 | EXPECT_TRUE(cell == returnCells[0] ^ cell == returnCells[1]); 122 | 123 | } 124 | } 125 | } 126 | 127 | TEST(GRID_UTILITIES_TESTS, CELL_TO_NODE_TEST) 128 | { 129 | int testSize = 1000; 130 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 131 | { 132 | Vec2i cell = (10000. * Vec2d::Random()).cast(); 133 | 134 | for (int nodeIndex = 0; nodeIndex < 4; ++nodeIndex) 135 | { 136 | Vec2i node = cellToNode(cell, nodeIndex); 137 | 138 | { 139 | int matchedCellCount = 0; 140 | for (int cellIndex = 0; cellIndex < 4; ++cellIndex) 141 | { 142 | Vec2i returnedCell = nodeToCell(node, cellIndex); 143 | matchedCellCount += cell == returnedCell; 144 | } 145 | 146 | EXPECT_EQ(matchedCellCount, 1); 147 | } 148 | 149 | { 150 | int matchedCellCount = 0; 151 | for (int cellIndex = 0; cellIndex < 4; ++cellIndex) 152 | { 153 | Vec2i returnedCell = nodeToCellCCW(node, cellIndex); 154 | matchedCellCount += cell == returnedCell; 155 | } 156 | 157 | EXPECT_EQ(matchedCellCount, 1); 158 | } 159 | } 160 | 161 | for (int nodeIndex = 0; nodeIndex < 4; ++nodeIndex) 162 | { 163 | Vec2i node = cellToNodeCCW(cell, nodeIndex); 164 | 165 | { 166 | int matchedCellCount = 0; 167 | for (int cellIndex = 0; cellIndex < 4; ++cellIndex) 168 | { 169 | Vec2i returnedCell = nodeToCell(node, cellIndex); 170 | matchedCellCount += cell == returnedCell; 171 | } 172 | 173 | EXPECT_EQ(matchedCellCount, 1); 174 | } 175 | 176 | { 177 | int matchedCellCount = 0; 178 | for (int cellIndex = 0; cellIndex < 4; ++cellIndex) 179 | { 180 | Vec2i returnedCell = nodeToCellCCW(node, cellIndex); 181 | matchedCellCount += cell == returnedCell; 182 | } 183 | 184 | EXPECT_EQ(matchedCellCount, 1); 185 | } 186 | } 187 | } 188 | } 189 | 190 | TEST(GRID_UTILITIES_TESTS, FACE_TO_NODE) 191 | { 192 | int testSize = 1000; 193 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 194 | { 195 | Vec2i face = (10000. * Vec2d::Random()).cast(); 196 | 197 | for (int faceAxis : {0, 1}) 198 | { 199 | for (int direction : {0, 1}) 200 | { 201 | Vec2i node = faceToNode(face, faceAxis, direction); 202 | 203 | int offsetAxis = (faceAxis + 1) % 2; 204 | Vec2i returnFace = nodeToFace(node, offsetAxis, (direction + 1) % 2); 205 | 206 | EXPECT_EQ(face[0], returnFace[0]); 207 | EXPECT_EQ(face[1], returnFace[1]); 208 | } 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /UnitTests/UniformGridTests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "UniformGrid.h" 4 | 5 | using namespace FluidSim2D; 6 | 7 | template 8 | static void fillUniformGrid(const ValFunc& valFunc, UniformGrid& grid) 9 | { 10 | forEachVoxelRange(Vec2i::Zero(), grid.size(), [&](const Vec2i& cell) 11 | { 12 | grid(cell) = valFunc(cell); 13 | }); 14 | } 15 | 16 | TEST(UNIFORM_GRID_TESTS, CONSTRUCTOR_SIZE_TEST) 17 | { 18 | Vec2i size(75, 100); 19 | UniformGrid testGrid(size); 20 | 21 | EXPECT_EQ(size[0], testGrid.size()[0]); 22 | EXPECT_EQ(size[1], testGrid.size()[1]); 23 | } 24 | 25 | TEST(UNIFORM_GRID_TESTS, CONSTRUCTOR_SIZE_AND_VALUE_TEST) 26 | { 27 | Vec2i size(75, 100); 28 | int val = 10; 29 | UniformGrid testGrid(size, val); 30 | 31 | EXPECT_EQ(size[0], testGrid.size()[0]); 32 | EXPECT_EQ(size[1], testGrid.size()[1]); 33 | 34 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 35 | { 36 | EXPECT_EQ(val, testGrid(cell)); 37 | EXPECT_EQ(val, testGrid(cell[0], cell[1])); 38 | }); 39 | } 40 | 41 | TEST(UNIFORM_GRID_TESTS, STORAGE_ROUND_TRIP) 42 | { 43 | Vec2i size(75, 100); 44 | 45 | auto valFunc = [](const Vec2i& cell) -> double 46 | { 47 | return double(cell[0]) * double(cell[1]); 48 | }; 49 | 50 | UniformGrid testGrid(size); 51 | fillUniformGrid(valFunc, testGrid); 52 | 53 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 54 | { 55 | EXPECT_EQ(valFunc(cell), testGrid(cell)); 56 | EXPECT_EQ(valFunc(cell), testGrid(cell[0], cell[1])); 57 | }); 58 | } 59 | 60 | TEST(UNIFORM_GRID_TESTS, CLEAR_TEST) 61 | { 62 | Vec2i size(75, 100); 63 | UniformGrid testGrid(size); 64 | 65 | testGrid.clear(); 66 | EXPECT_TRUE(testGrid.empty()); 67 | EXPECT_EQ(testGrid.size()[0], Vec2i::Zero()[0]); 68 | EXPECT_EQ(testGrid.size()[1], Vec2i::Zero()[1]); 69 | } 70 | 71 | TEST(UNIFORM_GRID_TESTS, RESIZE_LARGER_TEST) 72 | { 73 | Vec2i size(75, 100); 74 | 75 | auto valFunc = [](const Vec2i& cell) -> double 76 | { 77 | return double(cell[0]) * double(cell[1]); 78 | }; 79 | 80 | UniformGrid testGrid(size); 81 | fillUniformGrid(valFunc, testGrid); 82 | 83 | // Resize grid 84 | Vec2i expandSize = 2 * size; 85 | testGrid.resize(expandSize); 86 | fillUniformGrid(valFunc, testGrid); 87 | 88 | EXPECT_EQ(expandSize[0], testGrid.size()[0]); 89 | EXPECT_EQ(expandSize[1], testGrid.size()[1]); 90 | 91 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 92 | { 93 | EXPECT_EQ(valFunc(cell), testGrid(cell)); 94 | }); 95 | } 96 | 97 | TEST(UNIFORM_GRID_TESTS, RESIZE_SMALLER_TEST) 98 | { 99 | Vec2i size(75, 100); 100 | 101 | auto valFunc = [](const Vec2i& cell) -> double 102 | { 103 | return double(cell[0]) * double(cell[1]); 104 | }; 105 | 106 | UniformGrid testGrid(size); 107 | fillUniformGrid(valFunc, testGrid); 108 | 109 | // Resize grid 110 | Vec2i shrinkSize(size[0] / 2, size[1] / 2); 111 | testGrid.resize(shrinkSize); 112 | fillUniformGrid(valFunc, testGrid); 113 | 114 | EXPECT_EQ(shrinkSize[0], testGrid.size()[0]); 115 | EXPECT_EQ(shrinkSize[1], testGrid.size()[1]); 116 | 117 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 118 | { 119 | EXPECT_EQ(valFunc(cell), testGrid(cell)); 120 | }); 121 | } 122 | 123 | TEST(UNIFORM_GRID_TESTS, RESIZE_VALUE_TEST) 124 | { 125 | Vec2i size(75, 100); 126 | double startValue = 10.; 127 | UniformGrid testGrid(size, startValue); 128 | 129 | // Resize grid 130 | Vec2i expandSize = 2 * size; 131 | double expandValue = 100.; 132 | testGrid.resize(expandSize, expandValue); 133 | 134 | EXPECT_EQ(expandSize[0], testGrid.size()[0]); 135 | EXPECT_EQ(expandSize[1], testGrid.size()[1]); 136 | 137 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 138 | { 139 | EXPECT_EQ(expandValue, testGrid(cell)); 140 | }); 141 | } 142 | 143 | TEST(UNIFORM_GRID_TESTS, RESET_TEST) 144 | { 145 | Vec2i size(75, 100); 146 | double startValue = 10.; 147 | UniformGrid testGrid(size, startValue); 148 | 149 | double resetValue = 100.; 150 | testGrid.reset(resetValue); 151 | 152 | EXPECT_EQ(size[0], testGrid.size()[0]); 153 | EXPECT_EQ(size[1], testGrid.size()[1]); 154 | 155 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 156 | { 157 | EXPECT_EQ(resetValue, testGrid(cell)); 158 | }); 159 | } 160 | 161 | TEST(UNIFORM_GRID_TESTS, VOXEL_COUNT_TEST) 162 | { 163 | Vec2i size(75, 100); 164 | UniformGrid testGrid(size); 165 | 166 | EXPECT_EQ(size[0] * size[1], testGrid.voxelCount()); 167 | } 168 | 169 | TEST(UNIFORM_GRID_TESTS, FLATTEN_UNFLATTEN_TEST) 170 | { 171 | int testSize = 100; 172 | for (int testIndex = 0; testIndex < testSize; ++testIndex) 173 | { 174 | Vec2i size = (100. * (Vec2d::Random() + Vec2d::Ones())).cast(); 175 | 176 | if (size[0] <= 0 || size[1] <= 0) 177 | continue; 178 | 179 | UniformGrid testGrid(size); 180 | 181 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 182 | { 183 | EXPECT_EQ(cell[0], testGrid.unflatten(testGrid.flatten(cell))[0]); 184 | EXPECT_EQ(cell[1], testGrid.unflatten(testGrid.flatten(cell))[1]); 185 | }); 186 | } 187 | } 188 | 189 | TEST(UNIFORM_GRID_TESTS, COPY_GRID_TEST) 190 | { 191 | Vec2i size(75, 100); 192 | UniformGrid testGrid(size, 10); 193 | 194 | UniformGrid copyGrid = testGrid; 195 | 196 | EXPECT_EQ(testGrid.size()[0], copyGrid.size()[0]); 197 | EXPECT_EQ(testGrid.size()[1], copyGrid.size()[1]); 198 | 199 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 200 | { 201 | EXPECT_EQ(testGrid(cell), copyGrid(cell)); 202 | }); 203 | 204 | copyGrid.reset(15); 205 | 206 | forEachVoxelRange(Vec2i::Zero(), testGrid.size(), [&](const Vec2i& cell) 207 | { 208 | EXPECT_NE(testGrid(cell), copyGrid(cell)); 209 | }); 210 | } -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/MultiMaterialLiquid/MultiMaterialBubbles.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "EdgeMesh.h" 5 | #include "InitialGeometry.h" 6 | #include "LevelSet.h" 7 | #include "MultiMaterialLiquidSimulator.h" 8 | #include "Renderer.h" 9 | #include "Transform.h" 10 | #include "Utilities.h" 11 | 12 | using namespace FluidSim2D; 13 | 14 | std::unique_ptr multiMaterialSimulator; 15 | std::unique_ptr renderer; 16 | 17 | static int frameCount = 0; 18 | static bool runSimulation = false; 19 | static bool runSingleTimestep = false; 20 | static bool isDisplayDirty = true; 21 | 22 | static constexpr double dt = 1. / 60.; 23 | 24 | static constexpr double cfl = 5; 25 | 26 | static bool printFrame = false; 27 | 28 | static int liquidMaterialCount; 29 | static int currentMaterial = 0; 30 | 31 | static Transform xform; 32 | static Vec2i gridSize; 33 | 34 | static const double bubbleDensity = 1; 35 | static const double liquidDensity = 1000; 36 | 37 | void display() 38 | { 39 | if (runSimulation || runSingleTimestep) 40 | { 41 | double frameTime = 0.; 42 | std::cout << "\nStart of frame: " << frameCount << ". Timestep: " << dt << std::endl; 43 | 44 | while (frameTime < dt) 45 | { 46 | // Set CFL condition 47 | double speed = multiMaterialSimulator->maxVelocityMagnitude(); 48 | double localDt = dt - frameTime; 49 | assert(localDt >= 0); 50 | 51 | if (speed > 1E-6) 52 | { 53 | double cflDt = cfl * xform.dx() / speed; 54 | if (localDt > cflDt) 55 | { 56 | localDt = cflDt; 57 | std::cout << "\n Throttling frame with substep: " << localDt << "\n" << std::endl; 58 | } 59 | } 60 | 61 | if (localDt <= 0) 62 | break; 63 | 64 | for (int material = 0; material < liquidMaterialCount; ++material) 65 | multiMaterialSimulator->addForce(localDt, material, Vec2d(0., -9.8)); 66 | 67 | multiMaterialSimulator->runTimestep(localDt); 68 | 69 | // Store accumulated substep times 70 | frameTime += localDt; 71 | } 72 | std::cout << "\n\nEnd of frame: " << frameCount << "\n" << std::endl; 73 | ++frameCount; 74 | 75 | runSingleTimestep = false; 76 | isDisplayDirty = true; 77 | } 78 | if (isDisplayDirty) 79 | { 80 | renderer->clear(); 81 | for (int material = 0; material < liquidMaterialCount; ++material) 82 | multiMaterialSimulator->drawMaterialSurface(*renderer, material); 83 | 84 | multiMaterialSimulator->drawSolidSurface(*renderer); 85 | 86 | for (int material = 0; material < liquidMaterialCount; ++material) 87 | { 88 | if (currentMaterial == material) 89 | multiMaterialSimulator->drawMaterialVelocity(*renderer, .25, material); 90 | } 91 | 92 | if (printFrame) 93 | { 94 | std::string frameCountString = std::to_string(frameCount); 95 | std::string renderFilename = "multimaterial_" + std::to_string(unsigned(bubbleDensity)) + "_" + std::string(4 - frameCountString.length(), '0') + frameCountString; 96 | renderer->printImage(renderFilename); 97 | } 98 | 99 | isDisplayDirty = false; 100 | 101 | glutPostRedisplay(); 102 | } 103 | } 104 | 105 | void keyboard(unsigned char key, int, int) 106 | { 107 | if (key == ' ') 108 | runSimulation = !runSimulation; 109 | else if (key == 'n') 110 | runSingleTimestep = true; 111 | else if (key == 'm') 112 | { 113 | currentMaterial = (currentMaterial + 1) % liquidMaterialCount; 114 | isDisplayDirty = true; 115 | } 116 | else if (key == 'p') 117 | { 118 | printFrame = !printFrame; 119 | isDisplayDirty = true; 120 | } 121 | } 122 | 123 | int main(int argc, char** argv) 124 | { 125 | // Scene settings 126 | double dx = .015; 127 | double boundaryPadding = 10; 128 | 129 | Vec2d topRightCorner(1.5, 2.5); 130 | topRightCorner.array() += dx * boundaryPadding; 131 | 132 | Vec2d bottomLeftCorner(-1.5, -2.5); 133 | bottomLeftCorner.array() -= dx * boundaryPadding; 134 | 135 | Vec2d simulationSize = topRightCorner - bottomLeftCorner; 136 | gridSize = (simulationSize.array() / dx).cast(); 137 | 138 | xform = Transform(dx, bottomLeftCorner); 139 | Vec2d center = .5 * (topRightCorner + bottomLeftCorner); 140 | 141 | int pixelHeight = 1080; 142 | int pixelWidth = pixelHeight * int((topRightCorner[0] - bottomLeftCorner[0]) / (topRightCorner[1] - bottomLeftCorner[1])); 143 | renderer = std::make_unique("Multimaterial Liquid Simulator", Vec2i(pixelWidth, pixelHeight), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 144 | 145 | // Build outer boundary grid. 146 | EdgeMesh solidMesh = makeSquareMesh(center, .5 * simulationSize - xform.dx() * Vec2d(boundaryPadding, boundaryPadding)); 147 | solidMesh.reverse(); 148 | assert(solidMesh.unitTestMesh()); 149 | 150 | LevelSet solidSurface(xform, gridSize, 5); 151 | solidSurface.setBackgroundNegative(); 152 | solidSurface.initFromMesh(solidMesh, false); 153 | 154 | multiMaterialSimulator = std::make_unique(xform, gridSize, 2, 5); 155 | 156 | multiMaterialSimulator->setSolidSurface(solidSurface); 157 | 158 | EdgeMesh bubbleMesh = makeCircleMesh(center, .75, 40); 159 | 160 | LevelSet bubbleSurface = LevelSet(xform, gridSize, 5); 161 | bubbleSurface.initFromMesh(bubbleMesh, false); 162 | bubbleMesh.reverse(); 163 | 164 | EdgeMesh liquidMesh = solidMesh; 165 | liquidMesh.reverse(); 166 | liquidMesh.insertMesh(bubbleMesh); 167 | 168 | LevelSet liquidSurface = LevelSet(xform, gridSize, 5); 169 | liquidSurface.initFromMesh(liquidMesh, false); 170 | 171 | multiMaterialSimulator->setMaterial(liquidSurface, liquidDensity, 0); 172 | multiMaterialSimulator->setMaterial(bubbleSurface, bubbleDensity, 1); 173 | 174 | liquidMaterialCount = 2; 175 | 176 | std::function displayFunc = display; 177 | renderer->setUserDisplay(displayFunc); 178 | 179 | std::function keyboardFunc = keyboard; 180 | renderer->setUserKeyboard(keyboardFunc); 181 | 182 | renderer->run(); 183 | } -------------------------------------------------------------------------------- /UnitTests/AnalyticalPoissonSolverTests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include "tbb/blocked_range.h" 6 | #include "tbb/parallel_for.h" 7 | #include "tbb/parallel_reduce.h" 8 | 9 | #include "LevelSet.h" 10 | #include "ScalarGrid.h" 11 | #include "Transform.h" 12 | #include "Utilities.h" 13 | #include "VectorGrid.h" 14 | 15 | using namespace FluidSim2D; 16 | 17 | class AnalyticalPoissonSolver 18 | { 19 | 20 | public: 21 | AnalyticalPoissonSolver(const Transform& xform, const Vec2i& size) 22 | : myXform(xform) 23 | { 24 | myPoissonGrid = ScalarGrid(myXform, size, 0); 25 | } 26 | 27 | template 28 | double solve(const RHS& rhsFunction, const Solution& solutionFunction); 29 | 30 | private: 31 | 32 | Transform myXform; 33 | ScalarGrid myPoissonGrid; 34 | }; 35 | 36 | template 37 | double AnalyticalPoissonSolver::solve(const RHS& rhsFuction, const Solution& solutionFunction) 38 | { 39 | UniformGrid solvableCells(myPoissonGrid.size(), -1); 40 | 41 | int solutionDOFCount = 0; 42 | 43 | Vec2i gridSize = myPoissonGrid.size(); 44 | 45 | forEachVoxelRange(Vec2i::Zero(), gridSize, [&](const Vec2i& cell) { solvableCells(cell) = solutionDOFCount++; }); 46 | 47 | std::vector> sparseMatrixElements; 48 | 49 | VectorXd rhsVector = VectorXd::Zero(solutionDOFCount); 50 | 51 | double dx = myPoissonGrid.dx(); 52 | double coeff = std::pow(dx, 2); 53 | 54 | tbb::enumerable_thread_specific>> parallelSparseElements; 55 | tbb::parallel_for(tbb::blocked_range(0, solvableCells.voxelCount()), [&](const tbb::blocked_range& range) { 56 | 57 | auto& localSparseElements = parallelSparseElements.local(); 58 | 59 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 60 | { 61 | Vec2i cell = solvableCells.unflatten(cellIndex); 62 | int row = solvableCells(cell); 63 | 64 | assert(row >= 0); 65 | 66 | // Build RHS 67 | Vec2d gridPoint = myPoissonGrid.indexToWorld(cell.cast()); 68 | 69 | rhsVector(row) = -coeff * rhsFuction(gridPoint); 70 | 71 | for (auto axis : {0, 1}) 72 | for (auto direction : {0, 1}) 73 | { 74 | Vec2i adjacentCell = cellToCell(cell, axis, direction); 75 | 76 | // Bounds check. Use analytical solution for Dirichlet condition. 77 | if ((direction == 0 && adjacentCell[axis] < 0) || (direction == 1 && adjacentCell[axis] >= gridSize[axis])) 78 | { 79 | Vec2d adjacentPoint = myPoissonGrid.indexToWorld(adjacentCell.cast()); 80 | rhsVector(row) -= solutionFunction(adjacentPoint); 81 | } 82 | else 83 | { 84 | // If neighbouring cell is solvable, it should have an entry in the system 85 | int adjacentRow = solvableCells(adjacentCell); 86 | assert(adjacentRow >= 0); 87 | 88 | localSparseElements.emplace_back(row, adjacentRow, -1); 89 | } 90 | } 91 | localSparseElements.emplace_back(row, row, 4); 92 | } 93 | }); 94 | 95 | mergeLocalThreadVectors(sparseMatrixElements, parallelSparseElements); 96 | 97 | Eigen::SparseMatrix sparseMatrix(solutionDOFCount, solutionDOFCount); 98 | sparseMatrix.setFromTriplets(sparseMatrixElements.begin(), sparseMatrixElements.end()); 99 | 100 | Eigen::ConjugateGradient, Eigen::Upper | Eigen::Lower> solver; 101 | solver.compute(sparseMatrix); 102 | 103 | if (solver.info() != Eigen::Success) 104 | { 105 | return -1; 106 | } 107 | 108 | VectorXd solutionVector = solver.solve(rhsVector); 109 | 110 | if (solver.info() != Eigen::Success) 111 | { 112 | return -1; 113 | } 114 | 115 | double error = tbb::parallel_reduce(tbb::blocked_range(0, solvableCells.voxelCount()), double(0), 116 | [&](const tbb::blocked_range& range, double error) -> double 117 | { 118 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 119 | { 120 | Vec2i cell = solvableCells.unflatten(cellIndex); 121 | 122 | int row = solvableCells(cell); 123 | 124 | assert(row >= 0); 125 | 126 | Vec2d gridPoint = myPoissonGrid.indexToWorld(cell.cast()); 127 | double localError = fabs(solutionVector(row) - solutionFunction(gridPoint)); 128 | error = std::max(error, localError); 129 | } 130 | 131 | return error; 132 | }, 133 | [](double a, double b) -> double { 134 | return std::max(a, b); 135 | }); 136 | 137 | return error; 138 | } 139 | 140 | TEST(ANALYTICAL_POISSON_SOLVER_TEST, CONVERGENCE_TEST) 141 | { 142 | auto rhs = [](const Vec2d& pos) -> double 143 | { 144 | return 2. * std::exp(-pos[0] - pos[1]); 145 | }; 146 | 147 | auto solution = [](const Vec2d& pos) -> double 148 | { 149 | return std::exp(-pos[0] - pos[1]); 150 | }; 151 | 152 | const int startGrid = 16; 153 | const int endGrid = startGrid * int(pow(2, 4)); 154 | 155 | std::vector errors; 156 | for (int gridSize = startGrid; gridSize < endGrid; gridSize *= 2) 157 | { 158 | double dx = PI / double(gridSize); 159 | Vec2d origin = Vec2d::Zero(); 160 | Vec2i size = Vec2i(std::round(PI / dx), std::round(PI / dx)); 161 | Transform xform(dx, origin); 162 | 163 | AnalyticalPoissonSolver solver(xform, size); 164 | double error = solver.solve(rhs, solution); 165 | 166 | errors.push_back(error); 167 | EXPECT_GT(error, 0.); 168 | } 169 | 170 | for (int errorIndex = 1; errorIndex < errors.size(); ++errorIndex) 171 | { 172 | double errorRatio = errors[errorIndex - 1] / errors[errorIndex]; 173 | EXPECT_GT(errorRatio, 4.); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/ViscousLiquid/ViscousLiquid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "EdgeMesh.h" 5 | #include "EulerianLiquidSimulator.h" 6 | #include "InitialGeometry.h" 7 | #include "Integrator.h" 8 | #include "LevelSet.h" 9 | #include "Renderer.h" 10 | #include "Transform.h" 11 | #include "TestVelocityFields.h" 12 | #include "Utilities.h" 13 | 14 | using namespace FluidSim2D; 15 | 16 | static std::unique_ptr renderer; 17 | 18 | static std::unique_ptr simulator; 19 | static std::unique_ptr solidVelocityField; 20 | 21 | static int frameCount = 0; 22 | static bool runSimulation = false; 23 | static bool runSingleTimestep = false; 24 | static bool isDisplayDirty = true; 25 | 26 | static constexpr double dt = 1. / 30; 27 | 28 | static constexpr double cfl = 5.; 29 | 30 | static Transform xform; 31 | static Vec2i gridSize; 32 | 33 | static EdgeMesh movingSolidsMesh; 34 | static EdgeMesh staticSolidsMesh; 35 | 36 | static LevelSet seedLiquidSurface; 37 | 38 | void display() 39 | { 40 | if (runSimulation || runSingleTimestep) 41 | { 42 | double frameTime = 0.; 43 | std::cout << "\nStart of frame: " << frameCount << ". Timestep: " << dt << std::endl; 44 | 45 | while (frameTime < dt) 46 | { 47 | // Set CFL condition 48 | double speed = simulator->maxVelocityMagnitude(); 49 | 50 | double localDt = dt - frameTime; 51 | assert(localDt >= 0); 52 | 53 | if (speed > 1E-6) 54 | { 55 | double cflDt = cfl * xform.dx() / speed; 56 | if (localDt > cflDt) 57 | { 58 | localDt = cflDt; 59 | std::cout << "\n Throttling frame with substep: " << localDt << "\n" << std::endl; 60 | } 61 | } 62 | 63 | if (localDt <= 0) 64 | break; 65 | 66 | // Add gravity 67 | simulator->addForce(localDt, Vec2d(0., -9.8)); 68 | 69 | // Update moving solid 70 | movingSolidsMesh.advectMesh(localDt, *solidVelocityField, IntegrationOrder::RK3); 71 | 72 | // Need moving solid volume to build sampled velocity 73 | LevelSet movingSolidSurface(xform, gridSize, 5); 74 | movingSolidSurface.initFromMesh(movingSolidsMesh, false); 75 | 76 | VectorGrid movingSolidVelocity(xform, gridSize, 0, VectorGridSettings::SampleType::STAGGERED); 77 | 78 | // Set moving solid velocity 79 | for (int axis : {0, 1}) 80 | { 81 | tbb::parallel_for(tbb::blocked_range(0, movingSolidVelocity.grid(axis).voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 82 | { 83 | for (int faceIndex = range.begin(); faceIndex != range.end(); ++faceIndex) 84 | { 85 | Vec2i face = movingSolidVelocity.grid(axis).unflatten(faceIndex); 86 | 87 | Vec2d worldPosition = movingSolidVelocity.indexToWorld(face.cast(), axis); 88 | 89 | if (movingSolidSurface.biLerp(worldPosition) < xform.dx()) 90 | movingSolidVelocity(face, axis) = (*solidVelocityField)(0, worldPosition)[axis]; 91 | } 92 | }); 93 | } 94 | 95 | LevelSet combinedSolidSurface(xform, gridSize, 5); 96 | combinedSolidSurface.setBackgroundNegative(); 97 | combinedSolidSurface.initFromMesh(staticSolidsMesh, false); 98 | combinedSolidSurface.unionSurface(movingSolidSurface); 99 | 100 | simulator->setSolidSurface(combinedSolidSurface); 101 | simulator->setSolidVelocity(movingSolidVelocity); 102 | 103 | // Projection set unfortunately includes viscosity at the moment 104 | simulator->runTimestep(localDt); 105 | 106 | simulator->unionLiquidSurface(seedLiquidSurface); 107 | 108 | frameTime += localDt; 109 | } 110 | 111 | runSingleTimestep = false; 112 | isDisplayDirty = true; 113 | 114 | } 115 | if (isDisplayDirty) 116 | { 117 | renderer->clear(); 118 | 119 | simulator->drawLiquidSurface(*renderer); 120 | simulator->drawSolidSurface(*renderer); 121 | simulator->drawLiquidVelocity(*renderer, .5); 122 | 123 | isDisplayDirty = false; 124 | 125 | glutPostRedisplay(); 126 | } 127 | } 128 | 129 | void keyboard(unsigned char key, int, int) 130 | { 131 | if (key == ' ') 132 | runSimulation = !runSimulation; 133 | else if (key == 'n') 134 | runSingleTimestep = true; 135 | } 136 | 137 | int main(int argc, char** argv) 138 | { 139 | // Scene settings 140 | double dx = .025; 141 | Vec2d topRightCorner(2.5, 2.5); 142 | Vec2d bottomLeftCorner(-2.5, -2.5); 143 | gridSize = ((topRightCorner - bottomLeftCorner).array() / dx).matrix().cast(); 144 | xform = Transform(dx, bottomLeftCorner); 145 | Vec2d center = .5 * (topRightCorner + bottomLeftCorner); 146 | 147 | renderer = std::make_unique("Viscous Liquid Simulator", Vec2i(1000, 1000), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 148 | 149 | movingSolidsMesh = makeCircleMesh(center + Vec2d(1.2, 0), .25, 20); 150 | 151 | staticSolidsMesh = makeSquareMesh(center, Vec2d(2, 2)); 152 | staticSolidsMesh.reverse(); 153 | assert(staticSolidsMesh.unitTestMesh()); 154 | 155 | EdgeMesh beamLiquidMesh = makeSquareMesh(center - Vec2d(.8, 0), Vec2d(1.5, .2)); 156 | assert(beamLiquidMesh.unitTestMesh()); 157 | 158 | LevelSet beamLiquidSurface(xform, gridSize, 5); 159 | beamLiquidSurface.initFromMesh(beamLiquidMesh, false); 160 | 161 | EdgeMesh seedLiquidMesh = makeSquareMesh(center + Vec2d(0, .6), Vec2d(.075, .25)); 162 | assert(seedLiquidMesh.unitTestMesh()); 163 | 164 | seedLiquidSurface = LevelSet(xform, gridSize, 5); 165 | seedLiquidSurface.initFromMesh(seedLiquidMesh, false); 166 | 167 | LevelSet solidSurface(xform, gridSize, 5); 168 | solidSurface.setBackgroundNegative(); 169 | solidSurface.initFromMesh(staticSolidsMesh, false); 170 | 171 | // Set up simulation 172 | simulator = std::make_unique(xform, gridSize, 10); 173 | simulator->setLiquidSurface(beamLiquidSurface); 174 | simulator->unionLiquidSurface(seedLiquidSurface); 175 | simulator->setSolidSurface(solidSurface); 176 | 177 | simulator->setViscosity(30); 178 | 179 | // Set up simulation 180 | solidVelocityField = std::make_unique(center, .5); 181 | 182 | std::function displayFunc = display; 183 | renderer->setUserDisplay(displayFunc); 184 | 185 | std::function keyboardFunc = keyboard; 186 | renderer->setUserKeyboard(keyboardFunc); 187 | renderer->run(); 188 | } 189 | -------------------------------------------------------------------------------- /Library/SurfaceTrackers/EdgeMesh.cpp: -------------------------------------------------------------------------------- 1 | #include "EdgeMesh.h" 2 | 3 | #include 4 | 5 | namespace FluidSim2D 6 | { 7 | 8 | EdgeMesh::EdgeMesh(const VecVec2i& edges, const VecVec2d& vertices) 9 | { 10 | initialize(edges, vertices); 11 | } 12 | 13 | void EdgeMesh::reinitialize(const VecVec2i& edges, const VecVec2d& vertices) 14 | { 15 | myEdges.clear(); 16 | myVertices.clear(); 17 | 18 | initialize(edges, vertices); 19 | } 20 | 21 | void EdgeMesh::insertMesh(const EdgeMesh& mesh) 22 | { 23 | int edgeCount = int(myEdges.size()); 24 | int vertexCount = int(myVertices.size()); 25 | 26 | myVertices.insert(myVertices.end(), mesh.myVertices.begin(), mesh.myVertices.end()); 27 | myEdges.insert(myEdges.end(), mesh.myEdges.begin(), mesh.myEdges.end()); 28 | 29 | for (int edgeIndex = edgeCount; edgeIndex < int(myEdges.size()); ++edgeIndex) 30 | { 31 | for (int localVertexIndex : {0, 1}) 32 | { 33 | int vertexIndex = myEdges[edgeIndex][localVertexIndex]; 34 | 35 | myEdges[edgeIndex][localVertexIndex] = vertexIndex + vertexCount; 36 | 37 | assert(vertexIndex >= 0 && vertexIndex < mesh.vertexCount()); 38 | assert(myEdges[edgeIndex][localVertexIndex] < myVertices.size()); 39 | } 40 | } 41 | } 42 | 43 | const VecVec2i& EdgeMesh::edges() const 44 | { 45 | return myEdges; 46 | } 47 | 48 | const VecVec2d& EdgeMesh::vertices() const 49 | { 50 | return myVertices; 51 | } 52 | 53 | AlignedBox2d EdgeMesh::boundingBox() const 54 | { 55 | AlignedBox2d bbox; 56 | 57 | for (const Vec2d& vertex : myVertices) 58 | bbox.extend(vertex); 59 | 60 | return bbox; 61 | } 62 | 63 | void EdgeMesh::clear() 64 | { 65 | myVertices.clear(); 66 | myEdges.clear(); 67 | } 68 | 69 | void EdgeMesh::reverse() 70 | { 71 | for (auto& edge : myEdges) 72 | { 73 | std::swap(edge[0], edge[1]); 74 | } 75 | } 76 | 77 | void EdgeMesh::scale(double s) 78 | { 79 | for (auto& vertex : myVertices) 80 | { 81 | vertex.array() *= s; 82 | } 83 | } 84 | 85 | void EdgeMesh::translate(const Vec2d& t) 86 | { 87 | for (auto& vertex : myVertices) 88 | { 89 | vertex += t; 90 | } 91 | } 92 | 93 | void EdgeMesh::drawMesh(Renderer& renderer, 94 | Vec3d edgeColour, 95 | double edgeWidth, 96 | bool doRenderEdgeNormals, 97 | bool doRenderVertices, 98 | Vec3d vertexColour) const 99 | { 100 | VecVec2d startPoints(myEdges.size()); 101 | VecVec2d endPoints(myEdges.size()); 102 | 103 | for (int edgeIndex = 0; edgeIndex < myEdges.size(); ++edgeIndex) 104 | { 105 | startPoints[edgeIndex] = myVertices[myEdges[edgeIndex][0]]; 106 | endPoints[edgeIndex] = myVertices[myEdges[edgeIndex][1]]; 107 | } 108 | 109 | renderer.addLines(startPoints, endPoints, edgeColour, edgeWidth); 110 | 111 | if (doRenderEdgeNormals) 112 | { 113 | VecVec2d startNormals(myEdges.size()); 114 | VecVec2d endNormals(myEdges.size()); 115 | 116 | // Scale by average edge length 117 | double averageLength = 0.; 118 | for (const Vec2i& edge : myEdges) 119 | averageLength += (myVertices[edge[0]] - myVertices[edge[1]]).norm(); 120 | 121 | averageLength /= double(myEdges.size()); 122 | 123 | for (int edgeIndex = 0; edgeIndex < myEdges.size(); ++edgeIndex) 124 | { 125 | Vec2d midPoint = .5 * (myVertices[myEdges[edgeIndex][0]] + myVertices[myEdges[edgeIndex][1]]); 126 | Vec2d edgeNormal = normal(edgeIndex); 127 | 128 | startNormals[edgeIndex] = midPoint; 129 | endNormals[edgeIndex] = midPoint + edgeNormal * averageLength; 130 | } 131 | 132 | renderer.addLines(startNormals, endNormals, Vec3d::Zero()); 133 | } 134 | 135 | if (doRenderVertices) 136 | { 137 | renderer.addPoints(myVertices, vertexColour, 2); 138 | } 139 | } 140 | 141 | bool EdgeMesh::unitTestMesh() const 142 | { 143 | std::vector> adjacentEdges(myVertices.size()); 144 | 145 | for (int edgeIndex = 0; edgeIndex < myEdges.size(); ++edgeIndex) 146 | { 147 | for (int localVertexIndex : {0, 1}) 148 | { 149 | int vertexIndex = myEdges[edgeIndex][localVertexIndex]; 150 | adjacentEdges[vertexIndex].push_back(edgeIndex); 151 | } 152 | } 153 | 154 | // Verify vertex has two or more adjacent edges. Meaning no dangling edge. 155 | for (int vertexIndex = 0; vertexIndex < myVertices.size(); ++vertexIndex) 156 | { 157 | if (adjacentEdges[vertexIndex].size() < 2) 158 | { 159 | std::cout << "Unit test failed in valence check. Vertex: " << vertexIndex << ". Valence: " << myVertices[vertexIndex].size() << std::endl; 160 | return false; 161 | } 162 | } 163 | 164 | // Verify edge has two adjacent vertices. Meaning no dangling edge. 165 | for (int edgeIndex = 0; edgeIndex < myEdges.size(); ++edgeIndex) 166 | { 167 | for (int localVertexIndex : {0, 1}) 168 | { 169 | if (myEdges[edgeIndex][localVertexIndex] < 0 || myEdges[edgeIndex][localVertexIndex] > myVertices.size()) 170 | { 171 | std::cout << "Unit test failed in edge's vertex count test. Edge: " << edgeIndex << std::endl; 172 | return false; 173 | } 174 | } 175 | } 176 | 177 | // Verify vertex's adjacent edge reciprocates 178 | for (int vertexIndex = 0; vertexIndex < myVertices.size(); ++vertexIndex) 179 | { 180 | for (int edgeIndex : adjacentEdges[vertexIndex]) 181 | { 182 | if (!(myEdges[edgeIndex][0] == vertexIndex || myEdges[edgeIndex][1] == vertexIndex)) 183 | { 184 | std::cout << "Unit test failed in adjacent edge test. Vertex: " << vertexIndex << ". Edge: " << edgeIndex << std::endl; 185 | return false; 186 | } 187 | } 188 | } 189 | 190 | // Verify edge's adjacent vertex reciprocates 191 | for (int edgeIndex = 0; edgeIndex < myEdges.size(); ++edgeIndex) 192 | { 193 | for (int localVertexIndex : {0, 1}) 194 | { 195 | int vertexIndex = myEdges[edgeIndex][localVertexIndex]; 196 | if (std::find(adjacentEdges[vertexIndex].begin(), adjacentEdges[vertexIndex].end(), edgeIndex) == adjacentEdges[vertexIndex].end()) 197 | { 198 | std::cout << "Unit test failed in adjacent vertex test. Vertex: " << vertexIndex << ". Edge: " << edgeIndex << std::endl; 199 | return false; 200 | } 201 | } 202 | } 203 | 204 | // TODO: write a test that verifies winding order 205 | 206 | return true; 207 | } 208 | 209 | void EdgeMesh::initialize(const VecVec2i& edges, const VecVec2d& vertices) 210 | { 211 | myEdges.insert(myEdges.end(), edges.begin(), edges.end()); 212 | myVertices.insert(myVertices.end(), vertices.begin(), vertices.end()); 213 | } 214 | 215 | 216 | } -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/RisingSmoke/RisingSmoke.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "EdgeMesh.h" 5 | #include "EulerianSmokeSimulator.h" 6 | #include "InitialGeometry.h" 7 | #include "LevelSet.h" 8 | #include "Renderer.h" 9 | #include "Transform.h" 10 | #include "EdgeMesh.h" 11 | #include "Utilities.h" 12 | 13 | using namespace FluidSim2D; 14 | 15 | static std::unique_ptr renderer; 16 | static std::unique_ptr simulator; 17 | 18 | static ScalarGrid seedSmokeDensity; 19 | static ScalarGrid seedSmokeTemperature; 20 | 21 | static int frameCount = 0; 22 | static bool runSimulation = false; 23 | static bool runSingleTimestep = false; 24 | static bool isDisplayDirty = true; 25 | 26 | static constexpr double dt = 1./30.; 27 | static constexpr double ambientTemperature = 300; 28 | 29 | static constexpr double cfl = 5.; 30 | static Transform xform; 31 | 32 | void setSmokeSource(const LevelSet& sourceVolume, 33 | double defaultDensity, 34 | ScalarGrid& smokeDensity, 35 | double defaultTemperature, 36 | ScalarGrid& smokeTemperature) 37 | { 38 | Vec2i size = sourceVolume.size(); 39 | 40 | double samples = 2; 41 | double sampleDx = 1. / samples; 42 | 43 | double dx = sourceVolume.dx(); 44 | 45 | tbb::parallel_for(tbb::blocked_range(0, smokeDensity.voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 46 | { 47 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 48 | { 49 | Vec2i cell = smokeDensity.unflatten(cellIndex); 50 | // Super sample to determine 51 | if (sourceVolume(cell) < dx * 2.) 52 | { 53 | // Loop over super samples internally. i -.5 is the index space boundary of the sample. The 54 | // first sample point is the .5 * sample_dx closer to (i,j). 55 | int insideVolumeCount = 0; 56 | for (double x = (double(cell[0]) - .5) + (.5 * sampleDx); x <= double(cell[0]) + .5; x += sampleDx) 57 | for (double y = (double(cell[1]) - .5) + (.5 * sampleDx); y <= double(cell[1]) + .5; y += sampleDx) 58 | { 59 | if (sourceVolume.biLerp(sourceVolume.indexToWorld(cell.cast())) <= 0.) ++insideVolumeCount; 60 | } 61 | 62 | if (insideVolumeCount > 0) 63 | { 64 | double tempDensity = defaultDensity;// +.05 * Util::randhashd(cell[0] + cell[1] * sourceVolume.size()[0]); 65 | double tempTemperature = defaultTemperature;// +50. * Util::randhashd(cell[0] + cell[1] * sourceVolume.size()[0]); 66 | smokeDensity(cell) = tempDensity * double(insideVolumeCount) * sampleDx * sampleDx; 67 | smokeTemperature(cell) = tempTemperature * double(insideVolumeCount) * sampleDx * sampleDx; 68 | } 69 | } 70 | } 71 | }); 72 | } 73 | 74 | void display() 75 | { 76 | if (runSimulation || runSingleTimestep) 77 | { 78 | double frameTime = 0.; 79 | std::cout << "\nStart of frame: " << frameCount << ". Timestep: " << dt << std::endl; 80 | 81 | while (frameTime < dt) 82 | { 83 | // Set CFL condition 84 | double speed = simulator->maxVelocityMagnitude(); 85 | 86 | double localDt = dt - frameTime; 87 | assert(localDt >= 0); 88 | 89 | if (speed > 1E-6) 90 | { 91 | double cflDt = cfl * xform.dx() / speed; 92 | if (localDt > cflDt) 93 | { 94 | localDt = cflDt; 95 | std::cout << "\n Throttling frame with substep: " << localDt << "\n" << std::endl; 96 | } 97 | } 98 | 99 | if (localDt <= 0) 100 | break; 101 | 102 | simulator->runTimestep(localDt); 103 | 104 | // Add smoke density and temperature source to simulation frame 105 | simulator->setSmokeSource(seedSmokeDensity, seedSmokeTemperature); 106 | 107 | frameTime += localDt; 108 | } 109 | 110 | std::cout << "\n\nEnd of frame: " << frameCount << "\n" << std::endl; 111 | ++frameCount; 112 | 113 | runSingleTimestep = false; 114 | isDisplayDirty = true; 115 | } 116 | if (isDisplayDirty) 117 | { 118 | renderer->clear(); 119 | 120 | simulator->drawFluidDensity(*renderer, 1); 121 | simulator->drawSolidSurface(*renderer); 122 | 123 | isDisplayDirty = false; 124 | 125 | glutPostRedisplay(); 126 | } 127 | } 128 | 129 | void keyboard(unsigned char key, int, int) 130 | { 131 | if (key == ' ') 132 | runSimulation = !runSimulation; 133 | else if (key == 'n') 134 | runSingleTimestep = true; 135 | } 136 | 137 | int main(int argc, char** argv) 138 | { 139 | // Scene settings 140 | double dx = .025; 141 | double boundaryPadding = 10.; 142 | 143 | Vec2d topRightCorner(2.5, 2.5); 144 | topRightCorner.array() += dx * boundaryPadding; 145 | 146 | Vec2d bottomLeftCorner(-2.5, -2.5); 147 | bottomLeftCorner.array() -= dx * boundaryPadding; 148 | 149 | Vec2d simulationSize = topRightCorner - bottomLeftCorner; 150 | Vec2i gridSize = (simulationSize / dx).cast(); 151 | xform = Transform(dx, bottomLeftCorner); 152 | Vec2d center = .5 * (topRightCorner + bottomLeftCorner); 153 | 154 | renderer = std::make_unique("Smoke Simulator", Vec2i(1000, 1000), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 155 | 156 | EdgeMesh solidMesh = makeSquareMesh(center, .5 * simulationSize - Vec2d(boundaryPadding * xform.dx(), boundaryPadding * xform.dx())); 157 | solidMesh.reverse(); 158 | assert(solidMesh.unitTestMesh()); 159 | 160 | LevelSet solid(xform, gridSize, 5); 161 | solid.setBackgroundNegative(); 162 | solid.initFromMesh(solidMesh, false); 163 | 164 | simulator = std::make_unique(xform, gridSize, 300); 165 | simulator->setSolidSurface(solid); 166 | 167 | // Set up source for smoke density and smoke temperature 168 | EdgeMesh sourceMesh = makeCircleMesh(center - Vec2d(0, 2.), .25, 40); 169 | LevelSet sourceVolume(xform, gridSize, 5); 170 | sourceVolume.initFromMesh(sourceMesh, false); 171 | 172 | // Super sample source volume to get a smooth volumetric representation. 173 | seedSmokeDensity = ScalarGrid(xform, gridSize, 0); 174 | seedSmokeTemperature = ScalarGrid(xform, gridSize, ambientTemperature); 175 | 176 | setSmokeSource(sourceVolume, .2, seedSmokeDensity, 350, seedSmokeTemperature); 177 | 178 | simulator->setSmokeSource(seedSmokeDensity, seedSmokeTemperature); 179 | 180 | std::function displayFunc = display; 181 | renderer->setUserDisplay(displayFunc); 182 | 183 | std::function keyboardFunc = keyboard; 184 | renderer->setUserKeyboard(keyboardFunc); 185 | 186 | renderer->run(); 187 | } -------------------------------------------------------------------------------- /Library/SimTools/ComputeWeights.cpp: -------------------------------------------------------------------------------- 1 | #include "ComputeWeights.h" 2 | 3 | #include 4 | 5 | #include "tbb/blocked_range.h" 6 | #include "tbb/parallel_for.h" 7 | 8 | namespace FluidSim2D 9 | { 10 | 11 | VectorGrid computeGhostFluidWeights(const LevelSet& surface) 12 | { 13 | VectorGrid ghostFluidWeights(surface.xform(), surface.size(), 0, VectorGridSettings::SampleType::STAGGERED); 14 | 15 | for (int axis : {0, 1}) 16 | { 17 | tbb::parallel_for(tbb::blocked_range(0, ghostFluidWeights.grid(axis).voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range &range) 18 | { 19 | for (int faceIndex = range.begin(); faceIndex != range.end(); ++faceIndex) 20 | { 21 | Vec2i face = ghostFluidWeights.grid(axis).unflatten(faceIndex); 22 | Vec2i backwardCell = faceToCell(face, axis, 0); 23 | Vec2i forwardCell = faceToCell(face, axis, 1); 24 | 25 | if (backwardCell[axis] < 0 || forwardCell[axis] >= surface.size()[axis]) 26 | continue; 27 | else 28 | { 29 | double phiBackward = surface(backwardCell); 30 | double phiForward = surface(forwardCell); 31 | 32 | ghostFluidWeights(face, axis) = lengthFraction(phiBackward, phiForward); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | return ghostFluidWeights; 39 | } 40 | 41 | VectorGrid computeCutCellWeights(const LevelSet& surface, bool invert) 42 | { 43 | VectorGrid cutCellWeights(surface.xform(), surface.size(), 0, VectorGridSettings::SampleType::STAGGERED); 44 | 45 | for (int axis : {0, 1}) 46 | { 47 | tbb::parallel_for(tbb::blocked_range(0, cutCellWeights.grid(axis).voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range &range) 48 | { 49 | for (int faceIndex = range.begin(); faceIndex != range.end(); ++faceIndex) 50 | { 51 | Vec2i face = cutCellWeights.grid(axis).unflatten(faceIndex); 52 | 53 | int otherAxis = (axis + 1) % 2; 54 | 55 | Vec2d offset = Vec2d::Zero(); offset[otherAxis] = .5; 56 | 57 | Vec2d backwardNodePoint = cutCellWeights.indexToWorld(face.cast() - offset, axis); 58 | Vec2d forwardNodePoint = cutCellWeights.indexToWorld(face.cast() + offset, axis); 59 | 60 | double weight = lengthFraction(surface.biLerp(backwardNodePoint), surface.biLerp(forwardNodePoint)); 61 | 62 | if (invert) 63 | weight = 1. - weight; 64 | 65 | if (weight > 0) 66 | cutCellWeights(face, axis) = weight; 67 | } 68 | }); 69 | } 70 | 71 | return cutCellWeights; 72 | } 73 | 74 | // There is no assumption about grid alignment for this method because 75 | // we're computing weights for centers, faces, nodes, etc. that each 76 | // have their internal index space cell offsets. We can't make any 77 | // easy general assumptions about indices between grids anymore. 78 | ScalarGrid computeSupersampledAreas(const LevelSet& surface, ScalarGridSettings::SampleType sampleType, int samples) 79 | { 80 | assert(samples > 0); 81 | 82 | ScalarGrid areas(surface.xform(), surface.size(), 0, sampleType); 83 | 84 | double sampleDx = 1. / double(samples); 85 | double sampleCount = std::pow(double(samples), 2); 86 | tbb::parallel_for(tbb::blocked_range(0, areas.voxelCount(), tbbHeavyGrainSize), [&](const tbb::blocked_range& range) 87 | { 88 | for (int sampleIndex = range.begin(); sampleIndex != range.end(); ++sampleIndex) 89 | { 90 | Vec2i sampleCoord = areas.unflatten(sampleIndex); 91 | double sdf = surface.biLerp(areas.indexToWorld(sampleCoord.cast())); 92 | 93 | if (sdf > 2. * surface.dx()) 94 | continue; 95 | 96 | if (sdf <= -2. * surface.dx()) 97 | { 98 | areas(sampleCoord) = 1; 99 | continue; 100 | } 101 | 102 | Vec2d start = sampleCoord.cast() - .5 * Vec2d::Ones() + .5 * Vec2d(sampleDx, sampleDx); 103 | Vec2d end = sampleCoord.cast() + .5 * Vec2d::Ones(); 104 | 105 | int insideMaterialCount = 0; 106 | 107 | for (Vec2d sample = start; sample[0] <= end[0]; sample[0] += sampleDx) 108 | for (sample[1] = start[1]; sample[1] <= end[1]; sample[1] += sampleDx) 109 | { 110 | Vec2d worldSamplePoint = areas.indexToWorld(sample); 111 | 112 | if (surface.biLerp(worldSamplePoint) <= 0.) 113 | ++insideMaterialCount; 114 | } 115 | 116 | if (insideMaterialCount > 0) 117 | { 118 | double supersampledArea = double(insideMaterialCount) / sampleCount; 119 | supersampledArea = std::clamp(supersampledArea, 0., 1.); 120 | areas(sampleCoord) = supersampledArea; 121 | } 122 | } 123 | }); 124 | 125 | return areas; 126 | } 127 | 128 | VectorGrid computeSupersampledFaceAreas(const LevelSet& surface, int samples) 129 | { 130 | assert(samples > 0); 131 | 132 | VectorGrid areas(surface.xform(), surface.size(), 0, VectorGridSettings::SampleType::STAGGERED); 133 | 134 | double sampleDx = 1. / double(samples); 135 | double sampleCount = std::pow(double(samples), 2); 136 | 137 | for (int axis : {0, 1}) 138 | { 139 | tbb::parallel_for(tbb::blocked_range(0, areas.grid(axis).voxelCount(), tbbHeavyGrainSize), [&](const tbb::blocked_range& range) 140 | { 141 | for (int sampleIndex = range.begin(); sampleIndex != range.end(); ++sampleIndex) 142 | { 143 | Vec2i face = areas.grid(axis).unflatten(sampleIndex); 144 | double sdf = surface.biLerp(areas.indexToWorld(face.cast(), axis)); 145 | 146 | if (sdf > 2. * surface.dx()) 147 | continue; 148 | 149 | if (sdf <= -2. * surface.dx()) 150 | { 151 | areas(face, axis) = 1; 152 | continue; 153 | } 154 | 155 | Vec2d start = face.cast() - .5 * Vec2d::Ones() + .5 * Vec2d(sampleDx, sampleDx); 156 | Vec2d end = face.cast() + .5 * Vec2d::Ones(); 157 | 158 | double insideMaterialCount = 0; 159 | 160 | for (Vec2d point = start; point[0] <= end[0]; point[0] += sampleDx) 161 | for (point[1] = start[1]; point[1] <= end[1]; point[1] += sampleDx) 162 | { 163 | Vec2d worldSamplePoint = areas.indexToWorld(point, axis); 164 | 165 | if (surface.biLerp(worldSamplePoint) <= 0.) 166 | ++insideMaterialCount; 167 | } 168 | 169 | if (insideMaterialCount > 0) 170 | { 171 | double supersampledArea = insideMaterialCount / sampleCount; 172 | supersampledArea = std::clamp(supersampledArea, 0., 1.); 173 | areas(face, axis) = supersampledArea; 174 | } 175 | } 176 | }); 177 | } 178 | 179 | return areas; 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /Library/SurfaceTrackers/LevelSet.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_LEVELSET_H 2 | #define FLUIDSIM2D_LEVELSET_H 3 | 4 | #include "EdgeMesh.h" 5 | #include "FieldAdvector.h" 6 | #include "Integrator.h" 7 | #include "Predicates.h" 8 | #include "Renderer.h" 9 | #include "ScalarGrid.h" 10 | #include "Transform.h" 11 | #include "Utilities.h" 12 | #include "VectorGrid.h" 13 | 14 | /////////////////////////////////// 15 | // 16 | // LevelSet.h/cpp 17 | // Ryan Goldade 2016 18 | // 19 | // 2d level set surface tracker. 20 | // Uses a simple dense grid but with 21 | // a narrow band for most purposes. 22 | // Redistancing performs an interface 23 | // search for nodes near the zero crossing 24 | // and then fast marching to update the 25 | // remaining grid (w.r.t. narrow band). 26 | // 27 | //////////////////////////////////// 28 | 29 | namespace FluidSim2D 30 | { 31 | 32 | class LevelSet 33 | { 34 | public: 35 | LevelSet() 36 | : myNarrowBand(0) 37 | , myPhiGrid(Transform(0, Vec2d::Zero()), Vec2i::Zero()) 38 | , myIsBackgroundNegative(false) 39 | { exactinit(); } 40 | 41 | LevelSet(const Transform& xform, const Vec2i& size) : LevelSet(xform, size, double(size[0] * size[1])) {} 42 | LevelSet(const Transform& xform, const Vec2i& size, double bandwidth, bool isBoundaryNegative = false) 43 | : myNarrowBand(bandwidth * xform.dx()) 44 | , myPhiGrid(xform, size, isBoundaryNegative ? -myNarrowBand : myNarrowBand) 45 | , myIsBackgroundNegative(isBoundaryNegative) 46 | { 47 | assert(size[0] >= 0 && size[1] >= 0); 48 | // In order to deal with triangle meshes, we need to initialize 49 | // the geometric predicate library. 50 | exactinit(); 51 | } 52 | 53 | void initFromMesh(const EdgeMesh& initialMesh, bool doResizeGrid = true); 54 | 55 | void reinit(bool useMarchingSquares = false) 56 | { 57 | EdgeMesh tempMesh; 58 | if (useMarchingSquares) tempMesh = buildMSMesh(); 59 | else tempMesh = buildDCMesh(); 60 | initFromMeshImpl(tempMesh, false); 61 | } 62 | 63 | bool isGridMatched(const LevelSet& grid) const 64 | { 65 | if (size() != grid.size()) return false; 66 | if (xform() != grid.xform()) return false; 67 | return true; 68 | } 69 | 70 | bool isGridMatched(const ScalarGrid& grid) const 71 | { 72 | if (grid.sampleType() != ScalarGridSettings::SampleType::CENTER) return false; 73 | if ((size().array() != grid.size().array()).all()) return false; 74 | if (xform() != grid.xform()) return false; 75 | return true; 76 | } 77 | 78 | void unionSurface(const LevelSet& unionPhi); 79 | 80 | bool isBackgroundNegative() const { return myIsBackgroundNegative; } 81 | void setBackgroundNegative() { myIsBackgroundNegative = true; } 82 | 83 | EdgeMesh buildMSMesh() const; 84 | EdgeMesh buildDCMesh() const; 85 | 86 | template 87 | void advect(double dt, const VelocityField& velocity, IntegrationOrder order); 88 | 89 | FORCE_INLINE Vec2d normal(const Vec2d& worldPoint, bool useLinearIntep = true) const 90 | { 91 | Vec2d normal; 92 | 93 | if (useLinearIntep) 94 | { 95 | normal = myPhiGrid.biLerpGradient(worldPoint); 96 | } 97 | else 98 | { 99 | normal = myPhiGrid.biCubicGradient(worldPoint); 100 | } 101 | 102 | if ((normal.array() == Vec2d::Zero().array()).all()) return Vec2d::Zero(); 103 | 104 | return normal.normalized(); 105 | } 106 | 107 | void clear() { myPhiGrid.clear(); } 108 | void resize(const Vec2i& size) { myPhiGrid.resize(size); } 109 | 110 | FORCE_INLINE double narrowBand() { return myNarrowBand; } 111 | 112 | // There's no way to change the grid spacing inside the class. 113 | // The best way is to build a new grid and sample this one 114 | FORCE_INLINE double dx() const { return myPhiGrid.dx(); } 115 | FORCE_INLINE Vec2d offset() const { return myPhiGrid.offset(); } 116 | FORCE_INLINE Transform xform() const { return myPhiGrid.xform(); } 117 | FORCE_INLINE Vec2i size() const { return myPhiGrid.size(); } 118 | 119 | FORCE_INLINE Vec2d indexToWorld(const Vec2d& indexPoint) const { return myPhiGrid.indexToWorld(indexPoint); } 120 | FORCE_INLINE Vec2d worldToIndex(const Vec2d& worldPoint) const { return myPhiGrid.worldToIndex(worldPoint); } 121 | 122 | FORCE_INLINE double biLerp(const Vec2d& worldPoint) const 123 | { 124 | return myPhiGrid.biLerp(worldPoint); 125 | } 126 | 127 | FORCE_INLINE double biCubicInterp(const Vec2d& worldPoint) const 128 | { 129 | return myPhiGrid.biCubicInterp(worldPoint); 130 | } 131 | 132 | FORCE_INLINE double& operator()(int i, int j) { return myPhiGrid(i, j); } 133 | FORCE_INLINE double& operator()(const Vec2i& cell) { return myPhiGrid(cell); } 134 | 135 | FORCE_INLINE const double& operator()(int i, int j) const { return myPhiGrid(i, j); } 136 | FORCE_INLINE const double& operator()(const Vec2i& cell) const { return myPhiGrid(cell); } 137 | 138 | FORCE_INLINE int voxelCount() const { return myPhiGrid.voxelCount(); } 139 | FORCE_INLINE Vec2i unflatten(int cellIndex) const { return myPhiGrid.unflatten(cellIndex); } 140 | 141 | Vec2d findSurface(const Vec2d& worldPoint, int iterationLimit, double tolerance) const; 142 | 143 | // Interpolate the interface position between two nodes. This assumes 144 | // the caller has verified an interface (sign change) between the two. 145 | Vec2d interpolateInterface(const Vec2i& startPoint, const Vec2i& endPoint) const; 146 | 147 | void drawGrid(Renderer& renderer, bool doOnlyNarrowBand) const; 148 | void drawMeshGrid(Renderer& renderer) const; 149 | void drawSupersampledValues(Renderer& renderer, double radius = .5, int samples = 1, double sampleSize = 1) const; 150 | void drawNormals(Renderer& renderer, const Vec3d& colour = Vec3d(0, 0, 1), double length = .25) const; 151 | void drawSurface(Renderer& renderer, const Vec3d& colour = Vec3d::Zero(), double lineWidth = 1) const; 152 | void drawDCSurface(Renderer& renderer, const Vec3d& colour = Vec3d::Zero(), double lineWidth = 1) const; 153 | 154 | private: 155 | 156 | void initFromMeshImpl(const EdgeMesh& initialMesh, bool doResizeGrid); 157 | 158 | void reinitFastMarching(UniformGrid& interfaceCells); 159 | 160 | Vec2d findSurfaceIndex(const Vec2d& indexPoint, int iterationLimit, double tolerance) const; 161 | 162 | // The narrow band of signed distances around the interface 163 | double myNarrowBand; 164 | 165 | bool myIsBackgroundNegative; 166 | 167 | ScalarGrid myPhiGrid; 168 | }; 169 | 170 | template 171 | void LevelSet::advect(double dt, const VelocityField& velocity, IntegrationOrder order) 172 | { 173 | ScalarGrid tempPhiGrid = myPhiGrid; 174 | advectField(dt, tempPhiGrid, myPhiGrid, velocity, order, InterpolationOrder::CUBIC); 175 | 176 | std::swap(tempPhiGrid, myPhiGrid); 177 | } 178 | 179 | } 180 | #endif -------------------------------------------------------------------------------- /Library/SimTools/ExtrapolateField.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_EXTRAPOLATE_FIELD_H 2 | #define FLUIDSIM2D_EXTRAPOLATE_FIELD_H 3 | 4 | #include "UniformGrid.h" 5 | #include "Utilities.h" 6 | #include "VectorGrid.h" 7 | 8 | /////////////////////////////////// 9 | // 10 | // ExtrapolateField.h/cpp 11 | // Ryan Goldade 2016 12 | // 13 | // Extrapolates field from the boundary 14 | // of a mask outward based on a simple 15 | // BFS flood fill approach. Values 16 | // are averaged from FINISHED neighbours. 17 | // Note that because this process happens 18 | // "in order" there could be some bias 19 | // based on which boundary locations 20 | // are inserted into the queue first. 21 | // 22 | // 23 | //////////////////////////////////// 24 | 25 | namespace FluidSim2D 26 | { 27 | 28 | template 29 | void extrapolateField(Field& field, UniformGrid finishedCellMask, int bandwidth) 30 | { 31 | assert(bandwidth > 0); 32 | assert(field.size() == finishedCellMask.size()); 33 | 34 | // Build an initial list of cells adjacent to finished cells in the provided mask grid 35 | 36 | VecVec2i toVisitCells; 37 | 38 | tbb::enumerable_thread_specific parallelToVisitCells; 39 | tbb::parallel_for(tbb::blocked_range(0, finishedCellMask.voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 40 | { 41 | auto& localToVisitCells = parallelToVisitCells.local(); 42 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 43 | { 44 | Vec2i cell = finishedCellMask.unflatten(cellIndex); 45 | 46 | // Load up adjacent unfinished cells 47 | if (finishedCellMask(cell) == VisitedCellLabels::FINISHED_CELL) 48 | { 49 | for (int axis : {0, 1}) 50 | for (int direction : {0, 1}) 51 | { 52 | Vec2i adjacentCell = cellToCell(cell, axis, direction); 53 | 54 | if (adjacentCell[axis] < 0 || adjacentCell[axis] >= finishedCellMask.size()[axis]) 55 | continue; 56 | 57 | if (finishedCellMask(adjacentCell) != VisitedCellLabels::FINISHED_CELL) 58 | localToVisitCells.push_back(adjacentCell); 59 | } 60 | } 61 | } 62 | }); 63 | 64 | mergeLocalThreadVectors(toVisitCells, parallelToVisitCells); 65 | 66 | // Now flood outwards layer-by-layer 67 | for (int layer = 0; layer < bandwidth; ++layer) 68 | { 69 | // First sort the list because there could be duplicates 70 | tbb::parallel_sort(toVisitCells.begin(), toVisitCells.end(), [&](const Vec2i& vec0, const Vec2i& vec1) 71 | { 72 | return std::tie(vec0[0], vec0[1]) < std::tie(vec1[0], vec1[1]); 73 | }); 74 | 75 | // Compute values from adjacent finished cells 76 | tbb::parallel_for(tbb::blocked_range(0, int(toVisitCells.size()), tbbLightGrainSize), [&](const tbb::blocked_range& range) 77 | { 78 | // Because the list could contain duplicates, we need to advance forward through possible duplicates 79 | int cellIndex = range.begin(); 80 | 81 | if (cellIndex > 0) 82 | { 83 | while (cellIndex < int(toVisitCells.size()) && toVisitCells[cellIndex] == toVisitCells[cellIndex - 1]) 84 | ++cellIndex; 85 | } 86 | 87 | Vec2i oldCell(-1, -1); 88 | 89 | for (; cellIndex < range.end(); ++cellIndex) 90 | { 91 | Vec2i cell = toVisitCells[cellIndex]; 92 | 93 | if (cell == oldCell) 94 | continue; 95 | 96 | oldCell = cell; 97 | 98 | assert(finishedCellMask(cell) != VisitedCellLabels::FINISHED_CELL); 99 | 100 | // TODO: get template type from field instead of assuming float is valid 101 | double accumulatedValue = 0; 102 | double accumulatedCount = 0; 103 | 104 | for (int axis : {0, 1}) 105 | for (int direction : {0, 1}) 106 | { 107 | Vec2i adjacentCell = cellToCell(cell, axis, direction); 108 | 109 | if (adjacentCell[axis] < 0 || adjacentCell[axis] >= finishedCellMask.size()[axis]) 110 | continue; 111 | 112 | if (finishedCellMask(adjacentCell) == VisitedCellLabels::FINISHED_CELL) 113 | { 114 | accumulatedValue += field(adjacentCell); 115 | ++accumulatedCount; 116 | } 117 | } 118 | 119 | assert(accumulatedCount > 0); 120 | 121 | field(cell) = accumulatedValue / accumulatedCount; 122 | } 123 | }); 124 | 125 | // Set visited cells to finished 126 | tbb::parallel_for(tbb::blocked_range(0, int(toVisitCells.size()), tbbLightGrainSize), [&](const tbb::blocked_range& range) 127 | { 128 | // Because the list could contain duplicates, we need to advance forward through possible duplicates 129 | int cellIndex = range.begin(); 130 | 131 | if (cellIndex > 0) 132 | { 133 | while (cellIndex < int(toVisitCells.size()) && toVisitCells[cellIndex] == toVisitCells[cellIndex - 1]) 134 | ++cellIndex; 135 | } 136 | 137 | Vec2i oldCell(-1, -1); 138 | 139 | for (; cellIndex < range.end(); ++cellIndex) 140 | { 141 | Vec2i cell = toVisitCells[cellIndex]; 142 | 143 | if (cell == oldCell) 144 | continue; 145 | 146 | oldCell = cell; 147 | 148 | assert(finishedCellMask(cell) != VisitedCellLabels::FINISHED_CELL); 149 | finishedCellMask(cell) = VisitedCellLabels::FINISHED_CELL; 150 | } 151 | }); 152 | 153 | // Build new layer of cells 154 | if (layer < bandwidth - 1) 155 | { 156 | parallelToVisitCells.clear(); 157 | 158 | tbb::parallel_for(tbb::blocked_range(0, int(toVisitCells.size()), tbbLightGrainSize), [&](const tbb::blocked_range& range) 159 | { 160 | auto& localToVisitCells = parallelToVisitCells.local(); 161 | 162 | // Because the list could contain duplicates, we need to advance forward through possible duplicates 163 | int cellIndex = range.begin(); 164 | 165 | if (cellIndex > 0) 166 | { 167 | while (cellIndex < int(toVisitCells.size()) && toVisitCells[cellIndex] == toVisitCells[cellIndex - 1]) 168 | ++cellIndex; 169 | } 170 | 171 | Vec2i oldCell(-1, -1); 172 | 173 | for (; cellIndex < range.end(); ++cellIndex) 174 | { 175 | Vec2i cell = toVisitCells[cellIndex]; 176 | 177 | if (cell == oldCell) 178 | continue; 179 | 180 | oldCell = cell; 181 | 182 | assert(finishedCellMask(cell) == VisitedCellLabels::FINISHED_CELL); 183 | 184 | for (int axis : {0, 1}) 185 | for (int direction : {0, 1}) 186 | { 187 | Vec2i adjacentCell = cellToCell(cell, axis, direction); 188 | 189 | if (adjacentCell[axis] < 0 || adjacentCell[axis] >= finishedCellMask.size()[axis]) 190 | continue; 191 | 192 | if (finishedCellMask(adjacentCell) != VisitedCellLabels::FINISHED_CELL) 193 | localToVisitCells.push_back(adjacentCell); 194 | } 195 | } 196 | }); 197 | 198 | toVisitCells.clear(); 199 | mergeLocalThreadVectors(toVisitCells, parallelToVisitCells); 200 | } 201 | } 202 | } 203 | 204 | } 205 | #endif -------------------------------------------------------------------------------- /Projects/Simulations/Scenes/MultiMaterialLiquid/MultiMaterialLayers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "EdgeMesh.h" 5 | #include "InitialGeometry.h" 6 | #include "LevelSet.h" 7 | #include "MultiMaterialLiquidSimulator.h" 8 | #include "Renderer.h" 9 | #include "Transform.h" 10 | #include "Utilities.h" 11 | 12 | using namespace FluidSim2D; 13 | 14 | std::unique_ptr multiMaterialSimulator; 15 | std::unique_ptr renderer; 16 | 17 | static int frameCount = 0; 18 | static bool runSimulation = false; 19 | static bool runSingleTimestep = false; 20 | static bool isDisplayDirty = true; 21 | 22 | static constexpr double dt = 1. / 60.; 23 | 24 | static constexpr double cfl = 5; 25 | 26 | static bool printFrame = false; 27 | 28 | static int liquidMaterialCount; 29 | static int currentMaterial = 0; 30 | 31 | static Transform xform; 32 | static Vec2i gridSize; 33 | 34 | static const double lowDensity = 1; 35 | static const double mediumDensity = 1000; 36 | static const double highDensity = 10000; 37 | 38 | void display() 39 | { 40 | if (runSimulation || runSingleTimestep) 41 | { 42 | double frameTime = 0.; 43 | std::cout << "\nStart of frame: " << frameCount << ". Timestep: " << dt << std::endl; 44 | 45 | while (frameTime < dt) 46 | { 47 | // Set CFL condition 48 | double speed = multiMaterialSimulator->maxVelocityMagnitude(); 49 | double localDt = dt - frameTime; 50 | assert(localDt >= 0); 51 | 52 | if (speed > 1E-6) 53 | { 54 | double cflDt = cfl * xform.dx() / speed; 55 | if (localDt > cflDt) 56 | { 57 | localDt = cflDt; 58 | std::cout << "\n Throttling frame with substep: " << localDt << "\n" << std::endl; 59 | } 60 | } 61 | 62 | if (localDt <= 0) 63 | break; 64 | 65 | for (int material = 0; material < liquidMaterialCount; ++material) 66 | multiMaterialSimulator->addForce(localDt, material, Vec2d(0., -9.8)); 67 | 68 | multiMaterialSimulator->runTimestep(localDt); 69 | 70 | // Store accumulated substep times 71 | frameTime += localDt; 72 | } 73 | std::cout << "\n\nEnd of frame: " << frameCount << "\n" << std::endl; 74 | ++frameCount; 75 | 76 | runSingleTimestep = false; 77 | isDisplayDirty = true; 78 | } 79 | if (isDisplayDirty) 80 | { 81 | renderer->clear(); 82 | for (int material = 0; material < liquidMaterialCount; ++material) 83 | multiMaterialSimulator->drawMaterialSurface(*renderer, material); 84 | 85 | multiMaterialSimulator->drawSolidSurface(*renderer); 86 | 87 | for (int material = 0; material < liquidMaterialCount; ++material) 88 | { 89 | if (currentMaterial == material) 90 | multiMaterialSimulator->drawMaterialVelocity(*renderer, .25, material); 91 | } 92 | 93 | if (printFrame) 94 | { 95 | std::string frameCountString = std::to_string(frameCount); 96 | std::string renderFilename = "multimaterial_" + std::string(4 - frameCountString.length(), '0') + frameCountString; 97 | renderer->printImage(renderFilename); 98 | } 99 | 100 | isDisplayDirty = false; 101 | 102 | glutPostRedisplay(); 103 | } 104 | } 105 | 106 | void keyboard(unsigned char key, int, int) 107 | { 108 | if (key == ' ') 109 | runSimulation = !runSimulation; 110 | else if (key == 'n') 111 | runSingleTimestep = true; 112 | else if (key == 'm') 113 | { 114 | currentMaterial = (currentMaterial + 1) % liquidMaterialCount; 115 | isDisplayDirty = true; 116 | } 117 | else if (key == 'p') 118 | { 119 | printFrame = !printFrame; 120 | isDisplayDirty = true; 121 | } 122 | } 123 | 124 | int main(int argc, char** argv) 125 | { 126 | // Scene settings 127 | double dx = .015; 128 | double boundaryPadding = 10; 129 | 130 | Vec2d topRightCorner(1.5, 2.5); 131 | topRightCorner.array() += dx * boundaryPadding; 132 | 133 | Vec2d bottomLeftCorner(-1.5, -2.5); 134 | bottomLeftCorner.array() -= dx * boundaryPadding; 135 | 136 | Vec2d simulationSize = topRightCorner - bottomLeftCorner; 137 | gridSize = (simulationSize.array() / dx).matrix().cast(); 138 | 139 | xform = Transform(dx, bottomLeftCorner); 140 | Vec2d center = .5f * (topRightCorner + bottomLeftCorner); 141 | 142 | unsigned pixelHeight = 1080; 143 | unsigned pixelWidth = pixelHeight * int((topRightCorner[0] - bottomLeftCorner[0]) / (topRightCorner[1] - bottomLeftCorner[1])); 144 | renderer = std::make_unique("Multimaterial Liquid Simulator", Vec2i(pixelWidth, pixelHeight), bottomLeftCorner, topRightCorner[1] - bottomLeftCorner[1], &argc, argv); 145 | 146 | // Build outer boundary grid. 147 | EdgeMesh solidMesh = makeSquareMesh(center, .5 * simulationSize - Vec2d(boundaryPadding * xform.dx(), boundaryPadding * xform.dx())); 148 | solidMesh.reverse(); 149 | assert(solidMesh.unitTestMesh()); 150 | 151 | LevelSet solidSurface(xform, gridSize, 5); 152 | solidSurface.setBackgroundNegative(); 153 | solidSurface.initFromMesh(solidMesh, false); 154 | 155 | multiMaterialSimulator = std::make_unique(xform, gridSize, 3, 5.); 156 | 157 | multiMaterialSimulator->setSolidSurface(solidSurface); 158 | 159 | // Build three material surfaces 160 | 161 | EdgeMesh lowDensityMesh = makeSquareMesh(Vec2d(center[0], bottomLeftCorner[1] + dx * boundaryPadding + 1. / 6. * (topRightCorner[1] - bottomLeftCorner[1] - 2 * dx * boundaryPadding)), .5 * Vec2d(simulationSize[0] - 2. * boundaryPadding * xform.dx(), .33 * (simulationSize[1] - 2. * boundaryPadding * xform.dx()))); 162 | 163 | LevelSet lowDensitySurface(xform, gridSize, 5); 164 | lowDensitySurface.initFromMesh(lowDensityMesh, false); 165 | lowDensityMesh.reverse(); 166 | 167 | EdgeMesh highDensityMesh = makeSquareMesh(Vec2d(center[0], bottomLeftCorner[1] + dx * boundaryPadding + 5. / 6. * (topRightCorner[1] - bottomLeftCorner[1] - 2 * dx * boundaryPadding)), .5 * Vec2d(simulationSize[0] - 2. * boundaryPadding * xform.dx(), .33 * (simulationSize[1] - 2. * boundaryPadding * xform.dx()))); 168 | 169 | LevelSet highDensitySurface(xform, gridSize, 5); 170 | highDensitySurface.initFromMesh(highDensityMesh, false); 171 | highDensityMesh.reverse(); 172 | 173 | EdgeMesh mediumDensityMesh = solidMesh; 174 | mediumDensityMesh.reverse(); 175 | mediumDensityMesh.insertMesh(lowDensityMesh); 176 | mediumDensityMesh.insertMesh(highDensityMesh); 177 | 178 | LevelSet mediumDensitySurface(xform, gridSize, 5); 179 | mediumDensitySurface.initFromMesh(mediumDensityMesh, false); 180 | 181 | multiMaterialSimulator->setMaterial(lowDensitySurface, lowDensity, 0); 182 | multiMaterialSimulator->setMaterial(mediumDensitySurface, mediumDensity, 1); 183 | multiMaterialSimulator->setMaterial(highDensitySurface, highDensity, 2); 184 | 185 | liquidMaterialCount = 3; 186 | 187 | std::function displayFunc = display; 188 | renderer->setUserDisplay(displayFunc); 189 | 190 | std::function keyboardFunc = keyboard; 191 | renderer->setUserKeyboard(keyboardFunc); 192 | 193 | renderer->run(); 194 | } -------------------------------------------------------------------------------- /Library/SimTools/TestVelocityFields.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_TEST_VELOCITY_FIELD_H 2 | #define FLUIDSIM2D_TEST_VELOCITY_FIELD_H 3 | 4 | #include "Noise.h" 5 | #include "Renderer.h" 6 | #include "Utilities.h" 7 | 8 | /////////////////////////////////// 9 | // 10 | // TestVelocityFields.h 11 | // Ryan Goldade 2018 12 | // 13 | //////////////////////////////////// 14 | 15 | namespace FluidSim2D 16 | { 17 | 18 | //////////////////////////////////// 19 | // 20 | // 2-D single vortex velocity field simulation 21 | // from the standard surface tracker tests 22 | // 23 | //////////////////////////////////// 24 | 25 | class SingleVortexField 26 | { 27 | public: 28 | SingleVortexField() : mySimTime(0), myDeformationPeriod(6) 29 | {} 30 | 31 | SingleVortexField(double startTime, double deformationPeriod) 32 | : mySimTime(startTime) 33 | , myDeformationPeriod(deformationPeriod) 34 | {} 35 | 36 | void drawSimVectors(Renderer& renderer, double dt = 1, double radius = 10, const Vec3d& colour = Vec3d::Zero()) 37 | { 38 | VecVec2d startPoints; 39 | VecVec2d endPoints; 40 | 41 | for (double drawRadius = 0.; drawRadius <= radius; drawRadius += radius / 10.) 42 | for (double theta = 0.; theta < 2. * PI; theta += PI / 16.) 43 | { 44 | Vec2d start(drawRadius * std::cos(theta), drawRadius * std::sin(theta)); 45 | Vec2d end = start + dt * (*this)(dt, start); 46 | 47 | startPoints.push_back(start); 48 | endPoints.push_back(end); 49 | } 50 | 51 | renderer.addLines(startPoints, endPoints, colour); 52 | } 53 | 54 | void advance(double dt) 55 | { 56 | mySimTime += dt; 57 | } 58 | 59 | // Sample positions given in world space 60 | Vec2d operator()(double dt, const Vec2d& pos) const 61 | { 62 | double sinX = sin(PI * pos[0]); 63 | double sinY = sin(PI * pos[1]); 64 | 65 | double cosX = cos(PI * pos[0]); 66 | double cosY = cos(PI * pos[1]); 67 | 68 | double u = 2. * sinY * cosY * sinX * sinX; 69 | double v = -2. * sinX * cosX * sinY * sinY; 70 | 71 | return Vec2d(u, v) * cos(PI * (mySimTime + dt) / myDeformationPeriod); 72 | } 73 | 74 | private: 75 | double mySimTime, myDeformationPeriod; 76 | }; 77 | 78 | /////////////////////////////////// 79 | // 80 | // Curl noise velocity field generator. 81 | // Adapted from El Topo 82 | // 83 | //////////////////////////////////// 84 | 85 | class CurlNoiseField 86 | { 87 | public: 88 | CurlNoiseField() 89 | : myNoiseLengthScale(1), 90 | myNoiseGain(1), 91 | myNoiseFunction(), 92 | myDx(1E-4) 93 | { 94 | myNoiseLengthScale[0] = 1.5; 95 | myNoiseGain[0] = 1.3; 96 | } 97 | 98 | Vec2d getVelocity(const Vec2d& x) const 99 | { 100 | Vec2d vel; 101 | vel[0] = ((potential(x[0], x[1] + myDx)[2] - potential(x[0], x[1] - myDx)[2]) 102 | - (potential(x[0], x[1], myDx)[1] - potential(x[0], x[1], -myDx)[1])) / (2 * myDx); 103 | vel[1] = ((potential(x[0], x[1], myDx)[0] - potential(x[0], x[1], -myDx)[0]) 104 | - (potential(x[0] + myDx, x[1])[2] - potential(x[0] - myDx, x[1])[2])) / (2 * myDx); 105 | 106 | return vel; 107 | } 108 | 109 | Vec2d operator()(double, const Vec2d& pos) const 110 | { 111 | return getVelocity(pos); 112 | } 113 | 114 | private: 115 | 116 | Vec3d potential(double x, double y, double z = 0.) const 117 | { 118 | Vec3d psi(0, 0, 0); 119 | double heightFactor = 0.5; 120 | 121 | Vec3d centre(0.0, 1.0, 0.0); 122 | double radius = 4.0; 123 | 124 | for (int i = 0; i < myNoiseLengthScale.size(); ++i) 125 | { 126 | double sx = x / myNoiseLengthScale[i]; 127 | double sy = y / myNoiseLengthScale[i]; 128 | double sz = z / myNoiseLengthScale[i]; 129 | 130 | Vec3d psi_i(0, 0, noise2(sx, sy, sz)); 131 | 132 | double dist = (Vec3d(x, y, z) - centre).norm(); 133 | double scale = std::max((radius - dist) / radius, double(0)); 134 | psi_i *= scale; 135 | 136 | psi += heightFactor * myNoiseGain[i] * psi_i; 137 | } 138 | 139 | return psi; 140 | } 141 | 142 | double noise2(double x, double y, double z) const { return myNoiseFunction(z - 203.994, x + 169.47, y - 205.31); } 143 | 144 | std::vector myNoiseLengthScale, myNoiseGain; 145 | double myDx; 146 | 147 | FlowNoise3 myNoiseFunction; 148 | }; 149 | 150 | /////////////////////////////////// 151 | // 152 | // Child class fluid sim that just creates a velocity field that 153 | // rotates CCW. Useful sanity check for surface trackers. 154 | // 155 | //////////////////////////////////// 156 | 157 | class CircularField 158 | { 159 | public: 160 | CircularField() : myCenter(Vec2d(0)), myScale(1.) {} 161 | CircularField(const Vec2d& center, double scale = 1.) : myCenter(center), myScale(scale) {} 162 | 163 | void drawSimVectors(Renderer& renderer, double dt = 1., double radius = 10., const Vec3d& colour = Vec3d(0)) 164 | { 165 | VecVec2d startPoints; 166 | VecVec2d endPoints; 167 | 168 | for (double drawRadius = 0.0; drawRadius <= radius; drawRadius += radius / 10.) 169 | for (double theta = 0.0; theta < 2 * PI; theta += PI / 16.0) 170 | { 171 | Vec2d startPoint = Vec2d(drawRadius * cos(theta), drawRadius * sin(theta)) + myCenter; 172 | Vec2d endPoint = startPoint + dt * (*this)(dt, startPoint); 173 | 174 | startPoints.push_back(startPoint); 175 | endPoints.push_back(endPoint); 176 | } 177 | 178 | renderer.addLines(startPoints, endPoints, colour); 179 | }; 180 | 181 | Vec2d operator()(double, const Vec2d& pos) const 182 | { 183 | return Vec2d(pos[1] - myCenter[1], -(pos[0] - myCenter[0])) * myScale; 184 | } 185 | 186 | private: 187 | Vec2d myCenter; 188 | double myScale; 189 | }; 190 | 191 | /////////////////////////////////// 192 | // 193 | // Zalesak disk velocity field simulation 194 | // Assumes you're running the notched disk IC 195 | // 196 | //////////////////////////////////// 197 | 198 | class NotchedDiskField 199 | { 200 | public: 201 | 202 | void drawSimVectors(Renderer& renderer, double dt = 1., double radius = 10., const Vec3d& colour = Vec3d(0)) 203 | { 204 | VecVec2d startPoints; 205 | VecVec2d endPoints; 206 | 207 | for (double drawRadius = 0; drawRadius <= radius; drawRadius += radius / 10.) 208 | for (double theta = 0; theta < 2 * PI; theta += PI / 16.) 209 | { 210 | Vec2d startPoint = Vec2d(drawRadius * cos(theta), drawRadius * sin(theta)); 211 | Vec2d endPoint = startPoint + dt * (*this)(dt, startPoint); 212 | 213 | startPoints.push_back(startPoint); 214 | endPoints.push_back(endPoint); 215 | } 216 | 217 | renderer.addLines(startPoints, endPoints, colour); 218 | }; 219 | 220 | // Procedural velocity field 221 | Vec2d operator()(double, const Vec2d& pos) const 222 | { 223 | return Vec2d((PI / 314.) * (50.0 - pos[1]), (PI / 314.) * (pos[0] - 50.0)); 224 | } 225 | }; 226 | 227 | class DeformationField 228 | { 229 | public: 230 | 231 | // Procedural velocity field 232 | Vec2d operator()(double, const Vec2d& pos) const 233 | { 234 | return Vec2d(-std::sin(4 * PI * (pos[0] + .5)) * std::sin(4 * PI * (pos[1] + .5)), -std::cos(4 * PI * (pos[0] + .5)) * std::cos(4 * PI * (pos[1] + .5))); 235 | } 236 | }; 237 | 238 | 239 | 240 | } 241 | 242 | #endif -------------------------------------------------------------------------------- /Library/SimTools/GeometricMultigridOperators.h: -------------------------------------------------------------------------------- 1 | #ifndef GEOMETRIC_MULTIGRID_OPERATORS_H 2 | #define GEOMETRIC_MULTIGRID_OPERATORS_H 3 | 4 | #include 5 | 6 | #include "tbb/blocked_range.h" 7 | #include "tbb/parallel_for.h" 8 | 9 | #include "UniformGrid.h" 10 | #include "Utilities.h" 11 | #include "VectorGrid.h" 12 | 13 | namespace FluidSim2D::GeometricMultigridOperators 14 | { 15 | 16 | enum class CellLabels { INTERIOR_CELL, EXTERIOR_CELL, DIRICHLET_CELL, BOUNDARY_CELL }; 17 | 18 | void interiorJacobiPoissonSmoother(UniformGrid& solution, 19 | const UniformGrid& rhs, 20 | const UniformGrid& domainCellLabels, 21 | const double dx, 22 | const VectorGrid* boundaryWeights = nullptr); 23 | 24 | void boundaryJacobiPoissonSmoother(UniformGrid& solution, 25 | const UniformGrid& rhs, 26 | const UniformGrid& domainCellLabels, 27 | const VecVec2i& boundaryCells, 28 | const double dx, 29 | const VectorGrid* boundaryWeights = nullptr); 30 | 31 | void applyPoissonMatrix(UniformGrid& destination, 32 | const UniformGrid& source, 33 | const UniformGrid& domainCellLabels, 34 | const double dx, 35 | const VectorGrid* boundaryWeights = nullptr); 36 | 37 | void computePoissonResidual(UniformGrid& residual, 38 | const UniformGrid& solution, 39 | const UniformGrid& rhs, 40 | const UniformGrid& domainCellLabels, 41 | const double dx, 42 | const VectorGrid* boundaryWeights = nullptr); 43 | 44 | void downsample(UniformGrid& destinationGrid, 45 | const UniformGrid& sourceGrid, 46 | const UniformGrid& destinationCellLabels, 47 | const UniformGrid& sourceCellLabels); 48 | 49 | void upsampleAndAdd(UniformGrid& destinationGrid, 50 | const UniformGrid& sourceGrid, 51 | const UniformGrid& destinationCellLabels, 52 | const UniformGrid& sourceCellLabels); 53 | 54 | double dotProduct(const UniformGrid& vectorA, 55 | const UniformGrid& vectorB, 56 | const UniformGrid& domainCellLabels); 57 | 58 | void addToVector(UniformGrid& destination, 59 | const UniformGrid& source, 60 | const UniformGrid& domainCellLabels, 61 | const double scale); 62 | 63 | void addVectors(UniformGrid& destination, 64 | const UniformGrid& source, 65 | const UniformGrid& scaledSource, 66 | const UniformGrid& domainCellLabels, 67 | const double scale); 68 | 69 | double squaredl2Norm(const UniformGrid& vectorGrid, 70 | const UniformGrid& domainCellLabels); 71 | 72 | double lInfinityNorm(const UniformGrid& vectorGrid, 73 | const UniformGrid& domainCellLabels); 74 | 75 | Vec2i getChildCell(const Vec2i& cell, const int childIndex); 76 | 77 | UniformGrid buildCoarseCellLabels(const UniformGrid& sourceCellLabels); 78 | 79 | VecVec2i buildBoundaryCells(const UniformGrid& sourceCellLabels, int boundaryWidth); 80 | 81 | std::pair buildExpandedDomainLabels(UniformGrid& expandedDomainCellLabels, 82 | const UniformGrid& baseDomainCellLabels); 83 | 84 | template 88 | std::pair buildCustomExpandedDomainLabels(UniformGrid& expandedDomainCellLabels, 89 | const UniformGrid& baseCustomLabels, 90 | const IsExteriorFunctor& isExteriorFunctor, 91 | const IsDirichletFunctor& isDirichletFunctor, 92 | const IsInteriorFunctor& isInteriorFunctor); 93 | 94 | void buildExpandedBoundaryWeights(VectorGrid& expandedBoundaryWeights, 95 | const VectorGrid& baseBoundaryWeights, 96 | const UniformGrid& expandedDomainCellLabels, 97 | const Vec2i& exteriorOffset, 98 | const int axis); 99 | 100 | void setBoundaryDomainLabels(UniformGrid& sourceCellLabels, 101 | const VectorGrid& boundaryWeights); 102 | 103 | bool unitTestCoarsening(const UniformGrid& coarseCellLabels, 104 | const UniformGrid& fineCellLabels); 105 | 106 | bool unitTestBoundaryCells(const UniformGrid& domainCellLabels, const VectorGrid* boundaryWeights = nullptr); 107 | 108 | bool unitTestExteriorCells(const UniformGrid& domainCellLabels); 109 | 110 | // Implementation of template functions 111 | 112 | template 116 | std::pair buildCustomExpandedDomainLabels(UniformGrid& expandedDomainCellLabels, 117 | const UniformGrid& baseCustomLabels, 118 | const IsExteriorFunctor& isExteriorFunctor, 119 | const IsDirichletFunctor& isDirichletFunctor, 120 | const IsInteriorFunctor& isInteriorFunctor) 121 | { 122 | // Build domain labels with the appropriate padding to apply 123 | // geometric multigrid directly without a wasteful transfer 124 | // for each v-cycle. 125 | 126 | // Cap MG levels at 4 voxels in the smallest dimension 127 | double minLog = std::min(std::log2(double(baseCustomLabels.size()[0])), 128 | std::log2(double(baseCustomLabels.size()[1]))); 129 | 130 | int mgLevels = std::ceil(minLog) - std::log2(double(2)); 131 | 132 | // Add the necessary exterior cells so that after coarsening to the top level 133 | // there is still a single layer of exterior cells 134 | int exteriorPadding = std::pow(2, mgLevels - 1); 135 | 136 | Vec2i expandedGridSize = baseCustomLabels.size() + 2 * Vec2i(exteriorPadding); 137 | 138 | for (int axis : {0, 1}) 139 | { 140 | double logSize = std::log2(double(expandedGridSize[axis])); 141 | logSize = std::ceil(logSize); 142 | 143 | expandedGridSize[axis] = std::exp2(logSize); 144 | } 145 | 146 | Vec2i exteriorOffset = Vec2i(exteriorPadding); 147 | 148 | expandedDomainCellLabels.resize(expandedGridSize, CellLabels::EXTERIOR_CELL); 149 | 150 | // Copy initial domain labels to interior domain labels with padding 151 | tbb::parallel_for(tbb::blocked_range(0, baseCustomLabels.voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 152 | { 153 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 154 | { 155 | Vec2i cell = baseCustomLabels.unflatten(cellIndex); 156 | 157 | auto baseLabel = baseCustomLabels(cell); 158 | if (!isExteriorFunctor(baseLabel)) 159 | { 160 | Vec2i expandedCell = cell + exteriorOffset; 161 | if (isInteriorFunctor(baseLabel)) 162 | expandedDomainCellLabels(expandedCell) = CellLabels::INTERIOR_CELL; 163 | else 164 | { 165 | assert(isDirichletFunctor(baseLabel)); 166 | expandedDomainCellLabels(expandedCell) = CellLabels::DIRICHLET_CELL; 167 | } 168 | } 169 | } 170 | }); 171 | 172 | return std::pair(exteriorOffset, mgLevels); 173 | } 174 | 175 | } 176 | #endif -------------------------------------------------------------------------------- /Library/Utilities/SparseUniformGrid.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUIDSIM2D_SPARSE_UNIFORM_GRID_H 2 | #define FLUIDSIM2D_SPARSE_UNIFORM_GRID_H 3 | 4 | #include "tbb/blocked_range.h" 5 | #include "tbb/parallel_reduce.h" 6 | 7 | #include "GridUtilities.h" 8 | #include "Utilities.h" 9 | 10 | namespace FluidSim2D 11 | { 12 | 13 | template 14 | class SparseTile 15 | { 16 | public: 17 | SparseTile() : SparseTile(T(0)) 18 | {} 19 | 20 | SparseTile(const T& value) 21 | : myGrid(VectorXt::Constant(1, value)) 22 | {} 23 | 24 | bool constant() const 25 | { 26 | return myGrid.size() == 1; 27 | } 28 | 29 | void makeConstant(const T& value) 30 | { 31 | myGrid = VectorXt::Constant(1, value); 32 | } 33 | 34 | void expand() 35 | { 36 | if (constant()) 37 | { 38 | assert(myGrid.size() == 1); 39 | const T value = myGrid(0); 40 | myGrid = VectorXt::Constant(TILE_SIZE * TILE_SIZE, value); 41 | } 42 | } 43 | 44 | void collapseIfConstant() 45 | { 46 | if (!constant()) 47 | { 48 | if (myGrid.maxCoeff() == myGrid.minCoeff()) 49 | { 50 | myGrid = VectorXt::Constant(1, myGrid(0)); 51 | } 52 | } 53 | } 54 | 55 | FORCE_INLINE T getVoxel(const Vec2i& coord) const 56 | { 57 | for (int axis : {0, 1}) 58 | assert(coord[axis] >= 0 && coord[axis] < TILE_SIZE); 59 | 60 | if (constant()) 61 | { 62 | return myGrid(0); 63 | } 64 | else 65 | { 66 | return myGrid[flatten(coord)]; 67 | } 68 | } 69 | 70 | FORCE_INLINE void setVoxel(const Vec2i& coord, const T& value) 71 | { 72 | for (int axis : {0, 1}) 73 | assert(coord[axis] >= 0 && coord[axis] < TILE_SIZE); 74 | 75 | if (constant()) 76 | { 77 | // TODO: add lock to prevent race conditions 78 | expand(); 79 | } 80 | 81 | myGrid[flatten(coord)] = value; 82 | } 83 | 84 | FORCE_INLINE int flatten(const Vec2i& coord) const 85 | { 86 | return coord[1] + TILE_SIZE * coord[0]; 87 | } 88 | 89 | FORCE_INLINE Vec2i unflatten(int index) const 90 | { 91 | assert(index >= 0 && index < TILE_SIZE * TILE_SIZE); 92 | return Vec2i(index / TILE_SIZE, index % TILE_SIZE); 93 | } 94 | 95 | T maxValue() const 96 | { 97 | return myGrid.maxCoeff(); 98 | } 99 | 100 | T minValue() const 101 | { 102 | return myGrid.minCoeff(); 103 | } 104 | 105 | private: 106 | VectorXt myGrid; 107 | }; 108 | 109 | 110 | template 111 | class SparseUniformGrid 112 | { 113 | public: 114 | 115 | SparseUniformGrid() 116 | : myGridSize(Vec2i::Zero()) 117 | , myTileSize(Vec2i::Zero()) 118 | {} 119 | 120 | SparseUniformGrid(const Vec2i& size) : SparseUniformGrid(size, T(0)) {} 121 | 122 | SparseUniformGrid(const Vec2i& size, const T& value) 123 | : myGridSize(size) 124 | , myTileSize((size.cast() / double(TILE_SIZE)).array().ceil().cast()) 125 | , myTiles(tileCount(), SparseTile(value)) 126 | { 127 | for (int axis : {0, 1}) 128 | assert(size[axis] >= 0); 129 | } 130 | 131 | int tileCount() const { return myTileSize[0] * myTileSize[1]; } 132 | 133 | FORCE_INLINE int flattenTileIndex(const Vec2i& tileIndex) const 134 | { 135 | for (int axis : {0, 1}) 136 | assert(tileIndex[axis] >= 0 && tileIndex[axis] < myTileSize[axis]); 137 | 138 | return tileIndex[1] + myTileSize[1] * tileIndex[0]; 139 | } 140 | 141 | FORCE_INLINE Vec2i unflattenTileIndex(const int flatTileIndex) const 142 | { 143 | assert(flatTileIndex >= 0 && flatTileIndex < tileCount()); 144 | return Vec2i(flatTileIndex / myTileSize[1], flatTileIndex % myTileSize[1]); 145 | } 146 | 147 | const SparseTile& tile(const Vec2i& tileIndex) const 148 | { 149 | for (int axis : {0, 1}) 150 | assert(tileIndex[axis] >= 0 && tileIndex[axis] < myTileSize[axis]); 151 | 152 | return myTiles[flattenTileIndex(tileIndex)]; 153 | } 154 | 155 | SparseTile& tile(const Vec2i& tileIndex) 156 | { 157 | for (int axis : {0, 1}) 158 | assert(tileIndex[axis] >= 0 && tileIndex[axis] < myTileSize[axis]); 159 | 160 | return myTiles[flattenTileIndex(tileIndex)]; 161 | } 162 | 163 | std::pair tileVoxelRange(const Vec2i& tileIndex) 164 | { 165 | std::pair voxelRange; 166 | voxelRange.first = tileIndex * TILE_SIZE; 167 | voxelRange.second = ((tileIndex + Vec2i::Ones()) * TILE_SIZE).cwiseMin(myGridSize); 168 | 169 | return voxelRange; 170 | } 171 | 172 | T getVoxel(const Vec2i& voxelIndex) const 173 | { 174 | // Get tile index 175 | Vec2i tileIndex = voxelIndex / TILE_SIZE; 176 | 177 | Vec2i localVoxelIndex = voxelIndex - tileIndex * TILE_SIZE; 178 | 179 | return tile(tileIndex).getVoxel(localVoxelIndex); 180 | } 181 | 182 | void setVoxel(const Vec2i& voxelIndex, const T& value) 183 | { 184 | // Get tile index 185 | Vec2i tileIndex = voxelIndex / TILE_SIZE; 186 | 187 | auto& localTile = tile(tileIndex); 188 | if (localTile.constant()) 189 | { 190 | // Expand constant tile 191 | localTile.expand(); 192 | } 193 | 194 | Vec2i localVoxelIndex = voxelIndex - tileIndex * TILE_SIZE; 195 | 196 | localTile.setVoxel(localVoxelIndex, value); 197 | } 198 | 199 | bool isTileConstant(const Vec2i& tileIndex) const 200 | { 201 | return tile(tileIndex).constant(); 202 | } 203 | 204 | void expandTile(const Vec2i& tileIndex) 205 | { 206 | tile(tileIndex).expand(); 207 | } 208 | 209 | void collapseTiles() 210 | { 211 | for (auto& localTile : myTiles) 212 | { 213 | localTile.collapseIfConstant(); 214 | } 215 | } 216 | 217 | const Vec2i& gridSize() const 218 | { 219 | return myGridSize; 220 | } 221 | 222 | const Vec2i& tileSize() const 223 | { 224 | return myTileSize; 225 | } 226 | 227 | void resize(const Vec2i& size) 228 | { 229 | resize(size, T(0)); 230 | } 231 | 232 | void resize(const Vec2i& size, const T& value) 233 | { 234 | for (int axis : {0, 1}) 235 | assert(size[axis] >= 0); 236 | 237 | myGridSize = size; 238 | myTileSize = (size.cast() / double(TILE_SIZE)).array().ceil().cast(); 239 | myTiles.clear(); 240 | myTiles.resize(tileCount(), SparseTile(value)); 241 | } 242 | 243 | T maxValue() const 244 | { 245 | return tbb::parallel_reduce(tbb::blocked_range(0, tileCount()) std::numeric_limits::lowest(), [&](const tbb::blocked_range& range, T localMaxValue) 246 | { 247 | for (int flatTileIndex = range.begin(); flatTileIndex != range.end(); ++flatTileIndex) 248 | { 249 | Vec2i tileCoord = unflattenTileIndex(flatTileIndex); 250 | localMaxValue = std::max(localMaxValue, tile(tileCoord).maxValue()); 251 | } 252 | 253 | return localMaxValue; 254 | }, 255 | [&](const T a, const T b) 256 | { 257 | return std::max(a, b); 258 | }); 259 | } 260 | 261 | T minValue() const 262 | { 263 | return tbb::parallel_reduce(tbb::blocked_range(0, tileCount()) std::numeric_limits::max(), [&](const tbb::blocked_range& range, T localMinValue) 264 | { 265 | for (int flatTileIndex = range.begin(); flatTileIndex != range.end(); ++flatTileIndex) 266 | { 267 | Vec2i tileCoord = unflattenTileIndex(flatTileIndex); 268 | localMinValue = std::min(localMinValue, tile(tileCoord).minValue()); 269 | } 270 | 271 | return localMinValue; 272 | }, 273 | [&](const T a, const T b) 274 | { 275 | return std::min(a, b); 276 | }); 277 | 278 | } 279 | 280 | std::pair minAndMaxValue() const 281 | { 282 | return std::make_pair(minValue(), maxValue()); 283 | } 284 | 285 | private: 286 | 287 | Vec2i myGridSize; 288 | Vec2i myTileSize; 289 | 290 | std::vector> myTiles; 291 | }; 292 | 293 | 294 | } 295 | 296 | #endif 297 | -------------------------------------------------------------------------------- /Projects/Simulations/Library/MultiMaterialLiquidSimulator.cpp: -------------------------------------------------------------------------------- 1 | #include "MultiMaterialLiquidSimulator.h" 2 | 3 | #include 4 | 5 | #include "ComputeWeights.h" 6 | #include "ExtrapolateField.h" 7 | #include "MultiMaterialPressureProjection.h" 8 | #include "Timer.h" 9 | 10 | namespace FluidSim2D 11 | { 12 | 13 | void MultiMaterialLiquidSimulator::drawMaterialSurface(Renderer& renderer, int material) 14 | { 15 | myFluidSurfaces[material].drawSurface(renderer, Vec3d(0., 0., 1.), 2.); 16 | } 17 | 18 | void MultiMaterialLiquidSimulator::drawMaterialVelocity(Renderer& renderer, double length, int material) const 19 | { 20 | myFluidVelocities[material].drawSamplePointVectors(renderer, Vec3d::Zero(), myFluidVelocities[material].dx() * length); 21 | } 22 | 23 | void MultiMaterialLiquidSimulator::drawSolidSurface(Renderer& renderer) 24 | { 25 | mySolidSurface.drawSurface(renderer, Vec3d::Zero(), 2); 26 | } 27 | 28 | template 29 | void MultiMaterialLiquidSimulator::addForce(double dt, int material, const ForceSampler& force) 30 | { 31 | for (int axis : {0, 1}) 32 | { 33 | tbb::parallel_for(tbb::blocked_range(0, myFluidVelocities[material].grid(axis).voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 34 | { 35 | for (int faceIndex = range.begin(); faceIndex != range.end(); ++faceIndex) 36 | { 37 | Vec2i face = myFluidVelocities[0].grid(axis).unflatten(faceIndex); 38 | Vec2d worldPosition = myFluidVelocities[0].indexToWorld(face.cast(), axis); 39 | 40 | for (int material = 0; material < myMaterialCount; ++material) 41 | myFluidVelocities[material](face, axis) += dt * force(worldPosition, axis); 42 | } 43 | }); 44 | } 45 | } 46 | 47 | void MultiMaterialLiquidSimulator::addForce(double dt, int material, const Vec2d& force) 48 | { 49 | addForce(dt, material, [&](Vec2d, int axis) { return force[axis]; }); 50 | } 51 | 52 | void MultiMaterialLiquidSimulator::advectFluidVelocities(double dt, IntegrationOrder integrator, InterpolationOrder interpolator) 53 | { 54 | for (int material = 0; material < myMaterialCount; ++material) 55 | { 56 | auto velocityFunc = [&](double, const Vec2d& point) { return myFluidVelocities[material].biLerp(point); }; 57 | 58 | VectorGrid tempVelocity(myFluidVelocities[material].xform(), myFluidVelocities[material].gridSize(), 0, VectorGridSettings::SampleType::STAGGERED); 59 | 60 | for (int axis : {0, 1}) 61 | advectField(dt, tempVelocity.grid(axis), myFluidVelocities[material].grid(axis), velocityFunc, integrator, interpolator); 62 | 63 | std::swap(myFluidVelocities[material], tempVelocity); 64 | } 65 | } 66 | 67 | void MultiMaterialLiquidSimulator::advectFluidSurfaces(double dt, IntegrationOrder integrator) 68 | { 69 | for (int material = 0; material < myMaterialCount; ++material) 70 | { 71 | auto velocityFunc = [&](double, const Vec2d& point) { return myFluidVelocities[material].biLerp(point); }; 72 | 73 | EdgeMesh localMesh = myFluidSurfaces[material].buildMSMesh(); 74 | localMesh.advectMesh(dt, velocityFunc, integrator); 75 | myFluidSurfaces[material].initFromMesh(localMesh, false); 76 | } 77 | 78 | // Fix possible overlaps between the materials. 79 | tbb::parallel_for(tbb::blocked_range(0, myFluidSurfaces[0].voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 80 | { 81 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 82 | { 83 | Vec2i cell = myFluidSurfaces[0].unflatten(cellIndex); 84 | 85 | double firstMin = std::min(myFluidSurfaces[0](cell), mySolidSurface(cell)); 86 | double secondMin = std::max(myFluidSurfaces[0](cell), mySolidSurface(cell)); 87 | 88 | for (int material = 1; material < myMaterialCount; ++material) 89 | { 90 | double localMin = myFluidSurfaces[material](cell); 91 | if (localMin < firstMin) 92 | { 93 | secondMin = firstMin; 94 | firstMin = localMin; 95 | } 96 | else secondMin = std::min(localMin, secondMin); 97 | } 98 | 99 | double avgSDF = .5 * (firstMin + secondMin); 100 | 101 | for (int material = 0; material < myMaterialCount; ++material) 102 | myFluidSurfaces[material](cell) -= avgSDF; 103 | } 104 | }); 105 | 106 | for (int material = 0; material < myMaterialCount; ++material) 107 | myFluidSurfaces[material].reinit(); 108 | } 109 | 110 | void MultiMaterialLiquidSimulator::setSolidSurface(const LevelSet& solidSurface) 111 | { 112 | assert(solidSurface.isBackgroundNegative()); 113 | 114 | EdgeMesh localMesh = solidSurface.buildDCMesh(); 115 | 116 | mySolidSurface.setBackgroundNegative(); 117 | mySolidSurface.initFromMesh(localMesh, false /* don't resize grid*/); 118 | } 119 | 120 | void MultiMaterialLiquidSimulator::runTimestep(double dt) 121 | { 122 | std::cout << "\nStarting simulation loop\n" << std::endl; 123 | 124 | Timer simTimer; 125 | 126 | // 127 | // Extrapolate materials into solids 128 | // 129 | 130 | std::vector extrapolatedSurfaces(myMaterialCount); 131 | 132 | for (int material = 0; material < myMaterialCount; ++material) 133 | extrapolatedSurfaces[material] = myFluidSurfaces[material]; 134 | 135 | double dx = mySolidSurface.dx(); 136 | 137 | tbb::parallel_for(tbb::blocked_range(0, myFluidSurfaces[0].voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 138 | { 139 | for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex) 140 | { 141 | Vec2i cell = myFluidSurfaces[0].unflatten(cellIndex); 142 | 143 | for (int material = 0; material < myMaterialCount; ++material) 144 | { 145 | if (mySolidSurface(cell) <= 0. || 146 | (mySolidSurface(cell) <= dx && myFluidSurfaces[material](cell) <= 0)) 147 | extrapolatedSurfaces[material](cell) -= dx; 148 | } 149 | } 150 | }); 151 | 152 | for (int material = 0; material < myMaterialCount; ++material) 153 | extrapolatedSurfaces[material].reinit(); 154 | 155 | std::cout << " Extrapolate into solids: " << simTimer.stop() << "s" << std::endl; 156 | 157 | simTimer.reset(); 158 | 159 | MultiMaterialPressureProjection pressureSolver(extrapolatedSurfaces, myFluidDensities, mySolidSurface); 160 | 161 | pressureSolver.setInitialGuess(myOldPressure); 162 | pressureSolver.project(myFluidVelocities); 163 | 164 | myOldPressure = pressureSolver.getPressureGrid(); 165 | 166 | std::cout << " Solve for multi-material pressure: " << simTimer.stop() << "s" << std::endl; 167 | 168 | simTimer.reset(); 169 | 170 | // 171 | // Get valid faces for each material so we can extrapolate velocities. 172 | // 173 | 174 | for (int material = 0; material < myMaterialCount; ++material) 175 | { 176 | const VectorGrid& validFaces = pressureSolver.getValidFaces(material); 177 | 178 | // Zero out-of-bounds velocity 179 | for (int axis : {0, 1}) 180 | { 181 | tbb::parallel_for(tbb::blocked_range(0, validFaces.grid(axis).voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range& range) 182 | { 183 | for (int flatIndex = range.begin(); flatIndex != range.end(); ++flatIndex) 184 | { 185 | Vec2i face = validFaces.grid(axis).unflatten(flatIndex); 186 | 187 | if (validFaces(face, axis) != VisitedCellLabels::FINISHED_CELL) 188 | myFluidVelocities[material](face, axis) = 0; 189 | } 190 | }); 191 | } 192 | 193 | // Extrapolate velocity 194 | for (int axis : {0, 1}) 195 | extrapolateField(myFluidVelocities[material].grid(axis), validFaces.grid(axis), 5); 196 | } 197 | 198 | std::cout << " Extrapolate velocity: " << simTimer.stop() << "s" << std::endl; 199 | simTimer.reset(); 200 | 201 | advectFluidSurfaces(dt, IntegrationOrder::RK3); 202 | advectFluidVelocities(dt, IntegrationOrder::RK3); 203 | 204 | std::cout << " Advect simulation: " << simTimer.stop() << "s" << std::endl; 205 | } 206 | 207 | } --------------------------------------------------------------------------------