├── .gitignore ├── CMakeLists.txt ├── INSTALL ├── README.md ├── cmake ├── FindEigen3.cmake └── FindUmfpack.cmake ├── demo.gif ├── doc ├── model-notes.lyx └── model-notes.pdf ├── res └── android.jpg └── src ├── BiFlattener.hpp ├── CMakeLists.txt ├── qtgui ├── CMakeLists.txt ├── MainWindow.cpp ├── MainWindow.h ├── MainWindow.ui ├── MyGLWidget.cpp ├── MyGLWidget.h ├── main.cpp └── main.qrc ├── rendering ├── ImageRenderer.cpp ├── ImageRenderer.h ├── MeshRenderer.cpp └── MeshRenderer.h └── simulation ├── GridData.cpp ├── GridData.h ├── InteractionController.cpp ├── InteractionController.h ├── Perturbator.cpp ├── Perturbator.h ├── Simulation.cpp └── Simulation.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | CMakeLists.txt.user* 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | 3 | project("Wave simulation" CXX C) 4 | find_package(Boost 1.46.0 REQUIRED) 5 | find_package(Qt4 REQUIRED QtCore QtGui QtOpenGL) 6 | find_package(OpenGL) 7 | 8 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") 9 | find_package(Eigen3 3.0.0 REQUIRED) 10 | find_package(Umfpack) 11 | 12 | add_subdirectory(src) 13 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | 2 | === Dependencies === 3 | Boost, Qt4 4 | Eigen3 libeigen3-dev 5 | UmfPack libsuitesparse-dev 6 | 7 | Also, some features from C++11 are used in the code, so you'll need at least 8 | partially complying compiler to build it; GCC 4.6.1 worked just fine for me. 9 | 10 | === Build === 11 | Standard CMake build: 12 | $ mkdir build 13 | $ ( cd build && cmake .. ) 14 | $ make -sC build 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wave-simulation 2 | =============== 3 | 4 | *A C++11/CMake/Boost/Qt/OpenGL excersize.* 5 | 6 | This code started in Feb 2012 as an employment excersize. Local Samsung R&D center[¹] gave me an assignment, which was roughly formulated as: 7 | > Make a wave going over a picture. Bonus points for reflections, radial waves, etc. 8 | 9 | [¹]: http://www.samsung.com/ua/aboutsamsung/careers/samsungukraineresearchcenter/SamsungUkraineResearchCenter.html 10 | 11 | I approached the problem by continuously solving the two-dimensional [wave equation] for a 2D displacement vector field using the [finite difference method]. Then there was a visualization using [QGLWidget]. 12 | 13 | The result looks like this: 14 | ![](demo.gif) 15 | *(15 MiB GIF, be patient).* 16 | 17 | [wave equation]: https://en.wikipedia.org/wiki/Wave_equation 18 | [finite difference method]: http://en.wikipedia.org/wiki/Finite_difference_method 19 | [QGLWidget]: http://qt-project.org/doc/qt-4.8/qglwidget.html 20 | 21 | ---- 22 | 23 | The primary use of the code was **to be read**, by my interviewers. Thus I had a lovely excuse to making the whole project's code pretty and enjoyable to read. Yet it still even works! 24 | 25 | The points I'm especially proud of in the codebase: 26 | 27 | * The use of a descriptive build system, [CMake]. Just start reading from the top-level [`CMakeLists.txt`](CMakeLists.txt), then compare that with what you'd be seeing with MSVC, or say, XCode project files. Hint: massive repetitive barely readable, uneditable, un-vcs-mergeable hunks of GUID-filled piles of XML — none of that with CMake. That's why I love it (and also for nice&clean colorful `make` output with build percentage gauge; and for the `ccmake` build configuration tool; also for cross-platform generator backends (which means CMake generates GNU Makefiles, MSVC `.vcproj` files, XCode `.xcodeproj`'s, etc — so that you can build the same CMake project on different platforms with different compilers and toolchains), easy IDE integration and modularity. Wait, did I just forget to mention its (by itself awesome) [backwards compatibility management system][cmake policies]?). Yeah... I love CMake. 28 | 29 | [CMake]: http://www.cmake.org/ 30 | [CMake policies]: http://www.cmake.org/Wiki/CMake/Policies 31 | 32 | * The use of C++11. Back in February 2012 it was still new and cool and exciting. You'll find quite some C++11 features used directly in the code; as this was my personal project, I dropped them in whenever I could, just to try it out. Great stuff. 33 | 34 | * The separation of GUI-unrelated simulation logic from GUI itself. The core code is actually compiled as an independent static library (again, enjoy the breeze of CMake handling it), which doesn't link to Qt. Even for this small-sized project proper decoupling and separation of concerns is important: coding ODE solving inside the `onTimer` Qt event handler would be really bad engineering. In fact, I wanted to build an Android GUI too, very possibly with some touch input — and for this to ever happen (Qt 5 wasn't yet released back then), it'd be necessary for the core simulation library to be strictly separated from Qt GUI. And then some NDK with JNI, but that's an easy part. 35 | 36 | * The use of external components. As much as I liked braking up the monolithic project to loosely coupled components, I liked plugging in open-source components which I didn't write. I used Qt for GUI, OpenGL for drawing (like, 4 or 5 gl calls total in the code — but still, it's used), [Eigen] with [UmfPack] plugin for solving sparse algebra. I also like how all the dependencies are clearly declared in the top-level [`CMakeLists.txt`](CMakeLists.txt). It's... beautiful. 37 | 38 | [Eigen]: http://eigen.tuxfamily.org 39 | [UmfPack]: http://www.cise.ufl.edu/research/sparse/umfpack/ 40 | 41 | I hope you enjoy reading the code as much as I enjoyed writing it. 42 | 43 | Feel free to drop me a letter about how you feel about this. 44 | -------------------------------------------------------------------------------- /cmake/FindEigen3.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Eigen3 lib 2 | # 3 | # This module supports requiring a minimum version, e.g. you can do 4 | # find_package(Eigen3 3.1.2) 5 | # to require version 3.1.2 or newer of Eigen3. 6 | # 7 | # Once done this will define 8 | # 9 | # EIGEN3_FOUND - system has eigen lib with correct version 10 | # EIGEN3_INCLUDE_DIR - the eigen include directory 11 | # EIGEN3_VERSION - eigen version 12 | 13 | # Copyright (c) 2006, 2007 Montel Laurent, 14 | # Copyright (c) 2008, 2009 Gael Guennebaud, 15 | # Copyright (c) 2009 Benoit Jacob 16 | # Redistribution and use is allowed according to the terms of the 2-clause BSD license. 17 | 18 | if(NOT Eigen3_FIND_VERSION) 19 | if(NOT Eigen3_FIND_VERSION_MAJOR) 20 | set(Eigen3_FIND_VERSION_MAJOR 2) 21 | endif(NOT Eigen3_FIND_VERSION_MAJOR) 22 | if(NOT Eigen3_FIND_VERSION_MINOR) 23 | set(Eigen3_FIND_VERSION_MINOR 91) 24 | endif(NOT Eigen3_FIND_VERSION_MINOR) 25 | if(NOT Eigen3_FIND_VERSION_PATCH) 26 | set(Eigen3_FIND_VERSION_PATCH 0) 27 | endif(NOT Eigen3_FIND_VERSION_PATCH) 28 | 29 | set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") 30 | endif(NOT Eigen3_FIND_VERSION) 31 | 32 | macro(_eigen3_check_version) 33 | file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) 34 | 35 | string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") 36 | set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") 37 | string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") 38 | set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") 39 | string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") 40 | set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") 41 | 42 | set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) 43 | if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 44 | set(EIGEN3_VERSION_OK FALSE) 45 | else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 46 | set(EIGEN3_VERSION_OK TRUE) 47 | endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 48 | 49 | if(NOT EIGEN3_VERSION_OK) 50 | 51 | message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " 52 | "but at least version ${Eigen3_FIND_VERSION} is required") 53 | endif(NOT EIGEN3_VERSION_OK) 54 | endmacro(_eigen3_check_version) 55 | 56 | if (EIGEN3_INCLUDE_DIR) 57 | 58 | # in cache already 59 | _eigen3_check_version() 60 | set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) 61 | 62 | else (EIGEN3_INCLUDE_DIR) 63 | 64 | find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library 65 | PATHS 66 | ${CMAKE_INSTALL_PREFIX}/include 67 | ${KDE4_INCLUDE_DIR} 68 | PATH_SUFFIXES eigen3 eigen 69 | ) 70 | 71 | if(EIGEN3_INCLUDE_DIR) 72 | _eigen3_check_version() 73 | endif(EIGEN3_INCLUDE_DIR) 74 | 75 | include(FindPackageHandleStandardArgs) 76 | find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) 77 | 78 | mark_as_advanced(EIGEN3_INCLUDE_DIR) 79 | 80 | endif(EIGEN3_INCLUDE_DIR) 81 | 82 | -------------------------------------------------------------------------------- /cmake/FindUmfpack.cmake: -------------------------------------------------------------------------------- 1 | # Umfpack lib usually requires linking to a blas library. 2 | # It is up to the user of this module to find a BLAS and link to it. 3 | 4 | if (UMFPACK_INCLUDES AND UMFPACK_LIBRARIES) 5 | set(UMFPACK_FIND_QUIETLY TRUE) 6 | endif (UMFPACK_INCLUDES AND UMFPACK_LIBRARIES) 7 | 8 | find_path(UMFPACK_INCLUDES 9 | NAMES 10 | umfpack.h 11 | PATHS 12 | $ENV{UMFPACKDIR} 13 | ${INCLUDE_INSTALL_DIR} 14 | PATH_SUFFIXES 15 | suitesparse 16 | ) 17 | 18 | find_library(UMFPACK_LIBRARIES umfpack PATHS $ENV{UMFPACKDIR} ${LIB_INSTALL_DIR}) 19 | 20 | if(UMFPACK_LIBRARIES) 21 | 22 | get_filename_component(UMFPACK_LIBDIR ${UMFPACK_LIBRARIES} PATH) 23 | 24 | find_library(AMD_LIBRARY amd PATHS ${UMFPACK_LIBDIR} $ENV{UMFPACKDIR} ${LIB_INSTALL_DIR}) 25 | if (AMD_LIBRARY) 26 | set(UMFPACK_LIBRARIES ${UMFPACK_LIBRARIES} ${AMD_LIBRARY}) 27 | #else (AMD_LIBRARY) 28 | # set(UMFPACK_LIBRARIES FALSE) 29 | endif (AMD_LIBRARY) 30 | 31 | endif(UMFPACK_LIBRARIES) 32 | 33 | if(UMFPACK_LIBRARIES) 34 | 35 | find_library(COLAMD_LIBRARY colamd PATHS ${UMFPACK_LIBDIR} $ENV{UMFPACKDIR} ${LIB_INSTALL_DIR}) 36 | if (COLAMD_LIBRARY) 37 | set(UMFPACK_LIBRARIES ${UMFPACK_LIBRARIES} ${COLAMD_LIBRARY}) 38 | #else (COLAMD_LIBRARY) 39 | # set(UMFPACK_LIBRARIES FALSE) 40 | endif (COLAMD_LIBRARY) 41 | 42 | endif(UMFPACK_LIBRARIES) 43 | 44 | 45 | include(FindPackageHandleStandardArgs) 46 | find_package_handle_standard_args(UMFPACK DEFAULT_MSG 47 | UMFPACK_INCLUDES UMFPACK_LIBRARIES) 48 | 49 | mark_as_advanced(UMFPACK_INCLUDES UMFPACK_LIBRARIES AMD_LIBRARY COLAMD_LIBRARY) 50 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulidtko/wave-simulation/803453738f2de237238eb8bf4bc42d95c1a965ee/demo.gif -------------------------------------------------------------------------------- /doc/model-notes.lyx: -------------------------------------------------------------------------------- 1 | #LyX 2.0 created this file. For more info see http://www.lyx.org/ 2 | \lyxformat 413 3 | \begin_document 4 | \begin_header 5 | \textclass article 6 | \use_default_options true 7 | \begin_modules 8 | logicalmkup 9 | fixltx2e 10 | theorems-starred 11 | \end_modules 12 | \maintain_unincluded_children false 13 | \language russian 14 | \language_package default 15 | \inputencoding utf8 16 | \fontencoding global 17 | \font_roman Liberation Serif 18 | \font_sans DejaVu Sans 19 | \font_typewriter DejaVu Sans Mono 20 | \font_default_family default 21 | \use_non_tex_fonts true 22 | \font_sc false 23 | \font_osf false 24 | \font_sf_scale 100 25 | \font_tt_scale 80 26 | 27 | \graphics default 28 | \default_output_format default 29 | \output_sync 0 30 | \bibtex_command default 31 | \index_command default 32 | \paperfontsize default 33 | \spacing single 34 | \use_hyperref false 35 | \papersize default 36 | \use_geometry false 37 | \use_amsmath 1 38 | \use_esint 1 39 | \use_mhchem 1 40 | \use_mathdots 1 41 | \cite_engine basic 42 | \use_bibtopic false 43 | \use_indices false 44 | \paperorientation portrait 45 | \suppress_date false 46 | \use_refstyle 1 47 | \index Index 48 | \shortcut idx 49 | \color #008000 50 | \end_index 51 | \secnumdepth 0 52 | \tocdepth 3 53 | \paragraph_separation indent 54 | \paragraph_indentation default 55 | \quotes_language french 56 | \papercolumns 1 57 | \papersides 1 58 | \paperpagestyle default 59 | \listings_params "basicstyle={\ttfamily},extendedchars=false" 60 | \tracking_changes false 61 | \output_changes false 62 | \html_math_output 0 63 | \html_css_as_file 0 64 | \html_be_strict false 65 | \end_header 66 | 67 | \begin_body 68 | 69 | \begin_layout Section* 70 | Model 71 | \end_layout 72 | 73 | \begin_layout Standard 74 | Let us build a model of elastic 2D grid. 75 | \end_layout 76 | 77 | \begin_layout Standard 78 | \begin_inset Formula $U(x,y,t)\rightarrow\mathbb{R}^{2}$ 79 | \end_inset 80 | 81 | , 82 | \end_layout 83 | 84 | \begin_layout Standard 85 | \begin_inset Formula $U(x,y,t)=\left(u(x,y,t),\, v(x,y,t)\right);$ 86 | \end_inset 87 | 88 | 89 | \end_layout 90 | 91 | \begin_layout Standard 92 | \begin_inset Formula $U$ 93 | \end_inset 94 | 95 | is vector function which represents displacement field, consisting at each 96 | spacetime point of horizontal and vertical displacement. 97 | \end_layout 98 | 99 | \begin_layout Section* 100 | Wave equation 101 | \end_layout 102 | 103 | \begin_layout Standard 104 | The well-known wave equation for our displacement function: 105 | \end_layout 106 | 107 | \begin_layout Standard 108 | \begin_inset Formula 109 | \[ 110 | U_{tt}=c^{2}\left(U_{xx}+U_{yy}\right); 111 | \] 112 | 113 | \end_inset 114 | 115 | where 116 | \begin_inset Formula $c$ 117 | \end_inset 118 | 119 | is the wave propagation speed which parametrizes medium properties. 120 | \end_layout 121 | 122 | \begin_layout Section* 123 | Discretization and integration 124 | \end_layout 125 | 126 | \begin_layout Standard 127 | We will discretize out model over even space grid with step 128 | \begin_inset Formula $h$ 129 | \end_inset 130 | 131 | and even time intervals with step 132 | \begin_inset Formula $d$ 133 | \end_inset 134 | 135 | . 136 | Thus we get values 137 | \begin_inset Formula $U_{i,j}^{n}=\left(u_{i,j}^{n},v_{i,j}^{n}\right)$ 138 | \end_inset 139 | 140 | , indexed by space indexes 141 | \begin_inset Formula $i$ 142 | \end_inset 143 | 144 | , 145 | \begin_inset Formula $j$ 146 | \end_inset 147 | 148 | and timestep index 149 | \begin_inset Formula $n$ 150 | \end_inset 151 | 152 | . 153 | \end_layout 154 | 155 | \begin_layout Standard 156 | Finite difference approximation for spatial laplacians, using the well-known 157 | five-point stencil: 158 | \end_layout 159 | 160 | \begin_layout Standard 161 | \begin_inset Formula 162 | \[ 163 | {\displaystyle \Delta U_{i,j}^{n}\approx\frac{1}{h^{2}}\left(U_{i+1,j}^{n}+U_{i-1,j}^{n}+U_{i,j+1}^{n}+U_{i,j-1}^{n}-4\cdot U_{i,j}^{n}\right)}. 164 | \] 165 | 166 | \end_inset 167 | 168 | 169 | \end_layout 170 | 171 | \begin_layout Standard 172 | Backward finite difference approximation for second time derivative: 173 | \end_layout 174 | 175 | \begin_layout Standard 176 | \begin_inset Formula 177 | \[ 178 | \left(U_{tt}\right)_{i,j}^{n}\approx\frac{1}{d^{2}}\left(2U_{i,j}^{n}-5U_{i,j}^{n-1}+4U_{i,j}^{n-2}-U_{i,j}^{n-3}\right). 179 | \] 180 | 181 | \end_inset 182 | 183 | 184 | \end_layout 185 | 186 | \begin_layout Standard 187 | Explicit scheme: 188 | \begin_inset Formula 189 | \[ 190 | \left(U_{tt}\right)_{i,j}^{n+1}=c^{2}\cdot\Delta U_{i,j}^{n}. 191 | \] 192 | 193 | \end_inset 194 | 195 | 196 | \end_layout 197 | 198 | \begin_layout Standard 199 | Substituting the approximations into it we get explicit solution: 200 | \begin_inset Formula 201 | \[ 202 | \frac{2U_{i,j}^{n+1}-5U_{i,j}^{n}+4U_{i,j}^{n-1}-U_{i,j}^{n-2}}{d^{2}}=c^{2}\cdot\Delta U_{i,j}^{n} 203 | \] 204 | 205 | \end_inset 206 | 207 | 208 | \begin_inset Formula 209 | \[ 210 | U_{i,j}^{n+1}=\frac{1}{2}\left(c^{2}d^{2}\cdot\Delta U_{i,j}^{n}+5U_{i,j}^{n}-4U_{i,j}^{n-1}+U_{i,j}^{n-2}\right), 211 | \] 212 | 213 | \end_inset 214 | 215 | 216 | \begin_inset Formula 217 | \[ 218 | U_{i,j}^{n+1}=\frac{1}{2}\left(c^{2}\cdot\frac{d^{2}}{h^{2}}\left(U_{i+1,j}^{n}+U_{i-1,j}^{n}+U_{i,j+1}^{n}+U_{i,j-1}^{n}-4\cdot U_{i,j}^{n}\right)+5U_{i,j}^{n}-4U_{i,j}^{n-1}+U_{i,j}^{n-2}\right). 219 | \] 220 | 221 | \end_inset 222 | 223 | 224 | \end_layout 225 | 226 | \begin_layout Section 227 | Stability considerations 228 | \end_layout 229 | 230 | \begin_layout Standard 231 | Explicit solution blows up. 232 | It's unstable as long as time and grid steps are not small enough. 233 | \end_layout 234 | 235 | \begin_layout Section 236 | Implicit solution 237 | \begin_inset Formula 238 | \[ 239 | \left(U_{tt}\right)_{i,j}^{n+1}=c^{2}\cdot\Delta U_{i,j}^{n+1}; 240 | \] 241 | 242 | \end_inset 243 | 244 | 245 | \end_layout 246 | 247 | \begin_layout Standard 248 | \begin_inset Formula 249 | \[ 250 | \frac{2U_{i,j}^{n+1}-5U_{i,j}^{n}+4U_{i,j}^{n-1}-U_{i,j}^{n-2}}{d^{2}}=c^{2}\cdot\Delta U_{i,j}^{n+1}; 251 | \] 252 | 253 | \end_inset 254 | 255 | 256 | \begin_inset Formula 257 | \[ 258 | 2U_{i,j}^{n+1}-5U_{i,j}^{n}+4U_{i,j}^{n-1}-U_{i,j}^{n-2}=c^{2}\cdot\frac{d^{2}}{h^{2}}\left(U_{i+1,j}^{n+1}+U_{i-1,j}^{n+1}+U_{i,j+1}^{n+1}+U_{i,j-1}^{n+1}-4\cdot U_{i,j}^{n+1}\right); 259 | \] 260 | 261 | \end_inset 262 | 263 | 264 | \end_layout 265 | 266 | \begin_layout Standard 267 | abbreviate 268 | \begin_inset Formula $r=c^{2}\cdot\frac{d^{2}}{h^{2}}$ 269 | \end_inset 270 | 271 | ; 272 | \begin_inset Formula 273 | \[ 274 | \left(2+4r\right)U_{i,j}^{n+1}-r\left(U_{i+1,j}^{n+1}+U_{i-1,j}^{n+1}+U_{i,j+1}^{n+1}+U_{i,j-1}^{n+1}\right)=5U_{i,j}^{n}-4U_{i,j}^{n-1}+U_{i,j}^{n-2}. 275 | \] 276 | 277 | \end_inset 278 | 279 | 280 | \end_layout 281 | 282 | \end_body 283 | \end_document 284 | -------------------------------------------------------------------------------- /doc/model-notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulidtko/wave-simulation/803453738f2de237238eb8bf4bc42d95c1a965ee/doc/model-notes.pdf -------------------------------------------------------------------------------- /res/android.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ulidtko/wave-simulation/803453738f2de237238eb8bf4bc42d95c1a965ee/res/android.jpg -------------------------------------------------------------------------------- /src/BiFlattener.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BIFLATTENER_HPP 2 | #define BIFLATTENER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | /** A bidirectional map (bijection) between 11 | * pairs of integers and a single (usually bigger) integer: 12 | * (Int, Int) <--> Int 13 | * Uses a stride parameter to densely and efficiently pack all 14 | * possible (1..M, 1..N) pairs into exactly M times N consecutive integers. 15 | **/ 16 | template 17 | struct BiFlattener 18 | { 19 | BOOST_STATIC_ASSERT_MSG(std::is_integral::value, 20 | "usable only with integral types"); 21 | 22 | typedef T narrow_type; 23 | typedef typename boost::integral_promotion::type wider_type; 24 | 25 | wider_type flatten(narrow_type i, narrow_type j) { return i + j * stride; } 26 | 27 | std::tuple 28 | unflatten(wider_type k) { return std::make_tuple(k % stride, k / stride); } 29 | 30 | const narrow_type stride; 31 | BiFlattener(narrow_type d) : stride(d) {} 32 | }; 33 | 34 | #endif // BIFLATTENER_HPP 35 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(. 2 | ${Boost_INCLUDE_DIRS} 3 | ${QT_INCLUDES} 4 | ${EIGEN3_INCLUDE_DIR} 5 | ${UMFPACK_INCLUDES} 6 | ) 7 | 8 | set(CMAKE_CXX_FLAGS "-std=c++11 -Wall") 9 | 10 | add_library(core STATIC 11 | simulation/Simulation.cpp 12 | simulation/GridData.cpp 13 | simulation/Perturbator.cpp 14 | simulation/InteractionController.cpp 15 | rendering/MeshRenderer.cpp 16 | rendering/ImageRenderer.cpp 17 | ) 18 | 19 | target_link_libraries(core 20 | ${UMFPACK_LIBRARIES} 21 | ) 22 | 23 | add_subdirectory(qtgui) 24 | -------------------------------------------------------------------------------- /src/qtgui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(${QT_USE_FILE}) 2 | 3 | QT4_WRAP_CPP(qtgui_MOC 4 | MainWindow.h 5 | MyGLWidget.h 6 | ) 7 | QT4_WRAP_UI(qtgui_FORMS MainWindow.ui) 8 | include_directories(. ${CMAKE_CURRENT_BINARY_DIR}) #-- a workaround for generated headers 9 | 10 | QT4_ADD_RESOURCES(qtgui_RCC main.qrc) 11 | 12 | add_executable(qtgui 13 | main.cpp 14 | MainWindow.cpp 15 | MyGLWidget.cpp 16 | ${qtgui_MOC} 17 | ${qtgui_FORMS} 18 | ${qtgui_RCC} 19 | ) 20 | 21 | target_link_libraries(qtgui 22 | core 23 | ${QT_LIBRARIES} 24 | ${OPENGL_LIBRARIES} 25 | ) 26 | -------------------------------------------------------------------------------- /src/qtgui/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "ui_MainWindow.h" 3 | 4 | #include 5 | 6 | const float kMaxTps = 20.0; // simulation ticks per second 7 | 8 | MainWindow::MainWindow(QWidget *parent) 9 | : QMainWindow(parent) 10 | , ui(new Ui::MainWindow) 11 | , grid(new Grid(12.0, 8.0, 0.2)) 12 | , simulation(new Simulation(grid, 1.0 / kMaxTps, 3.0)) 13 | { 14 | ui->setupUi(this); 15 | ui->centralwidget->setGrid(grid); 16 | ui->centralwidget->setSimulation(simulation); 17 | 18 | simulation->reset(); 19 | simulation->filters.emplace_back(std::ref( 20 | simulation->interactor.getInteractivityFilter() 21 | )); 22 | 23 | timer.setInterval(1000 / kMaxTps); 24 | this->connect(&timer, SIGNAL(timeout()), SLOT(onTimer())); 25 | timer.start(); 26 | } 27 | 28 | void MainWindow::onTimer() { 29 | simulation->advanceOneTick(); 30 | ui->centralwidget->repaint(); 31 | } 32 | -------------------------------------------------------------------------------- /src/qtgui/MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef Q_MOC_RUN // workaround for QTBUG-22829 9 | 10 | #include "simulation/GridData.h" 11 | #include "simulation/Simulation.h" 12 | 13 | #endif // Q_MOC_RUN 14 | 15 | namespace Ui { 16 | class MainWindow; 17 | } 18 | 19 | class MainWindow : public QMainWindow 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit MainWindow(QWidget *parent = 0); 25 | 26 | private: 27 | std::shared_ptr ui; 28 | 29 | std::shared_ptr grid; 30 | std::shared_ptr simulation; 31 | 32 | QTimer timer; 33 | 34 | private slots: 35 | void onTimer(); 36 | }; 37 | 38 | #endif // MAINWINDOW_H 39 | -------------------------------------------------------------------------------- /src/qtgui/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | MyGLWidget 23 | QWidget 24 |
MyGLWidget.h
25 |
26 |
27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /src/qtgui/MyGLWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "MyGLWidget.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "simulation/Simulation.h" 11 | #include "rendering/MeshRenderer.h" 12 | #include "rendering/ImageRenderer.h" 13 | 14 | void MyGLWidget::initializeGL() { 15 | glEnable(GL_TEXTURE_2D); 16 | glClearColor(0.9, 0.9, 0.9, 0.0); 17 | 18 | auto img = QImage(":/android.jpg"); 19 | texName = bindTexture(img, GL_TEXTURE_2D, GL_RGB, 20 | QGLContext::InvertedYBindOption | QGLContext::LinearFilteringBindOption); 21 | } 22 | 23 | void MyGLWidget::resizeGL(int w, int h) { 24 | glViewport(0, 0, w, h); 25 | 26 | glMatrixMode(GL_PROJECTION); 27 | glLoadIdentity(); 28 | int m = std::min(w, h); 29 | glOrtho(-0.5*w/m, +9.0*w/m, -1.0*h/m, +8.5*h/m, -1.0, 1.0); 30 | 31 | glMatrixMode(GL_MODELVIEW); 32 | } 33 | 34 | void MyGLWidget::paintGL() { 35 | glClear(GL_COLOR_BUFFER_BIT); 36 | 37 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); 38 | glBindTexture(GL_TEXTURE_2D, texName); 39 | 40 | //ImageRenderer::render(*grid); 41 | MeshRenderer::render(*grid); 42 | } 43 | 44 | tuple window2scene(const QPoint& winCoord) { 45 | /* this function is © 1997-2014 NeHe Productions at Gamedev.net */ 46 | GLint viewport[4]; 47 | GLdouble modelview[16]; 48 | GLdouble projection[16]; 49 | 50 | glGetDoublev(GL_MODELVIEW_MATRIX, modelview); 51 | glGetDoublev(GL_PROJECTION_MATRIX, projection); 52 | glGetIntegerv(GL_VIEWPORT, viewport); 53 | 54 | GLfloat winX = winCoord.x(); 55 | GLfloat winY = viewport[3] - winCoord.y(); 56 | GLfloat winZ; 57 | glReadPixels(winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ); 58 | 59 | GLdouble resX, resY, resZ; 60 | gluUnProject(winX, winY, winZ, 61 | modelview, projection, viewport, 62 | &resX, &resY, &resZ); 63 | return std::make_tuple(resX, resY); 64 | } 65 | 66 | void MyGLWidget::mousePressEvent(QMouseEvent* e) { 67 | simulation->interactor.action1(window2scene(e->pos())); 68 | } 69 | 70 | void MyGLWidget::mouseMoveEvent(QMouseEvent* e) { 71 | 72 | } 73 | 74 | void MyGLWidget::mouseReleaseEvent(QMouseEvent* e) { 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/qtgui/MyGLWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef MYGLWIDGET_H 2 | #define MYGLWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | class Grid; 8 | class Simulation; 9 | 10 | class MyGLWidget : public QGLWidget 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit MyGLWidget(QWidget *parent = 0) 15 | : QGLWidget(parent) 16 | {} 17 | 18 | void setGrid(std::shared_ptr arg) { grid = arg; } 19 | void setSimulation(std::shared_ptr arg) { simulation = arg; } 20 | 21 | protected: 22 | void initializeGL(); 23 | void resizeGL(int w, int h); 24 | void paintGL(); 25 | 26 | protected: 27 | void mousePressEvent(QMouseEvent* e); 28 | void mouseMoveEvent(QMouseEvent* e); 29 | void mouseReleaseEvent(QMouseEvent* e); 30 | 31 | private: 32 | GLuint texName; 33 | 34 | std::shared_ptr grid; 35 | std::shared_ptr simulation; 36 | }; 37 | 38 | #endif // MYGLWIDGET_H 39 | -------------------------------------------------------------------------------- /src/qtgui/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "MainWindow.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | QApplication app(argc, argv); 7 | app.setApplicationName("Wave Simulation"); 8 | 9 | MainWindow w; 10 | w.show(); 11 | 12 | return app.exec(); 13 | } 14 | -------------------------------------------------------------------------------- /src/qtgui/main.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ../../res/android.jpg 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/rendering/ImageRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageRenderer.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | using std::tuple; 8 | using std::tie; 9 | 10 | template 11 | void inline glVertex2(Float x, Float y); 12 | template<> void glVertex2(double x, double y) { glVertex2d(x, y); } 13 | template<> void glVertex2(float x, float y) { glVertex2f(x, y); } 14 | 15 | template 16 | void inline glVertex(const tuple& p2) { 17 | Float x, y; 18 | tie(x, y) = p2; 19 | glVertex2(x, y); 20 | } 21 | 22 | template 23 | void inline glTexCoord2(T x, T y); 24 | template<> void glTexCoord2(double x, double y) { glTexCoord2d(x, y); } 25 | template<> void glTexCoord2(float x, float y) { glTexCoord2f(x, y); } 26 | 27 | template 28 | void inline glTexCoord(const tuple& p2) { 29 | T x, y; 30 | tie(x, y) = p2; 31 | glTexCoord2(x, y); 32 | } 33 | 34 | 35 | void ImageRenderer::render(const Grid &grid) { 36 | std::array grid_shape; 37 | std::copy_n(grid.data.shape(), 3, grid_shape.begin()); 38 | 39 | for(size_t i = 1; i < grid_shape[0]; ++i) { 40 | glBegin(GL_QUAD_STRIP); 41 | for(size_t j = 1; j < grid_shape[1]; ++j) { 42 | glTexCoord(grid.getNodeOrigin(i-1, j-1)); glVertex(grid.getNodePosition(i-1, j-1)); 43 | glTexCoord(grid.getNodeOrigin(i, j-1)); glVertex(grid.getNodePosition(i, j-1)); 44 | glTexCoord(grid.getNodeOrigin(i-1, j)); glVertex(grid.getNodePosition(i-1, j)); 45 | glTexCoord(grid.getNodeOrigin(i, j)); glVertex(grid.getNodePosition(i, j)); 46 | } 47 | glEnd(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/rendering/ImageRenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGERENDERER_H 2 | #define IMAGERENDERER_H 3 | 4 | #include "simulation/GridData.h" 5 | 6 | class ImageRenderer 7 | { 8 | public: 9 | static void render(const Grid& grid); 10 | }; 11 | 12 | #endif // IMAGERENDERER_H 13 | -------------------------------------------------------------------------------- /src/rendering/MeshRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshRenderer.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "simulation/GridData.h" 7 | 8 | using std::tie; 9 | 10 | void MeshRenderer::render(const Grid& grid) { 11 | glClear(GL_COLOR_BUFFER_BIT); 12 | 13 | glColor3ub(255, 255, 255); 14 | 15 | std::array grid_shape; 16 | std::copy_n(grid.data.shape(), 3, grid_shape.begin()); 17 | 18 | for(size_t i = 0; i < grid_shape[0]; ++i) { 19 | glBegin(GL_LINE_STRIP); 20 | for(size_t j = 0; j < grid_shape[1]; ++j) { 21 | double x, y; 22 | tie(x, y) = grid.getNodePosition(i, j); 23 | glVertex2d(x, y); 24 | } 25 | glEnd(); 26 | } 27 | 28 | for(size_t j = 0; j < grid_shape[1]; ++j) { 29 | glBegin(GL_LINE_STRIP); 30 | for(size_t i = 0; i < grid_shape[0]; ++i) { 31 | double x, y; 32 | tie(x, y) = grid.getNodePosition(i, j); 33 | glVertex2d(x, y); 34 | } 35 | glEnd(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/rendering/MeshRenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef MESHRENDERER_H 2 | #define MESHRENDERER_H 3 | 4 | class Grid; 5 | 6 | class MeshRenderer 7 | { 8 | public: 9 | static void render(const Grid& grid); 10 | }; 11 | 12 | #endif // MESHRENDERER_H 13 | -------------------------------------------------------------------------------- /src/simulation/GridData.cpp: -------------------------------------------------------------------------------- 1 | #include "GridData.h" 2 | 3 | #include 4 | 5 | using std::make_tuple; 6 | using std::get; 7 | 8 | Grid::Grid(float width, float height, float grid_step) 9 | : width(width) 10 | , height(height) 11 | , grid_step(grid_step) 12 | , shape(this->initShape()) 13 | , data(shape) 14 | {} 15 | 16 | decltype(Grid::shape) 17 | Grid::initShape() 18 | { 19 | uint width_cells = width / grid_step; 20 | uint height_cells = height / grid_step; 21 | 22 | return {width_cells, height_cells, 2}; 23 | } 24 | 25 | 26 | void Grid::fill(double value) 27 | { 28 | for(auto d2 : data) 29 | for(auto d1 : d2) 30 | for(auto& el : d1) 31 | { 32 | el = value; 33 | } 34 | } 35 | 36 | 37 | tuple Grid::getNodeOrigin(int i, int j) const { 38 | return make_tuple(i * grid_step, j * grid_step); 39 | } 40 | 41 | 42 | tuple Grid::getNodePosition(int i, int j) const { 43 | return make_tuple(i * grid_step + data[i][j][0], 44 | j * grid_step + data[i][j][1]); 45 | } 46 | 47 | tuple Grid::getNearestNode(tuple pos) const { 48 | return make_tuple(round(get<0>(pos) / grid_step), 49 | round(get<1>(pos) / grid_step)); 50 | } 51 | -------------------------------------------------------------------------------- /src/simulation/GridData.h: -------------------------------------------------------------------------------- 1 | #ifndef GRIDDATA_H 2 | #define GRIDDATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using std::tuple; 9 | 10 | class Grid 11 | { 12 | public: 13 | const float width; 14 | const float height; 15 | const float grid_step; 16 | const std::array shape; 17 | 18 | typedef boost::multi_array data_type; 19 | data_type data; 20 | 21 | Grid(float width, float height, float grid_step); 22 | 23 | void fill(double value); 24 | 25 | tuple getNodeOrigin(int i, int j) const; 26 | tuple getNodePosition(int i, int j) const; 27 | tuple getNearestNode(tuple pos) const; 28 | 29 | private: 30 | auto initShape() -> decltype(shape); 31 | }; 32 | 33 | 34 | #endif // GRIDDATA_H 35 | -------------------------------------------------------------------------------- /src/simulation/InteractionController.cpp: -------------------------------------------------------------------------------- 1 | #include "InteractionController.h" 2 | 3 | #include "Simulation.h" 4 | 5 | InteractionController::InteractionController(Simulation & simulation) 6 | : sim(simulation) 7 | {} 8 | 9 | InteractionController& 10 | InteractionController::action1(std::tuple targetPoint) 11 | { 12 | using std::get; 13 | 14 | auto gridpos = sim.grid->getNearestNode(targetPoint); 15 | auto pulse = Gesture::RadialPulse{get<0>(gridpos), get<1>(gridpos), 3.0, 0.4}; 16 | perturber.enqueue(pulse); 17 | 18 | return *this; 19 | } 20 | -------------------------------------------------------------------------------- /src/simulation/InteractionController.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIONCONTROLLER_H 2 | #define INTERACTIONCONTROLLER_H 3 | 4 | #include 5 | 6 | #include "Perturbator.h" 7 | 8 | class Simulation; 9 | 10 | 11 | class InteractionController 12 | { 13 | Simulation const& sim; 14 | Perturbator perturber; 15 | 16 | public: 17 | InteractionController(Simulation&); 18 | 19 | InteractionController& 20 | action1(std::tuple targetPoint); 21 | 22 | auto getInteractivityFilter() -> Perturbator& { return perturber; } 23 | }; 24 | 25 | 26 | #endif // INTERACTIONCONTROLLER_H 27 | -------------------------------------------------------------------------------- /src/simulation/Perturbator.cpp: -------------------------------------------------------------------------------- 1 | #include "Perturbator.h" 2 | 3 | #include 4 | #include 5 | 6 | using std::tuple; 7 | using std::tie; 8 | using std::vector; 9 | 10 | struct gesture_visitor : boost::static_visitor { 11 | explicit gesture_visitor(Grid::data_type& grid) 12 | : grid(grid) 13 | {} 14 | Grid::data_type& grid; 15 | 16 | void operator() (const Gesture::RadialPulse& pulse) const { 17 | vector> circlePoints; 18 | auto addPoint = [&](int x, int y) { circlePoints.emplace_back(x, y); }; 19 | 20 | // the Midpoint circle algorithm 21 | int x = pulse.radius; 22 | int y = 0; 23 | int radiusError = 1 - x; 24 | while (x >= y) { 25 | addPoint( x, y); 26 | addPoint(-x, y); 27 | addPoint( x, -y); 28 | addPoint(-x, -y); 29 | addPoint( y, x); 30 | addPoint(-y, x); 31 | addPoint( y, -x); 32 | addPoint(-y, -x); 33 | 34 | ++y; 35 | if (radiusError < 0) 36 | radiusError += 2 * y + 1; 37 | else { 38 | --x; 39 | radiusError += 2 * (y - x + 1); 40 | } 41 | } 42 | 43 | for (auto& p: circlePoints) { 44 | int cx, cy; 45 | tie(cx, cy) = p; 46 | 47 | uint j = cx + pulse.j0; 48 | uint i = cy + pulse.i0; 49 | if (i > 0 && i < grid.shape()[0] && 50 | j > 0 && j < grid.shape()[1]) 51 | { 52 | grid[i][j][0] += cy / pulse.radius * pulse.intensity; 53 | grid[i][j][1] += cx / pulse.radius * pulse.intensity; 54 | } 55 | } 56 | } 57 | 58 | void operator() (const Gesture::MotionDrag& drag) const { 59 | // TODO 60 | } 61 | }; 62 | 63 | void Perturbator::apply(Grid::data_type& grid) { 64 | for (auto& gesture: queue) { 65 | boost::apply_visitor(gesture_visitor(grid), gesture); 66 | } 67 | queue.clear(); 68 | } 69 | -------------------------------------------------------------------------------- /src/simulation/Perturbator.h: -------------------------------------------------------------------------------- 1 | #ifndef PERTURBATOR_H 2 | #define PERTURBATOR_H 3 | 4 | #include 5 | #include 6 | 7 | #include "GridData.h" 8 | 9 | namespace Gesture { 10 | 11 | struct RadialPulse { 12 | int i0, j0; 13 | float radius; 14 | float intensity; 15 | }; 16 | 17 | struct MotionDrag { 18 | int i0, j0; 19 | float di, dj; 20 | float brushradius; 21 | float intensity; 22 | }; 23 | 24 | struct LocalShear { 25 | // TODO 26 | }; 27 | } // namespace Gesture 28 | 29 | typedef 30 | boost::variant< 31 | Gesture::RadialPulse, 32 | Gesture::MotionDrag 33 | > 34 | AGesture; 35 | 36 | 37 | class Perturbator 38 | { 39 | public: 40 | void operator() (Grid::data_type& d) { this->apply(d); } 41 | void apply(Grid::data_type&); 42 | 43 | Perturbator& enqueue(const AGesture& g) { this->queue.push_back(g); return *this; } 44 | 45 | private: 46 | std::deque queue; 47 | }; 48 | 49 | #endif // PERTURBATOR_H 50 | -------------------------------------------------------------------------------- /src/simulation/Simulation.cpp: -------------------------------------------------------------------------------- 1 | #include "Simulation.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "BiFlattener.hpp" 10 | 11 | auto static i_all = boost::multi_array_types::index_range(); // "all" index range 12 | 13 | 14 | template 15 | void printFloatMatrix(const M& matrix) { 16 | using std::printf; 17 | for(const auto& row : matrix) { 18 | for(const auto& elem : row) { 19 | printf("%5.3f ", elem); 20 | } 21 | printf("\n"); 22 | } 23 | } 24 | 25 | Simulation::Simulation(std::shared_ptr grid, float time_step, float c) 26 | : grid(grid) 27 | , time_step(time_step) 28 | , c(c) 29 | , interactor(*this) 30 | { 31 | reset(); 32 | prepareEquationsLU(); 33 | } 34 | 35 | 36 | void Simulation::reset() 37 | { 38 | tick_counter = 0; 39 | 40 | grid->fill(0.0); 41 | 42 | history.clear(); 43 | history.push_front(grid->data); 44 | history.push_front(grid->data); 45 | history.push_front(grid->data); 46 | 47 | auto initial_edge_pulse = [this](Grid::data_type& grid) { 48 | if(tick_counter == 0) { 49 | for(size_t i = 0; i < grid.shape()[1]; ++i) { 50 | grid[1][i][0] = 0.2 * c * c; 51 | } 52 | } 53 | }; 54 | 55 | auto edge_fixer = [](Grid::data_type& grid) { 56 | size_t m = grid.shape()[0]; 57 | for(size_t i = 0; i < grid.shape()[1]; ++i) { 58 | grid[m-1][i][0] = 0.0; 59 | grid[0][i][0] = 0.0; 60 | // grid[m-1][i][1] = 0.0; 61 | } 62 | }; 63 | 64 | struct dissipation { 65 | double k; 66 | explicit dissipation(double k) : k(k) {} 67 | void operator() (Grid::data_type& grid) { 68 | for(auto row: grid) { 69 | for(auto elem: row) { 70 | elem[0] *= k; 71 | elem[1] *= k; 72 | } 73 | } 74 | } 75 | }; 76 | 77 | filters.push_back(initial_edge_pulse); 78 | filters.push_back(edge_fixer); 79 | // filters.push_back(dissipation(0.99)); 80 | } 81 | 82 | 83 | void Simulation::advanceOneTick() 84 | { 85 | auto next_grid = solveImplicitStep(); 86 | 87 | for(auto& filter: filters) { 88 | filter(next_grid); 89 | } 90 | 91 | grid->data = std::move(next_grid); // this should happen sort of atomically 92 | 93 | #if 0 94 | using boost::indices; 95 | printf("displacement field, x:\n"); 96 | printFloatMatrix(grid->data[ indices [i_all] [i_all] [0] ]); 97 | printf("\n"); 98 | #endif 99 | 100 | history.push_front(grid->data); 101 | history.pop_back(); 102 | 103 | ++tick_counter; 104 | } 105 | 106 | 107 | auto Simulation::solveExplicitStep() -> Grid::data_type { 108 | boost::multi_array spatial_laplacian(grid->shape); 109 | 110 | for(size_t i = 0; i < grid->shape[0]; ++i) { 111 | for(size_t j = 0; j < grid->shape[1]; ++j) { 112 | for(size_t k = 0; k < grid->shape[2]; ++k) 113 | { 114 | spatial_laplacian[i][j][k] = ((i > 0) ? grid->data[i-1][j][k] : 0) 115 | + ((j > 0) ? grid->data[i][j-1][k] : 0) 116 | + ((i+1 < grid->shape[0]) ? grid->data[i+1][j][k] : 0) 117 | + ((j+1 < grid->shape[1]) ? grid->data[i][j+1][k] : 0) 118 | - 4 * grid->data[i][j][k]; 119 | } 120 | } 121 | } 122 | 123 | boost::multi_array result(grid->shape); 124 | 125 | double r = pow(c * time_step / grid->grid_step, 2); 126 | for(size_t i = 0; i < grid->shape[0]; ++i) { 127 | for(size_t j = 0; j < grid->shape[1]; ++j) { 128 | for(size_t k = 0; k < grid->shape[2]; ++k) 129 | { 130 | result[i][j][k] = 0.5 * ( + r * spatial_laplacian[i][j][k] 131 | + 5 * history[0][i][j][k] 132 | - 4 * history[1][i][j][k] 133 | + 1 * history[2][i][j][k] 134 | ); 135 | } 136 | } 137 | } 138 | 139 | return result; // move semantics! 140 | } 141 | 142 | auto Simulation::solveImplicitStep() -> Grid::data_type { 143 | int N = grid->shape[0] * grid->shape[1]; // total number of nodes in the grid 144 | 145 | BiFlattener index_helper {grid->shape[0]}; 146 | 147 | // fill the equation system's vector b 148 | Eigen::VectorXd b0(N); 149 | Eigen::VectorXd b1(N); 150 | for(size_t i = 0; i < grid->shape[0]; ++i) { 151 | for(size_t j = 0; j < grid->shape[1]; ++j) { 152 | int eqn = index_helper.flatten(i, j); 153 | b0[eqn] = 5 * history[0][i][j][0] - 4 * history[1][i][j][0] + history[2][i][j][0]; 154 | b1[eqn] = 5 * history[0][i][j][1] - 4 * history[1][i][j][1] + history[2][i][j][1]; 155 | } 156 | } 157 | 158 | // perform the decomposed matrix multiplication 159 | Eigen::VectorXd solution0 = equations_lu.solve(b0); 160 | Eigen::VectorXd solution1 = equations_lu.solve(b1); 161 | if(equations_lu.info() != Eigen::Success) { 162 | throw std::runtime_error("decomposed matrix multiplication failed"); 163 | } 164 | 165 | // prepare result 166 | Grid::data_type result(grid->shape); 167 | for(int eqn = 0; eqn < N; ++eqn) { 168 | int i, j; 169 | std::tie(i, j) = index_helper.unflatten(eqn); 170 | result[i][j][0] = solution0[eqn]; 171 | result[i][j][1] = solution1[eqn]; 172 | } 173 | 174 | return result; // move semantics! 175 | } 176 | 177 | void Simulation::prepareEquationsLU() { 178 | double r = pow(c * time_step / grid->grid_step, 2); 179 | 180 | unsigned N = grid->shape[0] * grid->shape[1]; // total number of nodes in the grid 181 | 182 | BiFlattener index_helper {grid->shape[0]}; 183 | auto flatten = [&](size_t i, size_t j) { return index_helper.flatten(i, j); }; 184 | 185 | equations.resize(N, N); 186 | for(size_t i = 0; i < grid->shape[0]; ++i) { 187 | for(size_t j = 0; j < grid->shape[1]; ++j) { 188 | int eqn = flatten(i, j); 189 | 190 | equations.insert(flatten(i, j), eqn) = (2 + 4 * r); 191 | 192 | if(i > 0) 193 | equations.insert(flatten(i-1, j), eqn) = -r; 194 | 195 | if(i + 1 < grid->shape[0]) 196 | equations.insert(flatten(i+1, j), eqn) = -r; 197 | 198 | if(j > 0) 199 | equations.insert(flatten(i, j-1), eqn) = -r; 200 | 201 | if(j + 1 < grid->shape[1]) 202 | equations.insert(flatten(i, j+1), eqn) = -r; 203 | } 204 | } 205 | equations.finalize(); 206 | 207 | equations_lu.compute(equations); 208 | if(equations_lu.info() != Eigen::Success) { 209 | throw std::runtime_error("LU decomposition failed"); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/simulation/Simulation.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATION_H 2 | #define SIMULATION_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "GridData.h" 11 | #include "InteractionController.h" 12 | 13 | 14 | class Simulation 15 | { 16 | public: 17 | explicit Simulation(std::shared_ptr grid, 18 | float time_step = 0.01, 19 | float c = 10.0); 20 | 21 | void reset(); 22 | void advanceOneTick(); 23 | 24 | typedef std::function Filter; 25 | std::vector filters; 26 | 27 | private: 28 | std::shared_ptr grid; 29 | float time_step; 30 | int tick_counter; 31 | float c; // speed of wave propagation 32 | 33 | std::deque history; 34 | 35 | public: 36 | InteractionController interactor; 37 | friend class InteractionController; 38 | 39 | private: 40 | Eigen::SparseMatrix equations; 41 | Eigen::UmfPackLU equations_lu; 42 | 43 | void prepareEquationsLU(); 44 | 45 | private: 46 | auto solveExplicitStep() -> Grid::data_type; 47 | auto solveImplicitStep() -> Grid::data_type; 48 | }; 49 | 50 | #endif // SIMULATION_H 51 | --------------------------------------------------------------------------------