gotutiyan’s blog

競技プログラミングをやったりopenframeworksでお絵かきをしたりしています。

くるりんくるりん

円周上を回る円の円周上を回る円の円周上の・・・
完成形をぐっと想像したら、再帰関数をやればできそうな気がしたので書いたらできました。


止まっている円だけ普通にofDrawCircleで書いて、後は再帰関数でうまいことやります。
再帰関数の引数は半径を表すradius再帰関数が何層まで深いとこまで行くかを持つcount、そして各円を回すために必要な角度情報degです。

今回作成したクラスPICでは、初期化関数init()で円を何個書くか、つまり再帰関数の深さを何層まで許すかを決めて、move()で最初のradiusを渡すことにしています。(特に理由はなくて、気づいたらこんな実装になってた)

また、小さい円ほど早く回らないと「ぐるぐるする ところを さらにぐるぐる する」感じが無いのですが、この実装に意外と苦労してしまいまして。なぜか全ての円が一直線に並んで仲良く回転するようになってしまっていました。
このため「うーん」と唸りながら色々試していたら偶然上手く行ったので、正直仕組みがよく分かってません。

ただ確実に言えるのは、再帰の深さを表すcountは円が小さくなるほど増えるので、これを円の回る速さに使えば良いということです。ここは上手くできたと思います。
総評としては、もう少しスマートな実装がある気がしていますが、見てて楽しいものができたので良しとします。

#include "ofApp.h"
#define rep(i,j,k) for(int i=j;i<k;i++)

int diff=0;
class PIC{
    float x,y;
    int limit;  //limitは再帰の深さの最大値
public:
 //初期化
    void init(int elimit){
        limit=elimit;
    }
    
 
    void move(int radius,int count,float deg){
        if(count==limit)return;
  //degに足す値にcountを使うことで、小さい円ほど早く回ってくれそう
        deg+=count*0.1+0.1;
        x=radius*cos((deg+count)*DEG_TO_RAD);
        y=radius*sin((deg+count)*DEG_TO_RAD);
        
        ofTranslate(x,y);
        ofDrawCircle(0,0,radius/2);
        
        //再帰を書きます。半径は半分に、countを1増やして、degはそれっぽく書きました。
        move(radius/2,count+1,deg+(count*0.5+1)*diff);
    }
};
PIC p;
float deg=0;
void ofApp::setup(){
    ofSetFrameRate(60);
    ofSetBackgroundColor(0);
    ofSetColor(255);
    ofSetCircleResolution(64);
    ofNoFill();
    
    p.init(5);
}

void ofApp::update(){
    diff++;
}

void ofApp::draw(){
    deg+=0.1;
    ofTranslate(ofGetWidth()/2,ofGetHeight()/2);
    ofDrawCircle(0,0,150);  //止まっている一番大きい円
    p.move(150,0,deg);  //再帰の親です。ここからlimit分再帰します
}