[work 75] Addition

[work 75] Addition

Movie

Source code

about

  • 数字のアウトラインを描画する
  • 数字同士が接近したら、それらの足し算をした結果を描画する
  • ofPolyLineベクトルのindex 0に数字の一番外側の線が格納されている前提(この条件を満たすフォントを選択した)

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;
    std::shared_ptr<ofTrueTypeFont> font;
};
#include "ofApp.h"


ofApp::ofApp(){
    
}

ofApp::~ofApp(){
    
}

//--------------------------------------------------------------
void ofApp::setup(){
    double fps = 30;
    
    
    ofSetFrameRate(fps);
    ofBackground(255);
    ofSetBackgroundAuto(true);
    ofSetVerticalSync(true);
    
    ofTrueTypeFontSettings settings("font/HiraginoProNW4.ttc", 30);
    settings.contours = true;
    font = make_shared<ofTrueTypeFont>();
    font->load(settings);
    
    
    for (int i = 0; i < 15; i++) {
        ameba.push_back(make_shared<Ameba>());
        ameba.back()->setFont(font);
        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);
    }
    
    auto itr = ameba.begin();
    while (itr != ameba.end()) {
        bool erase = false;
        for (auto dst = itr + 1; dst != ameba.end(); ++dst) {
            if ((*itr)->approach((*dst))) {
                
                ameba.push_back(make_shared<Ameba>());
                ameba.back()->setFont(font);
                ameba.back()->unite((*itr), (*dst));
                ameba.back()->update();

                ameba.erase(dst);
                erase = true;
                break;
            }
        }

        if (erase) {
            itr = ameba.erase(itr);
        } else {
            ++itr;
        }
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    for (shared_ptr<Ameba> a : ameba) {
        a->display();
    }
    
    ofNoFill();
    ofSetColor(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);
    void setFont(std::shared_ptr<ofTrueTypeFont> f);
    void unite(shared_ptr<Ameba> a1, shared_ptr<Ameba> a2);
    
private:
    void setTarget(int num);
    
    int number;
    std::vector<ofVec2f> points;
    std::vector<ofVec2f> process;
    std::vector<ofVec2f> targets;
    std::vector<ofVec2f> orgs;
    std::vector<int> counts;
    ofRectangle boundingBox;
    ofVec2f leftBottom;
    float coreRadius;
    ofVec2f velocity;
    ofColor color;
    
    ofRectangle area;
    std::shared_ptr<ofTrueTypeFont> font;
};
#endif /* Ameba_hpp */
#include "Ameba.hpp"


Ameba::Ameba()
{
    
}


Ameba::~Ameba()
{
    
}


void Ameba::setup()
{
    setTarget((int)ofRandom(0, 10));
    
    for (int i = 0; i < targets.size(); i++) {
        process.push_back(ofVec2f(0, 0));
        points.push_back(ofVec2f(0, 0));
        orgs.push_back(ofVec2f(0, 0));
        counts.push_back(0);
    }
    
    int offset = 100;
    leftBottom = ofVec2f(ofRandom(offset, ofGetWidth() - (offset + boundingBox.width)), ofRandom(offset + boundingBox.height, ofGetHeight() - offset));
    color = ofColor(0);
    velocity = ofVec2f(2, 0).rotate(ofRandom(360));
}


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) = leftBottom + process.at(i);
        counts.at(i) = (counts.at(i) + 1) % maxFrame;
    }
    leftBottom += velocity;
}


void Ameba::display()
{
    ofSetPolyMode(OF_POLY_WINDING_NONZERO);
    ofNoFill();
    ofSetColor(color);
    ofBeginShape();
    ofVertices(points);
    ofEndShape();
}


bool Ameba::approach(shared_ptr<Ameba> a)
{
    ofVec2f center1, center2;
    center1.x = this->leftBottom.x + this->boundingBox.width / 2;
    center1.y = this->leftBottom.y - this->boundingBox.height / 2;
    
    center2.x = a->leftBottom.x + a->boundingBox.width / 2;
    center2.y = a->leftBottom.y - a->boundingBox.height / 2;
    
    float radius1, radius2;
    radius1 = (center1 - this->leftBottom).length();
    radius2 = (center2 - a->leftBottom).length();
    
    float distance = 20;
    
    return (radius1 + radius2 + distance > (center1 - center2).length());
}


