[work 74] Amebas

[work 74] Amebas

Movie

Source code

about

  • 複数のAmebaが枠内を移動する
  • Ameba同士が近づくと合体する

file

  • 上部にあるファイル名が表示されているボタンを押すと、表示されるファイルが切り替わります
  • 別ウィンドウ表示したい時や行番号などが無いRawMode表示したい時は、コード内右上のボタンを押してください(ボタンはマウスオーバーすると表示されます)
#include "ofMain.h"
#include "ofApp.h"

//================================
int main( ){

    // 4K:4096x2160
    // 2K:2048x1080
    // FullHD:1920x1080
    // HD:1440x1080
    // HD720p:1280x720
    // DVD:720x480
    // setup the GL context
    ofSetupOpenGL(1280, 720, OF_WINDOW);

	// this kicks off the running of my app
	// can be OF_WINDOW or OF_FULLSCREEN
	// pass in width and height too:
	ofRunApp( new ofApp());

}
#pragma once

#include "ofMain.h"

#include "Ameba.hpp"


class ofApp : public ofBaseApp{
public:
    ofApp();
    ~ofApp();
    
    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);
    
private:
    std::vector<std::shared_ptr<Ameba>> ameba;
    ofRectangle area;
};
#include "ofApp.h"


ofApp::ofApp(){
    
}

ofApp::~ofApp(){
    
}

//--------------------------------------------------------------
void ofApp::setup(){
    double fps = 30;
    
    
    ofSetFrameRate(fps);
    ofBackground(255);
    ofSetBackgroundAuto(true);
    ofSetVerticalSync(true);
    
    for (int i = 0; i < 15; i++) {
        ameba.push_back(make_shared<Ameba>());
        ameba.back()->setup();
    }
    
    area = ofRectangle(50, 50, ofGetWidth() - 100, ofGetHeight() - 100);
}

//--------------------------------------------------------------
void ofApp::update(){
    
    for (shared_ptr<Ameba> a : ameba) {
        a->update();
        a->rangeCheck(area);
    }
    
    for (auto itr = ameba.begin(); itr != ameba.end(); ++itr) {
        bool erase = false;
        for (auto dst = itr + 1; dst != ameba.end(); ++dst) {
            if ((*itr)->approach((*dst))) {
                ameba.push_back(Ameba::unite((*itr), (*dst)));
                ameba.back()->update();
                
                ameba.erase(dst);
                erase = true;
                break;
            }
        }
        
        if (erase) {
            ameba.erase(itr);
        }
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    for (shared_ptr<Ameba> a : ameba) {
        a->display();
    }
    
    ofNoFill();
    ofSetColor(255, 0, 0);
    ofDrawRectangle(area);
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key == 's') {
        ofImage img;
        img.grabScreen(0, 0, ofGetWidth(), ofGetHeight());
        img.save("screenshot.png");
    }
}
#ifndef Ameba_hpp
#define Ameba_hpp

#include "ofMain.h"
#include <stdio.h>

#include "ofxEasing.h"

class Ameba {
public:
    Ameba();
    ~Ameba();
    void setup();
    void update();
    void display();
    bool approach(shared_ptr<Ameba> a);
    void rangeCheck(ofRectangle r);
    
    static shared_ptr<Ameba> unite(shared_ptr<Ameba> a1, shared_ptr<Ameba> a2);
private:
    std::vector<ofVec2f> points;
    std::vector<ofVec2f> process;
    std::vector<ofVec2f> targets;
    std::vector<ofVec2f> orgs;
    ofVec2f center;
    float radius;
    float coreRadius;
    ofVec2f velocity;
    int pointNum;
    ofColor color;
    std::vector<int> counts;
    ofRectangle area;
};
#endif /* Ameba_hpp */
#include "Ameba.hpp"



