Particle System Attraction Force

Hi All,

I am working on a project that will attract a particle system to a series of characters with a keypressed command.

I figured out how to convert the characters to points so that they draw as circles on the screen. I am trying to use them as an attractor for the particle system I have floating on the top of my screen. I have a getForce function in my Letter class and then am calling it in ofApp, but I can’t get it to work. Essentially, I am trying to get the particles to attract to the character (just have it for ‘N’ for now) and then stop moving once they reach it.

Any help would be greatly appreciated!!

Git: https://github.com/Nedelstein/edeln591_dtOF_2018/tree/master/interactiveLettersMidterm

ofApp.h:

#pragma once

#include "ofMain.h"
#include "ofxCenteredTrueTypeFont.h"
#include "Letters.hpp"
#include "ParticleSystemTop.hpp"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
    
    ofxCenteredTrueTypeFont letterFont;
    

    Letters n;
    Letters o;
    Letters a;
    Letters h;
    
    Letters letter;
    vector<ParticleSystemTop> particleSystemTop;
    
    string _str;
    glm::vec2 center = glm::vec2(ofGetWidth()/2, ofGetHeight()/2);
    glm::vec2 attractionTop;
    bool elasticForceOn;
    
    map< char, Letters> noah;
    char currentChar;
};

ofApp.cpp:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(0);
    ofSetVerticalSync(false);
    ofSetCircleResolution(100);
    
    noah['n'] = Letters();
    noah['n'].setup('N', center);
    noah['o'] = Letters();
    noah['o'].setup('O', center);
    noah['a'] = Letters();
    noah['a'].setup('A', center);
    noah['h'] = Letters();
    noah['h'].setup('H', center);
    
    
    glm::vec2 posTop = glm::vec2(ofRandom(ofGetWidth()), ofRandom(0,5));
    ParticleSystemTop _particleSystemTop = ParticleSystemTop(posTop);
    particleSystemTop.push_back(_particleSystemTop);
    
    elasticForceOn = true;
    
    
}

//--------------------------------------------------------------
void ofApp::update(){
    
    
    
    for (int i=0; i<particleSystemTop.size(); i++){
        glm::vec2 force = noah[currentChar].getForce(particleSystemTop[i].pos);
        particleSystemTop[i].applyForce(force);
        particleSystemTop[i].applyDampingForce(0.2);
        particleSystemTop[i].update();
        
        if (elasticForceOn){
            particleSystemTop[i].applyElasticForce(0.3);
            
            
        }
    
        
        //particle force from top to center/////
        for (int i=0; i<particleSystemTop.size(); i++){
        glm::vec2 dirTop = noah[currentChar].pos - particleSystemTop[i].pos;
        float distanceTop = glm::length(dirTop);
        if(distanceTop >0){
            glm::vec2 normalizedDirTop = dirTop / distanceTop;

            attractionTop = normalizedDirTop;
        }

        }

    }
    
}

//--------------------------------------------------------------
void ofApp::draw(){

    for (int i=0; i<particleSystemTop.size(); i++){
        particleSystemTop[i].draw();
    }
    if (currentChar != ' '){
    noah[currentChar].draw();
    }
    
    


}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    
    
    if (key == 'f'){
        ofToggleFullscreen();
        
    }
    
    if (key == 'n' || key == 'o' || key == 'a' || key == 'h'){
        currentChar = key;
        for (int i=0; i<particleSystemTop.size(); i++){
        particleSystemTop[i].applyForce(attractionTop);
        particleSystemTop[i].collision = false;
        elasticForceOn = false;
        }
    }
    
//    if (key == 'n'){
//        for (int i=0; i<particleSystemTop.size(); i++){
//
////            letter.nDraw = true;
//
//        }
    
    
//    if (key == 'o'){
//        for(int i=0; i<particleSystemTop.size(); i++){
//            particleSystemTop[i].applyForce(attractionTop);
//            letter.oDraw = true;
//            particleSystemTop[i].oForce = true;
//        }
//    }
//
//    if (key == 'a'){
//
//    }
//    if (key == 'h'){
//
//    }
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){
    if (key == 'n' || key == 'o' || key == 'a' || key == 'h'){
        currentChar = ' ';
        for (int i=0; i<particleSystemTop.size(); i++){
//            elasticForceOn = true;
            particleSystemTop[i].applyElasticForce(1);
            particleSystemTop[i].oForce = false;
            particleSystemTop[i].collision = false;
    }
        letter.nDraw = false;
        letter.oDraw = false;
    }
}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}