void Ameba::rangeCheck(ofRectangle r)
{
    int offset = 5;
    for (int i = 0; i < process.size(); i++) {
        if (leftBottom.x + process.at(i).x < r.getLeft()) {
            process.at(i).x += r.getLeft() - (leftBottom.x + process.at(i).x) + offset;
            orgs.at(i).x = process.at(i).x;
            counts.at(i) = 0;
        }
        if (leftBottom.x + process.at(i).x > r.getRight()) {
            process.at(i).x -= (((leftBottom.x + process.at(i).x) - r.getRight()) + offset);
            orgs.at(i).x = process.at(i).x;
            counts.at(i) = 0;
        }
        if (leftBottom.y + process.at(i).y < r.getTop()) {
            process.at(i).y += r.getTop() - (leftBottom.y + process.at(i).y) + offset;
            orgs.at(i).y = process.at(i).y;
            counts.at(i) = 0;
        }
        if (leftBottom.y + process.at(i).y > r.getBottom()) {
            process.at(i).y -= (((leftBottom.y + process.at(i).y) - r.getBottom()) + offset);
            orgs.at(i).y = process.at(i).y;
            counts.at(i) = 0;
        }
    }
    
    ofVec2f center;
    center.x = leftBottom.x + boundingBox.width / 2;
    center.y = leftBottom.y - boundingBox.height / 2;
    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;
    }
}


void Ameba::setFont(std::shared_ptr<ofTrueTypeFont> f)
{
    font = f;
}


void Ameba::setTarget(int num)
{
    number = num;
    float scale = 1.5;
    
    std::vector<ofPath> paths = font->getStringAsPoints(ofToString(number), true, false);
    
    boundingBox.x = paths.at(0).getOutline().at(0).getBoundingBox().x;
    boundingBox.y = paths.at(0).getOutline().at(0).getBoundingBox().y;
    boundingBox.height = paths.at(0).getOutline().at(0).getBoundingBox().height * scale;
    
    for (ofPath path : paths) {
        std::vector<ofPolyline> lines = path.getOutline();
        
        std::vector<glm::vec3> v = lines.at(0).getVertices();
        
        boundingBox.width += lines.at(0).getBoundingBox().width * scale;
        
        for (glm::vec3 p : v) {
            targets.push_back(p * scale);
        }
    }
    
    coreRadius = std::min(boundingBox.width, boundingBox.height);
    coreRadius /= 4;
}


void Ameba::unite(shared_ptr<Ameba> a1, shared_ptr<Ameba> a2)
{
    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;
    
    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
    ofVec2f center, c1, c2;
    c1.x = a1->leftBottom.x + a1->boundingBox.width / 2;
    c1.y = a1->leftBottom.y - a1->boundingBox.height / 2;
    c2.x = a2->leftBottom.x + a2->boundingBox.width / 2;
    c2.y = a2->leftBottom.y - a2->boundingBox.height / 2;
    center = (c1 + c2) / 2;
    
    for (int idx = 0; idx <= endIdx1; idx++) {
        ofVec2f p = a1->process.at(idx) + (c1 - center);
        this->orgs.push_back(p);
        this->process.push_back(p);
    }
    
    if (startIdx2 != 0) {
        for (int idx = startIdx2; idx < a2->points.size(); idx++) {
            ofVec2f p = a2->process.at(idx) + (c2 - center);
            this->orgs.push_back(p);
            this->process.push_back(p);
        }
    }
    
    for (int idx = 0; idx <= endIdx2; idx++) {
        ofVec2f p = a2->process.at(idx) + (c2 - center);
        this->orgs.push_back(p);
        this->process.push_back(p);
    }
    
    if (startIdx1 != 0) {
        for (int idx = startIdx1; idx < a1->points.size(); idx++) {
            ofVec2f p = a1->process.at(idx) + (c1 - center);
            this->orgs.push_back(p);
            this->process.push_back(p);
        }
    }
    
    // set target
    setTarget(a1->number + a2->number);
    
    // resize
    int d = this->process.size() / this->targets.size();
    
    if (d != 0) {
        // thin out
        int index = 0;
        auto itr = this->process.begin();
        while ((itr != this->process.end()) && (this->process.size() != this->targets.size())) {
            if (index % d == 0) {
                itr = this->process.erase(itr);
            } else {
                ++itr;
            }
        }
    } else {
        // packing
        for (int i = this->process.size(); i < this->targets.size(); i++) {
            this->process.push_back(ofVec2f(0, 0));
        }
    }
    
    for (int i = 0; i < this->targets.size(); i++) {
        this->points.push_back(ofVec2f(0, 0));
        this->orgs.push_back(ofVec2f(0, 0));
        this->counts.push_back(0);
    }
    
    // set parameter
    this->leftBottom = (a1->leftBottom + a2->leftBottom) / 2;
    this->velocity = (a1->velocity + a2->velocity) / 2;
    this->color = ofColor(0);
}

Link to the reference page

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

categoryAPI/Lib
openframeworksofPath
openframeworksofPolyLine
openframeworksofTrueTypeFont
openframeworksofxeasing map
c++std::vector

Development environment

  • openframeworks 0.10.1
  • c++
  • macOS
  • Xcode