Fluid Engine Development 看了很久,应该要做 Demo 来演示算法了
项目 level_set_liquid_sim
里面有运行 Level Set 的模拟,输出三角面的示例代码
项目 hybrid_liquid_sim
里面有运行 PIC FLIP APIC 的模拟,但是只输出位置信息的示例代码,比如他保存成 xyz 格式
项目 particles2obj
可以把位置信息文件 xyz 转成三角面文件 obj
项目 sandbox
提供了自己创建求解器的模板,但是现在暂时应该用不到,现在想输出他的求解器的图像
然后他这个 Marching Cube 的算法里面写了对于边界的处理,原仓库的代码里面特意设置了不创建底部的面的标志,我不知道为什么要这么做
cpp
void triangulateAndSave(const ScalarGrid3Ptr& sdf, const std::string& rootDir,
int frameCnt) {
TriangleMesh3 mesh;
// int flag = kDirectionAll & ~kDirectionDown;
int flag = kDirectionAll;
marchingCubes(sdf->constDataAccessor(), sdf->gridSpacing(),
sdf->dataOrigin(), &mesh, 0.0, flag);
saveTriangleMesh(mesh, rootDir, frameCnt);
}
用 Blender 渲染的话,为了做光线追踪,液体网格应该封闭才能达成正确的光线反射,现在这个不创建底面直接就没有那个正确的效果了
一开始我还不知道有这个标记,于是等那个溃坝的模拟,一帧五分钟,等了五十帧,之后才开始做,这个时候才开始发现模型没有底面......浪费了很多时间
Blender 渲染的时候记得要在 Preference-System-Cycles Render Device 选 CUDA,明显渲染会快很多,可能十多倍都有可能
然后记得构建 VS 工程要构建成 Release,开启了代码优化,计算速度会快很多,两三倍吧
因为 PIC FLIP 那边的没有保存三角面的,所以简单拼接一下就可以了
在 example 里新建一个文件夹,里面是新项目的 main.cpp 和 CMakeList.txt。这个项目我命名为 comparsion
项目的 CMakeList
cmake
#
# Copyright (c) 2018 Doyub Kim
#
# I am making my contributions/submissions to this project solely in my personal
# capacity and am not conveying any rights to any intellectual property of any
# third parties.
#
# Target name
set(target comparsion)
# Includes
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# Sources
file(GLOB sources
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# Build executable
add_executable(${target}
${sources})
# Project options
set_target_properties(${target}
PROPERTIES
${DEFAULT_PROJECT_OPTIONS}
)
# Compile options
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
add_definitions(-D_USE_MATH_DEFINES) # for M_PI
endif ()
target_compile_options(${target}
PRIVATE
PUBLIC
${DEFAULT_COMPILE_OPTIONS}
INTERFACE
)
# Link libraries
target_link_libraries(${target}
PRIVATE
${DEFAULT_LINKER_OPTIONS}
jet
pystring)
要在根目录的 CMakeList 里加上新项目
cmake
add_subdirectory(src/examples/comparsion)
main.cpp
cpp
// Copyright (c) 2018 Doyub Kim
//
// I am making my contributions/submissions to this project solely in my
// personal capacity and am not conveying any rights to any intellectual
// property of any third parties.
#include <jet/jet.h>
#include <pystring/pystring.h>
#ifdef JET_WINDOWS
#include <direct.h>
#else
#include <sys/stat.h>
#endif
#include <example_utils/clara_utils.h>
#include <clara.hpp>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#define APP_NAME "comparsion"
using namespace jet;
void saveTriangleMesh(const TriangleMesh3& mesh, const std::string& rootDir,
int frameCnt) {
char basename[256];
snprintf(basename, sizeof(basename), "frame_%06d.obj", frameCnt);
std::string filename = pystring::os::path::join(rootDir, basename);
std::ofstream file(filename.c_str());
if (file) {
printf("Writing %s...\n", filename.c_str());
mesh.writeObj(&file);
file.close();
}
}
void triangulateAndSave(const ScalarGrid3Ptr& sdf, const std::string& rootDir,
int frameCnt) {
TriangleMesh3 mesh;
// int flag = kDirectionAll & ~kDirectionDown;
int flag = kDirectionAll;
marchingCubes(sdf->constDataAccessor(), sdf->gridSpacing(),
sdf->dataOrigin(), &mesh, 0.0, flag);
saveTriangleMesh(mesh, rootDir, frameCnt);
}
void printInfo(const PicSolver3Ptr& solver) {
auto grids = solver->gridSystemData();
Size3 resolution = grids->resolution();
BoundingBox3D domain = grids->boundingBox();
Vector3D gridSpacing = grids->gridSpacing();
printf("Resolution: %zu x %zu x %zu\n", resolution.x, resolution.y,
resolution.z);
printf("Domain: [%f, %f, %f] x [%f, %f, %f]\n", domain.lowerCorner.x,
domain.lowerCorner.y, domain.lowerCorner.z, domain.upperCorner.x,
domain.upperCorner.y, domain.upperCorner.z);
printf("Grid spacing: [%f, %f, %f]\n", gridSpacing.x, gridSpacing.y,
gridSpacing.z);
}
void runSimulation(const std::string& rootDir, const PicSolver3Ptr& solver,
int numberOfFrames, double fps) {
auto sdf = solver->signedDistanceField();
for (Frame frame(0, 1.0 / fps); frame.index < numberOfFrames; ++frame) {
solver->update(frame);
triangulateAndSave(sdf, rootDir, frame.index);
}
}
// Water-drop example (FLIP)
void runExample1(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
auto solver =
FlipSolver3::builder()
.withResolution({resolutionX, 2 * resolutionX, resolutionX})
.withDomainSizeX(1.0)
.makeShared();
auto grids = solver->gridSystemData();
auto particles = solver->particleSystemData();
Vector3D gridSpacing = grids->gridSpacing();
double dx = gridSpacing.x;
BoundingBox3D domain = grids->boundingBox();
// Build emitter
auto plane = Plane3::builder()
.withNormal({0, 1, 0})
.withPoint({0, 0.25 * domain.height(), 0})
.makeShared();
auto sphere = Sphere3::builder()
.withCenter(domain.midPoint())
.withRadius(0.15 * domain.width())
.makeShared();
auto emitter1 = VolumeParticleEmitter3::builder()
.withSurface(plane)
.withSpacing(0.5 * dx)
.withMaxRegion(domain)
.withIsOneShot(true)
.makeShared();
emitter1->setPointGenerator(std::make_shared<GridPointGenerator3>());
auto emitter2 = VolumeParticleEmitter3::builder()
.withSurface(sphere)
.withSpacing(0.5 * dx)
.withMaxRegion(domain)
.withIsOneShot(true)
.makeShared();
emitter2->setPointGenerator(std::make_shared<GridPointGenerator3>());
auto emitterSet = ParticleEmitterSet3::builder()
.withEmitters({emitter1, emitter2})
.makeShared();
solver->setParticleEmitter(emitterSet);
// Print simulation info
printf("Running example 1 (water-drop with FLIP)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
// Water-drop example (PIC)
void runExample2(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
auto solver =
PicSolver3::builder()
.withResolution({resolutionX, 2 * resolutionX, resolutionX})
.withDomainSizeX(1.0)
.makeShared();
auto grids = solver->gridSystemData();
auto particles = solver->particleSystemData();
Vector3D gridSpacing = grids->gridSpacing();
double dx = gridSpacing.x;
BoundingBox3D domain = grids->boundingBox();
// Build emitter
auto plane = Plane3::builder()
.withNormal({0, 1, 0})
.withPoint({0, 0.25 * domain.height(), 0})
.makeShared();
auto sphere = Sphere3::builder()
.withCenter(domain.midPoint())
.withRadius(0.15 * domain.width())
.makeShared();
auto emitter1 = VolumeParticleEmitter3::builder()
.withSurface(plane)
.withSpacing(0.5 * dx)
.withMaxRegion(domain)
.withIsOneShot(true)
.makeShared();
emitter1->setPointGenerator(std::make_shared<GridPointGenerator3>());
auto emitter2 = VolumeParticleEmitter3::builder()
.withSurface(sphere)
.withSpacing(0.5 * dx)
.withMaxRegion(domain)
.withIsOneShot(true)
.makeShared();
emitter2->setPointGenerator(std::make_shared<GridPointGenerator3>());
auto emitterSet = ParticleEmitterSet3::builder()
.withEmitters({emitter1, emitter2})
.makeShared();
solver->setParticleEmitter(emitterSet);
// Print simulation info
printf("Running example 1 (water-drop with PIC)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
// Dam-breaking example (FLIP)
void runExample3(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
Size3 resolution{3 * resolutionX, 2 * resolutionX, (3 * resolutionX) / 2};
auto solver = FlipSolver3::builder()
.withResolution(resolution)
.withDomainSizeX(3.0)
.makeShared();
solver->setUseCompressedLinearSystem(true);
auto grids = solver->gridSystemData();
double dx = grids->gridSpacing().x;
BoundingBox3D domain = grids->boundingBox();
double lz = domain.depth();
// Build emitter
auto box1 =
Box3::builder()
.withLowerCorner({0, 0, 0})
.withUpperCorner({0.5 + 0.001, 0.75 + 0.001, 0.75 * lz + 0.001})
.makeShared();
auto box2 =
Box3::builder()
.withLowerCorner({2.5 - 0.001, 0, 0.25 * lz - 0.001})
.withUpperCorner({3.5 + 0.001, 0.75 + 0.001, 1.5 * lz + 0.001})
.makeShared();
auto boxSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({box1, box2})
.makeShared();
auto emitter = VolumeParticleEmitter3::builder()
.withSurface(boxSet)
.withMaxRegion(domain)
.withSpacing(0.5 * dx)
.makeShared();
emitter->setPointGenerator(std::make_shared<GridPointGenerator3>());
solver->setParticleEmitter(emitter);
// Build collider
auto cyl1 = Cylinder3::builder()
.withCenter({1, 0.375, 0.375})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl2 = Cylinder3::builder()
.withCenter({1.5, 0.375, 0.75})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl3 = Cylinder3::builder()
.withCenter({2, 0.375, 1.125})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cylSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({cyl1, cyl2, cyl3})
.makeShared();
auto collider =
RigidBodyCollider3::builder().withSurface(cylSet).makeShared();
solver->setCollider(collider);
// Print simulation info
printf("Running example 3 (dam-breaking with FLIP)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
// Dam-breaking example (PIC)
void runExample4(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
Size3 resolution{3 * resolutionX, 2 * resolutionX, (3 * resolutionX) / 2};
auto solver = PicSolver3::builder()
.withResolution(resolution)
.withDomainSizeX(3.0)
.makeShared();
solver->setUseCompressedLinearSystem(true);
auto grids = solver->gridSystemData();
double dx = grids->gridSpacing().x;
BoundingBox3D domain = grids->boundingBox();
double lz = domain.depth();
// Build emitter
auto box1 =
Box3::builder()
.withLowerCorner({0, 0, 0})
.withUpperCorner({0.5 + 0.001, 0.75 + 0.001, 0.75 * lz + 0.001})
.makeShared();
auto box2 =
Box3::builder()
.withLowerCorner({2.5 - 0.001, 0, 0.25 * lz - 0.001})
.withUpperCorner({3.5 + 0.001, 0.75 + 0.001, 1.5 * lz + 0.001})
.makeShared();
auto boxSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({box1, box2})
.makeShared();
auto emitter = VolumeParticleEmitter3::builder()
.withSurface(boxSet)
.withMaxRegion(domain)
.withSpacing(0.5 * dx)
.makeShared();
emitter->setPointGenerator(std::make_shared<GridPointGenerator3>());
solver->setParticleEmitter(emitter);
// Build collider
auto cyl1 = Cylinder3::builder()
.withCenter({1, 0.375, 0.375})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl2 = Cylinder3::builder()
.withCenter({1.5, 0.375, 0.75})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl3 = Cylinder3::builder()
.withCenter({2, 0.375, 1.125})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cylSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({cyl1, cyl2, cyl3})
.makeShared();
auto collider =
RigidBodyCollider3::builder().withSurface(cylSet).makeShared();
solver->setCollider(collider);
// Print simulation info
printf("Running example 4 (dam-breaking with PIC)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
// Dam-breaking example (APIC)
void runExample5(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
Size3 resolution{3 * resolutionX, 2 * resolutionX, (3 * resolutionX) / 2};
auto solver = ApicSolver3::builder()
.withResolution(resolution)
.withDomainSizeX(3.0)
.makeShared();
solver->setUseCompressedLinearSystem(true);
auto grids = solver->gridSystemData();
double dx = grids->gridSpacing().x;
BoundingBox3D domain = grids->boundingBox();
double lz = domain.depth();
// Build emitter
auto box1 =
Box3::builder()
.withLowerCorner({0, 0, 0})
.withUpperCorner({0.5 + 0.001, 0.75 + 0.001, 0.75 * lz + 0.001})
.makeShared();
auto box2 =
Box3::builder()
.withLowerCorner({2.5 - 0.001, 0, 0.25 * lz - 0.001})
.withUpperCorner({3.5 + 0.001, 0.75 + 0.001, 1.5 * lz + 0.001})
.makeShared();
auto boxSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({box1, box2})
.makeShared();
auto emitter = VolumeParticleEmitter3::builder()
.withSurface(boxSet)
.withMaxRegion(domain)
.withSpacing(0.5 * dx)
.makeShared();
emitter->setPointGenerator(std::make_shared<GridPointGenerator3>());
solver->setParticleEmitter(emitter);
// Build collider
auto cyl1 = Cylinder3::builder()
.withCenter({1, 0.375, 0.375})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl2 = Cylinder3::builder()
.withCenter({1.5, 0.375, 0.75})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cyl3 = Cylinder3::builder()
.withCenter({2, 0.375, 1.125})
.withRadius(0.1)
.withHeight(0.75)
.makeShared();
auto cylSet = ImplicitSurfaceSet3::builder()
.withExplicitSurfaces({cyl1, cyl2, cyl3})
.makeShared();
auto collider =
RigidBodyCollider3::builder().withSurface(cylSet).makeShared();
solver->setCollider(collider);
// Print simulation info
printf("Running example 5 (dam-breaking with APIC)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
void runExample6(const std::string& rootDir, size_t resolutionX,
int numberOfFrames, double fps) {
// Build solver
auto solver = ApicSolver3::builder()
.withResolution({resolutionX, resolutionX, resolutionX})
.withDomainSizeX(1.0)
.makeShared();
solver->setUseCompressedLinearSystem(true);
// Build collider
auto sphere = Sphere3::builder()
.withCenter({0.5, 0.5, 0.5})
.withRadius(0.4)
.withIsNormalFlipped(true)
.makeShared();
auto collider =
RigidBodyCollider3::builder().withSurface(sphere).makeShared();
solver->setCollider(collider);
// Manually emit particles
printf("Start emitting particles...\n");
std::mt19937 rng;
std::uniform_real_distribution<> dist(-0.1 * solver->gridSpacing().x,
0.1 * solver->gridSpacing().x);
BccLatticePointGenerator pointGenerator;
pointGenerator.forEachPoint(
BoundingBox3D({0.75, 0, 0}, {1, 1, 1}), 0.5 * solver->gridSpacing().x,
[&](const Vector3D& pt) -> bool {
Vector3D newPos = pt + Vector3D{dist(rng), dist(rng), dist(rng)};
if ((pt - Vector3D{0.5, 0.5, 0.5}).length() < 0.4) {
solver->particleSystemData()->addParticle(newPos);
}
return true;
});
printf("Number of particles: %zu\n",
solver->particleSystemData()->numberOfParticles());
// Print simulation info
printf("Running example 6 (sphere boundary with APIC)\n");
printInfo(solver);
// Run simulation
runSimulation(rootDir, solver, numberOfFrames, fps);
}
int main(int argc, char* argv[]) {
bool showHelp = false;
size_t resolutionX = 50;
int numberOfFrames = 100;
double fps = 60.0;
int exampleNum = 1;
std::string logFilename = APP_NAME ".log";
std::string outputDir = APP_NAME "_output";
std::string format = "xyz";
// Parsing
auto parser =
clara::Help(showHelp) |
clara::Opt(resolutionX, "resolutionX")["-r"]["--resx"](
"grid resolution in x-axis (default is 50)") |
clara::Opt(numberOfFrames, "numberOfFrames")["-f"]["--frames"](
"total number of frames (default is 100)") |
clara::Opt(
fps, "fps")["-p"]["--fps"]("frames per second (default is 60.0)") |
clara::Opt(exampleNum, "exampleNum")["-e"]["--example"](
"example number (between 1 and 6, default is 1)") |
clara::Opt(logFilename, "logFilename")["-l"]["--log"](
"log file name (default is " APP_NAME ".log)") |
clara::Opt(outputDir, "outputDir")["-o"]["--output"](
"output directory name (default is " APP_NAME "_output)");
auto result = parser.parse(clara::Args(argc, argv));
if (!result) {
std::cerr << "Error in command line: " << result.errorMessage() << '\n';
exit(EXIT_FAILURE);
}
if (showHelp) {
std::cout << toString(parser) << '\n';
exit(EXIT_SUCCESS);
}
#ifdef JET_WINDOWS
_mkdir(outputDir.c_str());
#else
mkdir(outputDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
#endif
std::ofstream logFile(logFilename.c_str());
if (logFile) {
Logging::setAllStream(&logFile);
}
switch (exampleNum) {
case 1:
runExample1(outputDir, resolutionX, numberOfFrames, fps);
break;
case 2:
runExample2(outputDir, resolutionX, numberOfFrames, fps);
break;
case 3:
runExample3(outputDir, resolutionX, numberOfFrames, fps);
break;
case 4:
runExample4(outputDir, resolutionX, numberOfFrames, fps);
break;
case 5:
runExample5(outputDir, resolutionX, numberOfFrames, fps);
break;
case 6:
runExample6(outputDir, resolutionX, numberOfFrames, fps);
break;
default:
std::cout << toString(parser) << '\n';
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
这些就差不多了