shared_ptr<Ameba> Ameba::unite(shared_ptr<Ameba> a1, shared_ptr<Ameba> a2)
{
    auto newAmeba = make_shared<Ameba>();
    float minD = FLT_MAX;   // minimum distance
    int minIdx1 = 0;        // a1 index of minimum distance
    int minIdx2 = 0;        // a2 index of minimum distance
    int prevIdx1, prevIdx2; // previous index of minIdx
    int nextIdx1, nextIdx2; // next index of minIdx
    int startIdx1, endIdx1; // start(minIdx)->end(prevIdx) or start(nextIdx)->end(minIdx)
    int startIdx2, endIdx2;
    
    // set parameter
    newAmeba->center = (a1->center + a2->center) / 2;
    newAmeba->radius = std::sqrt(a1->radius * a1->radius + a2->radius * a2->radius);   // (pi)r^2 = (pi)(r1^2 + r2^2)
    newAmeba->coreRadius = newAmeba->radius * 3 / 4;
    newAmeba->velocity = (a1->radius * a1->velocity + a2->radius * a2->velocity) / (a1->radius + a2->radius);
    newAmeba->color = ofColor(ofRandom(255), ofRandom(255), ofRandom(255));
    
    for (int i = 0; i < a1->points.size(); i++) {
        for (int j = 0; j < a2->points.size(); j++) {
            float dist = (a1->points.at(i) - a2->points.at(j)).length();
            if (dist < minD) {
                minD = dist;
                minIdx1 = i;
                minIdx2 = j;
            }
        }
    }
    
    nextIdx1 = (minIdx1 + 1) % a1->points.size();
    if (minIdx2 != 0) {
        prevIdx2 = minIdx2 - 1;
    } else {
        prevIdx2 = a2->points.size() - 1;
    }
    float dist1 = (a1->points.at(nextIdx1) - a2->points.at(prevIdx2)).length();
    
    
    if (minIdx1 != 0) {
        prevIdx1 = minIdx1 - 1;
    } else {
        prevIdx1 = a1->points.size() - 1;
    }
    nextIdx2 = (minIdx2 + 1) % a2->points.size();
    float dist2 = (a1->points.at(prevIdx1) - a2->points.at(nextIdx2)).length();
    
    if (dist1 < dist2) {
        startIdx1 = nextIdx1;
        endIdx1 = minIdx1;
        startIdx2 = minIdx2;
        endIdx2 = prevIdx2;
    } else {
        startIdx1 = minIdx1;
        endIdx1 = prevIdx1;
        startIdx2 = nextIdx2;
        endIdx2 = minIdx2;
    }
    
    // unite a1 and a2
    for (int idx = 0; idx <= endIdx1; idx++) {
        ofVec2f p = a1->process.at(idx) + (a1->center - newAmeba->center);
        newAmeba->orgs.push_back(p);
        newAmeba->process.push_back(p);
    }
    
    if (startIdx2 != 0) {
        for (int idx = startIdx2; idx < a2->points.size(); idx++) {
            ofVec2f p = a2->process.at(idx) + (a2->center - newAmeba->center);
            newAmeba->orgs.push_back(p);
            newAmeba->process.push_back(p);
        }
    }
    
    for (int idx = 0; idx <= endIdx2; idx++) {
        ofVec2f p = a2->process.at(idx) + (a2->center - newAmeba->center);
        newAmeba->orgs.push_back(p);
        newAmeba->process.push_back(p);
    }
    
    if (startIdx1 != 0) {
        for (int idx = startIdx1; idx < a1->points.size(); idx++) {
            ofVec2f p = a1->process.at(idx) + (a1->center - newAmeba->center);
            newAmeba->orgs.push_back(p);
            newAmeba->process.push_back(p);
        }
    }
    
    newAmeba->pointNum = newAmeba->orgs.size();
    
    for (int i = 0; i < newAmeba->pointNum; i++) {
        float unitAngle = 360.0 / (float)newAmeba->pointNum;
        ofVec2f d = a1->points.at(0) - a1->center;
        d.normalize();
        // set final shape
        newAmeba->targets.push_back(d.rotate(unitAngle * i) * newAmeba->radius);
        
        newAmeba->points.push_back(ofVec2f(0, 0));
        
        newAmeba->counts.push_back(0);
    }
    
    return newAmeba;
}


Ameba::Ameba()
{
    
}


Ameba::~Ameba()
{
    
}


