Fluid Engine Development 创建 PIC, FLIP, Level Set 演示 Demo

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;
}

这些就差不多了

相关推荐
yuanbenshidiaos15 分钟前
C++-----图
开发语言·c++·算法
就一枚小白17 分钟前
UE--如何用 Python 调用 C++ 及蓝图函数
c++·python·ue5
✿ ༺ ོIT技术༻38 分钟前
同步&异步日志系统:设计模式
linux·c++·设计模式
獨枭7 小时前
CMake 构建项目并整理头文件和库文件
c++·github·cmake
小王爱吃月亮糖8 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
小哈龙10 小时前
c++ 类似与c# 线程 AutoResetEvent 和 ManualResetEvent的实现
c++·c#·多线程
yuanbenshidiaos10 小时前
C++--------------树
java·数据库·c++
海螺姑娘的小魏11 小时前
Effective C++ 条款 15:在资源管理类中提供对原始资源的访问
开发语言·c++
青青丘比特13 小时前
STL.string(下)
开发语言·c++
jjjxxxhhh12313 小时前
C++ 模板是为了解决啥问题
开发语言·c++·算法