├── CMakeLists.txt ├── README.md ├── abstractkernel.cpp ├── abstractkernel.h ├── abstractsolver.cpp ├── abstractsolver.h ├── abstractspatialstruct.cpp ├── abstractspatialstruct.h ├── boundary.h ├── canvas.cpp ├── canvas.h ├── linearsearch.cpp ├── linearsearch.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── particle.h ├── poly6kernel.cpp ├── poly6kernel.h ├── radixsort.cpp ├── radixsort.h ├── simulation.cl ├── simulation.h ├── solver.cpp ├── solver.h ├── solver_gpu.cpp ├── solver_gpu.h ├── spikykernel.cpp ├── spikykernel.h ├── visckernel.cpp └── visckernel.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(PSF2D LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 15 | 16 | find_package(Qt5 COMPONENTS Widgets REQUIRED) 17 | 18 | add_executable(PSF2D 19 | main.cpp 20 | mainwindow.cpp 21 | mainwindow.h 22 | mainwindow.ui 23 | canvas.cpp 24 | canvas.h 25 | particle.h 26 | abstractsolver.cpp 27 | abstractsolver.h 28 | solver.cpp 29 | solver.h 30 | solver_gpu.cpp 31 | solver_gpu.h 32 | boundary.h 33 | abstractkernel.cpp 34 | abstractkernel.h 35 | poly6kernel.cpp 36 | poly6kernel.h 37 | spikykernel.cpp 38 | spikykernel.h 39 | visckernel.cpp 40 | visckernel.h 41 | abstractspatialstruct.cpp 42 | abstractspatialstruct.h 43 | linearsearch.cpp 44 | linearsearch.h 45 | radixsort.cpp 46 | radixsort.h 47 | simulation.h 48 | ) 49 | 50 | find_package(OpenCL) 51 | find_package(OpenMP) 52 | 53 | include_directories(${OpenCL_INCLUDE_DIRS}) 54 | link_directories(${OpenCL_LIBRARY}) 55 | target_link_libraries(PSF2D PRIVATE Qt5::Widgets OpenMP::OpenMP_CXX ${OpenCL_LIBRARY}) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pbf_tutorial 2 | Position Based Fluids 2D Tutorial 3 | 4 | This is the implementation of Position Based Fluids in 2D for a tutorial I made at https://wleusch.wordpress.com/position-based-fluids-2d/. 5 | 6 | To build this implementation, Qt5, glm, OpenMP and OpenCL are required. In this implementation you can play around with borders by right-clicking into the canvas and dragging a border line. Particles can be added by left-clicking and and moving the mouse in the canvas. The step button simulates a single solver step, whereas the start/stop button continuously updates the simulation. In the view menu, the visualization of the particles pressure can be activated. Last but not least, you can play around with the parameters in the GUI. 7 | -------------------------------------------------------------------------------- /abstractkernel.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractkernel.h" 2 | 3 | AbstractKernel::AbstractKernel() 4 | { 5 | 6 | } 7 | 8 | AbstractKernel::~AbstractKernel() 9 | { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /abstractkernel.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTKERNEL_H 2 | #define ABSTRACTKERNEL_H 3 | #include 4 | 5 | class AbstractKernel 6 | { 7 | public: 8 | AbstractKernel(); 9 | virtual ~AbstractKernel(); 10 | 11 | virtual float evaluate(float r, float h) = 0; 12 | virtual glm::vec2 gradient(float r, float h,const glm::vec2 n) = 0; 13 | virtual float laplace(float r, float h) = 0; 14 | }; 15 | 16 | #endif // ABSTRACTKERNEL_H 17 | -------------------------------------------------------------------------------- /abstractsolver.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractsolver.h" 2 | #include "linearsearch.h" 3 | #include "radixsort.h" 4 | 5 | AbstractSolver::AbstractSolver(unsigned int width, unsigned int height) 6 | { 7 | domain_width = width; 8 | domain_height = height; 9 | boundaries.resize(4); 10 | 11 | iterations = 10; 12 | timestep = 0.1f; 13 | search_radius = 10.0f; 14 | resting_density = 10.0f; 15 | artificial_density = 0.0001f; 16 | viscosity = 0.0f; 17 | 18 | spatial_struct = new LinearSearch(particles); 19 | } 20 | 21 | AbstractSolver::~AbstractSolver() 22 | { 23 | delete spatial_struct; 24 | } 25 | 26 | void AbstractSolver::addBoundary(Boundary& boundary) 27 | { 28 | boundaries.push_back(boundary); 29 | } 30 | 31 | void AbstractSolver::addParticle(Particle& particle) 32 | { 33 | particle.vel = glm::vec2(0.0f,0.0f); 34 | particles.push_back(particle); 35 | } 36 | 37 | void AbstractSolver::changeDomain(float right, float top, float left, float bottom) 38 | { 39 | domain_width = (-right+left); 40 | domain_height = (-bottom+top); 41 | 42 | boundaries[0].line[0] = glm::vec2(left,bottom); 43 | boundaries[0].line[1] = glm::vec2(left,top); 44 | boundaries[0].normal = glm::vec2(-1.0,0.0); 45 | 46 | boundaries[1].line[0] = glm::vec2(left,top); 47 | boundaries[1].line[1] = glm::vec2(right,top); 48 | boundaries[1].normal = glm::vec2(0.0,-1.0); 49 | 50 | 51 | boundaries[2].line[0] = glm::vec2(right,top); 52 | boundaries[2].line[1] = glm::vec2(right,bottom); 53 | boundaries[2].normal = glm::vec2(-1.0,0.0); 54 | 55 | 56 | boundaries[3].line[0] = glm::vec2(right,bottom); 57 | boundaries[3].line[1] = glm::vec2(left,bottom); 58 | boundaries[3].normal = glm::vec2(0.0,-1.0); 59 | 60 | } 61 | 62 | void AbstractSolver::setIterations(float niter) 63 | { 64 | iterations = niter; 65 | } 66 | 67 | void AbstractSolver::setTimeStep(float step) 68 | { 69 | timestep = step; 70 | } 71 | 72 | void AbstractSolver::setSearchRadius(float radius) 73 | { 74 | search_radius = radius; 75 | } 76 | 77 | void AbstractSolver::setRestingDensity(float restingDensity) 78 | { 79 | resting_density = restingDensity; 80 | } 81 | 82 | void AbstractSolver::setArtificialDensity(float artificialDensity) 83 | { 84 | artificial_density = artificialDensity; 85 | } 86 | 87 | void AbstractSolver::setViscosity(float visc) 88 | { 89 | viscosity = visc; 90 | } 91 | 92 | void AbstractSolver::setSpatialStruct(SpatialStructType type) 93 | { 94 | delete spatial_struct; 95 | if(type == LINEAR_SEARCH) 96 | { 97 | spatial_struct = new LinearSearch(particles); 98 | } 99 | else if(type == RADIX_SORT) 100 | { 101 | spatial_struct = new RadixSort(particles); 102 | } 103 | } 104 | 105 | void AbstractSolver::setParticles(std::vector &parts) 106 | { 107 | particles.clear(); 108 | particles.insert(particles.begin(), parts.begin(), parts.end()); 109 | } 110 | 111 | void AbstractSolver::setBoundaries(std::vector &bounds) 112 | { 113 | boundaries.clear(); 114 | boundaries.insert(boundaries.begin(), bounds.begin(), bounds.end()); 115 | } 116 | 117 | const std::vector& AbstractSolver::getBoundaries() 118 | { 119 | return boundaries; 120 | } 121 | 122 | const std::vector& AbstractSolver::getParticles() 123 | { 124 | return particles; 125 | } 126 | -------------------------------------------------------------------------------- /abstractsolver.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTSOLVER_H 2 | #define ABSTRACTSOLVER_H 3 | #include "particle.h" 4 | #include "boundary.h" 5 | #include "abstractspatialstruct.h" 6 | #include 7 | 8 | class AbstractSolver 9 | { 10 | public: 11 | enum SpatialStructType 12 | { 13 | LINEAR_SEARCH, 14 | RADIX_SORT 15 | }; 16 | AbstractSolver(unsigned int width, unsigned int height); 17 | virtual ~AbstractSolver(); 18 | 19 | virtual void solve() = 0; 20 | 21 | void addBoundary(Boundary& boundary); 22 | void addParticle(Particle& particle); 23 | 24 | void changeDomain(float right, float top, float left, float bottom); 25 | 26 | void setIterations(float niter); 27 | void setTimeStep(float step); 28 | void setSearchRadius(float radius); 29 | void setRestingDensity(float restingDensity); 30 | void setArtificialDensity(float artificialDensity); 31 | void setViscosity(float visc); 32 | void setSpatialStruct(SpatialStructType type); 33 | 34 | void setParticles(std::vector &particles); 35 | void setBoundaries(std::vector &particles); 36 | 37 | 38 | const std::vector& getBoundaries(); 39 | const std::vector& getParticles(); 40 | protected: 41 | const glm::vec2 gravity = glm::vec2(0.0,-9.81); 42 | 43 | AbstractSpatialStruct* spatial_struct; 44 | float domain_width; 45 | float domain_height; 46 | 47 | std::vector particles; 48 | std::vector boundaries; 49 | 50 | int iterations; 51 | float timestep; 52 | float artificial_density; 53 | float search_radius; 54 | float resting_density; 55 | float viscosity; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /abstractspatialstruct.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractspatialstruct.h" 2 | 3 | AbstractSpatialStruct::AbstractSpatialStruct(std::vector& particles) 4 | : particles(particles) 5 | { 6 | } 7 | 8 | AbstractSpatialStruct::~AbstractSpatialStruct() 9 | { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /abstractspatialstruct.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTSPATIALSTRUCT_H 2 | #define ABSTRACTSPATIALSTRUCT_H 3 | #include 4 | #include "particle.h" 5 | 6 | class AbstractSpatialStruct 7 | { 8 | public: 9 | AbstractSpatialStruct(std::vector& particles); 10 | virtual ~AbstractSpatialStruct(); 11 | virtual void rebuild(float width, float height, float radius) = 0; 12 | virtual std::vector findNeighbors(unsigned int index, float r) = 0; 13 | protected: 14 | std::vector& particles; 15 | }; 16 | 17 | #endif // ABSTRACTSPATIALSTRUCT_H 18 | -------------------------------------------------------------------------------- /boundary.h: -------------------------------------------------------------------------------- 1 | #ifndef BOUNDARY_H 2 | #define BOUNDARY_H 3 | #include 4 | 5 | struct Boundary 6 | { 7 | glm::vec2 line[2]; 8 | glm::vec2 normal; 9 | }; 10 | 11 | #endif // BOUNDARY_H 12 | -------------------------------------------------------------------------------- /canvas.cpp: -------------------------------------------------------------------------------- 1 | #include "canvas.h" 2 | #include 3 | #include 4 | #include "solver.h" 5 | #include "solver_gpu.h" 6 | 7 | Canvas::Canvas(QWidget *parent) : QWidget(parent) 8 | { 9 | solver = new Solver(width(),height()); 10 | particleSize = 10.0f; 11 | showPressure = false; 12 | 13 | connect(&updateTimer,SIGNAL(timeout()),this,SLOT(simulate())); 14 | updateTimer.setInterval(1000.0/60.0); 15 | updateTimer.setSingleShot(false); 16 | } 17 | 18 | void Canvas::toggleSimulation() 19 | { 20 | if(updateTimer.isActive()) 21 | { 22 | updateTimer.stop(); 23 | } 24 | else 25 | { 26 | updateTimer.start(); 27 | } 28 | } 29 | 30 | void Canvas::stepSimulation() 31 | { 32 | solver->solve(); 33 | update(); 34 | } 35 | 36 | void Canvas::togglePressureView(bool value) 37 | { 38 | showPressure = value; 39 | update(); 40 | } 41 | 42 | void Canvas::changeParticleSize(double value) 43 | { 44 | particleSize = value; 45 | update(); 46 | } 47 | 48 | void Canvas::changeSearchRadius(double value) 49 | { 50 | solver->setSearchRadius(value); 51 | } 52 | 53 | void Canvas::changeRestingDensity(double value) 54 | { 55 | solver->setRestingDensity(value); 56 | } 57 | 58 | void Canvas::changeArtificialDensity(double value) 59 | { 60 | solver->setArtificialDensity(value); 61 | } 62 | 63 | void Canvas::changeViscosity(double value) 64 | { 65 | solver->setViscosity(value); 66 | } 67 | 68 | void Canvas::changeTimestep(double value) 69 | { 70 | solver->setTimeStep(value); 71 | } 72 | 73 | void Canvas::changeIterations(int niter) 74 | { 75 | solver->setIterations(niter); 76 | } 77 | 78 | void Canvas::changeSpatialStruct(int index) 79 | { 80 | Solver::SpatialStructType type; 81 | switch (index) 82 | { 83 | case 0: 84 | { 85 | type = Solver::LINEAR_SEARCH; 86 | break; 87 | } 88 | case 1: 89 | { 90 | type = Solver::RADIX_SORT; 91 | break; 92 | } 93 | } 94 | solver->setSpatialStruct(type); 95 | } 96 | 97 | void Canvas::setGPU(bool gpu) 98 | { 99 | std::vector particles = solver->getParticles(); 100 | std::vector boundaries = solver->getBoundaries(); 101 | delete solver; 102 | if(gpu) 103 | { 104 | 105 | solver = new SolverGPU(width(),height()); 106 | } 107 | else 108 | { 109 | solver = new Solver(width(),height()); 110 | } 111 | solver->changeDomain(0.0,height()-100,width()-100,0.0f); 112 | solver->setParticles(particles); 113 | solver->setBoundaries(boundaries); 114 | } 115 | 116 | void Canvas::simulate() 117 | { 118 | solver->solve(); 119 | update(); 120 | } 121 | 122 | void Canvas::resizeEvent(QResizeEvent *event) 123 | { 124 | solver->changeDomain(0.0,height()-100,width()-100,0.0f); 125 | } 126 | 127 | void Canvas::paintEvent(QPaintEvent *event) 128 | { 129 | QPainter painter(this); 130 | QBrush brush(QColor(0,0,255)); 131 | brush.setStyle(Qt::BrushStyle::SolidPattern); 132 | painter.setPen(QColor(0,0,0)); 133 | painter.setBrush(brush); 134 | 135 | painter.drawLine(line[0].x,line[0].y,line[1].x,line[1].y); 136 | 137 | const std::vector& particles = solver->getParticles(); 138 | const std::vector& boundaries = solver->getBoundaries(); 139 | for(unsigned int i=0;ibutton()) 171 | { 172 | case Qt::MouseButton::LeftButton: 173 | { 174 | break; 175 | } 176 | case Qt::MouseButton::RightButton: 177 | { 178 | line[0] = glm::vec2(event->pos().x(), event->pos().y()); 179 | line[1] = glm::vec2(event->pos().x(), event->pos().y()); 180 | 181 | break; 182 | } 183 | } 184 | } 185 | 186 | void Canvas::mouseMoveEvent(QMouseEvent *event) 187 | { 188 | switch(event->buttons()) 189 | { 190 | case Qt::MouseButton::LeftButton: 191 | { 192 | Particle p; 193 | p.pos = glm::vec2(event->pos().x(), height()-event->pos().y()); 194 | solver->addParticle(p); 195 | break; 196 | } 197 | case Qt::MouseButton::RightButton: 198 | { 199 | line[1] = glm::vec2(event->pos().x(), event->pos().y()); 200 | break; 201 | } 202 | } 203 | update(); 204 | 205 | } 206 | 207 | void Canvas::mouseReleaseEvent(QMouseEvent *event) 208 | { 209 | switch (event->button()) 210 | { 211 | case Qt::MouseButton::LeftButton: 212 | { 213 | break; 214 | } 215 | case Qt::MouseButton::RightButton: 216 | { 217 | Boundary boundary; 218 | boundary.line[0] = line[0]; 219 | boundary.line[0].y = height() - boundary.line[0].y; 220 | boundary.line[1] = line[1]; 221 | boundary.line[1].y = height() - boundary.line[1].y; 222 | if(line[0].x>line[1].x) 223 | { 224 | glm::vec2 temp = line[0]; 225 | line[0] = line[1]; 226 | line[1] = temp; 227 | } 228 | glm::vec2 line_vec = (line[1]-line[0]); 229 | glm::vec2 normal = glm::vec2(line_vec.y,line_vec.x); 230 | boundary.normal = glm::normalize(normal); 231 | solver->addBoundary(boundary); 232 | 233 | line[0] = glm::vec2(0.0f); 234 | line[1] = glm::vec2(0.0f); 235 | 236 | break; 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /canvas.h: -------------------------------------------------------------------------------- 1 | #ifndef CANVAS_H 2 | #define CANVAS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "abstractsolver.h" 8 | 9 | class Canvas : public QWidget 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit Canvas(QWidget *parent = nullptr); 14 | 15 | protected: 16 | void resizeEvent(QResizeEvent *event) override; 17 | void paintEvent(QPaintEvent *event) override; 18 | void mousePressEvent(QMouseEvent *event) override; 19 | void mouseMoveEvent(QMouseEvent *event) override; 20 | void mouseReleaseEvent(QMouseEvent *event) override; 21 | public slots: 22 | void toggleSimulation(); 23 | void stepSimulation(); 24 | void togglePressureView(bool value); 25 | void changeParticleSize(double value); 26 | void changeSearchRadius(double value); 27 | void changeRestingDensity(double value); 28 | void changeArtificialDensity(double value); 29 | void changeViscosity(double value); 30 | void changeTimestep(double value); 31 | void changeIterations(int niter); 32 | void changeSpatialStruct(int index); 33 | void setGPU(bool gpu); 34 | private slots: 35 | void simulate(); 36 | private: 37 | bool showPressure; 38 | float particleSize; 39 | QTimer updateTimer; 40 | 41 | AbstractSolver* solver; 42 | glm::vec2 line[2]; 43 | }; 44 | 45 | #endif // CANVAS_H 46 | -------------------------------------------------------------------------------- /linearsearch.cpp: -------------------------------------------------------------------------------- 1 | #include "linearsearch.h" 2 | 3 | LinearSearch::LinearSearch(std::vector& particles) 4 | : AbstractSpatialStruct(particles) 5 | { 6 | 7 | } 8 | 9 | LinearSearch::~LinearSearch() 10 | { 11 | 12 | } 13 | 14 | void LinearSearch::rebuild(float width, float height, float radius) 15 | { 16 | } 17 | 18 | std::vector LinearSearch::findNeighbors(unsigned int index, float r) 19 | { 20 | const Particle particle = particles[index]; 21 | std::vector neighbors; 22 | for(unsigned int i=0;i& particles); 10 | ~LinearSearch(); 11 | 12 | void rebuild(float width, float height, float radius) override; 13 | std::vector findNeighbors(unsigned int index, float r) override; 14 | }; 15 | 16 | #endif // LINEARSEARCH_H 17 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "./ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) 5 | : QMainWindow(parent) 6 | , ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | MainWindow::~MainWindow() 12 | { 13 | delete ui; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class MainWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private: 19 | Ui::MainWindow *ui; 20 | }; 21 | #endif // MAINWINDOW_H 22 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1024 10 | 768 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 1024 22 | 768 23 | 24 | 25 | 26 | 27 | 1024 28 | 768 29 | 30 | 31 | 32 | MainWindow 33 | 34 | 35 | 36 | 37 | QLayout::SetDefaultConstraint 38 | 39 | 40 | 41 | 42 | 43 | 0 44 | 0 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | QLayout::SetFixedSize 53 | 54 | 55 | 56 | 57 | 58 | 59 | Spacial Struct: 60 | 61 | 62 | 63 | 64 | 65 | 66 | Linear Search 67 | 68 | 69 | 0 70 | 71 | 72 | 73 | Linear Search 74 | 75 | 76 | 77 | 78 | Radix Sort 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Particle size: 91 | 92 | 93 | 94 | 95 | 96 | 97 | 10.000000000000000 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | QLayout::SetFixedSize 107 | 108 | 109 | 110 | 111 | Search Radius: 112 | 113 | 114 | 115 | 116 | 117 | 118 | 1.000000000000000 119 | 120 | 121 | 10.000000000000000 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | QLayout::SetFixedSize 131 | 132 | 133 | 134 | 135 | Resting Density: 136 | 137 | 138 | 139 | 140 | 141 | 142 | 1.000000000000000 143 | 144 | 145 | 1000000.000000000000000 146 | 147 | 148 | 10.000000000000000 149 | 150 | 151 | 10.000000000000000 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | QLayout::SetFixedSize 161 | 162 | 163 | 164 | 165 | Artificial Density: 166 | 167 | 168 | 169 | 170 | 171 | 172 | 8 173 | 174 | 175 | 0.000001000000000 176 | 177 | 178 | 1.000000000000000 179 | 180 | 181 | 0.000100000000000 182 | 183 | 184 | 0.000100000000000 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | QLayout::SetFixedSize 194 | 195 | 196 | 197 | 198 | Timestep: 199 | 200 | 201 | 202 | 203 | 204 | 205 | 0.100000000000000 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | Iterations: 217 | 218 | 219 | 220 | 221 | 222 | 223 | 1 224 | 225 | 226 | 10 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | Viscosity: 238 | 239 | 240 | 241 | 242 | 243 | 244 | 1.000000000000000 245 | 246 | 247 | 0.010000000000000 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | GPU 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | Step 266 | 267 | 268 | 269 | 270 | 271 | 272 | Start/Stop 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 0 286 | 0 287 | 1024 288 | 22 289 | 290 | 291 | 292 | 293 | View 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | true 303 | 304 | 305 | Pressure 306 | 307 | 308 | 309 | 310 | Velocity Absolute 311 | 312 | 313 | 314 | 315 | Next Step 316 | 317 | 318 | 319 | 320 | Next Particle 321 | 322 | 323 | 324 | 325 | Start 326 | 327 | 328 | 329 | 330 | Pause 331 | 332 | 333 | 334 | 335 | 336 | Canvas 337 | QWidget 338 |
canvas.h
339 | 1 340 | 341 | toggleSimulation() 342 | stepSimulation() 343 | changeParticleSize(double) 344 | changeIterations(int) 345 | changeTimestep(double) 346 | changeSearchRadius(double) 347 | changeRestingDensity(double) 348 | togglePressureView(bool) 349 | changeArtificialDensity(double) 350 | changeSpatialStruct(int) 351 | changeViscosity(double) 352 | setGPU(bool) 353 | 354 |
355 |
356 | 357 | 358 | 359 | pushButton_2 360 | clicked() 361 | widget 362 | toggleSimulation() 363 | 364 | 365 | 930 366 | 695 367 | 368 | 369 | 622 370 | 579 371 | 372 | 373 | 374 | 375 | pushButton 376 | clicked() 377 | widget 378 | stepSimulation() 379 | 380 | 381 | 875 382 | 699 383 | 384 | 385 | 718 386 | 653 387 | 388 | 389 | 390 | 391 | doubleSpinBox_5 392 | valueChanged(double) 393 | widget 394 | changeParticleSize(double) 395 | 396 | 397 | 1012 398 | 154 399 | 400 | 401 | 668 402 | 180 403 | 404 | 405 | 406 | 407 | spinBox 408 | valueChanged(int) 409 | widget 410 | changeIterations(int) 411 | 412 | 413 | 1012 414 | 533 415 | 416 | 417 | 713 418 | 546 419 | 420 | 421 | 422 | 423 | doubleSpinBox_4 424 | valueChanged(double) 425 | widget 426 | changeTimestep(double) 427 | 428 | 429 | 1012 430 | 457 431 | 432 | 433 | 733 434 | 487 435 | 436 | 437 | 438 | 439 | doubleSpinBox_2 440 | valueChanged(double) 441 | widget 442 | changeRestingDensity(double) 443 | 444 | 445 | 1012 446 | 306 447 | 448 | 449 | 721 450 | 357 451 | 452 | 453 | 454 | 455 | doubleSpinBox 456 | valueChanged(double) 457 | widget 458 | changeSearchRadius(double) 459 | 460 | 461 | 1012 462 | 230 463 | 464 | 465 | 649 466 | 251 467 | 468 | 469 | 470 | 471 | actionPressure 472 | toggled(bool) 473 | widget 474 | togglePressureView(bool) 475 | 476 | 477 | -1 478 | -1 479 | 480 | 481 | 392 482 | 383 483 | 484 | 485 | 486 | 487 | doubleSpinBox_3 488 | valueChanged(double) 489 | widget 490 | changeArtificialDensity(double) 491 | 492 | 493 | 1012 494 | 381 495 | 496 | 497 | 637 498 | 397 499 | 500 | 501 | 502 | 503 | cbSpatialStruct 504 | currentIndexChanged(int) 505 | widget 506 | changeSpatialStruct(int) 507 | 508 | 509 | 922 510 | 71 511 | 512 | 513 | 675 514 | 92 515 | 516 | 517 | 518 | 519 | doubleSpinBox_6 520 | valueChanged(double) 521 | widget 522 | changeViscosity(double) 523 | 524 | 525 | 1012 526 | 608 527 | 528 | 529 | 689 530 | 610 531 | 532 | 533 | 534 | 535 | checkBox 536 | toggled(bool) 537 | widget 538 | setGPU(bool) 539 | 540 | 541 | 788 542 | 642 543 | 544 | 545 | 644 546 | 628 547 | 548 | 549 | 550 | 551 |
552 | -------------------------------------------------------------------------------- /particle.h: -------------------------------------------------------------------------------- 1 | #ifndef PARTICLE_H 2 | #define PARTICLE_H 3 | #include 4 | 5 | struct Particle 6 | { 7 | glm::vec2 pos; 8 | glm::vec2 vel; 9 | glm::vec2 proj_pos; 10 | glm::vec2 delta; 11 | float lambda; 12 | float pressure; 13 | unsigned int bucked_id; 14 | char pad[2]; 15 | }; 16 | 17 | #endif // PARTICLE_H 18 | -------------------------------------------------------------------------------- /poly6kernel.cpp: -------------------------------------------------------------------------------- 1 | #include "poly6kernel.h" 2 | #include 3 | 4 | Poly6Kernel::Poly6Kernel() 5 | { 6 | 7 | } 8 | 9 | Poly6Kernel::~Poly6Kernel() 10 | { 11 | 12 | } 13 | 14 | float Poly6Kernel::evaluate(float r, float h) 15 | { 16 | if(r==0.0f) 17 | { 18 | return 0.0f; 19 | } 20 | return (315/(64*M_PI*std::pow(h,9)))*std::pow(h*h-r*r,3); 21 | } 22 | 23 | glm::vec2 Poly6Kernel::gradient(float r, float h, const glm::vec2 n) 24 | { 25 | 26 | } 27 | 28 | float Poly6Kernel::laplace(float r, float h) 29 | { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /poly6kernel.h: -------------------------------------------------------------------------------- 1 | #ifndef POLY6KERNEL_H 2 | #define POLY6KERNEL_H 3 | #include 4 | 5 | class Poly6Kernel : public AbstractKernel 6 | { 7 | public: 8 | Poly6Kernel(); 9 | ~Poly6Kernel(); 10 | float evaluate(float r, float h) override; 11 | glm::vec2 gradient(float r, float h, const glm::vec2 n) override; 12 | float laplace(float r, float h) override; 13 | }; 14 | 15 | #endif // POLY6KERNEL_H 16 | -------------------------------------------------------------------------------- /radixsort.cpp: -------------------------------------------------------------------------------- 1 | #include "radixsort.h" 2 | 3 | RadixSort::RadixSort(std::vector& particles) 4 | : AbstractSpatialStruct(particles) 5 | { 6 | } 7 | 8 | RadixSort::~RadixSort() 9 | { 10 | 11 | } 12 | 13 | void RadixSort::rebuild(float width, float height, float radius) 14 | { 15 | discrete_width = ceil(width/radius); 16 | discrete_height = ceil(height/radius); 17 | unsigned int num_buckets = discrete_width * discrete_height; 18 | 19 | std::vector> buckets(num_buckets); 20 | #pragma omp parallel for 21 | for(unsigned int i=0;i0 ? histogram[bucket_id-1] : 0; 61 | unsigned int offset = histogram_offset + buckets[bucket_id].fetch_add(1); 62 | particlesSorted[offset] = particle; 63 | } 64 | particles.swap(particlesSorted); 65 | } 66 | 67 | std::vector RadixSort::findNeighbors(unsigned int index, float r) 68 | { 69 | const Particle particle = particles[index]; 70 | std::vector neighbors; 71 | float r_squared = r*r; 72 | for(int y=-1;y<=1;y++) 73 | { 74 | int y_offset = particle.bucked_id/discrete_width + y; 75 | if(y_offset<0 || y_offset>=discrete_height) continue; 76 | for(int x=-1;x<=1;x++) 77 | { 78 | int x_offset = particle.bucked_id%discrete_width + x; 79 | if(x_offset<0 || x_offset>=discrete_width) continue; 80 | 81 | unsigned int array_offset = x_offset+y_offset*discrete_width; 82 | unsigned int range_begin = x_offset>0 ? histogram[array_offset - 1] : 0; 83 | unsigned int range_end = histogram[array_offset]; 84 | for(unsigned int i=range_begin;i 5 | 6 | class RadixSort : public AbstractSpatialStruct 7 | { 8 | public: 9 | RadixSort(std::vector& particles); 10 | ~RadixSort(); 11 | 12 | void rebuild(float width, float height, float radius) override; 13 | std::vector findNeighbors(unsigned int index, float r) override; 14 | private: 15 | unsigned int discrete_width; 16 | unsigned int discrete_height; 17 | std::vector histogram; 18 | 19 | std::vector particlesSorted; 20 | }; 21 | 22 | #endif // LINEARSEARCH_H 23 | -------------------------------------------------------------------------------- /simulation.cl: -------------------------------------------------------------------------------- 1 | #pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable 2 | typedef struct 3 | { 4 | float2 pos; 5 | float2 vel; 6 | float2 proj_pos; 7 | float2 delta; 8 | float lambda; 9 | float pressure; 10 | uint id; 11 | } Particle; 12 | 13 | typedef struct 14 | { 15 | float2 begin; 16 | float2 end; 17 | float2 normal; 18 | } Boundary; 19 | 20 | float poly6_kernel(float r, float h) 21 | { 22 | return (315/(64*M_PI_F*pown(h,9)))*pown(h*h-r*r,3); 23 | } 24 | 25 | float2 spiky_kernel_gradient(float r, float h, float2 n) 26 | { 27 | return ((float)(-45.0f/(M_PI_F*pown(h,6))*pown(h-r,2)))*n; 28 | } 29 | 30 | float visc_kernel(float r, float h) 31 | { 32 | return 45.0f/(M_PI_F*pown(h,6))*(h-r); 33 | } 34 | 35 | 36 | float3 intersect_line_line(const float2 a, const float2 b,const float2 c, const float2 d) 37 | { 38 | float t = ((a.x-c.x)*(c.y-d.y)-(a.y-c.y)*(c.x-d.x))/((a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x)); 39 | return (float3)(a+t*(b-a),t); 40 | } 41 | 42 | void __kernel bucket_count(global Particle* particles, global int* counters, uint2 dims) 43 | { 44 | int index = get_global_id(0); 45 | Particle* particle = &particles[index]; 46 | particle->id = particle->pos.x/dims.x + (particle->pos.y/dims.y)*dims.x; 47 | atomic_add(&counters[particle->id],1); 48 | } 49 | 50 | void __kernel histogram(global int* counters, global int* histogram, global int* collect) 51 | { 52 | int index = get_global_id(0); 53 | histogram[index] = work_group_scan_inclusive_add(counters[index]); 54 | if(get_local_id(0)==get_local_size(0)-1) 55 | { 56 | collect[get_group_id(0)] = histogram[index]; 57 | } 58 | } 59 | void __kernel histogram2(global int* counters, global int* histogram) 60 | { 61 | int index = get_global_id(0); 62 | histogram[index] = work_group_scan_inclusive_add(counters[index]); 63 | } 64 | void __kernel histogram3(global int* counters, global int* histogram, global int* temp) 65 | { 66 | int index = get_local_id(0) + (get_group_id(0)+1)*get_local_size(0); 67 | histogram[index] += counters[get_group_id(0)]; 68 | } 69 | 70 | void __kernel sort_particles(global Particle* particles_front, global Particle* particles_back, global uint* offsets, global uint* temp) 71 | { 72 | uint index = get_global_id(0); 73 | Particle* particle = &particles_front[index]; 74 | uint counter_offset = particle->id>0 ? offsets[particle->id-1] : 0; 75 | uint offset = counter_offset + atomic_add(&temp[particle->id],1); 76 | particles_back[offset] = *particle; 77 | } 78 | 79 | void __kernel add_external_forces(global Particle* particles, float timestep, float2 gravity) 80 | { 81 | Particle* particle = &particles[get_global_id(0)]; 82 | particle->vel += timestep*gravity; 83 | particle->proj_pos = particle->pos + timestep * particle->vel; 84 | } 85 | 86 | void __kernel compute_lambda(global Particle* particles, uint2 dims, global int* histogram, float radius, float resting_density, float artificial_density) 87 | { 88 | uint index = get_global_id(0); 89 | Particle* particle = &particles[index]; 90 | float density = 0.0f; 91 | float pressureSum = 0.0f; 92 | float2 out_pressure = (float2)(0.0f,0.0f); 93 | float r_squared = radius*radius; 94 | for(int y=-1;y<=1;y++) 95 | { 96 | int y_offset = particle->id/dims.x + y; 97 | if(y_offset<0 || y_offset>=dims.y) continue; 98 | for(int x=-1;x<=1;x++) 99 | { 100 | int x_offset = particle->id%dims.x + x; 101 | if(x_offset<0 || x_offset>=dims.x) continue; 102 | 103 | uint array_offset = x_offset+y_offset*dims.x; 104 | uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0; 105 | uint range_end = histogram[array_offset]; 106 | for(uint i=range_begin;ipos-particle->proj_pos; 111 | float d = dot(distVec,distVec); 112 | if(dpressure = density/resting_density; 126 | particle->lambda = -densityConstraint/(pressureSum + artificial_density); 127 | } 128 | 129 | void __kernel update_delta(global uint* histogram, uint2 dims, float search_radius, float resting_density, global Particle* particles) 130 | { 131 | int index = get_global_id(0); 132 | Particle* particle = &particles[index]; 133 | float2 delta = (float2)(0.0f,0.0f); 134 | float2 particle_proj_pos = particle->proj_pos; 135 | float2 particle_lambda = particle->lambda; 136 | float r_squared = search_radius*search_radius; 137 | for(int y=-1;y<=1;y++) 138 | { 139 | int y_offset = particle->id/dims.x + y; 140 | if(y_offset<0 || y_offset>=dims.y) continue; 141 | for(int x=-1;x<=1;x++) 142 | { 143 | int x_offset = particle->id%dims.x + x; 144 | if(x_offset<0 || x_offset>=dims.x) continue; 145 | uint array_offset = x_offset+y_offset*dims.x; 146 | uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0; 147 | uint range_end = histogram[array_offset]; 148 | for(uint i=range_begin;ipos+particle_proj_pos; 153 | float d = dot(distVec,distVec); 154 | if(dlambda + sCorr) * spiky_kernel_gradient(length(distVec),search_radius,normalize(distVec)); 158 | } 159 | } 160 | } 161 | } 162 | particle->delta = delta; 163 | } 164 | 165 | void __kernel check_boundary_collision(global Particle* particles, global Boundary* boundaries, uint num_boundaries) 166 | { 167 | int index = get_global_id(0); 168 | Particle* particle = &particles[index]; 169 | for(int i=0;inormal,bound->begin-particle->pos); 173 | float d2 = dot(bound->normal,bound->begin-((particle->proj_pos + particle->delta))); 174 | if((sign(d1)!=sign(d2))) 175 | { 176 | float si = sign(d2); 177 | float3 pos = intersect_line_line(bound->begin, 178 | bound->end, 179 | particle->pos, 180 | particle->proj_pos + particle->delta); 181 | if(pos.z<0.0 || pos.z>1.0) continue; 182 | particle->delta = -particle->proj_pos + ((float2)(pos.x,pos.y) + 0.1f * si*bound->normal); 183 | } 184 | } 185 | } 186 | void __kernel update_projected_positions(global Particle* particles) 187 | { 188 | int index = get_global_id(0); 189 | Particle* particle = &particles[index]; 190 | particle->proj_pos = particle->proj_pos + particle->delta; 191 | } 192 | 193 | void __kernel update_particles(global Particle* particles, float timestep, uint2 dims, global uint* histogram, float visc, float radius) 194 | { 195 | int index = get_global_id(0); 196 | Particle* particle = &particles[index]; 197 | particle->vel = (1.0f/timestep) * (particle->proj_pos-particle->pos); 198 | barrier(CLK_GLOBAL_MEM_FENCE); 199 | float2 viscAcc = (float2)(0.0f,0.0f); 200 | float r_squared = radius * radius; 201 | for(int y=-1;y<=1;y++) 202 | { 203 | int y_offset = particle->id/dims.x + y; 204 | if(y_offset<0 || y_offset>=dims.y) continue; 205 | for(int x=-1;x<=1;x++) 206 | { 207 | int x_offset = particle->id%dims.x + x; 208 | if(x_offset<0 || x_offset>=dims.x) continue; 209 | 210 | uint array_offset = x_offset+y_offset*dims.x; 211 | uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0; 212 | uint range_end = histogram[array_offset]; 213 | for(uint i=range_begin;ipos-particle->proj_pos; 218 | float d = dot(distVec,distVec); 219 | if(dvel - particle->vel; 222 | viscAcc += viscVel*visc_kernel(distance(particle->pos,p->pos),radius); 223 | } 224 | } 225 | } 226 | } 227 | barrier(CLK_LOCAL_MEM_FENCE); 228 | particle->vel += visc*viscAcc; 229 | particle->pos = particle->proj_pos; 230 | } 231 | -------------------------------------------------------------------------------- /simulation.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATION_H 2 | #define SIMULATION_H 3 | 4 | static const char* simulation_source = 5 | "#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable\n" 6 | "typedef struct\n" 7 | "{\n" 8 | " float2 pos;\n" 9 | " float2 vel;\n" 10 | " float2 proj_pos;\n" 11 | " float2 delta;\n" 12 | " float lambda;\n" 13 | " float pressure;\n" 14 | " uint id;\n" 15 | "} Particle;\n" 16 | "\n" 17 | "typedef struct\n" 18 | "{\n" 19 | " float2 begin;\n" 20 | " float2 end;\n" 21 | " float2 normal;\n" 22 | "} Boundary;\n" 23 | "\n" 24 | "float poly6_kernel(float r, float h)\n" 25 | "{\n" 26 | " return (315/(64*M_PI_F*pown(h,9)))*pown(h*h-r*r,3);\n" 27 | "}\n" 28 | "\n" 29 | "float2 spiky_kernel_gradient(float r, float h, float2 n)\n" 30 | "{\n" 31 | " return ((float)(-45.0f/(M_PI_F*pown(h,6))*pown(h-r,2)))*n;\n" 32 | "}\n" 33 | "\n" 34 | "float visc_kernel(float r, float h)\n" 35 | "{\n" 36 | " return 45.0f/(M_PI_F*pown(h,6))*(h-r);\n" 37 | "}\n" 38 | "\n" 39 | "\n" 40 | "float3 intersect_line_line(const float2 a, const float2 b,const float2 c, const float2 d)\n" 41 | "{\n" 42 | " float t = ((a.x-c.x)*(c.y-d.y)-(a.y-c.y)*(c.x-d.x))/((a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x));\n" 43 | " return (float3)(a+t*(b-a),t);\n" 44 | "}\n" 45 | "\n" 46 | "void __kernel bucket_count(global Particle* particles, global int* counters, uint2 dims)\n" 47 | "{\n" 48 | " int index = get_global_id(0);\n" 49 | " Particle* particle = &particles[index];\n" 50 | " particle->id = particle->pos.x/dims.x + (particle->pos.y/dims.y)*dims.x;\n" 51 | " atomic_add(&counters[particle->id],1);\n" 52 | "}\n" 53 | "\n" 54 | "void __kernel histogram(global int* counters, global int* histogram, global int* collect)\n" 55 | "{\n" 56 | " int index = get_global_id(0);" 57 | " histogram[index] = work_group_scan_inclusive_add(counters[index]);\n" 58 | " if(get_local_id(0)==get_local_size(0)-1)\n" 59 | " {\n" 60 | " collect[get_group_id(0)] = histogram[index];" 61 | " }\n" 62 | "}\n" 63 | "void __kernel histogram2(global int* counters, global int* histogram)\n" 64 | "{\n" 65 | " int index = get_global_id(0);" 66 | " histogram[index] = work_group_scan_inclusive_add(counters[index]);\n" 67 | "}\n" 68 | "void __kernel histogram3(global int* counters, global int* histogram, global int* temp)\n" 69 | "{\n" 70 | " int index = get_local_id(0) + (get_group_id(0)+1)*get_local_size(0);" 71 | " histogram[index] += counters[get_group_id(0)];\n" 72 | "}\n" 73 | "\n" 74 | "void __kernel sort_particles(global Particle* particles_front, global Particle* particles_back, global uint* offsets, global uint* temp)\n" 75 | "{\n" 76 | " uint index = get_global_id(0);\n" 77 | " Particle* particle = &particles_front[index];\n" 78 | " uint counter_offset = particle->id>0 ? offsets[particle->id-1] : 0;\n" 79 | " uint offset = counter_offset + atomic_add(&temp[particle->id],1);\n" 80 | " particles_back[offset] = *particle;" 81 | "}\n" 82 | "\n" 83 | "void __kernel add_external_forces(global Particle* particles, float timestep, float2 gravity)\n" 84 | "{\n" 85 | " Particle* particle = &particles[get_global_id(0)];\n" 86 | " particle->vel += timestep*gravity;\n" 87 | " particle->proj_pos = particle->pos + timestep * particle->vel;\n" 88 | "}\n" 89 | "\n" 90 | "void __kernel compute_lambda(global Particle* particles, uint2 dims, global int* histogram, float radius, float resting_density, float artificial_density)\n" 91 | "{\n" 92 | " uint index = get_global_id(0);\n" 93 | " Particle* particle = &particles[index];\n" 94 | " float density = 0.0f;\n" 95 | " float pressureSum = 0.0f;\n" 96 | " float2 out_pressure = (float2)(0.0f,0.0f);\n" 97 | " float r_squared = radius*radius;\n" 98 | " for(int y=-1;y<=1;y++)\n" 99 | " {\n" 100 | " int y_offset = particle->id/dims.x + y;\n" 101 | " if(y_offset<0 || y_offset>=dims.y) continue;\n" 102 | " for(int x=-1;x<=1;x++)\n" 103 | " {\n" 104 | " int x_offset = particle->id%dims.x + x;\n" 105 | " if(x_offset<0 || x_offset>=dims.x) continue;\n" 106 | "\n" 107 | " uint array_offset = x_offset+y_offset*dims.x;\n" 108 | " uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0;\n" 109 | " uint range_end = histogram[array_offset];\n" 110 | " for(uint i=range_begin;ipos-particle->proj_pos;\n" 115 | " float d = dot(distVec,distVec);\n" 116 | " if(dpressure = density/resting_density;\n" 130 | " particle->lambda = -densityConstraint/(pressureSum + artificial_density);\n" 131 | "}\n" 132 | "\n" 133 | "void __kernel update_delta(global uint* histogram, uint2 dims, float search_radius, float resting_density, global Particle* particles)\n" 134 | "{\n" 135 | " int index = get_global_id(0);\n" 136 | " Particle* particle = &particles[index];\n" 137 | " float2 delta = (float2)(0.0f,0.0f);\n" 138 | " float2 particle_proj_pos = particle->proj_pos;\n" 139 | " float2 particle_lambda = particle->lambda;\n" 140 | " float r_squared = search_radius*search_radius;\n" 141 | " for(int y=-1;y<=1;y++)\n" 142 | " {\n" 143 | " int y_offset = particle->id/dims.x + y;\n" 144 | " if(y_offset<0 || y_offset>=dims.y) continue;\n" 145 | " for(int x=-1;x<=1;x++)\n" 146 | " {\n" 147 | " int x_offset = particle->id%dims.x + x;\n" 148 | " if(x_offset<0 || x_offset>=dims.x) continue;\n" 149 | " uint array_offset = x_offset+y_offset*dims.x;\n" 150 | " uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0;\n" 151 | " uint range_end = histogram[array_offset];\n" 152 | " for(uint i=range_begin;ipos+particle_proj_pos;\n" 157 | " float d = dot(distVec,distVec);\n" 158 | " if(dlambda + sCorr) * spiky_kernel_gradient(length(distVec),search_radius,normalize(distVec));\n" 162 | " }\n" 163 | " }\n" 164 | " }\n" 165 | " }\n" 166 | " particle->delta = delta;\n" 167 | "}\n" 168 | "\n" 169 | "void __kernel check_boundary_collision(global Particle* particles, global Boundary* boundaries, uint num_boundaries)\n" 170 | "{\n" 171 | " int index = get_global_id(0);\n" 172 | " Particle* particle = &particles[index];\n" 173 | " for(int i=0;inormal,bound->begin-particle->pos);\n" 177 | " float d2 = dot(bound->normal,bound->begin-((particle->proj_pos + particle->delta)));\n" 178 | " if((sign(d1)!=sign(d2)))\n" 179 | " {\n" 180 | " float si = sign(d2);\n" 181 | " float3 pos = intersect_line_line(bound->begin,\n" 182 | " bound->end,\n" 183 | " particle->pos,\n" 184 | " particle->proj_pos + particle->delta);\n" 185 | " if(pos.z<0.0 || pos.z>1.0) continue;\n" 186 | " particle->delta = -particle->proj_pos + ((float2)(pos.x,pos.y) + 0.1f * si*bound->normal);\n" 187 | " }\n" 188 | " }\n" 189 | "}\n" 190 | "void __kernel update_projected_positions(global Particle* particles)\n" 191 | "{\n" 192 | " int index = get_global_id(0);\n" 193 | " Particle* particle = &particles[index];\n" 194 | " particle->proj_pos = particle->proj_pos + particle->delta;\n" 195 | "}\n" 196 | "\n" 197 | "void __kernel update_particles(global Particle* particles, float timestep, uint2 dims, global uint* histogram, float visc, float radius)\n" 198 | "{\n" 199 | " int index = get_global_id(0);\n" 200 | " Particle* particle = &particles[index];" 201 | " particle->vel = (1.0f/timestep) * (particle->proj_pos-particle->pos);\n" 202 | " barrier(CLK_GLOBAL_MEM_FENCE);\n" 203 | " float2 viscAcc = (float2)(0.0f,0.0f);" 204 | " float r_squared = radius * radius;\n" 205 | " for(int y=-1;y<=1;y++)\n" 206 | " {\n" 207 | " int y_offset = particle->id/dims.x + y;\n" 208 | " if(y_offset<0 || y_offset>=dims.y) continue;\n" 209 | " for(int x=-1;x<=1;x++)\n" 210 | " {\n" 211 | " int x_offset = particle->id%dims.x + x;\n" 212 | " if(x_offset<0 || x_offset>=dims.x) continue;\n" 213 | "\n" 214 | " uint array_offset = x_offset+y_offset*dims.x;\n" 215 | " uint range_begin = x_offset>0 ? histogram[array_offset - 1] : 0;\n" 216 | " uint range_end = histogram[array_offset];\n" 217 | " for(uint i=range_begin;ipos-particle->proj_pos;\n" 222 | " float d = dot(distVec,distVec);\n" 223 | " if(dvel - particle->vel;\n" 226 | " viscAcc += viscVel*visc_kernel(distance(particle->pos,p->pos),radius);\n" 227 | " }\n" 228 | " }\n" 229 | " }\n" 230 | " }\n" 231 | " barrier(CLK_LOCAL_MEM_FENCE);\n" 232 | " particle->vel += visc*viscAcc;\n" 233 | " particle->pos = particle->proj_pos;" 234 | "}\n" 235 | ""; 236 | 237 | #endif 238 | -------------------------------------------------------------------------------- /solver.cpp: -------------------------------------------------------------------------------- 1 | #include "solver.h" 2 | #include "linearsearch.h" 3 | #include "radixsort.h" 4 | #include "poly6kernel.h" 5 | #include "spikykernel.h" 6 | #include "visckernel.h" 7 | #include 8 | #include 9 | 10 | float sign(float a) 11 | { 12 | return a>0.0f ? 1.0f : -1.0f; 13 | } 14 | 15 | glm::vec3 intersect_line_line(const glm::vec2& a, const glm::vec2& b, 16 | const glm::vec2& c, const glm::vec2& d) 17 | { 18 | float t = ((a.x-c.x)*(c.y-d.y)-(a.y-c.y)*(c.x-d.x))/((a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x)); 19 | return glm::vec3(a+t*(b-a),t); 20 | } 21 | 22 | Solver::Solver(unsigned int width, unsigned int height) : AbstractSolver(width,height) 23 | { 24 | densityKernel = new Poly6Kernel(); 25 | gradientKernel = new SpikyKernel(); 26 | viscKernel = new ViscKernel(); 27 | } 28 | 29 | Solver::~Solver() 30 | { 31 | delete densityKernel; 32 | delete gradientKernel; 33 | delete viscKernel; 34 | } 35 | 36 | void Solver::solve() 37 | { 38 | if(particles.size()!=viscAcc.size()) 39 | { 40 | viscAcc.resize(particles.size()); 41 | } 42 | spatial_struct->rebuild(domain_width,domain_height,search_radius); 43 | 44 | #pragma omp parallel for 45 | for(unsigned int i=0;i neighbors = spatial_struct->findNeighbors(p, search_radius); 60 | float density = computeDensity(particle, neighbors); 61 | float densityConstraint = density/resting_density; 62 | float gradSum = computePressureGradientSum(particle, neighbors); 63 | particle.pressure = density/resting_density; 64 | particle.lambda = -densityConstraint/(gradSum + artificial_density); 65 | } 66 | //Collision response 67 | #pragma omp parallel for 68 | for(unsigned int p=0;p neighbors = spatial_struct->findNeighbors(p, search_radius); 72 | particle.delta = glm::vec2(0.0f,0.0f); 73 | for(unsigned int i=0;ievaluate(glm::length(distVec),search_radius)/densityKernel->evaluate(0.3*search_radius,search_radius)),4.0f); 78 | particle.delta += (1.0f/resting_density) * (particle.lambda + neighbor.lambda + sCorr) * gradientKernel->gradient(glm::distance(particle.proj_pos,neighbor.pos),search_radius,glm::normalize(particle.proj_pos - neighbor.pos)); 79 | } 80 | 81 | for(unsigned int b=0;b1.0) continue; 99 | 100 | particle.delta = -particle.proj_pos + (glm::vec2(pos) + 0.1f * si*bound.normal); 101 | } 102 | } 103 | } 104 | 105 | //Update projected particle position for next solver iteration 106 | #pragma omp parallel for 107 | for(unsigned int p=0;p neighbors = spatial_struct->findNeighbors(i,search_radius); 119 | 120 | Particle& p = particles[i]; 121 | p.vel = (1.0f/timestep)*(p.proj_pos-p.pos); 122 | p.pos = p.proj_pos; 123 | } 124 | 125 | //Compute viscosity influence 126 | #pragma omp parallel for 127 | for(unsigned int i=0;i neighbors = spatial_struct->findNeighbors(i,search_radius); 130 | Particle& p = particles[i]; 131 | 132 | viscAcc[i] = glm::vec2(0.0f,0.0f); 133 | for(unsigned int n=0;nlaplace(glm::distance(p.pos,neighbor.pos),search_radius); 138 | } 139 | } 140 | 141 | //Update Final Velocity 142 | #pragma omp parallel for 143 | for(unsigned int i=0;i neighbors = spatial_struct->findNeighbors(i,search_radius); 146 | Particle& p = particles[i]; 147 | p.vel += viscosity*viscAcc[i]; 148 | } 149 | } 150 | 151 | float Solver::computeDensity(const Particle& particle, const std::vector &neighbors) 152 | { 153 | float density = 0.0f; 154 | for(unsigned int p=0;pevaluate(glm::distance(particle.proj_pos,neighbor.pos),search_radius); 158 | } 159 | return density; 160 | } 161 | 162 | float Solver::computePressureGradientSum(const Particle& particle, const std::vector &neighbors) 163 | { 164 | float pressureSum = 0.0f; 165 | 166 | glm::vec2 out_pressure = glm::vec2(0.0f, 0.0f); 167 | 168 | for(int i=0;igradient(glm::length(distVec), search_radius, glm::normalize(distVec)); 173 | } 174 | out_pressure = (1.0f/resting_density) * out_pressure; 175 | pressureSum = glm::dot(out_pressure,out_pressure); 176 | 177 | for(int i=0;igradient(glm::length(distVec), search_radius, glm::normalize(distVec)); 182 | pressureSum += glm::dot(in_pressure,in_pressure); 183 | } 184 | return pressureSum; 185 | } 186 | -------------------------------------------------------------------------------- /solver.h: -------------------------------------------------------------------------------- 1 | #ifndef SOLVER_H 2 | #define SOLVER_H 3 | #include 4 | #include "particle.h" 5 | #include "boundary.h" 6 | #include "abstractsolver.h" 7 | #include "abstractspatialstruct.h" 8 | #include "abstractkernel.h" 9 | 10 | class Solver : public AbstractSolver 11 | { 12 | public: 13 | Solver(unsigned int width, unsigned int height); 14 | ~Solver(); 15 | 16 | void solve() override; 17 | private: 18 | float computeDensity(const Particle& particle, const std::vector& neighbors); 19 | float computePressureGradientSum(const Particle& particle, const std::vector &neighbors); 20 | 21 | std::vector viscAcc; 22 | AbstractKernel* densityKernel; 23 | AbstractKernel* gradientKernel; 24 | AbstractKernel* viscKernel; 25 | }; 26 | 27 | #endif // SOLVER_H 28 | -------------------------------------------------------------------------------- /solver_gpu.cpp: -------------------------------------------------------------------------------- 1 | #include "solver_gpu.h" 2 | #include "simulation.h" 3 | #include 4 | #include 5 | #include 6 | 7 | void create_context_callback(const char* message, const void* data, size_t data_size, void* userdata) 8 | { 9 | printf("%s\n",message); 10 | exit(-1); 11 | } 12 | 13 | void create_kernel_callback(cl_program program, void* userdata) 14 | { 15 | int build_status; 16 | clGetProgramBuildInfo(program, (cl_device_id)userdata, CL_PROGRAM_BUILD_STATUS, 0, &build_status, 0); 17 | if(build_status!=CL_BUILD_SUCCESS) 18 | { 19 | size_t length; 20 | char buffer[2048]; 21 | clGetProgramBuildInfo(program, (cl_device_id)userdata, CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &length); 22 | printf("--- Build log ---\n %s\n",buffer); 23 | exit(build_status); 24 | } 25 | } 26 | 27 | SolverGPU::SolverGPU(unsigned int width, unsigned int height) : AbstractSolver(width, height) 28 | { 29 | last_num_buckets = 0; 30 | last_num_particles = 0; 31 | last_num_boundaries = 0; 32 | 33 | // Get platform and device information 34 | cl_platform_id platform_id = NULL; 35 | cl_uint ret_num_devices; 36 | cl_uint ret_num_platforms; 37 | cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms); 38 | ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_GPU, 1, 39 | &device_id, &ret_num_devices); 40 | 41 | cl_context_properties props[] = { 42 | CL_CONTEXT_PLATFORM,(cl_context_properties) platform_id, 43 | 0 44 | }; 45 | 46 | context = clCreateContext(props,1,&device_id,create_context_callback,0,&ret); 47 | 48 | queue = clCreateCommandQueueWithProperties(context,device_id,0,&ret); 49 | 50 | size_t source_len = strlen(simulation_source); 51 | program = clCreateProgramWithSource(context,1 ,&simulation_source, &source_len,nullptr); 52 | 53 | clBuildProgram(program,1,&device_id,"-cl-std=CL2.0",create_kernel_callback,device_id); 54 | bucket_count = clCreateKernel(program,"bucket_count",nullptr); 55 | histogram = clCreateKernel(program,"histogram",nullptr); 56 | histogram2 = clCreateKernel(program,"histogram2",nullptr); 57 | histogram3 = clCreateKernel(program,"histogram3",nullptr); 58 | sort_particles = clCreateKernel(program,"sort_particles",nullptr); 59 | compute_lambda = clCreateKernel(program,"compute_lambda",nullptr); 60 | update_delta = clCreateKernel(program,"update_delta",nullptr); 61 | check_boundary_collision = clCreateKernel(program,"check_boundary_collision",nullptr); 62 | update_projected_positions = clCreateKernel(program,"update_projected_positions",nullptr); 63 | 64 | add_external_forces = clCreateKernel(program,"add_external_forces",nullptr); 65 | update_particles = clCreateKernel(program,"update_particles",nullptr); 66 | 67 | } 68 | 69 | SolverGPU::~SolverGPU() 70 | { 71 | clReleaseMemObject(particles_buffer_front); 72 | clReleaseMemObject(particles_buffer_back); 73 | clReleaseMemObject(counter_buffer); 74 | clReleaseMemObject(histogram_buffer); 75 | clReleaseMemObject(scratch_buffer); 76 | clReleaseMemObject(scratch_buffer2); 77 | 78 | clReleaseKernel(bucket_count); 79 | clReleaseKernel(histogram); 80 | clReleaseKernel(histogram2); 81 | clReleaseKernel(histogram3); 82 | clReleaseKernel(sort_particles); 83 | clReleaseKernel(add_external_forces); 84 | clReleaseKernel(compute_lambda); 85 | clReleaseKernel(update_delta); 86 | clReleaseKernel(update_projected_positions); 87 | clReleaseKernel(update_particles); 88 | 89 | clReleaseProgram(program); 90 | clReleaseDevice(device_id); 91 | clReleaseContext(context); 92 | } 93 | 94 | void SolverGPU::rebuildParticleBufferGPU() 95 | { 96 | if(last_num_particles!=0) 97 | { 98 | clReleaseMemObject(particles_buffer_front); 99 | clReleaseMemObject(particles_buffer_back); 100 | } 101 | particles_buffer_front = clCreateBuffer(context,CL_MEM_READ_WRITE|CL_MEM_USE_HOST_PTR,sizeof(Particle)*particles.size(),particles.data(),nullptr); 102 | particles_buffer_back = clCreateBuffer(context,CL_MEM_READ_WRITE|CL_MEM_USE_HOST_PTR,sizeof(Particle)*particles.size(),particles.data(),nullptr); 103 | } 104 | 105 | void SolverGPU::rebuildBoundaryBufferGPU() 106 | { 107 | if(last_num_boundaries!=0) 108 | { 109 | clReleaseMemObject(boundaries_buffer); 110 | } 111 | boundaries_buffer = clCreateBuffer(context,CL_MEM_READ_ONLY|CL_MEM_USE_HOST_PTR,boundaries.size() * sizeof(Boundary), boundaries.data(), nullptr); 112 | } 113 | 114 | void SolverGPU::rebuildBucketBufferGPU() 115 | { 116 | size_t num_buckets = std::ceil(domain_width/search_radius)*std::ceil(domain_height/search_radius); 117 | if(last_num_buckets!=0) 118 | { 119 | clReleaseMemObject(counter_buffer); 120 | clReleaseMemObject(histogram_buffer); 121 | clReleaseMemObject(scratch_buffer); 122 | clReleaseMemObject(scratch_buffer2); 123 | 124 | } 125 | counter_buffer = clCreateBuffer(context,CL_MEM_READ_WRITE,num_buckets,nullptr,nullptr); 126 | histogram_buffer = clCreateBuffer(context,CL_MEM_READ_WRITE,num_buckets,nullptr,nullptr); 127 | scratch_buffer = clCreateBuffer(context,CL_MEM_READ_WRITE,num_buckets,nullptr,nullptr); 128 | scratch_buffer2 = clCreateBuffer(context,CL_MEM_READ_WRITE,num_buckets,nullptr,nullptr); 129 | } 130 | 131 | void SolverGPU::solve() 132 | { 133 | size_t num_particles = particles.size(); 134 | size_t num_borders = boundaries.size(); 135 | glm::uvec2 dims(std::ceil(domain_width/search_radius),std::ceil(domain_height/search_radius)); 136 | size_t num_buckets = dims.x * dims.y; 137 | 138 | if(num_buckets!=last_num_buckets) 139 | { 140 | rebuildBucketBufferGPU(); 141 | } 142 | if(num_particles!=last_num_particles) 143 | { 144 | rebuildParticleBufferGPU(); 145 | } 146 | if(num_borders!=last_num_boundaries) 147 | { 148 | rebuildBoundaryBufferGPU(); 149 | } 150 | 151 | unsigned int pattern = 0; 152 | 153 | clEnqueueFillBuffer(queue, counter_buffer, &pattern, sizeof(pattern),0, num_buckets * sizeof(unsigned int),0,nullptr,nullptr); 154 | clEnqueueFillBuffer(queue, scratch_buffer, &pattern, sizeof(pattern),0, num_buckets * sizeof(unsigned int),0,nullptr,nullptr); 155 | clEnqueueFillBuffer(queue, scratch_buffer2, &pattern, sizeof(pattern),0, std::ceil(num_buckets/128) * sizeof(unsigned int),0,nullptr,nullptr); 156 | 157 | clSetKernelArg(bucket_count,0,sizeof(cl_mem),&particles_buffer_front); 158 | clSetKernelArg(bucket_count,1,sizeof(cl_mem),&counter_buffer); 159 | clSetKernelArg(bucket_count,2,sizeof(glm::uvec2),glm::value_ptr(dims)); 160 | clEnqueueNDRangeKernel(queue, bucket_count,1,nullptr,&num_particles, nullptr, 0, nullptr, nullptr); 161 | 162 | size_t local_size = 128; 163 | size_t global_size = num_buckets; 164 | clSetKernelArg(histogram,0,sizeof(cl_mem),&counter_buffer); 165 | clSetKernelArg(histogram,1,sizeof(cl_mem),&histogram_buffer); 166 | clSetKernelArg(histogram,2,sizeof(cl_mem),&scratch_buffer); 167 | clEnqueueNDRangeKernel(queue, histogram,1,nullptr,&global_size, &local_size, 0, nullptr, nullptr); 168 | 169 | global_size = std::ceil(num_buckets/128.0); 170 | clSetKernelArg(histogram2,0,sizeof(cl_mem),&scratch_buffer); 171 | clSetKernelArg(histogram2,1,sizeof(cl_mem),&scratch_buffer2); 172 | clEnqueueNDRangeKernel(queue, histogram2,1,nullptr,&global_size, &local_size, 0, nullptr, nullptr); 173 | 174 | global_size=num_buckets-128; 175 | clSetKernelArg(histogram3,0,sizeof(cl_mem),&scratch_buffer2); 176 | clSetKernelArg(histogram3,1,sizeof(cl_mem),&histogram_buffer); 177 | clSetKernelArg(histogram3,2,sizeof(cl_mem),&counter_buffer); 178 | 179 | clEnqueueNDRangeKernel(queue, histogram3,1,nullptr,&global_size, &local_size, 0, nullptr, nullptr); 180 | 181 | clEnqueueFillBuffer(queue, scratch_buffer, &pattern, sizeof(pattern),0, num_buckets * sizeof(unsigned int),0,nullptr,nullptr); 182 | 183 | clSetKernelArg(sort_particles,0,sizeof(cl_mem),&particles_buffer_front); 184 | clSetKernelArg(sort_particles,1,sizeof(cl_mem),&particles_buffer_back); 185 | clSetKernelArg(sort_particles,2,sizeof(cl_mem),&histogram_buffer); 186 | clSetKernelArg(sort_particles,3,sizeof(cl_mem),&scratch_buffer); 187 | clEnqueueNDRangeKernel(queue, sort_particles,1,nullptr,&num_particles, nullptr, 0, nullptr, nullptr); 188 | 189 | clSetKernelArg(add_external_forces,0,sizeof(cl_mem),&particles_buffer_back); 190 | clSetKernelArg(add_external_forces,1,sizeof(float),×tep); 191 | clSetKernelArg(add_external_forces,2,sizeof(glm::vec2),glm::value_ptr(gravity)); 192 | clEnqueueNDRangeKernel(queue, add_external_forces,1,nullptr,&num_particles, nullptr, 0, nullptr, nullptr); 193 | 194 | for(unsigned int i=0;i 8 | 9 | class SolverGPU : public AbstractSolver 10 | { 11 | public: 12 | SolverGPU(unsigned int width, unsigned int height); 13 | ~SolverGPU(); 14 | 15 | void solve() override; 16 | 17 | private: 18 | void rebuildParticleBufferGPU(); 19 | void rebuildBoundaryBufferGPU(); 20 | void rebuildBucketBufferGPU(); 21 | 22 | unsigned int last_num_buckets; 23 | unsigned int last_num_particles; 24 | unsigned int last_num_boundaries; 25 | 26 | cl_context context; 27 | cl_command_queue queue; 28 | cl_device_id device_id; 29 | cl_program program; 30 | 31 | cl_kernel bucket_count; 32 | cl_kernel histogram; 33 | cl_kernel histogram2; 34 | cl_kernel histogram3; 35 | cl_kernel sort_particles; 36 | cl_kernel add_external_forces; 37 | cl_kernel compute_lambda; 38 | cl_kernel update_delta; 39 | cl_kernel check_boundary_collision; 40 | cl_kernel update_projected_positions; 41 | cl_kernel update_particles; 42 | 43 | 44 | cl_mem particles_buffer_front; 45 | cl_mem particles_buffer_back; 46 | cl_mem counter_buffer; 47 | cl_mem histogram_buffer; 48 | cl_mem scratch_buffer; 49 | cl_mem scratch_buffer2; 50 | 51 | cl_mem boundaries_buffer; 52 | }; 53 | 54 | #endif // SOLVER_GPU_H 55 | -------------------------------------------------------------------------------- /spikykernel.cpp: -------------------------------------------------------------------------------- 1 | #include "spikykernel.h" 2 | #include 3 | 4 | SpikyKernel::SpikyKernel() 5 | { 6 | 7 | } 8 | 9 | SpikyKernel::~SpikyKernel() 10 | { 11 | } 12 | 13 | float SpikyKernel::evaluate(float r, float h) 14 | { 15 | return (15/(M_PI*std::pow(h,6)))*std::pow(h-r,3); 16 | } 17 | 18 | glm::vec2 SpikyKernel::gradient(float r, float h,const glm::vec2 n) 19 | { 20 | if(r==0.0f) 21 | { 22 | return glm::vec2(0.0f); 23 | } 24 | return ((float)(-45.0f/(M_PI*std::pow(h,6))*std::pow(h-r,2)))*n; 25 | } 26 | 27 | float SpikyKernel::laplace(float r, float h) 28 | { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /spikykernel.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIKYKERNEL_H 2 | #define SPIKYKERNEL_H 3 | #include 4 | 5 | class SpikyKernel : public AbstractKernel 6 | { 7 | public: 8 | SpikyKernel(); 9 | ~SpikyKernel(); 10 | float evaluate(float r, float h) override; 11 | glm::vec2 gradient(float r, float h, const glm::vec2 n) override; 12 | float laplace(float r, float h) override; 13 | }; 14 | 15 | #endif // POLY6KERNEL_H 16 | -------------------------------------------------------------------------------- /visckernel.cpp: -------------------------------------------------------------------------------- 1 | #include "visckernel.h" 2 | #include 3 | 4 | ViscKernel::ViscKernel() 5 | { 6 | 7 | } 8 | 9 | ViscKernel::~ViscKernel() 10 | { 11 | 12 | } 13 | 14 | float ViscKernel::evaluate(float r, float h) 15 | { 16 | return 0.0f; 17 | } 18 | 19 | glm::vec2 ViscKernel::gradient(float r, float h, const glm::vec2 n) 20 | { 21 | return glm::vec2(0.0f,0.0f); 22 | } 23 | 24 | float ViscKernel::laplace(float r, float h) 25 | { 26 | 27 | if(r==0.0f) 28 | { 29 | return 0.0f; 30 | } 31 | return 45.0f/(M_PI*std::pow(h,6))*(h-r); 32 | } 33 | -------------------------------------------------------------------------------- /visckernel.h: -------------------------------------------------------------------------------- 1 | #ifndef VISCKERNEL_H 2 | #define VISC6KERNEL_H 3 | #include 4 | 5 | class ViscKernel : public AbstractKernel 6 | { 7 | public: 8 | ViscKernel(); 9 | ~ViscKernel(); 10 | float evaluate(float r, float h) override; 11 | glm::vec2 gradient(float r, float h, const glm::vec2 n) override; 12 | float laplace(float r, float h) override; 13 | }; 14 | 15 | #endif // POLY6KERNEL_H 16 | --------------------------------------------------------------------------------