├── .gitignore ├── Lib ├── Globals.cpp ├── Globals.h ├── ThreadPool.cpp ├── AtomicCounter.cpp ├── Setup.h ├── AtomicCounter.h ├── Particle.h ├── Setup.cpp ├── Vector2.h ├── Display.h ├── Integrators.h ├── Particle.cpp ├── ThreadPool.h ├── Universe.h ├── Universe.cpp └── Display.cpp ├── Setups ├── Fonts │ ├── DroidSans.ttf │ └── DroidSans.ttf.LICENSE ├── Sprites │ ├── Large.bmp │ ├── Small.bmp │ ├── HeavyInert.bmp │ ├── DefaultPointer.bmp │ ├── DecreasePointer.bmp │ └── IncreasePointer.bmp ├── recordIntro1.txt ├── recordIntro2.txt ├── web.txt ├── default.txt └── record.txt ├── PhaseTransition ├── PhaseTransition.html └── PhaseTransition.cpp ├── Tests ├── Vector2Test.cpp ├── IntegratorTest.cpp ├── ParticleTest.cpp └── UniverseTest.cpp ├── LICENCE ├── CMakeLists.txt ├── README.md └── cmake ├── FindSDL2Image.cmake ├── FindSDL2TTF.cmake └── FindSDL2.cmake /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | recording/ 3 | .idea/ 4 | cmake-build* 5 | build/ 6 | -------------------------------------------------------------------------------- /Lib/Globals.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Globals.h" 3 | 4 | std::mt19937 randomGenerator; -------------------------------------------------------------------------------- /Setups/Fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Fonts/DroidSans.ttf -------------------------------------------------------------------------------- /Setups/Sprites/Large.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/Large.bmp -------------------------------------------------------------------------------- /Setups/Sprites/Small.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/Small.bmp -------------------------------------------------------------------------------- /Setups/Sprites/HeavyInert.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/HeavyInert.bmp -------------------------------------------------------------------------------- /Setups/Sprites/DefaultPointer.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/DefaultPointer.bmp -------------------------------------------------------------------------------- /Setups/Sprites/DecreasePointer.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/DecreasePointer.bmp -------------------------------------------------------------------------------- /Setups/Sprites/IncreasePointer.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongaskristjan/PhaseTransition/HEAD/Setups/Sprites/IncreasePointer.bmp -------------------------------------------------------------------------------- /Lib/Globals.h: -------------------------------------------------------------------------------- 1 | #ifndef __GLOBALS_H__ 2 | #define __GLOBALS_H__ 3 | 4 | #include 5 | 6 | extern std::mt19937 randomGenerator; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Lib/ThreadPool.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "Lib/ThreadPool.h" 4 | 5 | #ifndef __EMSCRIPTEN__ 6 | ThreadPool threadPool(std::thread::hardware_concurrency() ? std::thread::hardware_concurrency() : 1); 7 | #endif 8 | -------------------------------------------------------------------------------- /Setups/recordIntro1.txt: -------------------------------------------------------------------------------- 1 | recordingPrefix ../recording/intro/intro1- 2 | gravity 0 3 | sizeX 1920 4 | sizeY 1080 5 | particleType 1 4 2 0.8 20 small Sprites/Small.bmp 6 | particle 1460 502 -2.5 0.25 0 7 | particle 660 240 1.5 1.5 0 8 | -------------------------------------------------------------------------------- /Setups/recordIntro2.txt: -------------------------------------------------------------------------------- 1 | recordingPrefix ../recording/intro/intro2- 2 | gravity 0 3 | sizeX 1920 4 | sizeY 1080 5 | particleType 1 4 2 0.8 20 small Sprites/Small.bmp 6 | particle 360 345 3 1 0 7 | particle 1060 840 -0.5 -1.5 0 8 | -------------------------------------------------------------------------------- /Setups/web.txt: -------------------------------------------------------------------------------- 1 | gravity 1e-2 2 | sizeX 1280 3 | sizeY 720 4 | particleType 1 4 2 0.8 20 small Sprites/Small.bmp 5 | particleType 1 5.6 2.8 1.12 28 large Sprites/Large.bmp 6 | particleType 1 5.6 11.2 0 28 heavy_inert Sprites/HeavyInert.bmp 7 | -------------------------------------------------------------------------------- /Setups/default.txt: -------------------------------------------------------------------------------- 1 | gravity 1e-2 2 | sizeX 1920 3 | sizeY 1080 4 | particleType 1 4 2 0.8 20 small Sprites/Small.bmp 5 | particleType 1 5.6 2.8 1.12 28 large Sprites/Large.bmp 6 | particleType 1 5.6 11.2 0 28 heavy_inert Sprites/HeavyInert.bmp 7 | -------------------------------------------------------------------------------- /Setups/record.txt: -------------------------------------------------------------------------------- 1 | recordingPrefix ../recording/ 2 | gravity 1e-2 3 | sizeX 1920 4 | sizeY 1080 5 | particleType 1 4 2 0.8 20 small Sprites/Small.bmp 6 | particleType 1 5.6 2.8 1.12 28 large Sprites/Large.bmp 7 | particleType 1 5.6 11.2 0 28 heavy_inert Sprites/HeavyInert.bmp 8 | -------------------------------------------------------------------------------- /Lib/AtomicCounter.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "AtomicCounter.h" 3 | 4 | AtomicCounter::AtomicCounter(std::size_t total): idx(0), total_(total) { 5 | } 6 | 7 | std::size_t AtomicCounter::next() { 8 | return idx.fetch_add(1, std::memory_order_relaxed); 9 | } 10 | 11 | std::size_t AtomicCounter::total() const { 12 | return total_; 13 | } 14 | -------------------------------------------------------------------------------- /PhaseTransition/PhaseTransition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tests/Vector2Test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Particle.h" 3 | #include 4 | #include 5 | 6 | TEST(Vector2Test, Mixed) { 7 | Vector2D a(4, 3), b(1, 0); 8 | EXPECT_VECTOR2_EQ(a + b, Vector2D(5, 3)); 9 | EXPECT_VECTOR2_EQ(a - b, Vector2D(3, 3)); 10 | EXPECT_VECTOR2_EQ(a * 2., Vector2D(8, 6)); 11 | EXPECT_VECTOR2_EQ(a / 2., Vector2D(2, 1.5)); 12 | EXPECT_VECTOR2_EQ(-a, Vector2D(-4, -3)); 13 | 14 | EXPECT_DOUBLE_EQ(a.magnitude(), 5); 15 | EXPECT_DOUBLE_EQ(dotProduct(a, a), 5 * 5); 16 | EXPECT_DOUBLE_EQ(dotProduct(a, b), 4); 17 | EXPECT_DOUBLE_EQ(crossProduct(a, a), 0); 18 | } 19 | -------------------------------------------------------------------------------- /Lib/Setup.h: -------------------------------------------------------------------------------- 1 | #ifndef __SETUP_H__ 2 | #define __SETUP_H__ 3 | 4 | #include 5 | #include 6 | #include "Lib/Particle.h" 7 | #include "Lib/Universe.h" 8 | 9 | struct ParticleSetup { 10 | int type; 11 | Vector2D pos, v; 12 | }; 13 | 14 | struct Setup { 15 | std::string directoryPath; 16 | std::string recordingPrefix; 17 | std::string displayedCaption; 18 | std::vector particleTypes; 19 | std::vector particles; 20 | int sizeX = 0, sizeY = 0; 21 | double gravity = 0; 22 | double forceFactor = 1e-2; 23 | double dT = 0.5; 24 | 25 | Setup(std::string filePath); 26 | inline Setup() { 27 | } 28 | 29 | void addParticlesToUniverse(Universe &universe) const; 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Lib/AtomicCounter.h: -------------------------------------------------------------------------------- 1 | #ifndef ATOMICCOUNTER_H 2 | #define ATOMICCOUNTER_H 3 | 4 | #include 5 | 6 | /* AtomicCounter enforces safe parallel usage of an atomic counter. Example: 7 | * 8 | * for(int idx = atomicCounter.next(); idx < atomicCounter.total(); idx = atomicCounter.next()) { ... } */ 9 | 10 | class AtomicCounter { 11 | public: 12 | AtomicCounter(std::size_t total); 13 | std::size_t next(); 14 | std::size_t total() const; 15 | 16 | private: 17 | // spacer1 and spacer2 force non-atomic variables to different cache lines than atomic variables 18 | // This significantly improves performance of using address-wise nearby variables 19 | char spacer1[256]; 20 | std::atomic idx; 21 | char spacer2[256]; 22 | std::size_t total_; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Tests/IntegratorTest.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Integrators.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // Integrate x(0) = 1, dx/dt = x. x(1) should be e 8 | 9 | class Linear { 10 | public: 11 | void prepareDifferentiation(double) const { 12 | } 13 | 14 | void derivative(double &der, double, double x) const { 15 | der = x; 16 | } 17 | }; 18 | 19 | TEST(IntegratorTest, Exponent) { 20 | double xEuler = 1, xRK4 = 1; 21 | int nSteps = 1000; 22 | double dT = 1. / nSteps; 23 | 24 | Linear diff; 25 | for(int i = 0; i < nSteps; ++i) { 26 | advanceEuler(xEuler, diff, dT); 27 | advanceRungeKutta4(xRK4, diff, dT); 28 | } 29 | 30 | EXPECT_NEAR(xEuler, M_E, 1e-2); 31 | EXPECT_NEAR(xRK4, M_E, 1e-8); 32 | } 33 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kristjan Kongas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tests/ParticleTest.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Particle.h" 3 | #include 4 | #include 5 | #include 6 | 7 | TEST(ParticleTest, ForcePlausibility) { 8 | ParticleType type0(1, 1, 1, 1, 10); 9 | ParticleType type1(2, 2, 2, 2, 20); 10 | ParticleState state0(Vector2D(0, 0)); 11 | ParticleState state1(Vector2D(2, 0)); 12 | 13 | Vector2D force0 = type0.computeForce(type1, state0, state1); 14 | Vector2D force1 = type1.computeForce(type0, state1, state0); 15 | Vector2D forceSelf = type0.computeForce(type0, state0, state0); 16 | 17 | EXPECT_GT(force0.magnitude(), 0); 18 | EXPECT_DOUBLE_EQ(force0.y, 0); 19 | EXPECT_VECTOR2_EQ(forceSelf, Vector2D(0, 0)); 20 | EXPECT_VECTOR2_EQ(force0, -force1); 21 | } 22 | 23 | TEST(ParticleTest, ForceSmoothness) { 24 | ParticleType type(1, 1, 1, 1, 10); 25 | ParticleState state0(Vector2D(0, 0)); 26 | double range = type.getRange(); 27 | 28 | std::vector forces; 29 | const double diff = 1e-3; 30 | for(double x = -range - 1; x < range + 1; x += diff) { 31 | ParticleState state(Vector2D(x, 0)); 32 | forces.push_back(type.computeForce(type, state, state0).x); 33 | } 34 | 35 | for(size_t i = 1; i < forces.size() - 1; i += ++i) { 36 | ASSERT_NEAR((forces[i - 1] + forces[i + 1]) / 2, forces[i], 10 * diff * diff); 37 | } 38 | } 39 | 40 | TEST(ParticleTest, ForceRange) { 41 | ParticleType type0(1, 1, 1, 1, 3); 42 | ParticleType type1(2, 2, 2, 2, 3); 43 | 44 | double range = std::min(type0.getRange(), type1.getRange()); 45 | ParticleState state0(Vector2D(0, 0)); 46 | ParticleState state1(Vector2D(range - 1., 0)); 47 | ParticleState state2(Vector2D(range, 0)); 48 | 49 | EXPECT_DOUBLE_EQ(type0.computeForce(type1, state0, state2).x, 0); 50 | } 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(CMAKE_CXX_STANDARD 14) 3 | project(PhaseTransition) 4 | 5 | #add_compile_options("-pg") 6 | #add_link_options("-pg") 7 | 8 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 9 | 10 | if(NOT EMSCRIPTEN) 11 | find_package(Threads REQUIRED) 12 | find_package(SDL2 REQUIRED) 13 | find_package(SDL2TTF REQUIRED) 14 | endif() 15 | 16 | find_package(SDL2Image) 17 | if(SDLIMAGE_FOUND) 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D SDL2_IMAGE_ENABLED") 19 | endif() 20 | 21 | FILE(GLOB LibSources Lib/*.cpp Lib/*.h) 22 | add_library(library ${LibSources}) 23 | 24 | FILE(GLOB RunSources PhaseTransition/*.cpp PhaseTransition/*.h) 25 | add_executable(PhaseTransition ${RunSources}) 26 | if(EMSCRIPTEN) 27 | include_directories(.) 28 | target_link_libraries(PhaseTransition --bind library) 29 | 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL=2 -s USE_SDL_TTF=2 -s ASSERTIONS=1 -s ALLOW_MEMORY_GROWTH=1") 31 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --embed-file ${CMAKE_SOURCE_DIR}/Setups@PhaseTransition") 32 | 33 | configure_file(PhaseTransition/PhaseTransition.html PhaseTransition.html COPY_ONLY) 34 | else() 35 | include_directories(${SDL2_INCLUDE_DIRS} ${SDL2TTF_INCLUDE_DIR} ${SDL2_IMAGE_INCLUDE_DIR} .) 36 | 37 | target_link_libraries(PhaseTransition library ${SDL2_LIBRARIES} ${SDL2TTF_LIBRARY} ${SDL2_IMAGE_LIBRARY} Threads::Threads) 38 | endif() 39 | 40 | # Testing 41 | 42 | find_package(GTest) 43 | 44 | if(GTest_FOUND) 45 | FILE(GLOB TestSources Tests/*.cpp) 46 | add_executable(RunTests ${TestSources}) 47 | target_link_libraries(RunTests library ${SDL2_LIBRARIES} ${SDL2TTF_LIBRARY} Threads::Threads GTest::GTest GTest::Main) 48 | 49 | gtest_discover_tests(RunTests) 50 | add_test(NAME monolithic COMMAND RunTests) 51 | endif() 52 | -------------------------------------------------------------------------------- /Lib/Particle.h: -------------------------------------------------------------------------------- 1 | #ifndef __PARTICLE_TYPE_H__ 2 | #define __PARTICLE_TYPE_H__ 3 | 4 | #include "Lib/Vector2.h" 5 | #include 6 | #include 7 | 8 | class ParticleType; 9 | 10 | struct ParticleState { 11 | const ParticleType *type = nullptr; 12 | Vector2D pos, v; 13 | 14 | ParticleState(); 15 | ParticleState(const Vector2D &_pos); 16 | ParticleState(const Vector2D &_pos, const Vector2D &_v); 17 | 18 | ParticleState & operator+=(const ParticleState & rhs); 19 | ParticleState & operator*=(double rhs); 20 | 21 | Vector2D computeForce(const ParticleState &rhs) const; 22 | }; 23 | 24 | ParticleState operator+(const ParticleState & lhs, const ParticleState & rhs); 25 | ParticleState operator*(const ParticleState & lhs, double rhs); 26 | 27 | class ParticleType { 28 | public: 29 | ParticleType(double _mass, double _radius, double _exclusionConstant, double _dipoleMoment, double _range); 30 | ParticleType(const std::string &_name, const std::string &spritePath, 31 | double _mass, double _radius, double _exclusionConstant, double _dipoleMoment, double _range); 32 | Vector2D computeForce(const ParticleType &other, const ParticleState &myState, const ParticleState &otherState) const; 33 | 34 | inline const std::string& getName() const { return name; } 35 | inline const SDL_Surface *getSpriteSurface() const { return spriteSurface; } 36 | inline double getRange() const { return range; } 37 | inline double getMass() const { return mass; } 38 | inline double getRadius() const { return radius; } 39 | 40 | private: 41 | double computeForceComponent(double d) const; 42 | double computeForceFactor(double totalRadius, double minRange, double dist) const; 43 | double superSmoothZeroToOne(double x) const; 44 | 45 | std::string name; 46 | double mass, radius, exclusionConstant, dipoleMoment, range; 47 | SDL_Surface *spriteSurface = nullptr; 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /Lib/Setup.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Particle.h" 3 | #include "Lib/Setup.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Setup::Setup(std::string filePath) { 10 | size_t lastBackslash = filePath.rfind('/'); 11 | directoryPath = lastBackslash == std::string::npos ? "" : filePath.substr(0, lastBackslash + 1); 12 | 13 | std::ifstream fin(filePath); 14 | while(fin.good()) { 15 | std::string key; 16 | fin >> key; 17 | 18 | if(key == "recordingPrefix") { 19 | fin >> recordingPrefix; 20 | recordingPrefix = directoryPath + recordingPrefix; 21 | } 22 | if(key == "displayedCaption") { 23 | fin >> displayedCaption; 24 | std::replace(displayedCaption.begin(), displayedCaption.end(), '_', ' '); 25 | } 26 | if(key == "particleType") { 27 | double mass, radius, exclusionConstant, dipoleMoment, range; 28 | fin >> mass >> radius >> exclusionConstant >> dipoleMoment >> range; 29 | std::string name, spriteLocation; 30 | fin >> name >> spriteLocation; 31 | std::replace(name.begin(), name.end(), '_', ' '); 32 | 33 | particleTypes.emplace_back(name, directoryPath + spriteLocation, mass, radius, exclusionConstant, dipoleMoment, range); 34 | } 35 | if(key == "particle") { 36 | ParticleSetup p; 37 | fin >> p.pos.x >> p.pos.y >> p.v.x >> p.v.y >> p.type; 38 | particles.push_back(p); 39 | } 40 | if(key == "sizeX") fin >> sizeX; 41 | if(key == "sizeY") fin >> sizeY; 42 | if(key == "gravity") fin >> gravity; 43 | if(key == "forceFactor") fin >> forceFactor; 44 | if(key == "dT") fin >> dT; 45 | } 46 | 47 | assert(particleTypes.size() > 0); 48 | assert(sizeX > 0 && sizeY > 0); 49 | } 50 | 51 | 52 | void Setup::addParticlesToUniverse(Universe &universe) const { 53 | for(const ParticleSetup &p: particles) 54 | universe.addParticle(p.type, ParticleState(p.pos, p.v)); 55 | } 56 | -------------------------------------------------------------------------------- /Lib/Vector2.h: -------------------------------------------------------------------------------- 1 | #ifndef __VECTOR2_H__ 2 | #define __VECTOR2_H__ 3 | 4 | #include 5 | 6 | template 7 | struct Vector2 { 8 | T x, y; 9 | 10 | Vector2(): x(0), y(0) {} 11 | Vector2(T _x, T _y): x(_x), y(_y) {} 12 | 13 | Vector2& operator+=(const Vector2 &rhs) { x += rhs.x; y += rhs.y; return *this; } 14 | Vector2& operator-=(const Vector2 &rhs) { x -= rhs.x; y -= rhs.y; return *this; } 15 | Vector2& operator*=(const T &rhs) { x *= rhs; y *= rhs; return *this; } 16 | Vector2& operator/=(const T &rhs) { T mul = 1. / rhs; x *= mul; y *= mul; return *this; } 17 | 18 | T magnitude2() const { return x * x + y * y; } 19 | T magnitude() const { return sqrt(magnitude2()); } 20 | Vector2 norm() const { return Vector2(*this) /= magnitude(); } 21 | }; 22 | 23 | template Vector2 operator+(const Vector2 &lhs, const Vector2 &rhs) { return Vector2(lhs) += rhs; } 24 | template Vector2 operator-(const Vector2 &lhs, const Vector2 &rhs) { return Vector2(lhs) -= rhs; } 25 | template Vector2 operator*(const Vector2 &lhs, const T &rhs) { return Vector2(lhs) *= rhs; } 26 | template Vector2 operator/(const Vector2 &lhs, const T &rhs) { return Vector2(lhs) /= rhs; } 27 | template Vector2 operator-(const Vector2 &v) { return Vector2(-v.x, -v.y); } 28 | 29 | template T dotProduct(const Vector2 &lhs, const Vector2 &rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; } 30 | template T crossProduct(const Vector2 &lhs, const Vector2 &rhs) { return lhs.x * rhs.y - lhs.y * rhs.x; } 31 | 32 | typedef Vector2 Vector2F; 33 | typedef Vector2 Vector2D; 34 | 35 | #define EXPECT_VECTOR2_EQ(lhs, rhs) do {\ 36 | EXPECT_DOUBLE_EQ((lhs).x, (rhs).x);\ 37 | EXPECT_DOUBLE_EQ((lhs).y, (rhs).y);\ 38 | } while(false) 39 | /* 40 | template 41 | void EXPECT_VECTOR2_EQ(const Vector2 &lhs, const Vector2 &rhs) { 42 | EXPECT_DOUBLE_EQ(lhs.x, rhs.x); 43 | EXPECT_DOUBLE_EQ(lhs.y, rhs.y); 44 | } 45 | */ 46 | #endif 47 | -------------------------------------------------------------------------------- /PhaseTransition/PhaseTransition.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Lib/Setup.h" 8 | #include "Lib/Display.h" 9 | #include "Lib/Universe.h" 10 | #include "Lib/Particle.h" 11 | 12 | #ifdef __EMSCRIPTEN__ 13 | #include 14 | #endif 15 | 16 | 17 | std::unique_ptr globalSetup = nullptr; 18 | std::unique_ptr globalUniverse = nullptr; 19 | std::unique_ptr globalDisplay = nullptr; 20 | bool exitFlag = false; 21 | 22 | 23 | void oneStep(); 24 | std::string currentDateTime(); 25 | 26 | int main(int argc, char **argv) { 27 | #ifdef __EMSCRIPTEN__ 28 | globalSetup.reset(new Setup("/PhaseTransition/web.txt")); 29 | #else 30 | assert(argc == 2 && "Expected setup file as argument"); 31 | globalSetup.reset(new Setup(argv[1])); 32 | #endif 33 | 34 | std::string recordingPath; 35 | if(! globalSetup->recordingPrefix.empty()) recordingPath = globalSetup->recordingPrefix + currentDateTime() + "/"; 36 | globalUniverse.reset(new Universe({ globalSetup->sizeX, globalSetup->sizeY, globalSetup->forceFactor, globalSetup->gravity }, 37 | globalSetup->particleTypes)); 38 | globalSetup->addParticlesToUniverse(*globalUniverse); 39 | globalDisplay.reset(new Display(*globalUniverse, "Phase Transition", 40 | globalSetup->displayedCaption, globalSetup->directoryPath, recordingPath)); 41 | 42 | #ifdef __EMSCRIPTEN__ 43 | emscripten_set_main_loop(oneStep, 60, 1); 44 | #else 45 | while(! exitFlag) { 46 | oneStep(); 47 | } 48 | #endif 49 | 50 | return 0; 51 | } 52 | 53 | void oneStep() { 54 | const CallbackHandler &handler = globalDisplay->update(); 55 | UniverseModifier::modify(*globalUniverse, handler, globalSetup->dT); 56 | 57 | if (handler.quit) { 58 | exitFlag = true; 59 | return; 60 | } 61 | 62 | for (int j = 0; j < 5; ++j) 63 | globalUniverse->advance(globalSetup->dT / 5); 64 | } 65 | 66 | std::string currentDateTime() { 67 | time_t now = time(0); 68 | struct tm tstruct; 69 | char buf[80]; 70 | tstruct = *localtime(&now); 71 | strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct); 72 | return buf; 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phase Transition 2 | 3 | [Try it out in your browser](https://kongaskristjan.github.io/2020/02/09/phase-transition.html) or watch this video: 4 | 5 | [![Video of the simulation](https://img.youtube.com/vi/SFf3pcE08NM/0.jpg)](https://youtu.be/SFf3pcE08NM) 6 | 7 | ### Build 8 | 9 | Requirements: 10 | 11 | * C++14 compatible compiler 12 | * cmake 3.10+ 13 | * pthreads 14 | * SDL 2 and SDL_ttf 2 15 | * SDL_image 2 (optional - recording video) 16 | * gtest (optional - running tests) 17 | * emscripten (optional - building for web) 18 | 19 | Building: 20 | 21 | * Create and change working directory to build directory: `mkdir build && cd build` 22 | * Build, run tests and program on Linux: `cmake -D CMAKE_BUILD_TYPE=Release .. && make && ./RunTests && ./PhaseTransition ../Setups/default.txt`, 23 | where `./RunTests` is optional. 24 | * Build for web using emscripten: `emconfigure cmake -D CMAKE_BUILD_TYPE=Release .. && emmake make`. This should generate PhaseTransition html, js and wasm files. You probably need a web server to actually run this in your browser: `python3 -m http.server 8080` (still from the build directory). Then go to . Currently the web build is slow because it's single-threaded. 25 | 26 | Default simulation resolution, particle properties, etc. can be modified in Setups/default.txt. For web build, modify Setups/web.txt and force a rebuild by removing all files in the build directory. 27 | 28 | ### Usage 29 | 30 | Use your mouse to create and influence the particles. There are four modes of interaction: creating, spraying, pushing and heating, each of which can be activated with keys c, s, p and h respectively. 31 | Once in a mode, this action can be carried out on particles by holding the left mouse button. 32 | Right mouse button does the opposite of the activated mode's function. Range of influence can be altered with mouse wheel. 33 | 34 | The number of particles, average velocity, and average temperature (inside the range of influence) are displayed in the upper left corner of display. 35 | 36 | ### Acknowledgements 37 | 38 | This project contains 39 | * ThreadPool library by Jakob Progsch and Václav Zeman. 40 | * DroidSans fonts by Google 41 | 42 | -------------------------------------------------------------------------------- /Lib/Display.h: -------------------------------------------------------------------------------- 1 | #ifndef __DISPLAY_H__ 2 | #define __DISPLAY_H__ 3 | 4 | #include "Lib/Universe.h" 5 | #include "Lib/Vector2.h" 6 | #include 7 | #include 8 | 9 | enum class MouseAction { heat, push, create, spray }; 10 | 11 | struct CallbackHandler { 12 | CallbackHandler(int _totalParticleTypes); 13 | void mouseCallback(const SDL_Event &event); 14 | void keyboardCallback(const SDL_Event &event); 15 | 16 | Vector2D pos = Vector2D(1e6, 1e6); 17 | int sign = 0; // left mouse = 1, none = 0, right mouse = -1 18 | double radius = 50; 19 | bool leftDown = false, rightDown = false; 20 | bool quit = false; 21 | 22 | MouseAction action = MouseAction::create; 23 | int particleTypeIdx = 0; 24 | private: 25 | const int totalParticleTypes = 1; 26 | }; 27 | 28 | class UniverseModifier { 29 | public: 30 | static void modify(Universe &universe, const CallbackHandler &handler, double dT); 31 | 32 | private: 33 | static void modifyExisting(Universe &universe, const CallbackHandler &handler, double dT); 34 | static void addNew(Universe &universe, const CallbackHandler &handler, double dT); 35 | }; 36 | 37 | class Display { 38 | public: 39 | Display(Universe &universe, const std::string &_windowCaption, const std::string &_displayedCaption, 40 | const std::string &_directoryPath, const std::string &recordingPath=""); 41 | ~Display(); 42 | const CallbackHandler & update(); 43 | 44 | private: 45 | void drawParticles(); 46 | void drawDisplayedCaption(); 47 | void drawPointer(); 48 | void drawStats(); 49 | void drawText(const std::string &text, int x, int y); 50 | void drawSpriteFromCenter(SDL_Surface *sprite, int x, int y); 51 | void recordAndDrawRecordingText(); 52 | std::tuple computeStats() const; 53 | 54 | Universe &universe; 55 | std::string windowCaption, displayedCaption; 56 | std::string directoryPath; 57 | CallbackHandler handler; 58 | SDL_Window *window = nullptr; 59 | SDL_Surface *surface = nullptr; 60 | SDL_Surface *defaultPointer = nullptr, *increasePointer = nullptr, *decreasePointer = nullptr; 61 | TTF_Font *font; 62 | 63 | std::string recordingPath; 64 | bool isRecording; 65 | 66 | int timestamp = 0; 67 | }; 68 | 69 | std::string to_string(double x, int precision); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /Tests/UniverseTest.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Universe.h" 3 | #include 4 | 5 | TEST(UniverseTest, IteratorTest) { 6 | Universe universe({ 400, 400, 1, 0 }, { ParticleType(1, 1, 1, 1, 10) }); 7 | ParticleState state(Vector2D(200, 200)); // Standing particle 8 | universe.addParticle(0, state); 9 | universe.addParticle(0, state); 10 | universe.addParticle(0, state); 11 | 12 | size_t size = 0; 13 | for(auto it = universe.begin(); it != universe.end(); ++it) ++size; 14 | EXPECT_EQ(3, size); 15 | EXPECT_EQ(3, universe.size()); 16 | 17 | universe.erase(universe.begin()); 18 | size = 0; 19 | for(auto it = universe.begin(); it != universe.end(); ++it) ++size; 20 | EXPECT_EQ(2, size); 21 | EXPECT_EQ(2, universe.size()); 22 | } 23 | 24 | TEST(UniverseTest, ParticleScatter) { 25 | Universe universe({ 20, 20, 1, 0 }, { ParticleType(1, 1, 1, 1, 10) }); 26 | ParticleState state0(Vector2D(0, 10.1), Vector2D(1, 0)); // Particle to be deflected 27 | ParticleState state1(Vector2D(0, 10)); // Standing particle 28 | universe.addParticle(0, state0); 29 | universe.addParticle(0, state1); 30 | const double dT = 1e-1; 31 | double t = 0; 32 | 33 | for(; t < 1.0; t += dT) { 34 | universe.advance(dT); // Particles have interacted 35 | } 36 | EXPECT_NE(universe.begin()->pos.y, state0.pos.y); 37 | } 38 | 39 | TEST(UniverseTest, Bounds) { 40 | Universe universe({ 1, 1, 1, 0 }, { ParticleType(1, 1, 0, 0, 10) }); // Non-interacting particle 41 | ParticleState state0(Vector2D(0.5, 0.5), Vector2D(1, 0)); 42 | ParticleState state1(Vector2D(0.5, 0.5), Vector2D(0, 1)); 43 | universe.addParticle(0, state0); 44 | universe.addParticle(0, state1); 45 | const double dT = 2e-2; 46 | for(double t = 0; t < 5; t += dT) universe.advance(dT); // Particles would be out of Universe if not bounded 47 | 48 | ASSERT_GT((universe.begin())->pos.x, -2); 49 | ASSERT_LT((universe.begin())->pos.x, 3); 50 | ASSERT_GT((++universe.begin())->pos.y, -2); 51 | ASSERT_LT((++universe.begin())->pos.y, 3); 52 | } 53 | 54 | TEST(UniverseTest, Gravity) { 55 | Universe universe({ 10, 10, 0, 1 }, { ParticleType(1, 1, 0, 0, 0) }); 56 | ParticleState state(Vector2D(5, 5)); 57 | universe.addParticle(0, state); 58 | 59 | const double dT = 1e-1; 60 | for(double t = 0; t < 10; t += dT) universe.advance(dT); // Particles would be out of Universe if not bounded 61 | 62 | ASSERT_GT((universe.begin())->pos.y, state.pos.y); 63 | } 64 | -------------------------------------------------------------------------------- /Lib/Integrators.h: -------------------------------------------------------------------------------- 1 | #ifndef __INTEGRATORS_H__ 2 | #define __INTEGRATORS_H__ 3 | 4 | /* 5 | Required functionality for IntegrableState and Differetiator: 6 | 7 | class IntegrableState { 8 | public: 9 | IntegrableState operator+=(const IntegrableState &); 10 | IntegrableState operator*=(double); 11 | }; 12 | 13 | class Differetiator { 14 | public: 15 | void prepareDifferentiation(IntegrableState &state) const; // Run every time before an integration step 16 | void derivative(IntegrableState &der, const IntegrableState &state) const; 17 | }; 18 | */ 19 | 20 | template 21 | void advanceEuler(IntegrableState &x, const Differetiator &diff, double dT); 22 | 23 | template 24 | void advanceRungeKutta4(IntegrableState &x, const Differetiator &diff, double dT); 25 | 26 | template 27 | void advanceEuler(IntegrableState &x, const Differetiator &diff, double dT) { 28 | // Compact form: x = x + diff.derivative(x) * dT; 29 | 30 | diff.prepareDifferentiation(x); 31 | static IntegrableState k1; 32 | static Buffers derivativeCache; // Only for performance reasons 33 | diff.derivative(k1, derivativeCache, x); 34 | k1 *= dT; 35 | x += k1; 36 | } 37 | 38 | template 39 | void advanceRungeKutta4(IntegrableState &x, const Differetiator &diff, double dT) { 40 | /* Equations from https://en.wikipedia.org/wiki/Runge-Kutta_methods. 41 | Can be written in more compact form: 42 | 43 | IntegrableState k1 = diff.derivative(x) * dT; 44 | IntegrableState k2 = diff.derivative(x + k1 * 0.5) * dT; 45 | IntegrableState k3 = diff.derivative(x + k2 * 0.5) * dT; 46 | IntegrableState k4 = diff.derivative(x + k3) * dT; 47 | x = x + (k1 + k2 * 2 + k3 * 2 + k4) * (1. / 6.); */ 48 | 49 | diff.prepareDifferentiation(x); 50 | 51 | static IntegrableState xInitial, xAdditive; 52 | static IntegrableState k1, k2, k3, k4; 53 | static Buffers derivativeBuffers; // Only for performance reasons 54 | 55 | xInitial = x; 56 | 57 | // Compute k1 and add to x 58 | diff.derivative(k1, derivativeBuffers, x); 59 | 60 | k1 *= dT; 61 | xAdditive = k1; 62 | xAdditive *= 1. / 6.; 63 | x += xAdditive; 64 | 65 | // Compute k2 and add to x 66 | k1 *= .5; 67 | k1 += xInitial; 68 | diff.derivative(k2, derivativeBuffers, k1); 69 | k2 *= dT; 70 | xAdditive = k2; 71 | xAdditive *= 2. / 6.; 72 | x += xAdditive; 73 | 74 | // Compute k3 and add to x 75 | k2 *= .5; 76 | k2 += xInitial; 77 | diff.derivative(k3, derivativeBuffers, k2); 78 | k3 *= dT; 79 | xAdditive = k3; 80 | xAdditive *= 2. / 6.; 81 | x += xAdditive; 82 | 83 | // Compute k4 and add to x 84 | k3 += xInitial; 85 | diff.derivative(k4, derivativeBuffers, k3); 86 | k4 *= dT; 87 | xAdditive = k4; 88 | xAdditive *= 1. / 6.; 89 | x += xAdditive; 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /Lib/Particle.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Particle.h" 3 | #include 4 | 5 | 6 | ParticleState::ParticleState(): pos(Vector2D(0, 0)), v(Vector2D(0, 0)) {} 7 | ParticleState::ParticleState(const Vector2D &_pos): pos(_pos), v(Vector2D(0, 0)) {} 8 | ParticleState::ParticleState(const Vector2D &_pos, const Vector2D &_v): pos(_pos), v(_v) {} 9 | ParticleState & ParticleState::operator+=(const ParticleState & rhs) { pos += rhs.pos; v += rhs.v; return *this; } 10 | ParticleState & ParticleState::operator*=(double rhs) { pos *= rhs; v *= rhs; return *this; } 11 | ParticleState operator+(const ParticleState & lhs, const ParticleState & rhs) { return ParticleState(lhs.pos + rhs.pos, lhs.v + rhs.v); } 12 | ParticleState operator*(const ParticleState & lhs, double rhs) { return ParticleState(lhs.pos * rhs, lhs.v * rhs); } 13 | Vector2D ParticleState::computeForce(const ParticleState &rhs) const { return type->computeForce(* rhs.type, *this, rhs); } 14 | 15 | 16 | ParticleType::ParticleType(double _mass, double _radius, double _exclusionConstant, double _dipoleMoment, double _range): 17 | ParticleType("", "", _mass, _radius, _exclusionConstant, _dipoleMoment, _range) { 18 | } 19 | 20 | ParticleType::ParticleType(const std::string &_name, const std::string &spritePath, 21 | double _mass, double _radius, double _exclusionConstant, double _dipoleMoment, double _range) { 22 | name = _name; 23 | if(! spritePath.empty()) spriteSurface = SDL_LoadBMP(spritePath.c_str()); 24 | mass = _mass; 25 | radius = _radius; 26 | exclusionConstant = _exclusionConstant; 27 | dipoleMoment = _dipoleMoment; 28 | range = _range; 29 | } 30 | 31 | Vector2D ParticleType::computeForce(const ParticleType &other, const ParticleState &myState, const ParticleState &otherState) const { 32 | const double totalRadius = radius + other.radius; 33 | const double totalExclusionFactor = exclusionConstant * other.exclusionConstant; 34 | const double totalDipoleFactor = dipoleMoment * other.dipoleMoment; 35 | const double minRange = std::min(range, other.range); 36 | 37 | Vector2D dVec = myState.pos - otherState.pos; 38 | double d = dVec.magnitude(); 39 | double forceFactor = computeForceFactor(totalRadius, minRange, d); 40 | Vector2D direction = dVec.norm(); 41 | if(forceFactor == 0 || dVec.magnitude() < 1e-6) { 42 | return Vector2D(0, 0); 43 | } 44 | 45 | double dNorm = d / totalRadius; 46 | double exclusionForce = totalExclusionFactor * computeForceComponent(dNorm); 47 | double dipoleForce = -totalDipoleFactor * computeForceComponent(0.5 * dNorm); 48 | double totalForce = exclusionForce + dipoleForce; 49 | double cutoffForce = totalForce * forceFactor; 50 | 51 | return direction * cutoffForce; 52 | } 53 | 54 | double ParticleType::computeForceComponent(double d) const { 55 | double d2 = d * d; 56 | double d4 = d2 * d2; 57 | double d8 = d4 * d4; 58 | double d16 = d8 * d8; 59 | return exp(-d16); 60 | } 61 | 62 | double ParticleType::computeForceFactor(double totalRadius, double minRange, double d) const { 63 | return superSmoothZeroToOne((minRange - d) / totalRadius); 64 | } 65 | 66 | // Super smooth function with f(x <= 0) == 0, f(x >= 1) == 1 67 | double ParticleType::superSmoothZeroToOne(double x) const { 68 | const double cutoff = 5e-2; 69 | if(x < cutoff) return 0; 70 | if(x > 1 - cutoff) return 1; 71 | 72 | double factor0 = exp(1 / -x); 73 | double factor1 = exp(1 / (x - 1)); 74 | double weightedSum = factor1 / (factor0 + factor1); 75 | return weightedSum; 76 | } 77 | -------------------------------------------------------------------------------- /Lib/ThreadPool.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Jakob Progsch, Václav Zeman 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * 8 | * Permission is granted to anyone to use this software for any purpose, 9 | * including commercial applications, and to alter it and redistribute it 10 | * freely, subject to the following restrictions: 11 | * 12 | * 1. The origin of this software must not be misrepresented; you must not 13 | * claim that you wrote the original software. If you use this software 14 | * in a product, an acknowledgment in the product documentation would be 15 | * appreciated but is not required. 16 | * 17 | * 2. Altered source versions must be plainly marked as such, and must not be 18 | * misrepresented as being the original software. 19 | * 20 | * 3. This notice may not be removed or altered from any source 21 | * distribution. 22 | */ 23 | 24 | // Per pt. 2: In addition to the original source by Jakob Progsch and Václav Zeman, a global ThreadPool object is declared. 25 | 26 | #ifndef THREAD_POOL_H 27 | #define THREAD_POOL_H 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | class ThreadPool { 40 | public: 41 | ThreadPool(size_t); 42 | template 43 | auto enqueue(F&& f, Args&&... args) 44 | -> std::future::type>; 45 | ~ThreadPool(); 46 | private: 47 | // need to keep track of threads so we can join them 48 | std::vector< std::thread > workers; 49 | // the task queue 50 | std::queue< std::function > tasks; 51 | 52 | // synchronization 53 | std::mutex queue_mutex; 54 | std::condition_variable condition; 55 | bool stop; 56 | }; 57 | 58 | // the constructor just launches some amount of workers 59 | inline ThreadPool::ThreadPool(size_t threads) 60 | : stop(false) 61 | { 62 | for(size_t i = 0;i task; 69 | 70 | { 71 | std::unique_lock lock(this->queue_mutex); 72 | this->condition.wait(lock, 73 | [this]{ return this->stop || !this->tasks.empty(); }); 74 | if(this->stop && this->tasks.empty()) 75 | return; 76 | task = std::move(this->tasks.front()); 77 | this->tasks.pop(); 78 | } 79 | 80 | task(); 81 | } 82 | } 83 | ); 84 | } 85 | 86 | // add new work item to the pool 87 | template 88 | auto ThreadPool::enqueue(F&& f, Args&&... args) 89 | -> std::future::type> 90 | { 91 | using return_type = typename std::result_of::type; 92 | 93 | auto task = std::make_shared< std::packaged_task >( 94 | std::bind(std::forward(f), std::forward(args)...) 95 | ); 96 | 97 | std::future res = task->get_future(); 98 | { 99 | std::unique_lock lock(queue_mutex); 100 | 101 | // don't allow enqueueing after stopping the pool 102 | if(stop) 103 | throw std::runtime_error("enqueue on stopped ThreadPool"); 104 | 105 | tasks.emplace([task](){ (*task)(); }); 106 | } 107 | condition.notify_one(); 108 | return res; 109 | } 110 | 111 | // the destructor joins all threads 112 | inline ThreadPool::~ThreadPool() 113 | { 114 | { 115 | std::unique_lock lock(queue_mutex); 116 | stop = true; 117 | } 118 | condition.notify_all(); 119 | for(std::thread &worker: workers) 120 | worker.join(); 121 | } 122 | 123 | #ifndef __EMSCRIPTEN__ 124 | extern ThreadPool threadPool; 125 | #endif 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /cmake/FindSDL2Image.cmake: -------------------------------------------------------------------------------- 1 | # Locate SDL_image library 2 | # 3 | # This module defines: 4 | # 5 | # :: 6 | # 7 | # SDL2_IMAGE_LIBRARIES, the name of the library to link against 8 | # SDL2_IMAGE_INCLUDE_DIRS, where to find the headers 9 | # SDL2_IMAGE_FOUND, if false, do not try to link against 10 | # SDL2_IMAGE_VERSION_STRING - human-readable string containing the version of SDL_image 11 | # 12 | # 13 | # 14 | # For backward compatibility the following variables are also set: 15 | # 16 | # :: 17 | # 18 | # SDLIMAGE_LIBRARY (same value as SDL2_IMAGE_LIBRARIES) 19 | # SDLIMAGE_INCLUDE_DIR (same value as SDL2_IMAGE_INCLUDE_DIRS) 20 | # SDLIMAGE_FOUND (same value as SDL2_IMAGE_FOUND) 21 | # 22 | # 23 | # 24 | # $SDLDIR is an environment variable that would correspond to the 25 | # ./configure --prefix=$SDLDIR used in building SDL. 26 | # 27 | # Created by Eric Wing. This was influenced by the FindSDL.cmake 28 | # module, but with modifications to recognize OS X frameworks and 29 | # additional Unix paths (FreeBSD, etc). 30 | 31 | #============================================================================= 32 | # Copyright 2005-2009 Kitware, Inc. 33 | # Copyright 2012 Benjamin Eikel 34 | # 35 | # Distributed under the OSI-approved BSD License (the "License"); 36 | # see accompanying file Copyright.txt for details. 37 | # 38 | # This software is distributed WITHOUT ANY WARRANTY; without even the 39 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 40 | # See the License for more information. 41 | #============================================================================= 42 | # (To distribute this file outside of CMake, substitute the full 43 | # License text for the above reference.) 44 | 45 | find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h 46 | HINTS 47 | ENV SDL2IMAGEDIR 48 | ENV SDL2DIR 49 | PATH_SUFFIXES SDL2 50 | # path suffixes to search inside ENV{SDLDIR} 51 | include/SDL2 include 52 | PATHS ${SDL2_IMAGE_PATH} 53 | ) 54 | 55 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 56 | set(VC_LIB_PATH_SUFFIX lib/x64) 57 | else() 58 | set(VC_LIB_PATH_SUFFIX lib/x86) 59 | endif() 60 | 61 | find_library(SDL2_IMAGE_LIBRARY 62 | NAMES SDL2_image 63 | HINTS 64 | ENV SDL2IMAGEDIR 65 | ENV SDL2DIR 66 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 67 | PATHS ${SDL2_IMAGE_PATH} 68 | ) 69 | 70 | if(SDL2_IMAGE_INCLUDE_DIR AND EXISTS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h") 71 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+[0-9]+$") 72 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+[0-9]+$") 73 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+[0-9]+$") 74 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MAJOR "${SDL2_IMAGE_VERSION_MAJOR_LINE}") 75 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MINOR "${SDL2_IMAGE_VERSION_MINOR_LINE}") 76 | string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_PATCH "${SDL2_IMAGE_VERSION_PATCH_LINE}") 77 | set(SDL2_IMAGE_VERSION_STRING ${SDL2_IMAGE_VERSION_MAJOR}.${SDL2_IMAGE_VERSION_MINOR}.${SDL2_IMAGE_VERSION_PATCH}) 78 | unset(SDL2_IMAGE_VERSION_MAJOR_LINE) 79 | unset(SDL2_IMAGE_VERSION_MINOR_LINE) 80 | unset(SDL2_IMAGE_VERSION_PATCH_LINE) 81 | unset(SDL2_IMAGE_VERSION_MAJOR) 82 | unset(SDL2_IMAGE_VERSION_MINOR) 83 | unset(SDL2_IMAGE_VERSION_PATCH) 84 | endif() 85 | 86 | set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) 87 | set(SDL2_IMAGE_INCLUDE_DIRS ${SDL2_IMAGE_INCLUDE_DIR}) 88 | 89 | include(FindPackageHandleStandardArgs) 90 | 91 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_image 92 | REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIRS 93 | VERSION_VAR SDL2_IMAGE_VERSION_STRING) 94 | 95 | # for backward compatibility 96 | set(SDLIMAGE_LIBRARY ${SDL2_IMAGE_LIBRARIES}) 97 | set(SDLIMAGE_INCLUDE_DIR ${SDL2_IMAGE_INCLUDE_DIRS}) 98 | set(SDLIMAGE_FOUND ${SDL2_IMAGE_FOUND}) 99 | 100 | mark_as_advanced(SDL2_IMAGE_LIBRARY SDL2_IMAGE_INCLUDE_DIR) 101 | -------------------------------------------------------------------------------- /Lib/Universe.h: -------------------------------------------------------------------------------- 1 | #ifndef __UNIVERSE_H__ 2 | #define __UNIVERSE_H__ 3 | 4 | #include 5 | #include 6 | #include "Lib/Particle.h" 7 | #include "Lib/AtomicCounter.h" 8 | #include "Lib/ThreadPool.h" 9 | 10 | /* 11 | * Universe handles the creation and destruction of particles and provides methods for iterating over them. 12 | * Universe::advance() advances the current state with the help of an integrator and UniverseState/-Differentiator. 13 | * 14 | * UniverseState holds the position/velocity information of all the particles in the universe. 15 | * UniverseDifferentiator has a method that returns the derivative of UniverseState, which is also a 16 | * UniverseState. Internally, UniverseState and -Differentiator hold their particles as put into a grid of 17 | * boxes. This allows checking interactions only between particles at nearby boxes, thus speeding up computations. 18 | * 19 | * Each box of the UniverseState is processed single-threadedly, and parallelization is achieved by 20 | * concurrently processing several boxes. Also, in order to save time, it is appropriate to compute 21 | * each interaction only once. These requirements however create a race condition, because thread 1 writing 22 | * to box 1 must also write to box 2, which thread 2 might be writing to at the same time. This problem 23 | * is eliminated by adding a few more accumulation buffers (UniverseBuffers), each of which can only be 24 | * written from a box at a pose relative to destination box. See UniverseDifferentiator::computeForcesOneThread 25 | * for details (Relative poses are set by std::array). 26 | */ 27 | 28 | class UniverseState; 29 | typedef std::array UniverseBuffers; 30 | 31 | struct UniverseConfig { 32 | int sizeX, sizeY; 33 | double forceFactor, gravity; 34 | }; 35 | 36 | struct UniverseState { 37 | std::vector>> state; 38 | size_t size_ = 0; 39 | double sizePerBlock = 1; 40 | 41 | void setInteractionDistance(const UniverseConfig &config, double dist); 42 | void prepareDifferentiation(); 43 | UniverseState & operator=(const UniverseState &rhs); 44 | UniverseState & operator+=(const UniverseState &rhs); 45 | UniverseState & operator*=(double rhs); 46 | size_t size() const { return size_; } 47 | 48 | class iterator { 49 | public: 50 | bool operator==(const iterator &rhs) const; 51 | bool operator!=(const iterator &rhs) const; 52 | iterator & operator++(); // prefix increment 53 | ParticleState & operator*() const; 54 | ParticleState * operator->() const; 55 | iterator & normalize(); 56 | 57 | UniverseState *obj = nullptr; 58 | std::vector>>::iterator itY; 59 | std::vector>::iterator itX; 60 | std::vector::iterator itI; 61 | }; 62 | 63 | iterator begin(); 64 | iterator end(); 65 | 66 | void insert(const ParticleState &state); 67 | iterator erase(iterator it); 68 | }; 69 | 70 | struct UniverseDifferentiator { 71 | UniverseConfig config; 72 | std::vector types; 73 | 74 | UniverseDifferentiator(const UniverseConfig &config, std::vector _types); 75 | void prepareDifferentiation(UniverseState &state) const; // Has to be called once before every iteration 76 | void derivative(UniverseState &der, UniverseBuffers &derBuffers, UniverseState &state) const; 77 | 78 | private: 79 | void initForces(UniverseState &der, const UniverseState &state) const; 80 | void computeForces(UniverseState &der, UniverseBuffers &derBuffers, const UniverseState &state) const; 81 | void computeForcesOneThread(UniverseState &der, UniverseBuffers &derBuffers, const UniverseState &state, 82 | AtomicCounter &counter) const; 83 | double boundForce(double overEdge) const; 84 | 85 | void forcesToAccel(UniverseState &der, const UniverseBuffers &derBuffers) const; 86 | }; 87 | 88 | 89 | class Universe { 90 | public: 91 | Universe(const UniverseConfig &_config, const std::vector &_types); 92 | void addParticle(int typeIndex, ParticleState pState); 93 | void removeParticle(int index); 94 | void advance(double dT); 95 | Vector2D clampInto(const Vector2D &pos); 96 | 97 | inline size_t size() const { return state.size(); } 98 | inline const UniverseConfig & getConfig() const { return diff.config; } 99 | inline const std::vector & getParticleTypes() const { return diff.types; } 100 | 101 | inline auto begin() { return state.begin(); } 102 | inline auto end() { return state.end(); } 103 | inline auto erase(const UniverseState::iterator &it) { return state.erase(it); } 104 | private: 105 | UniverseDifferentiator diff; 106 | UniverseState state; 107 | }; 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /cmake/FindSDL2TTF.cmake: -------------------------------------------------------------------------------- 1 | # Locate SDL2 library 2 | # This module defines 3 | # SDL2_LIBRARY, the name of the library to link against 4 | # SDL2_FOUND, if false, do not try to link to SDL2 5 | # SDL2_INCLUDE_DIR, where to find SDL.h 6 | # 7 | # This module responds to the the flag: 8 | # SDL2_BUILDING_LIBRARY 9 | # If this is defined, then no SDL2main will be linked in because 10 | # only applications need main(). 11 | # Otherwise, it is assumed you are building an application and this 12 | # module will attempt to locate and set the the proper link flags 13 | # as part of the returned SDL2_LIBRARY variable. 14 | # 15 | # Don't forget to include SDLmain.h and SDLmain.m your project for the 16 | # OS X framework based version. (Other versions link to -lSDL2main which 17 | # this module will try to find on your behalf.) Also for OS X, this 18 | # module will automatically add the -framework Cocoa on your behalf. 19 | # 20 | # 21 | # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration 22 | # and no SDL2_LIBRARY, it means CMake did not find your SDL2 library 23 | # (SDL2.dll, libsdl2.so, SDL2.framework, etc). 24 | # Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. 25 | # Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value 26 | # as appropriate. These values are used to generate the final SDL2_LIBRARY 27 | # variable, but when these values are unset, SDL2_LIBRARY does not get created. 28 | # 29 | # 30 | # $SDL2DIR is an environment variable that would 31 | # correspond to the ./configure --prefix=$SDL2DIR 32 | # used in building SDL2. 33 | # l.e.galup 9-20-02 34 | # 35 | # Modified by Eric Wing. 36 | # Added code to assist with automated building by using environmental variables 37 | # and providing a more controlled/consistent search behavior. 38 | # Added new modifications to recognize OS X frameworks and 39 | # additional Unix paths (FreeBSD, etc). 40 | # Also corrected the header search path to follow "proper" SDL guidelines. 41 | # Added a search for SDL2main which is needed by some platforms. 42 | # Added a search for threads which is needed by some platforms. 43 | # Added needed compile switches for MinGW. 44 | # 45 | # On OSX, this will prefer the Framework version (if found) over others. 46 | # People will have to manually change the cache values of 47 | # SDL2_LIBRARY to override this selection or set the CMake environment 48 | # CMAKE_INCLUDE_PATH to modify the search paths. 49 | # 50 | # Note that the header path has changed from SDL2/SDL.h to just SDL.h 51 | # This needed to change because "proper" SDL convention 52 | # is #include "SDL.h", not . This is done for portability 53 | # reasons because not all systems place things in SDL2/ (see FreeBSD). 54 | 55 | #============================================================================= 56 | # Copyright 2003-2009 Kitware, Inc. 57 | # 58 | # Distributed under the OSI-approved BSD License (the "License"); 59 | # see accompanying file Copyright.txt for details. 60 | # 61 | # This software is distributed WITHOUT ANY WARRANTY; without even the 62 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 63 | # See the License for more information. 64 | #============================================================================= 65 | # (To distribute this file outside of CMake, substitute the full 66 | # License text for the above reference.) 67 | 68 | SET(SDL2TTF_SEARCH_PATHS 69 | ~/Library/Frameworks 70 | /Library/Frameworks 71 | /usr/local 72 | /usr 73 | /sw # Fink 74 | /opt/local # DarwinPorts 75 | /opt/csw # Blastwave 76 | /opt 77 | ) 78 | 79 | FIND_PATH(SDL2TTF_INCLUDE_DIR SDL_ttf.h 80 | HINTS 81 | $ENV{SDL2TTFDIR} 82 | PATH_SUFFIXES include/SDL2 include 83 | PATHS ${SDL2TTF_SEARCH_PATHS} 84 | ) 85 | 86 | FIND_LIBRARY(SDL2TTF_LIBRARY_TEMP 87 | NAMES SDL2_ttf 88 | HINTS 89 | $ENV{SDL2TTFDIR} 90 | PATH_SUFFIXES lib64 lib 91 | PATHS ${SDL2TTF_SEARCH_PATHS} 92 | ) 93 | 94 | IF(NOT SDL2TTF_BUILDING_LIBRARY) 95 | IF(NOT ${SDL2TTF_INCLUDE_DIR} MATCHES ".framework") 96 | # Non-OS X framework versions expect you to also dynamically link to 97 | # SDL2TTFmain. This is mainly for Windows and OS X. Other (Unix) platforms 98 | # seem to provide SDL2TTFmain for compatibility even though they don't 99 | # necessarily need it. 100 | FIND_LIBRARY(SDL2TTFMAIN_LIBRARY 101 | NAMES SDL2_ttf 102 | HINTS 103 | $ENV{SDL2TTFDIR} 104 | PATH_SUFFIXES lib64 lib 105 | PATHS ${SDL2TTF_SEARCH_PATHS} 106 | ) 107 | ENDIF(NOT ${SDL2TTF_INCLUDE_DIR} MATCHES ".framework") 108 | ENDIF(NOT SDL2TTF_BUILDING_LIBRARY) 109 | 110 | # SDL2TTF may require threads on your system. 111 | # The Apple build may not need an explicit flag because one of the 112 | # frameworks may already provide it. 113 | # But for non-OSX systems, I will use the CMake Threads package. 114 | IF(NOT APPLE) 115 | FIND_PACKAGE(Threads) 116 | ENDIF(NOT APPLE) 117 | 118 | # MinGW needs an additional library, mwindows 119 | # It's total link flags should look like -lmingw32 -lSDL2TTFmain -lSDL2TTF -lmwindows 120 | # (Actually on second look, I think it only needs one of the m* libraries.) 121 | IF(MINGW) 122 | SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") 123 | ENDIF(MINGW) 124 | 125 | IF(SDL2TTF_LIBRARY_TEMP) 126 | # For SDL2TTFmain 127 | IF(NOT SDL2TTF_BUILDING_LIBRARY) 128 | IF(SDL2TTFMAIN_LIBRARY) 129 | SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTFMAIN_LIBRARY} ${SDL2TTF_LIBRARY_TEMP}) 130 | ENDIF(SDL2TTFMAIN_LIBRARY) 131 | ENDIF(NOT SDL2TTF_BUILDING_LIBRARY) 132 | 133 | # For OS X, SDL2TTF uses Cocoa as a backend so it must link to Cocoa. 134 | # CMake doesn't display the -framework Cocoa string in the UI even 135 | # though it actually is there if I modify a pre-used variable. 136 | # I think it has something to do with the CACHE STRING. 137 | # So I use a temporary variable until the end so I can set the 138 | # "real" variable in one-shot. 139 | IF(APPLE) 140 | SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTF_LIBRARY_TEMP} "-framework Cocoa") 141 | ENDIF(APPLE) 142 | 143 | # For threads, as mentioned Apple doesn't need this. 144 | # In fact, there seems to be a problem if I used the Threads package 145 | # and try using this line, so I'm just skipping it entirely for OS X. 146 | IF(NOT APPLE) 147 | SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTF_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 148 | ENDIF(NOT APPLE) 149 | 150 | # For MinGW library 151 | IF(MINGW) 152 | SET(SDL2TTF_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2TTF_LIBRARY_TEMP}) 153 | ENDIF(MINGW) 154 | 155 | # Set the final string here so the GUI reflects the final state. 156 | SET(SDL2TTF_LIBRARY ${SDL2TTF_LIBRARY_TEMP} CACHE STRING "Where the SDL2TTF Library can be found") 157 | # Set the temp variable to INTERNAL so it is not seen in the CMake GUI 158 | SET(SDL2TTF_LIBRARY_TEMP "${SDL2TTF_LIBRARY_TEMP}" CACHE INTERNAL "") 159 | ENDIF(SDL2TTF_LIBRARY_TEMP) 160 | 161 | INCLUDE(FindPackageHandleStandardArgs) 162 | 163 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2TTF REQUIRED_VARS SDL2TTF_LIBRARY SDL2TTF_INCLUDE_DIR) -------------------------------------------------------------------------------- /cmake/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindSDL2 6 | # ------- 7 | # 8 | # Locate SDL2 library 9 | # 10 | # This module defines 11 | # 12 | # :: 13 | # 14 | # SDL2_LIBRARY, the name of the library to link against 15 | # SDL2_FOUND, if false, do not try to link to SDL 16 | # SDL2_INCLUDE_DIR, where to find SDL.h 17 | # SDL2_VERSION_STRING, human-readable string containing the version of SDL 18 | # 19 | # 20 | # 21 | # This module responds to the flag: 22 | # 23 | # :: 24 | # 25 | # SDL2_BUILDING_LIBRARY 26 | # If this is defined, then no SDL2_main will be linked in because 27 | # only applications need main(). 28 | # Otherwise, it is assumed you are building an application and this 29 | # module will attempt to locate and set the proper link flags 30 | # as part of the returned SDL2_LIBRARY variable. 31 | # 32 | # 33 | # 34 | # Don't forget to include SDLmain.h and SDLmain.m your project for the 35 | # OS X framework based version. (Other versions link to -lSDLmain which 36 | # this module will try to find on your behalf.) Also for OS X, this 37 | # module will automatically add the -framework Cocoa on your behalf. 38 | # 39 | # 40 | # 41 | # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your 42 | # configuration and no SDL2_LIBRARY, it means CMake did not find your SDL 43 | # library (SDL.dll, libsdl.so, SDL.framework, etc). Set 44 | # SDL2_LIBRARY_TEMP to point to your SDL library, and configure again. 45 | # Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this 46 | # value as appropriate. These values are used to generate the final 47 | # SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY 48 | # does not get created. 49 | # 50 | # 51 | # 52 | # $SDL2DIR is an environment variable that would correspond to the 53 | # ./configure --prefix=$SDL2DIR used in building SDL. l.e.galup 9-20-02 54 | # 55 | # Modified by Eric Wing. Added code to assist with automated building 56 | # by using environmental variables and providing a more 57 | # controlled/consistent search behavior. Added new modifications to 58 | # recognize OS X frameworks and additional Unix paths (FreeBSD, etc). 59 | # Also corrected the header search path to follow "proper" SDL 60 | # guidelines. Added a search for SDLmain which is needed by some 61 | # platforms. Added a search for threads which is needed by some 62 | # platforms. Added needed compile switches for MinGW. 63 | # 64 | # On OSX, this will prefer the Framework version (if found) over others. 65 | # People will have to manually change the cache values of SDL2_LIBRARY to 66 | # override this selection or set the CMake environment 67 | # CMAKE_INCLUDE_PATH to modify the search paths. 68 | # 69 | # Note that the header path has changed from SDL/SDL.h to just SDL.h 70 | # This needed to change because "proper" SDL convention is #include 71 | # "SDL.h", not . This is done for portability reasons 72 | # because not all systems place things in SDL/ (see FreeBSD). 73 | 74 | if(NOT SDL2_DIR) 75 | set(SDL2_DIR "" CACHE PATH "SDL2 directory") 76 | endif() 77 | 78 | find_path(SDL2_INCLUDE_DIR SDL.h 79 | HINTS 80 | ENV SDL2DIR 81 | ${SDL2_DIR} 82 | PATH_SUFFIXES SDL2 83 | # path suffixes to search inside ENV{SDL2DIR} 84 | include/SDL2 include 85 | ) 86 | 87 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 88 | set(VC_LIB_PATH_SUFFIX lib/x64) 89 | else() 90 | set(VC_LIB_PATH_SUFFIX lib/x86) 91 | endif() 92 | 93 | find_library(SDL2_LIBRARY_TEMP 94 | NAMES SDL2 95 | HINTS 96 | ENV SDL2DIR 97 | ${SDL2_DIR} 98 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 99 | ) 100 | 101 | # Hide this cache variable from the user, it's an internal implementation 102 | # detail. The documented library variable for the user is SDL2_LIBRARY 103 | # which is derived from SDL2_LIBRARY_TEMP further below. 104 | set_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL) 105 | 106 | if(NOT SDL2_BUILDING_LIBRARY) 107 | if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") 108 | # Non-OS X framework versions expect you to also dynamically link to 109 | # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms 110 | # seem to provide SDLmain for compatibility even though they don't 111 | # necessarily need it. 112 | find_library(SDL2MAIN_LIBRARY 113 | NAMES SDL2main 114 | HINTS 115 | ENV SDL2DIR 116 | ${SDL2_DIR} 117 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 118 | PATHS 119 | /sw 120 | /opt/local 121 | /opt/csw 122 | /opt 123 | ) 124 | endif() 125 | endif() 126 | 127 | # SDL may require threads on your system. 128 | # The Apple build may not need an explicit flag because one of the 129 | # frameworks may already provide it. 130 | # But for non-OSX systems, I will use the CMake Threads package. 131 | if(NOT APPLE) 132 | find_package(Threads) 133 | endif() 134 | 135 | # MinGW needs an additional link flag, -mwindows 136 | # It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows 137 | if(MINGW) 138 | set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") 139 | endif() 140 | 141 | if(SDL2_LIBRARY_TEMP) 142 | # For SDLmain 143 | if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) 144 | list(FIND SDL2_LIBRARY_TEMP "${SDLMAIN_LIBRARY}" _SDL2_MAIN_INDEX) 145 | if(_SDL2_MAIN_INDEX EQUAL -1) 146 | set(SDL2_LIBRARY_TEMP "${SDLMAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP}) 147 | endif() 148 | unset(_SDL2_MAIN_INDEX) 149 | endif() 150 | 151 | # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. 152 | # CMake doesn't display the -framework Cocoa string in the UI even 153 | # though it actually is there if I modify a pre-used variable. 154 | # I think it has something to do with the CACHE STRING. 155 | # So I use a temporary variable until the end so I can set the 156 | # "real" variable in one-shot. 157 | if(APPLE) 158 | set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") 159 | endif() 160 | 161 | # For threads, as mentioned Apple doesn't need this. 162 | # In fact, there seems to be a problem if I used the Threads package 163 | # and try using this line, so I'm just skipping it entirely for OS X. 164 | if(NOT APPLE) 165 | set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 166 | endif() 167 | 168 | # For MinGW library 169 | if(MINGW) 170 | set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) 171 | endif() 172 | 173 | # Set the final string here so the GUI reflects the final state. 174 | set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") 175 | endif() 176 | 177 | if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL2_version.h") 178 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+[0-9]+$") 179 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+[0-9]+$") 180 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+[0-9]+$") 181 | string(REGEX REPLACE "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") 182 | string(REGEX REPLACE "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") 183 | string(REGEX REPLACE "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") 184 | set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) 185 | unset(SDL2_VERSION_MAJOR_LINE) 186 | unset(SDL2_VERSION_MINOR_LINE) 187 | unset(SDL2_VERSION_PATCH_LINE) 188 | unset(SDL2_VERSION_MAJOR) 189 | unset(SDL2_VERSION_MINOR) 190 | unset(SDL2_VERSION_PATCH) 191 | endif() 192 | 193 | set(SDL2_LIBRARIES ${SDL2_LIBRARY} ${SDL2MAIN_LIBRARY}) 194 | set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) 195 | 196 | include(FindPackageHandleStandardArgs) 197 | 198 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL 199 | REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR 200 | VERSION_VAR SDL2_VERSION_STRING) 201 | -------------------------------------------------------------------------------- /Setups/Fonts/DroidSans.ttf.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2 | Digitized data copyright © 2007, Google Corporation. 3 | 4 | License 5 | Apache License 6 | 7 | Version 2.0, January 2004 8 | 9 | http://www.apache.org/licenses/ 10 | 11 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 12 | 13 | 1. Definitions. 14 | 15 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 20 | 21 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 22 | 23 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 24 | 25 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 26 | 27 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 28 | 29 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 30 | 31 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 32 | 33 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 34 | 35 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 36 | 37 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 38 | 39 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 40 | 41 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 42 | 43 | You must cause any modified files to carry prominent notices stating that You changed the files; and 44 | 45 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 46 | 47 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 48 | 49 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 50 | 51 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 52 | 53 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 54 | 55 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 56 | 57 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 58 | 59 | -------------------------------------------------------------------------------- /Lib/Universe.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Lib/Integrators.h" 3 | #include "Lib/Universe.h" 4 | #include 5 | #include 6 | #include 7 | 8 | void UniverseState::setInteractionDistance(const UniverseConfig &config, double dist) { 9 | assert(size_ == 0); 10 | sizePerBlock = std::max(dist, 1.0); 11 | state.resize(config.sizeY / sizePerBlock + 1); 12 | for(auto &ySlice: state) { 13 | ySlice.resize(config.sizeX / sizePerBlock + 1); 14 | } 15 | } 16 | 17 | void UniverseState::prepareDifferentiation() { 18 | std::vector current; 19 | for(size_t y = 0; y < state.size(); ++y) { 20 | for(size_t x = 0; x < state[y].size(); ++x) { 21 | current.swap(state[y][x]); 22 | state[y][x].clear(); 23 | size_ -= current.size(); 24 | for(size_t i = 0; i < current.size(); ++i) { 25 | insert(current[i]); 26 | } 27 | } 28 | } 29 | } 30 | 31 | UniverseState & UniverseState::operator=(const UniverseState &rhs) { 32 | state.resize(rhs.state.size()); 33 | for(size_t y = 0; y < state.size(); ++y) { 34 | state[y].resize(rhs.state[y].size()); 35 | for(size_t x = 0; x < state[y].size(); ++x) { 36 | state[y][x] = rhs.state[y][x]; 37 | } 38 | } 39 | return *this; 40 | } 41 | 42 | UniverseState & UniverseState::operator+=(const UniverseState &rhs) { 43 | assert(state.size() == rhs.state.size()); 44 | for(size_t y = 0; y < state.size(); ++y) { 45 | assert(state[y].size() == rhs.state[y].size()); 46 | for(size_t x = 0; x < state[y].size(); ++x) { 47 | assert(state[y][x].size() == rhs.state[y][x].size()); 48 | for(size_t i = 0; i < state[y][x].size(); ++i) { 49 | state[y][x][i] += rhs.state[y][x][i]; 50 | } 51 | } 52 | } 53 | return *this; 54 | } 55 | 56 | UniverseState & UniverseState::operator*=(double rhs) { 57 | for(size_t y = 0; y < state.size(); ++y) { 58 | for(size_t x = 0; x < state[y].size(); ++x) { 59 | for(size_t i = 0; i < state[y][x].size(); ++i) { 60 | state[y][x][i] *= rhs; 61 | } 62 | } 63 | } 64 | return *this; 65 | } 66 | 67 | 68 | bool UniverseState::iterator::operator==(const UniverseState::iterator &rhs) const { 69 | return obj == rhs.obj && itY == rhs.itY && itX == rhs.itX && itI == rhs.itI; 70 | } 71 | 72 | bool UniverseState::iterator::operator!=(const UniverseState::iterator &rhs) const { 73 | return ! (*this == rhs); 74 | } 75 | 76 | UniverseState::iterator& UniverseState::iterator::operator++() { // prefix increment 77 | ++itI; 78 | return normalize(); 79 | } 80 | 81 | ParticleState & UniverseState::iterator::operator*() const { 82 | return *itI; 83 | } 84 | 85 | ParticleState * UniverseState::iterator::operator->() const { 86 | return &*itI; 87 | } 88 | 89 | UniverseState::iterator & UniverseState::iterator::normalize() { 90 | while(itI == itX->end()) { 91 | ++itX; 92 | if(itX == itY->end()) { 93 | ++itY; 94 | if(itY == obj->state.end()) { 95 | return *this; 96 | } 97 | itX = itY->begin(); 98 | } 99 | itI = itX->begin(); 100 | } 101 | return *this; 102 | } 103 | 104 | 105 | UniverseState::iterator UniverseState::begin() { 106 | UniverseState::iterator it{ this, state.begin(), state.front().begin(), state.front().front().begin() }; 107 | return it.normalize(); 108 | } 109 | 110 | UniverseState::iterator UniverseState::end() { 111 | return { this, state.end(), state.back().end(), state.back().back().end() }; 112 | } 113 | 114 | void UniverseState::insert(const ParticleState &pState) { 115 | assert(! state.empty()); 116 | int x = std::max(0, std::min((int) state[0].size() - 1, (int) (pState.pos.x / sizePerBlock))); 117 | int y = std::max(0, std::min((int) state.size() - 1, (int) (pState.pos.y / sizePerBlock))); 118 | state[y][x].push_back(pState); 119 | ++size_; 120 | } 121 | 122 | UniverseState::iterator UniverseState::erase(UniverseState::iterator it) { 123 | --size_; 124 | it.itX->erase(it.itI); 125 | return it.normalize(); 126 | } 127 | 128 | 129 | UniverseDifferentiator::UniverseDifferentiator(const UniverseConfig &_config, std::vector _types): 130 | config(_config), types(std::move(_types)) { 131 | } 132 | void UniverseDifferentiator::prepareDifferentiation(UniverseState &state) const { 133 | state.prepareDifferentiation(); 134 | } 135 | 136 | void UniverseDifferentiator::derivative(UniverseState &der, UniverseBuffers &derBuffers, UniverseState &state) const { 137 | // Using derivative cache as another accumulator for forces to avoid data race 138 | 139 | initForces(der, state); 140 | for (size_t i = 0; i < derBuffers.size(); ++i) 141 | initForces(derBuffers[i], state); 142 | 143 | computeForces(der, derBuffers, state); 144 | forcesToAccel(der, derBuffers); 145 | } 146 | 147 | void UniverseDifferentiator::initForces(UniverseState &der, const UniverseState &state) const { 148 | der.state.resize(state.state.size()); 149 | for(size_t y = 0; y < state.state.size(); ++y) { 150 | der.state[y].resize(state.state[y].size()); 151 | for(size_t x = 0; x < state.state[y].size(); ++x) { 152 | der.state[y][x].assign(state.state[y][x].size(), ParticleState()); 153 | for(size_t i = 0; i < state.state[y][x].size(); ++i) { 154 | der.state[y][x][i].type = state.state[y][x][i].type; 155 | der.state[y][x][i].pos = state.state[y][x][i].v; 156 | } 157 | } 158 | } 159 | } 160 | 161 | void UniverseDifferentiator::computeForces(UniverseState &der, UniverseBuffers &derBuffers, const UniverseState &state) const { 162 | assert(! state.state.empty()); 163 | 164 | AtomicCounter counter(state.state.size() * state.state[0].size()); 165 | 166 | #ifdef __EMSCRIPTEN__ 167 | computeForcesOneThread(der, derBuffers, state, counter); 168 | #else 169 | size_t nThreads = std::thread::hardware_concurrency() ? std::thread::hardware_concurrency() : 1; 170 | std::vector> futures; 171 | for(size_t i = 0; i < nThreads; ++i) { 172 | futures.push_back(threadPool.enqueue(& UniverseDifferentiator::computeForcesOneThread, this, 173 | std::ref(der), std::ref(derBuffers), std::cref(state), std::ref(counter))); 174 | } 175 | for(size_t i = 0; i < futures.size(); ++i) 176 | futures[i].wait(); 177 | #endif 178 | } 179 | 180 | void UniverseDifferentiator::computeForcesOneThread(UniverseState &der, UniverseBuffers &derBuffers, 181 | const UniverseState &state, AtomicCounter &counter) const { 182 | assert(! state.state.empty()); 183 | 184 | struct OtherCell { UniverseState &other; int x; int y; }; 185 | std::array cells = { 186 | OtherCell{der, 0, 0}, 187 | OtherCell{derBuffers[0], 1, 0}, 188 | OtherCell{derBuffers[1], -1, 1}, 189 | OtherCell{derBuffers[2], 0, 1}, 190 | OtherCell{derBuffers[3], 1, 1}, 191 | }; 192 | 193 | const int sizeX = state.state[0].size(); 194 | for (int idx = counter.next(); idx < counter.total(); idx = counter.next()) { 195 | int y0 = idx / sizeX; 196 | int x0 = idx % sizeX; 197 | for (size_t i0 = 0; i0 < state.state[y0][x0].size(); ++i0) { // Compute forces by edges and gravity 198 | const auto &pState0 = state.state[y0][x0][i0]; 199 | auto &pDer0 = der.state[y0][x0][i0]; 200 | pDer0.v.x += boundForce(-pState0.pos.x); 201 | pDer0.v.x -= boundForce(pState0.pos.x - config.sizeX); 202 | pDer0.v.y += boundForce(-pState0.pos.y); 203 | pDer0.v.y -= boundForce(pState0.pos.y - config.sizeY); 204 | pDer0.v.y += config.gravity * pState0.type->getMass(); 205 | } 206 | 207 | for (size_t cellIdx = 0; cellIdx < cells.size(); ++cellIdx) { // Compute interaction forces 208 | OtherCell &cell = cells[cellIdx]; 209 | int y1 = y0 + cell.y; 210 | int x1 = x0 + cell.x; 211 | if (y1 < 0 || x1 < 0 || y1 >= state.state.size() || x1 >= state.state[y1].size()) 212 | continue; 213 | 214 | for (size_t i0 = 0; i0 < state.state[y0][x0].size(); ++i0) { 215 | const auto &pState0 = state.state[y0][x0][i0]; 216 | auto &pDer0 = der.state[y0][x0][i0]; 217 | size_t maxI1 = cellIdx == 0 ? i0 : state.state[y1][x1].size(); 218 | for (size_t i1 = 0; i1 < maxI1; ++i1) { 219 | const auto &pState1 = state.state[y1][x1][i1]; 220 | auto &pDer1 = cell.other.state[y1][x1][i1]; 221 | Vector2D f = pState0.computeForce(pState1); 222 | pDer0.v += f; 223 | pDer1.v -= f; 224 | } 225 | } 226 | } 227 | } 228 | } 229 | 230 | double UniverseDifferentiator::boundForce(double overEdge) const { 231 | if(overEdge < 0) return 0; 232 | return config.forceFactor * overEdge * overEdge * overEdge * overEdge; 233 | } 234 | 235 | void UniverseDifferentiator::forcesToAccel(UniverseState &der, const UniverseBuffers &derBuffers) const { 236 | for(size_t y = 0; y < der.state.size(); ++y) 237 | for(size_t x = 0; x < der.state[y].size(); ++x) 238 | for(size_t i = 0; i < der.state[y][x].size(); ++i) { 239 | auto &pDer = der.state[y][x][i]; 240 | for(const UniverseState &buffer: derBuffers) { 241 | auto &pDerCache = buffer.state[y][x][i]; 242 | pDer.v += pDerCache.v; 243 | } 244 | pDer.v *= 1. / pDer.type->getMass(); 245 | } 246 | } 247 | 248 | Universe::Universe(const UniverseConfig &_config, const std::vector &_types): 249 | diff(_config, _types) { 250 | double interDist = 0; 251 | for(const auto &type: _types) { 252 | interDist = std::max(interDist, type.getRange()); 253 | } 254 | 255 | state.setInteractionDistance(_config, interDist); 256 | } 257 | 258 | void Universe::addParticle(int typeIndex, ParticleState pState) { 259 | pState.type = & diff.types[typeIndex]; 260 | state.insert(pState); 261 | } 262 | 263 | void Universe::removeParticle(int index) { 264 | state.state.erase(state.state.begin() + index); 265 | } 266 | 267 | void Universe::advance(double dT) { 268 | advanceRungeKutta4(state, diff, dT); 269 | } 270 | 271 | Vector2D Universe::clampInto(const Vector2D &pos) { 272 | double newX = std::min(std::max(pos.x, 0.), (double) diff.config.sizeX); 273 | double newY = std::min(std::max(pos.y, 0.), (double) diff.config.sizeY); 274 | return Vector2D(newX, newY); 275 | } 276 | -------------------------------------------------------------------------------- /Lib/Display.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "Display.h" 12 | #include "Globals.h" 13 | 14 | #ifdef SDL2_IMAGE_ENABLED 15 | #include 16 | #endif 17 | 18 | CallbackHandler::CallbackHandler(int _totalParticleTypes): 19 | totalParticleTypes(_totalParticleTypes) { 20 | } 21 | 22 | void CallbackHandler::mouseCallback(const SDL_Event &event) { 23 | int x, y; 24 | SDL_GetMouseState(& x, & y); 25 | pos = Vector2D(x, y); 26 | 27 | if(event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { 28 | bool down = event.type == SDL_MOUSEBUTTONDOWN; 29 | if (event.button.button == SDL_BUTTON_LEFT) leftDown = down; 30 | if (event.button.button == SDL_BUTTON_RIGHT) rightDown = down; 31 | } 32 | if(event.type == SDL_MOUSEWHEEL) { 33 | int scroll = 0; 34 | if(event.wheel.y > 0) scroll = 1; 35 | if(event.wheel.y < 0) scroll = -1; 36 | radius *= pow(1.2, -scroll); 37 | radius = std::max(radius, 10.); 38 | radius = std::min(radius, 200.); 39 | } 40 | sign = (int) leftDown - (int) rightDown; 41 | } 42 | 43 | void CallbackHandler::keyboardCallback(const SDL_Event &event) { 44 | int key = event.key.keysym.sym; 45 | if(key == 'h') action = MouseAction::heat; 46 | if(key == 'p') action = MouseAction::push; 47 | if(key == 'c') action = MouseAction::create; 48 | if(key == 's') action = MouseAction::spray; 49 | 50 | if('1' <= key && key <= '9') { 51 | int newParticleType = key - '0' - 1; 52 | if(newParticleType < totalParticleTypes) 53 | particleTypeIdx = newParticleType; 54 | } 55 | } 56 | 57 | 58 | void UniverseModifier::modify(Universe &universe, const CallbackHandler &handler, double dT) { 59 | if(! handler.sign) return; // No action from user 60 | 61 | modifyExisting(universe, handler, dT); 62 | addNew(universe, handler, dT); 63 | } 64 | 65 | void UniverseModifier::modifyExisting(Universe &universe, const CallbackHandler &handler, double dT) { 66 | const double heatingSpeed = 0.1; 67 | const double pushingSpeed = 0.5, pullingSpeed = 0.2; 68 | const double removeSpeed = 0.5; 69 | 70 | auto it = universe.begin(); 71 | while(it != universe.end()) { 72 | bool skipIncrement = false; 73 | auto &state = *it; 74 | if((state.pos - handler.pos).magnitude() < handler.radius) { 75 | switch(handler.action) { 76 | case MouseAction::heat: 77 | state.v *= 1. + handler.sign * heatingSpeed * dT; 78 | break; 79 | case MouseAction::push: 80 | if(handler.sign > 0) { 81 | state.v += (state.pos - handler.pos) * (pushingSpeed * dT / handler.radius); 82 | } else if (handler.sign < 0) { 83 | state.v -= (state.pos - handler.pos) * (pullingSpeed * dT / handler.radius); 84 | state.v *= 1. - heatingSpeed * dT; 85 | } 86 | break; 87 | case MouseAction::create: 88 | if(handler.sign > 0) { // pull and slow particles 89 | state.v -= (state.pos - handler.pos) * (pullingSpeed * dT / handler.radius); 90 | state.v *= 1. - heatingSpeed * dT; 91 | } 92 | case MouseAction::spray: 93 | if(handler.sign == -1) { 94 | double prob = removeSpeed * dT; 95 | if(std::bernoulli_distribution(prob)(randomGenerator)) { 96 | it = universe.erase(it); 97 | skipIncrement = true; 98 | } 99 | } 100 | break; 101 | } 102 | } 103 | 104 | if(! skipIncrement) ++it; 105 | } 106 | } 107 | 108 | void UniverseModifier::addNew(Universe &universe, const CallbackHandler &handler, double dT) { 109 | if(handler.sign <= 0) return; 110 | 111 | const double creationRadiusCoef = 0.8; 112 | const double sprayParticleSpeedCoef = 0.08; 113 | const double newParticlesCoef = 0.1; 114 | 115 | auto phiDistr = std::uniform_real_distribution<>(0., 2 * M_PI); 116 | std::array x = {0, creationRadiusCoef * handler.radius}; 117 | std::array y = {0, 1}; 118 | auto rDistr = std::piecewise_linear_distribution<>(x.begin(), x.end(), y.begin()); 119 | 120 | switch(handler.action) { 121 | case MouseAction::create: { 122 | double particleR = universe.getParticleTypes()[handler.particleTypeIdx].getRadius(); 123 | int newParticles = newParticlesCoef * handler.radius / particleR + 1; 124 | for(int i = 0; i < newParticles; ++i) { 125 | double phi = phiDistr(randomGenerator); 126 | double r = rDistr(randomGenerator); 127 | auto pos = universe.clampInto(handler.pos + Vector2D(r * cos(phi), r * sin(phi))); 128 | auto state = ParticleState(pos); 129 | universe.addParticle(handler.particleTypeIdx, state); 130 | } 131 | break; 132 | } 133 | case MouseAction::spray: { 134 | double phi = phiDistr(randomGenerator); 135 | double velocity = sprayParticleSpeedCoef * handler.radius; 136 | auto state = ParticleState(handler.pos, Vector2D(velocity * cos(phi), velocity * sin(phi))); 137 | universe.addParticle(handler.particleTypeIdx, state); 138 | break; 139 | } 140 | default: break; 141 | } 142 | } 143 | 144 | 145 | Display::Display(Universe &_universe, const std::string &_windowCaption, const std::string &_displayedCaption, 146 | const std::string &_directoryPath, const std::string &_recordingPath): 147 | universe(_universe), displayedCaption(_displayedCaption), directoryPath(_directoryPath), 148 | handler(_universe.getParticleTypes().size()), recordingPath(_recordingPath) { 149 | windowCaption = _windowCaption + " - " + _displayedCaption; 150 | 151 | if(SDL_Init(SDL_INIT_VIDEO) < 0) { 152 | std::cout << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; 153 | exit(1); 154 | } 155 | 156 | window = SDL_CreateWindow(windowCaption.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 157 | universe.getConfig().sizeX, universe.getConfig().sizeY, SDL_WINDOW_SHOWN); 158 | if(window == nullptr) { 159 | std::cout << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; 160 | exit(1); 161 | } 162 | surface = SDL_GetWindowSurface(window); 163 | defaultPointer = SDL_LoadBMP((directoryPath + "Sprites/DefaultPointer.bmp").c_str()); 164 | increasePointer = SDL_LoadBMP((directoryPath + "Sprites/IncreasePointer.bmp").c_str()); 165 | decreasePointer = SDL_LoadBMP((directoryPath + "Sprites/DecreasePointer.bmp").c_str()); 166 | 167 | TTF_Init(); 168 | font = TTF_OpenFont((directoryPath + "Fonts/DroidSans.ttf").c_str(), 24); 169 | 170 | isRecording = false; 171 | if(! recordingPath.empty()) { 172 | #if SDL2_IMAGE_ENABLED 173 | isRecording = true; 174 | system((std::string("mkdir -p ") + recordingPath).c_str()); 175 | #else 176 | std::cerr << "Warning: trying to record, but this build does not support recording" << std::endl; 177 | #endif 178 | } 179 | } 180 | 181 | Display::~Display() { 182 | SDL_FreeSurface(defaultPointer); 183 | SDL_FreeSurface(increasePointer); 184 | SDL_FreeSurface(decreasePointer); 185 | TTF_CloseFont(font); 186 | 187 | SDL_DestroyWindow(window); 188 | SDL_Quit(); 189 | } 190 | 191 | const CallbackHandler & Display::update() { 192 | SDL_FillRect(surface, nullptr, 0x000000); 193 | drawParticles(); 194 | drawDisplayedCaption(); 195 | drawStats(); 196 | drawPointer(); 197 | if(isRecording) recordAndDrawRecordingText(); 198 | SDL_UpdateWindowSurface(window); 199 | SDL_Event event; 200 | while(SDL_PollEvent(& event)) { 201 | if(event.type == SDL_QUIT) 202 | handler.quit = true; 203 | if(event.type == SDL_KEYDOWN) 204 | handler.keyboardCallback(event); 205 | if(event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEWHEEL 206 | || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) 207 | handler.mouseCallback(event); 208 | } 209 | return handler; 210 | } 211 | 212 | void Display::drawDisplayedCaption() { 213 | drawText(displayedCaption, 30, 60); 214 | } 215 | 216 | void Display::drawPointer() { 217 | SDL_Surface *pointer = defaultPointer; 218 | if(handler.sign > 0) pointer = increasePointer; 219 | if(handler.sign < 0) pointer = decreasePointer; 220 | 221 | const int numPointers = 12; 222 | for(int i = 0; i < numPointers; ++i) { 223 | double radians = (2. * M_PI / numPointers) * i; 224 | double dx = handler.radius * sin(radians), dy = handler.radius * cos(radians); 225 | drawSpriteFromCenter(pointer, (int) (handler.pos.x + dx), (int) (handler.pos.y + dy)); 226 | } 227 | 228 | std::string modeText = ""; 229 | if(handler.action == MouseAction::heat) modeText = "Heat mode"; 230 | if(handler.action == MouseAction::push) modeText = "Push mode"; 231 | if(handler.action == MouseAction::create) modeText = "Create mode"; 232 | if(handler.action == MouseAction::spray) modeText = "Spray mode"; 233 | drawText(modeText, 30, 120); 234 | 235 | int typeIdx = handler.particleTypeIdx; 236 | auto type = universe.getParticleTypes()[typeIdx]; 237 | std::string typeText = std::to_string(typeIdx + 1) + ": " + type.getName(); 238 | drawText(typeText, 30, 150); 239 | } 240 | 241 | void Display::drawStats() { 242 | int n; 243 | double velocity, temp; 244 | std::tie(n, velocity, temp) = computeStats(); 245 | 246 | const int prec = 2; 247 | drawText("n = " + std::to_string(n), 30, 200); 248 | drawText("velocity = " + to_string(velocity, prec), 30, 230); 249 | drawText("temp = " + to_string(temp, prec), 30, 260); 250 | } 251 | 252 | void Display::drawText(const std::string &text, int x, int y) { 253 | if(text == "") return; 254 | 255 | SDL_Color color = {255, 255, 255}; 256 | SDL_Surface* message = TTF_RenderText_Blended(font, text.c_str(), color); 257 | 258 | SDL_Rect outputRect; 259 | outputRect.x = x; 260 | outputRect.y = y; 261 | outputRect.w = message->w; 262 | outputRect.h = message->h; 263 | 264 | SDL_BlitSurface(message, nullptr, surface, & outputRect); 265 | SDL_FreeSurface(message); 266 | } 267 | 268 | std::tuple Display::computeStats() const { 269 | int n = 0; 270 | double mass = 0; 271 | Vector2D momentum; 272 | 273 | // Compute velocity 274 | for(auto it = universe.begin(); it != universe.end(); ++it) { 275 | double pMass = it->type->getMass(); 276 | Vector2D &pPos = it->pos, &pV = it->v; 277 | if((pPos - handler.pos).magnitude2() < handler.radius * handler.radius) { 278 | ++n; 279 | mass += pMass; 280 | momentum += pV * pMass; 281 | } 282 | } 283 | Vector2D velocity = momentum / mass; 284 | 285 | // Compute kinetic energy relative to average speed 286 | double energy = 0; 287 | for(auto it = universe.begin(); it != universe.end(); ++it) { 288 | double pMass = it->type->getMass(); 289 | Vector2D &pPos = it->pos, &pV = it->v; 290 | if((pPos - handler.pos).magnitude2() < handler.radius * handler.radius) 291 | energy += (pV - velocity).magnitude2() * pMass / 2; 292 | } 293 | double temp = energy / n; // E = kT * (degrees of freedom = 2) / 2, k == 1 (natural units) 294 | return { n, velocity.magnitude(), temp }; 295 | } 296 | 297 | void Display::drawParticles() { 298 | for(auto it = universe.begin(); it != universe.end(); ++it) { 299 | auto *particle = (SDL_Surface *) it->type->getSpriteSurface(); 300 | assert(particle != nullptr); 301 | drawSpriteFromCenter(particle, it->pos.x, it->pos.y); 302 | } 303 | } 304 | 305 | void Display::recordAndDrawRecordingText() { 306 | if(! isRecording) return; 307 | 308 | #ifdef SDL2_IMAGE_ENABLED 309 | IMG_SaveJPG(surface, (recordingPath + std::to_string(timestamp++) + ".jpg").c_str(), 95); 310 | #endif 311 | 312 | auto now = std::chrono::system_clock::now(); 313 | auto millisFromEpoch = std::chrono::duration_cast(now.time_since_epoch()).count(); 314 | if (millisFromEpoch % 1000 < 500) 315 | drawText("Recording...", 30, 30); 316 | } 317 | 318 | void Display::drawSpriteFromCenter(SDL_Surface *sprite, int x, int y) { 319 | assert(sprite != nullptr); 320 | SDL_Rect inputRect{0, 0, sprite->w, sprite->h}; 321 | SDL_Rect outputRect{x - sprite->w / 2, y - sprite->h / 2, sprite->w, sprite->h}; 322 | SDL_BlitSurface(sprite, & inputRect, surface, & outputRect); 323 | } 324 | 325 | std::string to_string(double x, int precision) { 326 | std::stringstream ss; 327 | ss << std::setprecision(precision) << std::fixed << x; 328 | return ss.str(); 329 | } 330 | --------------------------------------------------------------------------------