Letter.hpp:

#pragma once

#include "ofMain.h"
#include "Particle.hpp"

class Letters{
public:

    void setup(char c, glm::vec2 _pos);
    void update();
    void draw();
    void Ncollide();
    void Ocollide();
    glm::vec2 getForce(glm::vec2 pos);
    
    vector<glm::vec2> attractors;
    
    
ofTrueTypeFont letterFont;
    
    glm::vec2 pos;
    float mass;
    typedef ofPath ofTTFCharacter;
    
   ofPath letterN;
    float nStartX = ofGetWidth()/2 - 100;
    float nStartY = ofGetHeight() *0.75;
    bool nDraw;
   vector<Particle> particles;
    
    glm::vec2 oPos;
    float oRad;
    bool oDraw;
    
    
};

Letter.cpp

#include "Letters.hpp"

void Letters::setup(char c, glm::vec2 _pos) {
    pos = _pos;
    letterFont.loadFont("Courier New.ttf", 600, false,false,true);
    
    ofTTFCharacter nLetter = letterFont.getCharacterAsPoints(c, true, false);
    
    float width   = letterFont.stringWidth(ofToString(c));  // char width and height
    float height  = letterFont.stringHeight(ofToString(c));
    glm::vec2 adjustedPos = _pos + glm::vec2(-width*0.5, height*0.5);
    
    vector<ofPolyline> nPolylines = nLetter.getOutline();
    
    for (int j = 0; j<nPolylines.size(); j++){
        ofPolyline outline = nPolylines[j].getResampledBySpacing(4);
        outline.translate(adjustedPos);
        for (int k = 0; k < outline.size(); k++){
            
            attractors.push_back(outline[k]);
            }
        }
    
    
    oPos.x = ofGetWidth() *0.5;
    oPos.y = ofGetHeight() *0.5;
    oRad = ofGetWidth()/4;
    
    
}

glm::vec2 Letters::getForce(glm::vec2 particlePos){
    glm::vec2 force = glm::vec2(0,0);
    for (int i=0; i<attractors.size(); i++){
        glm::vec2 dir = attractors[i] - particlePos;
        float distance = glm::length(dir);
        if (distance !=0 ){
            glm::vec2 norm = dir/distance;
            force = norm * 0.001;
        }
        else if (distance !=0 && distance < 10){
            force*=-1;
            }
        }
    
    
//   clamp the total force strength like so:
    float totalStrength = glm::length(force);
    if (totalStrength > 0){
        force = force / totalStrength; // normalize
        totalStrength = ofClamp(totalStrength, 0, 0.5);
        force *= totalStrength; // reapply clamped strength
    }
    return force;
}

void Letters::update(){

}




void Letters::draw(){
    
    for(int i=0; i<attractors.size(); i++){
        ofDrawCircle(attractors[i], 2);
    }
}


ParticleSystemTop.hpp

#pragma once

#include "ofMain.h"
#include "Particle.hpp"
#include "Letters.hpp"

class ParticleSystemTop{
public:
    
    ParticleSystemTop();
    ParticleSystemTop(glm::vec2 _pos);
    
    void setup();
    void update();
    void applyForce(glm::vec2 force);
    void draw();
    void applyElasticForce(float strength);
    void applyDampingForce(float strength);
    void letterCollide();
    void oCollide();
    void nCollide();
    void init();
    
    
    glm::vec2 pos;
    
    glm::vec2 center = glm::vec2(ofGetWidth()/2, ofGetHeight()/2);
    
    vector<Particle> particles;
    
    int numParticles = 500;
    
