├── .gitignore ├── LICENSE ├── README.md ├── demo ├── collisions.cpp ├── gas_cloud.cpp ├── gif │ ├── collisions.gif │ ├── gas_cloud.gif │ └── soft_body.gif └── soft_body.cpp ├── include ├── cpparticles.hpp ├── environment.hpp ├── particle.hpp └── spring.hpp └── src ├── environment.cpp ├── particle.cpp └── spring.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # OS generated files # 35 | ###################### 36 | .DS_Store 37 | .DS_Store? 38 | ._* 39 | .Spotlight-V100 40 | .Trashes 41 | ehthumbs.db 42 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Faisha Surjatin 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPParticles 2 | A basic 2D physics library-ish written in C++ with included SFML demo programs. Made for practice and funsies with some guidance from 3 | [this Python tutorial](http://www.petercollingridge.co.uk/tutorials/pygame-physics-simulation/). 4 | 5 | ## Installation 6 | To use CPParticles in your project, include the `include` and `src` folders in your project directory. The `cpparticles.hpp` header file includes all the header files in the library: 7 | ```cpp 8 | #include "include/cpparticles.hpp" 9 | ``` 10 | Alternatively, you may also choose to include the individual header files. 11 | 12 | ## Demo 13 | This repository includes three demo files for your viewing pleasure (and also, in the meantime to serve as examples on how to use this library and 14 | demonstrate its capabilities because this readme is yet to be made fully extensive). 15 | 16 | > Compiling the demo programs requires [SFML](https://www.sfml-dev.org/) to be installed. 17 | 18 | ### collisions.cpp 19 | This program demonstrates particle physics in the library within the standard environment. 20 | 21 | ![Collisions demo](demo/gif/collisions.gif) 22 | 23 | ### gas_cloud.cpp 24 | This program demonstrates how changing the environment attributes in the library can be used to simulate a gas cloud. 25 | 26 | ![Gas cloud demo](demo/gif/gas_cloud.gif) 27 | 28 | ### soft_body.cpp 29 | This program demonstrates the use of springs to create a soft body. 30 | 31 | ![Soft body demo](demo/gif/soft_body.gif) 32 | 33 | ## License 34 | 35 | This project is licensed under the MIT license. See [LICENSE.md](LICENSE.md) for details. 36 | -------------------------------------------------------------------------------- /demo/collisions.cpp: -------------------------------------------------------------------------------- 1 | // Demonstrates particle physics in a 'regular' environment with gravity. 2 | #include 3 | #include "../include/cpparticles.hpp" 4 | 5 | int main() { 6 | 7 | // Set up the environment. 8 | Environment *env = new Environment(800, 600); 9 | Particle *selectedParticle = nullptr; 10 | 11 | // Create the main window. 12 | sf::RenderWindow window(sf::VideoMode(env->getWidth(), env->getHeight()), "Collision Simulation"); 13 | window.setFramerateLimit(60); 14 | 15 | // Add random particles to the environment. 16 | for (int i = 0; i < 10; i++) { 17 | env->addParticle(); 18 | } 19 | 20 | while (window.isOpen()) { 21 | 22 | // Process events. 23 | sf::Event event; 24 | while (window.pollEvent(event)) { 25 | 26 | // Close window: Exit. 27 | if (event.type == sf::Event::Closed) { 28 | window.close(); 29 | } 30 | 31 | // Escape: Exit. 32 | if (event.type == sf::Event::KeyPressed) { 33 | if (event.key.code == sf::Keyboard::Escape) { 34 | window.close(); 35 | } 36 | } 37 | 38 | // Hold left mouse button: Select and drag a particle. 39 | if (event.type == sf::Event::MouseButtonPressed) { 40 | if (event.mouseButton.button == sf::Mouse::Left) { 41 | float mouseX = event.mouseButton.x; 42 | float mouseY = event.mouseButton.y; 43 | selectedParticle = env->getParticle(mouseX, mouseY); 44 | } 45 | } 46 | 47 | // Release left mouse button: Release a particle. 48 | if (event.type == sf::Event::MouseButtonReleased) { 49 | if (event.mouseButton.button == sf::Mouse::Left) { 50 | selectedParticle = nullptr; 51 | } 52 | } 53 | } 54 | 55 | // Clear the window. 56 | window.clear(); 57 | 58 | // Update the environment. 59 | env->update(); 60 | 61 | // Move the selected particle to the cursor's position. 62 | if (selectedParticle) { 63 | float mouseX = sf::Mouse::getPosition(window).x; 64 | float mouseY = sf::Mouse::getPosition(window).y; 65 | selectedParticle->moveTo(mouseX, mouseY); 66 | } 67 | 68 | // Draw particles. 69 | for (int i = 0; i < env->getParticles().size(); i++) { 70 | Particle *particle = env->getParticles()[i]; 71 | sf::CircleShape circle(particle->getSize()); 72 | circle.setOrigin(particle->getSize(), particle->getSize()); 73 | circle.setPosition(particle->getX(), particle->getY()); 74 | window.draw(circle); 75 | } 76 | 77 | // Update the window. 78 | window.display(); 79 | } 80 | 81 | return EXIT_SUCCESS; 82 | } 83 | -------------------------------------------------------------------------------- /demo/gas_cloud.cpp: -------------------------------------------------------------------------------- 1 | // Demonstrates the use of different environment settings to simulate a gas cloud. 2 | #define _USE_MATH_DEFINES 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../include/cpparticles.hpp" 8 | 9 | int main() { 10 | 11 | // Set up the environment. 12 | Environment *env = new Environment(800, 600); 13 | env->setAllowAccelerate(false); 14 | env->setAllowAttract(true); 15 | env->setAllowBounce(false); 16 | env->setAllowCollide(false); 17 | env->setAllowCombine(true); 18 | env->setAllowDrag(false); 19 | 20 | // Create the main window. 21 | sf::RenderWindow window(sf::VideoMode(env->getWidth(), env->getHeight()), "Gas Cloud Simulation"); 22 | window.setFramerateLimit(60); 23 | 24 | // Set up the 'camera' values for viewing the environment. 25 | float dx = 0; 26 | float dy = 0; 27 | float mx = 0; 28 | float my = 0; 29 | float magnification = 1; 30 | bool paused = false; 31 | 32 | std::random_device rd; 33 | std::mt19937 engine(rd()); 34 | std::uniform_int_distribution massDist(1,5); 35 | 36 | // Add randomized particles to the environment. 37 | for (int i = 0; i < 500; i++) { 38 | int mass = massDist(rd); 39 | float size = 0.5 * pow(mass, 0.5); 40 | std::uniform_int_distribution xDist(size, env->getWidth() - size); 41 | std::uniform_int_distribution yDist(size, env->getHeight() - size); 42 | env->addParticle(xDist(rd), yDist(rd), size, mass, 0); 43 | } 44 | 45 | while (window.isOpen()) { 46 | 47 | // Process events. 48 | sf::Event event; 49 | while (window.pollEvent(event)) { 50 | 51 | // Close window: exit. 52 | if (event.type == sf::Event::Closed) { 53 | window.close(); 54 | } 55 | 56 | if (event.type == sf::Event::KeyPressed) { 57 | // Escape: exit. 58 | if (event.key.code == sf::Keyboard::Escape) { 59 | window.close(); 60 | } 61 | 62 | // Left arrow: move view window to the left. 63 | if (event.key.code == sf::Keyboard::Left) { 64 | dx += 0.2 * env->getWidth() / (magnification * 10); 65 | } 66 | 67 | // Right arrow: move view window to the right. 68 | if (event.key.code == sf::Keyboard::Right) { 69 | dx += -0.2 * env->getWidth() / (magnification * 10); 70 | } 71 | 72 | // Up arrow: move view window up. 73 | if (event.key.code == sf::Keyboard::Up) { 74 | dy += 0.2 * env->getHeight() / (magnification * 10); 75 | } 76 | 77 | // Down arrow: move view window down. 78 | if (event.key.code == sf::Keyboard::Down) { 79 | dy += -0.2 * env->getHeight() / (magnification * 10); 80 | } 81 | 82 | // Right bracket: zoom in. 83 | if (event.key.code == sf::Keyboard::RBracket) { 84 | magnification *= 1.2; 85 | mx = (1 - magnification) * env->getWidth() / 2; 86 | my = (1 - magnification) * env->getHeight() / 2; 87 | } 88 | 89 | // Left bracket: zoom out. 90 | if (event.key.code == sf::Keyboard::LBracket) { 91 | magnification *= 0.8; 92 | mx = (1 - magnification) * env->getWidth() / 2; 93 | my = (1 - magnification) * env->getHeight() / 2; 94 | } 95 | 96 | // "R" key: reset view window. 97 | if (event.key.code == sf::Keyboard::R) { 98 | dx = 0; 99 | dy = 0; 100 | mx = 0; 101 | my = 0; 102 | magnification = 1; 103 | } 104 | 105 | // Space: toggle pause. 106 | if (event.key.code == sf::Keyboard::Space) { 107 | if (paused) { 108 | paused = false; 109 | } else { 110 | paused = true; 111 | } 112 | } 113 | } 114 | } 115 | 116 | // Clear the window. 117 | window.clear(); 118 | 119 | // Update the environment. 120 | if (not paused) { 121 | env->update(); 122 | } 123 | 124 | for (int i = 0; i < env->getParticles().size(); i++) { 125 | Particle *particle = env->getParticles()[i]; 126 | 127 | // Combine colliding particles. 128 | if (particle->getCollideWith()) { 129 | env->removeParticle(particle->getCollideWith()); 130 | particle->setSize(0.5 * pow(particle->getMass(), 0.5)); 131 | } 132 | 133 | // Update view window by changing the position and size of the drawn particles. 134 | float x = mx + (dx + particle->getX()) * magnification; 135 | float y = my + (dy + particle->getY()) * magnification; 136 | float size = particle->getSize() * magnification; 137 | 138 | // Draw particles. 139 | sf::CircleShape circle(size); 140 | circle.setOrigin(size, size); 141 | circle.setPosition(x, y); 142 | window.draw(circle); 143 | } 144 | 145 | // Update the window. 146 | window.display(); 147 | } 148 | 149 | return EXIT_SUCCESS; 150 | } 151 | -------------------------------------------------------------------------------- /demo/gif/collisions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faishasj/cpparticles/dbf024eadf1dc51f647da1df7d0d9dc4750ef7f1/demo/gif/collisions.gif -------------------------------------------------------------------------------- /demo/gif/gas_cloud.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faishasj/cpparticles/dbf024eadf1dc51f647da1df7d0d9dc4750ef7f1/demo/gif/gas_cloud.gif -------------------------------------------------------------------------------- /demo/gif/soft_body.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faishasj/cpparticles/dbf024eadf1dc51f647da1df7d0d9dc4750ef7f1/demo/gif/soft_body.gif -------------------------------------------------------------------------------- /demo/soft_body.cpp: -------------------------------------------------------------------------------- 1 | // Demonstrates the use of springs to create a soft body. 2 | #include 3 | #include "../include/cpparticles.hpp" 4 | 5 | int main() { 6 | 7 | // Set up the environment. 8 | Environment *env = new Environment(800, 600); 9 | Particle *selectedParticle = nullptr; 10 | 11 | // Create the main window. 12 | sf::RenderWindow window(sf::VideoMode(env->getWidth(), env->getHeight()), "Soft Body Simulation"); 13 | window.setFramerateLimit(60); 14 | 15 | // Add particles for the soft body to the environment. 16 | int size = 10; 17 | int mass = 600; 18 | int speed = 0; 19 | int angle = 0; 20 | float elasticity = 0.1; 21 | 22 | Particle *p1 = env->addParticle(300, 300, size, mass, speed, angle, elasticity); 23 | Particle *p2 = env->addParticle(500, 300, size, mass, speed, angle, elasticity); 24 | Particle *p3 = env->addParticle(500, 500, size, mass, speed, angle, elasticity); 25 | Particle *p4 = env->addParticle(300, 500, size, mass, speed, angle, elasticity); 26 | 27 | // Connect particles using springs to create the soft body. 28 | int length = 200; 29 | int strength = 50; 30 | 31 | env->addSpring(p1, p2, length, strength); 32 | env->addSpring(p2, p3, length, strength); 33 | env->addSpring(p3, p4, length, strength); 34 | env->addSpring(p4, p1, length, strength); 35 | env->addSpring(p1, p3, length, strength); 36 | env->addSpring(p2, p4, length, strength); 37 | 38 | while (window.isOpen()) { 39 | 40 | // Process events. 41 | sf::Event event; 42 | while (window.pollEvent(event)) { 43 | 44 | // Close window: exit. 45 | if (event.type == sf::Event::Closed) { 46 | window.close(); 47 | } 48 | 49 | // Escape: exit. 50 | if (event.type == sf::Event::KeyPressed) { 51 | if (event.key.code == sf::Keyboard::Escape) { 52 | window.close(); 53 | } 54 | } 55 | 56 | // Left mouse button: select particle. 57 | if (event.type == sf::Event::MouseButtonPressed) { 58 | if (event.mouseButton.button == sf::Mouse::Left) { 59 | float mouseX = event.mouseButton.x; 60 | float mouseY = event.mouseButton.y; 61 | selectedParticle = env->getParticle(mouseX, mouseY); 62 | } 63 | } 64 | 65 | if (event.type == sf::Event::MouseButtonReleased) { 66 | if (event.mouseButton.button == sf::Mouse::Left) { 67 | selectedParticle = nullptr; 68 | } 69 | } 70 | } 71 | 72 | // Clear the window. 73 | window.clear(); 74 | 75 | // Update the environment. 76 | env->update(); 77 | 78 | // Move the selected particle to the cursor's position. 79 | if (selectedParticle) { 80 | float mouseX = sf::Mouse::getPosition(window).x; 81 | float mouseY = sf::Mouse::getPosition(window).y; 82 | selectedParticle->moveTo(mouseX, mouseY); 83 | } 84 | 85 | // Draw springs. 86 | for (int i=0; igetSprings().size(); i++) { 87 | Spring *spring = env->getSprings()[i]; 88 | sf::Vertex line[] = 89 | { 90 | sf::Vertex(sf::Vector2f(spring->getP1()->getX(), spring->getP1()->getY())), 91 | sf::Vertex(sf::Vector2f(spring->getP2()->getX(), spring->getP2()->getY())) 92 | }; 93 | window.draw(line, 2, sf::Lines); 94 | } 95 | 96 | // Update the window. 97 | window.display(); 98 | } 99 | 100 | return EXIT_SUCCESS; 101 | } 102 | -------------------------------------------------------------------------------- /include/cpparticles.hpp: -------------------------------------------------------------------------------- 1 | // Header package for the CPParticles 2D physics library. 2 | #ifndef CPParticles_hpp 3 | #define CPParticles_hpp 4 | 5 | #include "environment.hpp" 6 | #include "particle.hpp" 7 | #include "spring.hpp" 8 | 9 | #endif // cpparticles_hpp 10 | -------------------------------------------------------------------------------- /include/environment.hpp: -------------------------------------------------------------------------------- 1 | // Header for the Environment class. 2 | #ifndef environment_hpp 3 | #define environment_hpp 4 | #define _USE_MATH_DEFINES 5 | 6 | #include 7 | #include 8 | #include "particle.hpp" 9 | #include "spring.hpp" 10 | 11 | 12 | // Handles all interaction between particles, springs and attributes within the environment. 13 | class Environment { 14 | public: 15 | Environment(int width, int height); 16 | ~Environment(); 17 | int getHeight() { return height; } 18 | int getWidth() { return width; } 19 | Particle * addParticle(); 20 | Particle * addParticle(float x, float y, float size=10, float mass=100, float speed=0, float angle=0, float elasticity=0.9); 21 | Particle * getParticle(float x, float y); 22 | Spring * addSpring(Particle *p1, Particle *p2, float length=50, float strength=0.5); 23 | std::vector getParticles() { return particles; } 24 | std::vector getSprings() { return springs; } 25 | void bounce(Particle *particle); 26 | void removeParticle(Particle *particle); 27 | void removeSpring(Spring *spring); 28 | void setAirMass(float a) { airMass = a; } 29 | void setAllowAccelerate(bool setting) { allowAccelerate = setting; } 30 | void setAllowAttract(bool setting) { allowAttract = setting; } 31 | void setAllowBounce(bool setting) { allowBounce = setting; } 32 | void setAllowCollide(bool setting) { allowCollide = setting; } 33 | void setAllowCombine(bool setting) { allowCombine = setting; } 34 | void setAllowDrag(bool setting) { allowDrag = setting; } 35 | void setAllowMove(bool setting) { allowMove = setting; } 36 | void setElasticity(float e) { elasticity = e; } 37 | void update(); 38 | 39 | protected: 40 | const int height; 41 | const int width; 42 | bool allowAccelerate = true; 43 | bool allowAttract = false; 44 | bool allowBounce = true; 45 | bool allowCollide = true; 46 | bool allowCombine = false; 47 | bool allowDrag = true; 48 | bool allowMove = true; 49 | float airMass = 0.2; 50 | float elasticity = 0.75; 51 | std::vector particles; 52 | std::vector springs; 53 | Vector acceleration = {M_PI, 0.2}; 54 | }; 55 | 56 | #endif // environment_hpp 57 | -------------------------------------------------------------------------------- /include/particle.hpp: -------------------------------------------------------------------------------- 1 | // Header for the Particle class and Vector struct. 2 | #ifndef particle_hpp 3 | #define particle_hpp 4 | #define _USE_MATH_DEFINES 5 | 6 | #include 7 | 8 | 9 | // Contains direction (angle) and magnitude (speed). 10 | struct Vector { 11 | float angle; 12 | float speed; 13 | }; 14 | 15 | Vector operator+(Vector const& v1, Vector const& v2); 16 | 17 | 18 | // Handles the movement and forces acting upon the particle and surrounding particles. 19 | class Particle { 20 | public: 21 | Particle(float x, float y, float size, float mass, float speed, float angle, float elasticity, float drag); 22 | Particle *getCollideWith() { return collideWith; } 23 | float getAngle() { return angle; } 24 | float getDrag() { return drag; } 25 | float getElasticity() { return elasticity; } 26 | float getMass() { return mass; } 27 | float getSize() { return size; } 28 | float getSpeed() { return speed; } 29 | float getX() { return x; } 30 | float getY() { return y; } 31 | void accelerate(Vector vector); 32 | void attract(Particle *otherP); 33 | void collide(Particle *otherP); 34 | void combine(Particle *otherP); 35 | void experienceDrag(); 36 | void move(); 37 | void moveTo(float moveX, float moveY); 38 | void setAngle(float a) { angle = a; } 39 | void setDrag(float d) { drag = d; } 40 | void setElasticity(float e) { elasticity = e; } 41 | void setMass(float m) { mass = m; } 42 | void setSize(float s) { size = s; } 43 | void setSpeed(float s) { speed = s; } 44 | void setX(float xCoord) { x = xCoord; } 45 | void setY(float yCoord) { y = yCoord; } 46 | 47 | protected: 48 | float angle; 49 | float drag; 50 | float elasticity; 51 | float mass; 52 | float size; 53 | float speed; 54 | float x; 55 | float y; 56 | Particle *collideWith = NULL; 57 | }; 58 | 59 | #endif // particle_hpp 60 | -------------------------------------------------------------------------------- /include/spring.hpp: -------------------------------------------------------------------------------- 1 | // Header for the Spring class. 2 | #ifndef spring_hpp 3 | #define spring_hpp 4 | #define _USE_MATH_DEFINES 5 | 6 | #include 7 | #include "particle.hpp" 8 | 9 | 10 | // Handles the movement and forces acting upon the spring. 11 | class Spring { 12 | public: 13 | Spring(Particle *p1, Particle *p2, float length=50, float strength=0.5); 14 | Particle *getP1() { return p1; } 15 | Particle *getP2() { return p2; } 16 | void update(); 17 | 18 | protected: 19 | float length; 20 | float strength; 21 | Particle *p1; 22 | Particle *p2; 23 | }; 24 | 25 | #endif // spring_hpp 26 | -------------------------------------------------------------------------------- /src/environment.cpp: -------------------------------------------------------------------------------- 1 | // Contains member functions of the Environment class. 2 | // Handles all interaction between particles, springs and attributes within the environment. 3 | #include "../include/environment.hpp" 4 | 5 | 6 | // Environment constructor. 7 | Environment::Environment(int width, int height): 8 | width(width), height(height) { 9 | } 10 | 11 | 12 | // Environment destructor. Destroys all particles and springs in the environment. 13 | Environment::~Environment() { 14 | for (int i = 0; i < springs.size(); i++) { 15 | delete springs[i]; 16 | } 17 | for (int i = 0; i < particles.size(); i++) { 18 | delete particles[i]; 19 | } 20 | } 21 | 22 | 23 | // Adds a particle with randomly generated attributes to the environment and returns a pointer to the particle. 24 | Particle * Environment::addParticle() { 25 | std::random_device rd; 26 | std::mt19937 engine(rd()); 27 | std::uniform_int_distribution sizeDist(10,20); 28 | float size = sizeDist(rd); 29 | std::uniform_int_distribution massDist(100, 10000); 30 | float mass = massDist(rd); 31 | std::uniform_int_distribution xDist(size, width - size); 32 | float x = xDist(rd); 33 | std::uniform_int_distribution yDist(size, height - size); 34 | float y = yDist(rd); 35 | std::uniform_real_distribution speedDist (0, 1); 36 | float speed = speedDist(rd); 37 | std::uniform_real_distribution angleDist (0, 2 * M_PI); 38 | float angle = angleDist(rd); 39 | std::uniform_real_distribution elasticityDist (0.8, 1); 40 | float elasticity = elasticityDist(rd); 41 | return addParticle(x, y, size, mass, speed, angle, elasticity); 42 | } 43 | 44 | 45 | // Adds a particle with parameter-specified attributes to the environment and returns a pointer to the particle. 46 | Particle * Environment::addParticle(float x, float y, float size, float mass, float speed, float angle, float elasticity) { 47 | // Equation for drag [source]: http://www.petercollingridge.co.uk/tutorials/pygame-physics-simulation/mass/ 48 | float drag = pow((mass / (mass + airMass)), size); 49 | Particle *particle = new Particle(x, y, size, mass, speed, angle, elasticity, drag); 50 | particles.push_back(particle); 51 | return particle; 52 | } 53 | 54 | 55 | // Returns a pointer to the particle from the environment at the coordinates (x, y), otherwise nullptr. 56 | Particle * Environment::getParticle(float x, float y){ 57 | for (int i = 0; i < particles.size(); i++) { 58 | if (hypot(particles[i]->getX() - x, particles[i]->getY() - y) <= particles[i]->getSize()) { 59 | return particles[i]; 60 | } 61 | } 62 | return nullptr; 63 | } 64 | 65 | 66 | // Adds a spring connecting two particles in the environment and returns a pointer to the spring. 67 | Spring * Environment::addSpring(Particle *p1, Particle *p2, float length, float strength) { 68 | Spring *spring = new Spring(p1, p2, length, strength); 69 | springs.push_back(spring); 70 | return spring; 71 | } 72 | 73 | 74 | // Bounces a particle if in contact with boundary of the environment. 75 | void Environment::bounce(Particle *particle) { 76 | // Particle hits the right boundary: 77 | if (particle->getX() > (width - particle->getSize())) { 78 | particle->setX(2 * (width - particle->getSize()) - particle->getX()); 79 | particle->setAngle(-particle->getAngle()); 80 | particle->setSpeed(particle->getSpeed() * particle->getElasticity()); 81 | // Particle hits the left boundary: 82 | } else if (particle->getX() < particle->getSize()) { 83 | particle->setX(2 * particle->getSize() - particle->getX()); 84 | particle->setAngle(-particle->getAngle()); 85 | particle->setSpeed(particle->getSpeed() * particle->getElasticity()); 86 | } 87 | // Particle hits the bottom boundary: 88 | if (particle->getY() > (height - particle->getSize())) { 89 | particle->setY(2 * (height - particle->getSize()) - particle->getY()); 90 | particle->setAngle(M_PI - particle->getAngle()); 91 | particle->setSpeed(particle->getSpeed() * particle->getElasticity()); 92 | // Particle hits the top boundary: 93 | } else if (particle->getY() < particle->getSize()) { 94 | particle->setY(2 * particle->getSize() - particle->getY()); 95 | particle->setAngle(M_PI - particle->getAngle()); 96 | particle->setSpeed(particle->getSpeed() * particle->getElasticity()); 97 | } 98 | } 99 | 100 | 101 | // Removes a particle from the environment. 102 | void Environment::removeParticle(Particle *particle) { 103 | for (int i = 0; i < particles.size(); i++) { 104 | if (particle == particles[i]) { 105 | delete particles[i]; 106 | particles.erase(particles.begin() + i); 107 | } 108 | } 109 | } 110 | 111 | 112 | // Removes a spring from the environment. 113 | void Environment::removeSpring(Spring *spring) { 114 | for (int i = 0; i < springs.size(); i++) { 115 | if (spring == springs[i]) { 116 | delete springs[i]; 117 | springs.erase(springs.begin() + i); 118 | } 119 | } 120 | } 121 | 122 | 123 | // Updates all particles and springs in the environment. 124 | void Environment::update() { 125 | for (int i = 0; i < particles.size(); i++) { 126 | Particle *particle = particles[i]; 127 | if (allowAccelerate) { 128 | particle->accelerate(acceleration); 129 | } 130 | if (allowMove) { 131 | particle->move(); 132 | } 133 | if (allowDrag) { 134 | particle->experienceDrag(); 135 | } 136 | if (allowBounce) { 137 | bounce(particle); 138 | } 139 | // Allows interaction with other particles. 140 | for (int x = i + 1; x < particles.size(); x++) { 141 | Particle *otherParticle = particles[x]; 142 | if (allowCollide) { 143 | particle->collide(otherParticle); 144 | } 145 | if (allowAttract) { 146 | particle->attract(otherParticle); 147 | } 148 | if (allowCombine) { 149 | particle->combine(otherParticle); 150 | } 151 | } 152 | } 153 | for (int i = 0; i < springs.size(); i++) { 154 | Spring *spring = springs[i]; 155 | spring->update(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/particle.cpp: -------------------------------------------------------------------------------- 1 | // Contains Vector operator+ function, and member functions of the Particle class. 2 | // Handles the movement and forces acting upon the particle and surrounding particles. 3 | #include "../include/particle.hpp" 4 | 5 | 6 | // Adds two vectors and returns the resulting vector. 7 | Vector operator+(Vector const& v1, Vector const& v2) { 8 | float x = sin(v1.angle) * v1.speed + sin(v2.angle) * v2.speed; 9 | float y = cos(v1.angle) * v1.speed + cos(v2.angle) * v2.speed; 10 | return Vector{static_cast(0.5 * M_PI - atan2(y, x)), hypot(x, y)}; 11 | } 12 | 13 | 14 | // Particle constructor. 15 | Particle::Particle(float x, float y, float size, float mass, float speed, float angle, float elasticity, float drag): 16 | x(x), y(y), size(size), mass(mass), speed(speed), angle(angle), elasticity(elasticity), drag(drag) { 17 | } 18 | 19 | 20 | // Accelerates the particle. 21 | void Particle::accelerate(Vector vector) { 22 | Vector velocity = Vector{angle, speed} + vector; 23 | angle = velocity.angle; 24 | speed = velocity.speed; 25 | } 26 | 27 | 28 | // Attracts another particle to the particle. 29 | void Particle::attract(Particle *otherP) { 30 | float dx = x - otherP->x; 31 | float dy = y - otherP->y; 32 | float distance = hypot(dx, dy); 33 | float theta = atan2(dy, dx); 34 | float force = 0.2 * mass * otherP->mass / pow(distance, 2); 35 | accelerate(Vector {static_cast(theta - 0.5 * M_PI), force / mass}); 36 | otherP->accelerate(Vector {static_cast(theta + 0.5 * M_PI), force/otherP->mass}); 37 | } 38 | 39 | 40 | // Collides the particle with another particle. 41 | void Particle::collide(Particle *otherP) { 42 | float dx = x - otherP->x; 43 | float dy = y - otherP->y; 44 | float distance = hypot(dx, dy); 45 | 46 | if (distance < (size + otherP->size)) { // Collision detected. 47 | float tangent = atan2(dy, dx); 48 | float newAngle = 0.5 * M_PI + tangent; 49 | float totalMass = mass + otherP->mass; 50 | 51 | Vector v1 = Vector{angle, speed * (mass - otherP->mass) / totalMass} + Vector{newAngle, 2 * otherP->speed * otherP->mass / totalMass}; 52 | Vector v2 = Vector{otherP->angle, otherP->speed * (otherP->mass - mass) / totalMass} + Vector{static_cast(newAngle+M_PI), 2 * speed * mass / totalMass}; 53 | 54 | angle = v1.angle; 55 | speed = v1.speed; 56 | otherP->angle = v2.angle; 57 | otherP->speed = v2.speed; 58 | 59 | float newElasticity = elasticity * otherP->elasticity; 60 | speed *= newElasticity; 61 | otherP->speed *= newElasticity; 62 | 63 | float overlap = 0.5 * (size + otherP->size - distance + 1); 64 | x += sin(newAngle) * overlap; 65 | y -= cos(newAngle) * overlap; 66 | otherP->x -= sin(newAngle) * overlap; 67 | otherP->y += cos(newAngle) * overlap; 68 | } 69 | } 70 | 71 | 72 | // Combines the particle with another particle. 73 | void Particle::combine(Particle *otherP) { 74 | float dx = x - otherP->x; 75 | float dy = y - otherP->y; 76 | float distance = hypot(dx, dy); 77 | 78 | if (distance < (size + otherP->size)) { // Collision detected. 79 | float totalMass = mass + otherP->mass; 80 | x = (x * mass + otherP->x * otherP->mass) / totalMass; 81 | y = (y * mass + otherP->y * otherP->mass) / totalMass; 82 | Vector vector = Vector{angle, speed * mass / totalMass} + Vector{otherP->angle, otherP->speed * otherP->mass / totalMass}; 83 | angle = vector.angle; 84 | speed = vector.speed * (elasticity * otherP->elasticity); 85 | mass += otherP->mass; 86 | collideWith = otherP; 87 | } 88 | } 89 | 90 | 91 | // Affects the speed of the particle with drag. 92 | void Particle::experienceDrag() { 93 | speed *= drag; 94 | } 95 | 96 | 97 | // Updates the position of the particle. 98 | void Particle::move() { 99 | x += sin(angle) * speed; 100 | y -= cos(angle) * speed; 101 | } 102 | 103 | 104 | // Moves the particle to coordinates (x, y). 105 | void Particle::moveTo(float moveX, float moveY) { 106 | float dx = moveX - x; 107 | float dy = moveY - y; 108 | angle = atan2(dy, dx) + 0.5 * M_PI; 109 | speed = hypot(dx, dy) * 0.1; 110 | } 111 | -------------------------------------------------------------------------------- /src/spring.cpp: -------------------------------------------------------------------------------- 1 | // Contains member functions of the Spring class. 2 | // Handles the movement and forces acting upon the spring. 3 | #include "../include/spring.hpp" 4 | 5 | 6 | // Spring constructor. 7 | Spring::Spring(Particle *p1, Particle *p2, float length, float strength): 8 | p1(p1), p2(p2), length(length), strength(strength) { 9 | } 10 | 11 | 12 | // Updates the spring. 13 | void Spring::update() { 14 | float dx = p1->getX() - p2->getX(); 15 | float dy = p1->getY() - p2->getY(); 16 | float distance = hypot(dx, dy); 17 | float theta = atan2(dy, dx); 18 | float force = (length - distance) * strength; 19 | p1->accelerate(Vector{static_cast(theta + 0.5*M_PI), force / p1->getMass()}); 20 | p2->accelerate(Vector{static_cast(theta - 0.5*M_PI), force / p2->getMass()}); 21 | } 22 | --------------------------------------------------------------------------------