In this recipe, we will learn how we can create springs.
Springs are objects that connect two particles and force them to be at a defined rest distance.
In this example, we will create random particles, and whenever the user presses a mouse button, two random particles will be connected by a new spring with a random rest distance.
We will be using the same particle system developed in the previous recipe, Creating a particle system in 2D. Create the Particle
and ParticleSystem
classes described in that recipe and include the ParticleSystem.h
file at the top of the application source file.
We will be creating a Spring
class, so it is necessary to create the following files:
Spring.h
Spring.cpp
We will create springs that constrain the movement of particles. Perform the following steps to do so:
Spring.h
file, we will declare a Spring
class. The first thing we need to do is to add the #pragma once
macro and include the necessary files.#pragma once #include "Particle.h" #include "cinder/gl/gl.h"
Spring
class.class Spring{ };
Particle
pointers to reference the particles that will be connected by this spring, and the rest
and strengthfloat
variables.class Spring{ public: Particle *particleA; Particle *particleB; float strength, rest; };
Particle
objects, and the rest
and strength
values.We will also declare the update
and draw
methods.
The following is the final Spring
class declaration:
class Spring{ public: Spring( Particle *particleA, Particle *particleB, float rest, float strength ); void update(); void draw(); Particle *particleA; Particle *particleB; float strength, rest; };
Spring
class in the Spring.cpp
file.In the constructor, we will set the values of the member values to the ones passed in the arguments.
Spring::Spring( Particle *particleA, Particle *particleB, float rest, float strength ){ this->particleA = particleA; this->particleB = particleB; this->rest = rest; this->strength = strength; }
update
method of the Spring
class, we will calculate the difference between the particles' distance and the spring's rest distance, and adjust them accordingly.void Spring::update(){ ci::Vec2f delta = particleA->position - particleB->position; float length = delta.length(); float invMassA = 1.0f / particleA->mass; float invMassB = 1.0f / particleB->mass; float normDist = ( length - rest ) / ( length * ( invMassA + invMassB ) ) * strength; particleA->position -= delta * normDist * invMassA; particleB->position += delta * normDist * invMassB; }
draw
method of the Spring
class, we will simply draw a line connecting both particles.void Spring::draw(){ ci::gl::drawLine ( particleA->position, particleB->position ); }
ParticleSystem
class to allow the addition of springs.In the ParticleSystem
file, include the Spring.h
file.
#include "Spring.h"
std::vector<Spring*>
member in the class declaration.std::vector<Spring*> springs;
addSpring
and destroySpring
methods to add and destroy springs to the system.The following is the final ParticleSystem
class declaration:
classParticleSystem{ public: ~ParticleSystem(); void update(); void draw(); void addParticle( Particle *particle ); void destroyParticle( Particle *particle ); void addSpring( Spring *spring ); void destroySpring( Spring *spring ); std::vector<Particle*> particles; std::vector<Spring*> springs; };
addSpring
method. In the ParticleSystem.cpp
file, add the following code snippet:void ParticleSystem::addSpring( Spring *spring ){ springs.push_back( spring ); }
destroySpring
, we will find the correspondent iterator for the argument Spring
and remove it from springs. We will also delete the object.Add the following code snippet in the ParticleSystem.cpp
file:
void ParticleSystem::destroySpring( Spring *spring ){ std::vector<Spring*>::iterator it = std::find( springs.begin(), springs.end(), spring ); delete *it; springs.erase( it ); }
update
method to update all springs.The following code snippet shows what the final update should look like:
void ParticleSystem::update(){ for( std::vector<Particle*>::iterator it = particles.begin(); it != particles.end(); ++it ){ (*it)->update(); } for( std::vector<Spring*>::iterator it = springs.begin(); it != springs.end(); ++it ){ (*it)->update(); } }
draw
method, we will also need to iterate over all springs and call the draw
method on them.The final implementation of the
ParticleSystem::draw
method should be as follows:
void ParticleSystem::draw(){ for( std::vector<Particle*>::iterator it = particles.begin(); it != particles.end(); ++it ){ (*it)->draw(); } for( std::vector<Spring*>::iterator it = springs.begin(); it != springs.end(); ++it ){ (*it)->draw(); } }
Spring
class and making all necessary changes to the ParticleSystem
class.Let's go to our application's class and include the ParticleSystem.h
file:
#include "ParticleSystem.h"
ParticleSystem
object.ParticleSystem mParticleSystem;
setup
method:for( int i=0; i<100; i++ ){ float x = randFloat( getWindowWidth() ); float y = randFloat( getWindowHeight() ); float radius = randFloat( 5.0f, 15.0f ); float mass = radius; float drag = 0.9f; Particle *particle = new Particle( Vec2f( x, y ), radius, mass, drag ); mParticleSystem.addParticle( particle ); }
update
method, we will need to call the update
method on ParticleSystem
.void MyApp::update(){ mParticleSystem.update(); }
draw
method, clear the background, define the window's matrices, and call the draw
method on mParticleSystem
.void MyApp::draw(){ gl::clear( Color( 0, 0, 0 ) ); gl::setMatricesWindow( getWindowWidth(), getWindowHeight() ); mParticleSystem.draw(); }
mouseDown
method.Add the following code snippet to your application's class declaration:
void mouseDown( MouseEvent event );
mouseDown
implementation we will connect two random particles.Start by declaring a Particle
pointer and defining it as a random particle in the particle system.
Particle *particleA = mParticleSystem.particles[ randInt( mParticleSystem.particles.size() ) ];
Particle
pointer and make it equal to the first one. In the while
loop, we will set its value to a random particle in mParticleSystem
until both particles are different. This will avoid the case where both pointers point to the same particle.Particle *particleB = particleA; while( particleB == particleA ){ particleB = mParticleSystem.particles[ randInt( mParticleSystem.particles.size() ) ]; }
Spring
object that will connect both particles, define a random rest distance, and set strength
to 1.0
. Add the created spring to mParticleSystem
.The following is the final mouseDown
implementation:
void SpringsApp::mouseDown( MouseEvent event ) { Particle *particleA = mParticleSystem.particles[ randInt( mParticleSystem.particles.size() ) ]; Particle *particleB = particleA; while( particleB == particleA ){ particleB = mParticleSystem.particles[ randInt( mParticleSystem.particles.size() ) ]; } float rest = randFloat( 100.0f, 200.0f ); float strength = 1.0f; Spring *spring = new Spring ( particleA, particleB, rest, strength ); mParticleSystem.addSpring( spring ); }
A Spring
object will calculate the difference between two particles and correct their positions, so that the distance between the two particles will be equal to the springs' rest value.
By using their masses, we will also take into account each particle's mass, so that the correction will take into account the particles' weight.
The same principle can also be applied to particle systems in 3D.
If you are using a 3D particle, as explained in the There's more… section of the Creating a particle system in 2D recipe, the Spring
class simply needs to change its calculations to use ci::Vec3f
instead of ci::Vec2f
.
The update
method of the Spring
class would need to look like the following code snippet:
void Spring::update(){ ci::Vec3f delta = particleA->position - particleB->position; float length = delta.length(); float invMassA = 1.0f / particleA->mass; float invMassB = 1.0f / particleB->mass; float normDist = ( length - rest ) / ( length * ( invMassA + invMassB ) ) * strength; particleA->position -= delta * normDist * invMassA; particleB->position += delta * normDist * invMassB; }
3.135.188.121