    Letters letter;
    bool oForce;
    bool collision;
    
};

ParticleSystemTop.cpp:

#include "ParticleSystemTop.hpp"

ParticleSystemTop::ParticleSystemTop(){
    pos = glm::vec2(0,0);
//    init();
}

ParticleSystemTop::ParticleSystemTop(glm::vec2 _pos){
    pos = _pos;
    
    init();
    
}

void ParticleSystemTop::applyForce(glm::vec2 force){
    for (int i=0; i<particles.size(); i++){
        particles[i].applyForce(force);
    }
}
void ParticleSystemTop::applyDampingForce(float strength){
    for (int i = 0; i>particles.size(); i++){
        particles[i].applyDampingForce(strength);
    }
}

void ParticleSystemTop::applyElasticForce(float strength){
    for (int i=0; i<particles.size(); i++){
        particles[i].applyElasticForce(strength);
    }
}

void ParticleSystemTop::update(){

    for (int i=0; i <particles.size(); i++){
        particles[i].update();
        particles[i].checkEdges();
        
        if (collision){
        for (int j=0; j< particles.size(); j++){
            if(i !=j){
                if (particles[i].pos != particles[j].pos){

                particles[i].collide(particles[j]);
                }
            }
            }
        }
        }
}

void ParticleSystemTop::init(){
    for (int i=0; i <numParticles; i++){
        glm::vec2 vel = glm::vec2(0, 0);
        float mass = ofRandom(0.5, 2);
        pos.x = ofRandom(ofGetWidth());
        pos.y = ofRandom(1,30);
        particles.emplace_back(pos, vel, mass);
//        Particle particle = Particle(pos, vel, mass);
//        particles.push_back(particle);
}
}
void ParticleSystemTop::draw(){
    for(int i=0; i<particles.size(); i++){
        particles[i].draw();
    }
}


void ParticleSystemTop::oCollide(){
    if (oForce){
        for(int i=0; i<particles.size(); i++){
            glm::vec2 diff = letter.oPos - particles[i].pos;
            particles[i].applyForce(diff * 0.03);
            particles[i].applyDampingForce(0.1);
            float distance = ofDist(particles[i].pos.x, particles[i].pos.y, letter.oPos.x, letter.oPos.y);
            if (distance < letter.oRad + particles[i].radius){
                particles[i].vel = glm::vec2(0,0);
            }
        }
    }
}

//void ParticleSystemTop::nCollide(){
//    for(int i=0; i<particles.size(); i ++){
//        vector<ofPolyline> outline = letter.letterN.getOutline();
//        for (int j = 0; j < outline.size(); j++){
//            if(outline[j].inside(particles[i])){
//                particles[i].vel = glm::vec2(0,0);
//
//            }
//        }
//    } 
//}



//void ParticleSystemTop::letterCollide(){
//    for(int i=0; i<particles.size(); i++){
//        float distance = ofDist(particles[i].pos.x, particles[i].pos.y, letter.pos.x, letter.pos.y);
//        if (distance < 0){
//            particles[i].vel *=-1;
//        }
//
//    }
//}


Have a look at the example examples/math/particlesExample, there is one mode with attractors.

Hey edapx,

Thanks for the recommendation. I took a look at it and changed the getForce function code to the following, but it doesn’t seem to fix the issue.

It’s applying the force, but the force continues past the point when the particles intersect the character points.

    glm::vec2 Letters::getForce(glm::vec2 particlePos){
        glm::vec2 force = glm::vec2(0,0);
        glm::vec2 closestPt;
        int closest = -1;
        float closestDist = 9999999;
        for (unsigned int i = 0; i <attractors.size(); i++){
            glm::vec2 dir = attractors[i] - particlePos;
            float distance = glm::length(dir);
            if(distance < closestDist){
                closestDist = distance;
                closest = i;
            }
        }
        if (closest !=-1){
            closestPt = attractors.at(closest);
            float dist = sqrt(closestDist);
            force = closestPt - particlePos;
            
            if(dist < 300 && dist > 40){
                force *= 0.003;
            }
            
        }```