void Ameba::setup()
{
    radius = (int)ofRandom(20, 50);
    pointNum = (int)radius / 4;
    coreRadius = radius * 3 / 4;
    color = ofColor(ofRandom(255), ofRandom(255), ofRandom(255));
    
    int offset = 100;
    center = ofVec2f(ofRandom(offset, ofGetWidth() - offset), ofRandom(offset, ofGetHeight() - offset));
    velocity = ofVec2f(2, 0).rotate(ofRandom(360));
    
    for (int i = 0; i < pointNum; i++) {
        float unitAngle = 360.0 / (float)pointNum;
        ofVec2f d = ofVec2f(1, 0).rotate(unitAngle / 2);
        targets.push_back(d.rotate(unitAngle * i) * radius);
        process.push_back(ofVec2f(0, 0));
        
        points.push_back(ofVec2f(0, 0));
        orgs.push_back(ofVec2f(0, 0));
        
        counts.push_back(0);
    }
}


void Ameba::update()
{
    int maxFrame = (int)ofGetTargetFrameRate() * 6;
    
    for (int i = 0; i < points.size(); i++) {
        // Convert float to int to ignore errors
        int xP = (int)process.at(i).x;
        int yP = (int)process.at(i).y;
        int xT = (int)targets.at(i).x;
        int yT = (int)targets.at(i).y;
        
        if ((xP != xT) || (yP != yT)) {
            float step = ofxeasing::map(counts.at(i), 0, maxFrame - 1, 0, 1, ofxeasing::elastic::easeOut);
            process.at(i) = orgs.at(i) + (targets.at(i) - orgs.at(i)) * step;
        } else {
            orgs.at(i) = process.at(i);
        }
        
        points.at(i) = center + process.at(i);
        counts.at(i) = (counts.at(i) + 1) % maxFrame;
    }
    center += velocity;
}


void Ameba::display()
{
    ofSetPolyMode(OF_POLY_WINDING_NONZERO);
    ofFill();
    ofSetColor(color);
    
    ofBeginShape();
    ofCurveVertex(points.back());   // control point for p[0]-p[1]
    ofCurveVertices(points);
    ofCurveVertex(points.at(0));    // both vertex and control point(control point for p[last index - 1]-p[last index])
    ofCurveVertex(points.at(1));    // control point for p[last index]-p[0]
    ofEndShape();
}


bool Ameba::approach(shared_ptr<Ameba> a)
{
    std::vector<float> dists;
    for (int i = 0; i < this->points.size(); i++) {
        for (int j = 0; j < a->points.size(); j++) {
            float dist = (this->points.at(i) - a->points.at(j)).length();
            dists.push_back(dist);
        }
    }
    std::sort(dists.begin(), dists.end());
    
    return (dists.at(0) < 20);
}


void Ameba::rangeCheck(ofRectangle r)
{
    int offset = 5;
    for (int i = 0; i < process.size(); i++) {
        if (center.x + process.at(i).x < r.getLeft()) {
            process.at(i).x += r.getLeft() - (center.x + process.at(i).x) + offset;
            orgs.at(i).x = process.at(i).x;
            counts.at(i) = 0;
        }
        if (center.x + process.at(i).x > r.getRight()) {
            process.at(i).x -= (((center.x + process.at(i).x) - r.getRight()) + offset);
            orgs.at(i).x = process.at(i).x;
            counts.at(i) = 0;
        }
        if (center.y + process.at(i).y < r.getTop()) {
            process.at(i).y += r.getTop() - (center.y + process.at(i).y) + offset;
            orgs.at(i).y = process.at(i).y;
            counts.at(i) = 0;
        }
        if (center.y + process.at(i).y > r.getBottom()) {
            process.at(i).y -= (((center.y + process.at(i).y) - r.getBottom()) + offset);
            orgs.at(i).y = process.at(i).y;
            counts.at(i) = 0;
        }
    }
    
    if ((center.x - coreRadius < r.getLeft()) || (center.x + coreRadius > r.getRight())) {
        velocity.x *= -1;
    }
    if ((center.y - coreRadius < r.getTop()) || (center.y + coreRadius > r.getBottom())) {
        velocity.y *= -1;
    }
}

Link to the reference page

ソースコードで使用したAPIの中から要点になりそうなものをいくつか選んでリストアップしました。

categoryAPI/Lib
openframeworksofCurveVertex
openframeworksofCurveVertices
openframeworksofxeasing map
c++std::vector

Development environment

  • openframeworks 0.10.1
  • c++
  • macOS
  • Xcode