├── LICENSE ├── README.md ├── constraint.h ├── input_handler.h ├── main.cpp ├── makefile └── particle.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FelipesCoding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloth Simulation with SFML 2 | 3 | ## Description 4 | 5 | This project is a cloth simulation implemented using SFML (Simple and Fast Multimedia Library). 6 | It demonstrates basic cloth physics and rendering using SFML, which is a popular multimedia library for C++. 7 | 8 | ## Prerequisites 9 | 10 | To compile this project you will need: 11 | 12 | - **[Homebrew](https://brew.sh/)** (for managing packages on macOS/Linux) 13 | - **SFML** (installed via Homebrew - brew install sfml) 14 | 15 | Otherwise change CXXFLAGS and LDFLAGS in makefile. 16 | -------------------------------------------------------------------------------- /constraint.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTRAINT_H 2 | #define CONSTRAINT_H 3 | 4 | #include "particle.h" 5 | #include 6 | #include 7 | 8 | class Constraint { 9 | public: 10 | Particle *p1; 11 | Particle *p2; 12 | float initial_length; 13 | bool active; 14 | 15 | Constraint(Particle *p1, Particle *p2) : p1(p1), p2(p2) { 16 | initial_length = std::hypot(p2->position.x - p1->position.x, 17 | p2->position.y - p1->position.y); 18 | active = true; 19 | } 20 | 21 | void satisfy() { 22 | if (!active) return; 23 | 24 | sf::Vector2f delta = p2->position - p1->position; 25 | float current_length = std::hypot(delta.x, delta.y); 26 | float difference = (current_length - initial_length) / current_length; 27 | sf::Vector2f correction = delta * 0.5f * difference; 28 | 29 | if (!p1->is_pinned) p1->position += correction; 30 | if (!p2->is_pinned) p2->position -= correction; 31 | } 32 | 33 | void deactivate() { 34 | active = false; 35 | } 36 | 37 | }; 38 | 39 | #endif // CONSTRAINT_H 40 | -------------------------------------------------------------------------------- /input_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_HANDLER_H 2 | #define INPUT_HANDLER_H 3 | 4 | #include 5 | #include 6 | #include "particle.h" 7 | #include "constraint.h" 8 | 9 | const float CLICK_TOLERANCE = 5.0f; 10 | 11 | 12 | class InputHandler { 13 | public: 14 | static void handle_mouse_click(const sf::Event& event, std::vector& particles, 15 | std::vector& constraints) { 16 | if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) { 17 | float mouse_x = static_cast(event.mouseButton.x); 18 | float mouse_y = static_cast(event.mouseButton.y); 19 | tear_cloth(mouse_x, mouse_y, particles, constraints); 20 | } 21 | } 22 | private: 23 | static float point_to_segment_distance(float px, float py, float x1, float y1, float x2, float y2) { 24 | float ABx = x2 - x1; 25 | float ABy = y2 - y1; 26 | 27 | float APx = px - x1; 28 | float APy = py - y1; 29 | 30 | float BPx = px - x2; 31 | float BPy = py - y2; 32 | 33 | float AB_AP = ABx * APx + ABy * APy; 34 | float AB_AB = ABx * ABx + ABy * ABy; 35 | float t = AB_AP / AB_AB; 36 | 37 | // Project point P ont the line segment AB 38 | if (t < 0.0f) { 39 | // P is closer to A 40 | return std::sqrt(APx * APx + APy * APy); 41 | } 42 | else if (t > 1.0f) { 43 | // P is closer to B 44 | return std::sqrt(BPx * BPx + BPy * BPy); 45 | } 46 | else { 47 | // projection point is on the segment 48 | float proj_x = x1 + t * ABx; 49 | float proj_y = y1 + t * ABy; 50 | return std::sqrt((px - proj_x) * (px - proj_x) + (py - proj_y) * (py - proj_y)); 51 | } 52 | } 53 | 54 | static Constraint* find_nearest_constraint(float mouse_x, float mouse_y, 55 | const std::vector& constraints) { 56 | Constraint *nearest_constraint = nullptr; 57 | float min_distance = CLICK_TOLERANCE; 58 | 59 | for (const auto& constraint : constraints) { 60 | float distatnce = point_to_segment_distance(mouse_x, mouse_y, 61 | constraint.p1->position.x, constraint.p1->position.y, 62 | constraint.p2->position.x, constraint.p2->position.y); 63 | if (distatnce < min_distance) { 64 | min_distance = distatnce; 65 | nearest_constraint = const_cast(&constraint); 66 | } 67 | } 68 | return nearest_constraint; 69 | } 70 | 71 | 72 | static void tear_cloth(float mouse_x, float mouse_y, const std::vector& particles, 73 | std::vector& constraints) { 74 | Constraint *nearest = find_nearest_constraint(mouse_x, mouse_y, constraints); 75 | if (nearest) { 76 | nearest->deactivate(); 77 | } 78 | } 79 | }; 80 | 81 | #endif // INPUT_HANDLER_H -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "particle.h" 6 | #include "constraint.h" 7 | #include "input_handler.h" 8 | 9 | const int WIDTH = 1080; 10 | const int HEIGHT = 640; 11 | const float PARTICLE_RADIOUS = 10.0f; 12 | const float GRAVITY = 10.0f; 13 | const float TIME_STEP = 0.1f; 14 | 15 | const int ROW = 10; 16 | const int COL = 10; 17 | const float REST_DISTANCE = 30.0f; 18 | 19 | int main() { 20 | sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "Cloth Simulation"); 21 | window.setFramerateLimit(60); 22 | 23 | std::vector particles; 24 | std::vector constraints; 25 | 26 | for (int row = 0; row < ROW; row++) { 27 | for (int col = 0; col < COL; col++) { 28 | float x = col * REST_DISTANCE + WIDTH/3; 29 | float y = row * REST_DISTANCE + HEIGHT/3; 30 | bool pinned = (row == 0); 31 | particles.emplace_back(x, y, pinned); 32 | } 33 | } 34 | 35 | // Initialize constraints 36 | for (int row = 0; row < ROW; row++) { 37 | for (int col = 0; col < COL; col++) { 38 | if (col < COL - 1) { 39 | // Horizontal constraint 40 | constraints.emplace_back(&particles[row * COL + col], &particles[row * COL + col + 1]); 41 | } 42 | if (row < ROW - 1) { 43 | // Vertical constraint 44 | constraints.emplace_back(&particles[row * COL + col], &particles[(row + 1) * COL + col]); 45 | } 46 | } 47 | } 48 | 49 | while (window.isOpen()) { 50 | sf::Event event; 51 | while (window.pollEvent(event)) { 52 | if (event.type == sf::Event::Closed) { 53 | window.close(); 54 | } 55 | 56 | // handle mouse clicks 57 | InputHandler::handle_mouse_click(event, particles, constraints); 58 | 59 | } 60 | 61 | //apply gravity and update particles 62 | for (auto& particle : particles) { 63 | particle.apply_force(sf::Vector2f(0, GRAVITY)); 64 | particle.update(TIME_STEP); 65 | particle.constrain_to_bounds(WIDTH, HEIGHT); 66 | } 67 | 68 | for (size_t i = 0; i < 5; i++) { 69 | for (auto& constraint : constraints) { 70 | constraint.satisfy(); 71 | } 72 | } 73 | 74 | window.clear(sf::Color::Black); 75 | 76 | 77 | // Draw particles as balls 78 | // for (const auto& particle : particles) { 79 | // sf::CircleShape circle(PARTICLE_RADIOUS); 80 | // circle.setFillColor(sf::Color::White); 81 | // circle.setPosition(particle.position.x - PARTICLE_RADIOUS, 82 | // particle.position.y - PARTICLE_RADIOUS); 83 | // window.draw(circle); 84 | // } 85 | 86 | // Draw particles as points 87 | for (const auto& particle : particles) { 88 | sf::Vertex point(particle.position, sf::Color::White); 89 | window.draw(&point, 1, sf::Points); 90 | } 91 | 92 | 93 | // Draw constraints as lines 94 | for (const auto& constraint : constraints) { 95 | if (!constraint.active) { 96 | continue; 97 | } 98 | sf::Vertex line[] = { 99 | sf::Vertex(constraint.p1->position, sf::Color::White), 100 | sf::Vertex(constraint.p2->position, sf::Color::White), 101 | }; 102 | window.draw(line, 2, sf::Lines); 103 | } 104 | 105 | window.display(); 106 | } 107 | } -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ -std=c++17 2 | CXXFLAGS = -I/opt/homebrew/Cellar/sfml/2.6.1/include 3 | LDFLAGS = -L/opt/homebrew/Cellar/sfml/2.6.1/lib -lsfml-graphics -lsfml-window -lsfml-system 4 | 5 | TARGET = main 6 | SRC = main.cpp 7 | OBJ = $(SRC:.cpp=.o) 8 | 9 | all: $(TARGET) 10 | 11 | $(TARGET): $(OBJ) 12 | $(CXX) $(OBJ) -o $(TARGET) $(LDFLAGS) 13 | 14 | %.o: %.cpp 15 | $(CXX) $(CXXFLAGS) -c $< -o $@ 16 | 17 | clean: 18 | rm -f $(TARGET) $(OBJ) 19 | -------------------------------------------------------------------------------- /particle.h: -------------------------------------------------------------------------------- 1 | #ifndef PARTICLE_H 2 | #define PARTICLE_H 3 | 4 | #include 5 | 6 | class Particle { 7 | public: 8 | sf::Vector2f position; 9 | sf::Vector2f previous_position; 10 | sf::Vector2f acceleration; 11 | bool is_pinned; 12 | 13 | Particle(float x, float y, bool pinned = false) : position(x, y), previous_position(x, y), 14 | acceleration(0, 0), is_pinned(pinned) {} 15 | 16 | void apply_force(const sf::Vector2f& force) { 17 | if (!is_pinned) { 18 | acceleration += force; 19 | } 20 | } 21 | 22 | void update(float time_step) { 23 | // verlet intergration 24 | if (!is_pinned) { 25 | sf::Vector2f velocity = position - previous_position; 26 | previous_position = position; 27 | position += velocity + acceleration * time_step * time_step; 28 | acceleration = sf::Vector2f(0, 0); // reset after update 29 | } 30 | } 31 | 32 | void constrain_to_bounds(float width, float height) { 33 | if (position.x < 0) position.x = 0; 34 | if (position.x > width) position.x = width; 35 | if (position.y < 0) position.y = 0; 36 | if (position.y > height) position.y = height; 37 | } 38 | 39 | }; 40 | 41 | #endif // PARTICLE_H --------------------------------------------------------------------------------