スケーラブルアート論

提供:kuhalaboWiki
2019年11月8日 (金) 02:31時点におけるKuha (トーク | 投稿記録)による版

移動: 案内, 検索

目次

概要

前提スキル

一年生の時にメディアプログラミング演習Iを履修したのと同等のプログラミングスキルがあるものとして、授業を進めます。 もし、プログラミングに不安があるなら、上記テキストを使って、自分で予習や自習をしてください。 openFrameworksはProcessingと似ているため、Processingを知っていると、理解が早いです。

成績評価
  • 出席:学生証scan。3限PC演習室と4限5303教室で2度タッチすること。
  • 課題:セルオートマトン課題、再帰呼び出し図形課題、複素平面フラクタル課題
  • 小テスト:ハイスコア
授業概要及び到達目標
インタラクティブアートは芸術を基盤として科学や工学を統合する新しい領域である。生物科学に関連した分野として、人工生命、ライフゲーム、フラクタル、オートマトン、遺伝的アルゴリズム、ニューラルネットワークなど応用範囲の広いものが数多く存在する。

そういった生物に見られる特徴をアートに応用したジェネラティブアートの作品をC++のプログラミングを使用して、実際に作成してみる。

本講義の目標は以下の通り。

  1. 生物の特徴と生物的なシステムについて理解する。
  2. 複雑系システムについて理解し、応用例を作成できる。
  3. openFrameworksを使って作品のプログラミングができる。

開発環境

開発環境としてopenFrameworks/MacOS XCode, Processing, p5.jsを使用します。

テキストや開発環境については、以下を参照してください。

予定

2019年度
  1. 9/13(金) ガイダンス, 生物と情報とアート,openFrameworksプログラミング体験
  2. 9/20(金) 生物と情報とアート,XCodeを使用したopenFrameworksプログラミング実習
  3. 9/27(金) 幾何学図形の描画
    • (oF実習)
      • 教科書 1章 読み物として読む。
      • 教科書 2章 実際にプログラムを作ってみる。
      •  2-4「数値の記憶と計算」までをやり終えて、自作プログラムの実行ファイルを提出。
      • 幾何学図形の描画、色の設定、変数
      •  プロジェクトフォルダーのbinフォルダーの中にあります。
        • ファイル名 番号_名前のローマ字 例 1724000_suzukiichiro
    • 講義: ディープラーニングによる色の芸術的表現1
      • 完全情報確定ゼロサムゲーム、アルゴリズムとヒューリスティック、エキスパートシステム
  4. 10/4(金) 幾何学図形の描画
  5. 10/11(金)
    • (oF実習)
    •  2-11「より高度な表現」までを学習し、自作プログラムの実行ファイルを提出。
    • マウスアクション、摩擦、重力、軌跡のフェード
    • この日の授業までに2章を終えてください。2章までに学んで、制作したプログラムを提出してもらいます。
    • 講義:最大公約数と矩形分割
  6. 10/18(金)
    • (P5実習)
    •  Generative Art with MathのCh1, Ch2を学ぶ。
    • (oF実習)
    •  3-1,2,3を学習する。
      • 3-1 は、読んで理解してください。プログラム例は作らなくてもよいです。
      • 3-2 は、3-2-7「画像ファイルを扱う」だけでもよい。余裕がれば、他の単元を勉強してもよいです。、
      • 3-3 は、実際に新たにクラスを作成し、プログラムを作りながら、学習を進めてください。
    •  3-4「アドオンの利用」以降は学習しなくてもよい。関心に応じて学習してください。
    • 講義:らせん
  7. 10/25(金)
  8. 11/8(金)1次元セルオートマトン
    • (実習)
    • 講義:
  9. 11/15(金) ライフゲーム
    • (実習)
    • 講義:
  10. 11/22(金) ラングトンのアリ
    • (実習)
    • 講義:
  11. 11/29(金) Boid、物理エンジン Box2D
    • (実習)
    • 講義:
  12. 12/6(金)フラクタルと自己相似形と再帰呼び出し
    • (実習)
    • 講義:
  13. 12/13(金) ニューラルネットワーク、遺伝的アルゴリズム
    • (実習)
    • 講義:
  14. 12/20(金) 小テスト
    • 授業で扱った内容すべてが含まれます。
  15. 1/10(金) 予備日(出席は取りません)
  • 第1課題 2章までの練習プログラム
    • 提出締切日 10/16
  • 第2課題「創発ジェネラティブアートのプログラム」
    • 提出締切日 1/7(月) 
    • 「ライフゲーム、Boid、アリ、Box2D、フラクタル」の中から、1つ以上を選び、自分のオリジナリティを加えたプログラムを提出してください。
    • ソースプログラムも採点の対象としますので、プロジェクトのフォルダー全体をzip圧縮して、提出して下さい。


Contents
  1. Scalable art, Generative art, Mathematical art, Artificial Intelligence, Artificial Life, Complext sysytem
  2. openFrameworks C++ / Xcode MacOSX
  3. Logic circuit
  4. 完全情報ゲーム:チェッカー、オセロ、チェス、将棋、囲碁
  5. Cell auttomaton
  6. Conway's game of life
  7. Wire world
  8. Random walk
  9. Langton's ant
  10. Boid
  11. Box2D
  12. Fractal, Self-similar
  13. Recursive call
  14. Complex square
  15. Mandelbrot
  16. Neural network
  17. Genetic algorithm
  18. Code, Chyper, Encript
  19. Space X
  20. Robotics
  21. Expert system
  22. Fourier transform, spectrum
  23. Fibonacci number
  24. Belousov-Zhabotinsky reaction
  25. Gray-Scott model
  26. Turing pattern

資料

  • new ⇐  old
    • ofDrawLine ⇐ ofLine
    • ofDrawCurve ⇐  ofCurve
    • ofDrawBezier ⇐  ofBezier
    • ofDrawCircle ⇐  ofCircle
    • ofDrawEllipse ⇐  ofEllipse
    • ofDrawTriangle ⇐  ofTriangle
    • ofDrawRectangle ⇐  ofRect
    • ofDrawRectRounded ⇐  ofRectRounded
    • ofDrawSphere ⇐  ofSphere
    • ofDrawCone ⇐  ofCone
    • ofBox ⇐  ofDrawBox

oF新規プロジェクトの作成

  1. oFフォルイダー内のprojectGeneratorフォルダー内のprojectGeneratorを実行する。
  2. Project Path:にoFのあるフォルダーを指定する。
  3. Project Name:に、プロジェクトの名前を入れる。
  4. Addons:に、使用する追加機能(アドオン)を入れる。通常は、なしでよい。
  5. Generateをクリックする。
  6. Apps内のMyAppsにマイプロジェクトが作成されている。
  7. Open IDEをクリックした場合
    • XCodeが立ち上がることを確認する。
  8. Closeをクリックした場合
    • Apps内のMyAppsに作成したプロジェクトフォルダーを開く。
    • プロジェクト名.xcodeprojファイルをクリックして、XCodeを起動する。
  9. 作成したプロジェクトを起動し、srcを見てみる。
    • ofApp.cppのメソッドの中身が空っぽ。
    • ここにプログラムを書いていく。

oFクラスの作成

XCode

  1. 新しくクラスを作るには、「Fileメニュー > New > File」 を開く。
  2. 「macOS」タブの「Source」から、「C++ File」を選び、「Next」ボタンを押す。
  3. Nameにはクラス名を入れます。その際、「Also create a header file」のチェックを入れておきます。そして「Next」ボタンを押します。
    • 名前を それぞれXxx.cppXxx.hとし、場所はともに ..\src とする。
  4. srcの中にXxx.cppXxx.h が新規作成される。

Visual Studio

  1. 新しくクラスを作るには、「プロジェクト > 新しい項目の追加...」 を開き,「C++ファイル」「ヘッダーファイル」を一つずつ作る。
    • 名前を それぞれXxx.cppXxx.hとし、場所はともに ..\src とする
    • (注)「クラスの追加」や「クラスウィザード」は使えない。
  2. マウスカーソルをソリューションエクスプローラー上のsrcに置き、ハイライトさせる。
  3. ソリューションエクスプローラー上のsrcの中にXxx.cppXxx.h が新規作成される。

ヘッダファイル Xxx.h は,

#pragma once
#include "ofMain.h"

class Xxx {
private:
	ofPoint pos;
	float radius;

public:
	Xxx();
	void hogehoge();
};

などと、記述する。

  • ofMain.h をインクルードし、クラスの定義の最後にセミコロンがつくことに注意する。
  • #pragma once は,このヘッダファイルを複数回読み込まないようにするためのもの。
  • 括弧で囲まれた部分 {...} には,変数の宣言やメソッド(関数)の宣言を書く
  • private: に続く部分には,クラス内部のみで利用する変数、メソッドを宣言する。
  • public: に続く部分には,クラス外部からアクセスできる変数、メソッドを宣言する。
  • Xxx(): はコンストラクターといい、クラスと同じ名前のメソッドである。クラスのインスタンスを生成するときの初期化処理などを記述する。

C++ファイル Xxx.cpp は,

#include "Xxx.h" // クラスのヘッダーを読み込む

Xxx::Xxx(){
	pos = ofPoint(ofGetWidth()/2, ofGetHeight()/2);
	radius = 100.0;
}

void Xxx::hogehoge(){
	ofSetColor(31, 63, 255, 100);
	ofCircle(pos.x, pos.y, radius);

	ofSetColor(255, 0, 0, 200);
	ofCircle(pos.x, pos.y, radius);
}

などと、メソッドの本体を記述する。

  • メソッドの定義の最後にセミコロンがつかないことに注意する。


もとのcppファイルodApp.cppのヘッダーファイルofApp.hに、Xxx.hをincludeする。 例えば、ofApp.hは、以下のとおり。

#pragma once

#include "ofMain.h"
#include "Ball.h"

class ofApp : public ofBaseApp{
public:
	bool mouse_pressed;

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 windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);

	Xxx myCbj;
};

publicの領域にXxxクラスのインスタンスmyObjを宣言している。

生物と情報とアート

http://abandonedart.org/

論理回路

https://wwws.kobe-c.ac.jp/deguchi/sc180/logic/gate.html

最大公約数と矩形分割

現代アート作家 モンドリアンの代表作品、Composition

最大公約数

ユークリッドの互除法
  • 自然数x0,x1の最大公約数の求め方(x0 > x1)
    1. x0をx1で割り、余りをx2とする。
    2. x1をx2で割り、余りをx3とする。
      • 割り切れるまで、この操作を繰り返す。
    3. xNで割り切れたら、xNが最大公約数である。
//aとbに対してユークリッド互除法を行う
int a = 10;
int b = 6;
int c;  //商のための変数
int d = b;  //余りのための変数
int itr = 0;  //繰り返しの回数
//繰り返し処理
while (d > 0){    //余りが0以上のとき以下の処理を実行
  itr++;  //繰り返し回数を1増やす
  c = a / b;  //cに商を代入
  d = a % b ;  //dに余りを代入
  println(itr, ":", a, "/", b, "=", c, "...", d);  //計算結果を表示
  a = b;  //aにbを代入
  b = d;  //bに余りを代入
}
println("GCD is", a);  //最大公約数を表示

長方形の分割

ユークリッドの互除法を可視化してみる。
  • 自然数x0,x1を2辺とする長方形を正方形で分割する。
    • 最も小さい正方形の1辺が最大公約数
DivRect
//横縦比がnumA:numBの長方形を正方形によって分割
int numA = 10;
int numB = 6;
int scalar = 50;   //長方形の拡大倍率
numA *= scalar;       //数値の大きさを拡大
numB *= scalar;
//プログラム実行中に動く変数
int wd = numB;    //分割に使う正方形の幅の大きさ(初期値numB)
int xPos = 0;    //正方形のx位置(初期値0)
int yPos = 0;    //正方形のy位置(初期値0)
int itr = 0;  //分割の繰り返し回数(初期値0)
//描画
size(500, 500);    //描画ウィンドウサイズ
//繰り返し処理
while (wd > 0){ //幅が0になるまで以下を実行
  itr++;              //繰り返し回数を1増やす
  if (itr % 2 == 1){      //繰り返し回数が奇数のとき,x軸方向へ正方形を増やす
    while (xPos + wd <= numA){    //幅を足したとき,長方形を超えなければ以下を実行
      rect(xPos, yPos, wd, wd);      //(xPos,yPos)を左上の頂点とする1辺wdの正方形を描画
      xPos += wd;                //x位置を更新
    }
    wd = numA - xPos;             //幅を更新
  } else {              //繰り返し回数が偶数のとき,y軸方向へ正方形を加える
    while (yPos + wd <= numB){    //幅を足したとき,長方形を超えなければ以下を実行
      rect(xPos, yPos, wd, wd);      //(xPos,yPos)を左上の頂点とする1辺wdの正方形を描画
      yPos += wd;                //y位置を更新
    }
    wd = numB - yPos;            //幅を更新
  }
}
正方形に色を付けてみる
int numA = 10;
int numB = 6;
int scalar = 50;
numA *= scalar;
numB *= scalar;
int wd = numB;
int xPos = 0;
int yPos = 0;
int itr = 0;
color col;  //色のための変数
//描画
size(500, 500);
colorMode(HSB, 1);  //01区間をパラメータとするHSB色形式を使用
//ループ
while (wd > 0) {
  itr++;
  if (itr % 2 ==1) {
    while (xPos + wd <= numA) {
      col = color(random(1), 1, 1);  //色相のみを01区間でランダムに変える
      fill(col);
      rect(xPos, yPos, wd, wd);
      xPos += wd;
    }
    wd = numA - xPos;
  } else {
    while (yPos + wd <= numB) {
      col = color(random(1), 1, 1);
      fill(col);
      rect(xPos, yPos, wd, wd);
      yPos += wd;
    }
    wd = numB - yPos;
  }
}

正方形の分割

自然数x0とx1の縦横比x0:x1を使って、長方形を正方形に変形すると、正方形を長方形に分割することになる。

  • x0×x1の横長の長方形を x1/x0 に横方向に圧縮して正方形にする。
DivSquare
//縦横比がnumA:numBの長方形によって正方形の描画ウィンドウを分割
int numA = 10;
int numB = 6;
float ratio = (float) numB / numA;  //比率
float xPos = 0;
float yPos = 0;
int itr = 0;
//描画
size(500, 500);
colorMode(HSB, 1);
float wd = width;    //描画ウィンドウの横幅サイズを初期値とする
//繰り返し処理
while (wd > 0.1){   //幅が許容誤差より大きければ以下を実行
  itr++;
  if (itr % 2 == 1){  //縦幅がwdの長方形をx軸方向へ加える
    while (xPos + wd * ratio < width + 0.1){
      //幅を足したとき,横幅がウィンドウを超えなければ以下の処理を実行
      fill(color(random(1), 1, 1));
      rect(xPos, yPos, wd * ratio, wd);      //縦幅wd,縦横比がnumA:numBの長方形を描画
      xPos += wd * ratio;                //x位置を更新
    }
    wd = width - xPos;
  } else {  //横幅がwdの長方形をy軸方向へ加える
    while (yPos + wd / ratio < width + 0.1){
      //幅を足したとき,縦幅がウィンドウを超えなければ以下の処理を実行
      fill(color(random(1), 1, 1));  //ランダムに色を指定
      rect(xPos, yPos, wd, wd / ratio);      //横幅wd,縦横比がnumA:numBの長方形を描画
      yPos += wd / ratio;                //y位置を更新
    }
    wd = width - yPos;
  }
}

矩形の再帰的分割

長方形を正方形に分割し、その正方形を長方形に分割する。

//縦横比がnumB:numAの長方形を逆の比の長方形によって分割
int numA = 10;
int numB = 6;
float ratio = (float) numB / numA;
void setup(){ //最初に1度だけ実行する関数
  size(500, 500);
  colorMode(HSB, 1);
  //この関数内だけのローカル変数
  int itr = 0;
  float xPos = 0;
  float yPos = 0;
  float wd = width * ratio;
  while (wd > 0.1){
    itr++;
    if (itr % 2 == 1){
      while (xPos + wd < width + 0.1){
        divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        xPos += wd;
      }
      wd = width - xPos;
    } else {
      while (yPos + wd < width * ratio + 0.1){
        divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        yPos += wd;
      }
      wd = width * ratio - yPos;
    }
  }
}

正方形を長方形に分割する関数

//位置(xPos,yPos)にある1辺がwdの正方形を縦横比がnumA:numBの長方形で分割する
void divSquare(float xPos, float yPos, float wd){
  //この関数内だけのローカル変数
  int itr = 0;
  float xEndPos = wd + xPos;  //正方形の右下の頂点のx座標
  float yEndPos = wd + yPos;  //正方形の右下の頂点のy座標
  //繰り返し処理
  while (wd > 0.1){
    itr++;
    if (itr % 2 == 1){
      while (xPos + wd * ratio < xEndPos + 0.1){  //ratioはグローバル変数
        fill(color(random(1), 1, 1));
        rect(xPos, yPos, wd * ratio, wd);
        xPos += wd * ratio;
      }
      wd = xEndPos - xPos;
    } else {
      while (yPos + wd / ratio < yEndPos + 0.1){
        fill(color(random(1), 1, 1));
        rect(xPos, yPos, wd, wd / ratio);
        yPos += wd / ratio;
      }
      wd = yEndPos - yPos;
    }
  }
}


  • 正方形を長方形に分割し、その長方形を正方形に分割し、その正方形を長方形に分割し、・・・
  • 再帰的に関数を呼び出す。
    • 再帰呼び出しを止めるしきい値(Threshold)を設定する。
int numA = 10;
int numB = 6;
float ratio = (float) numB / numA;
float thr = 160;  //しきい値
void setup(){
  size(500, 500);
  colorMode(HSB, 1);
  divSquare(0, 0, width); //正方形の分割
}

divSquare

//位置(xPos,yPos)にある1辺がwdの正方形を縦横比がnumA:numBの長方形で分割する
void divSquare(float xPos, float yPos, float wd){
  int itr = 0;
  float xEndPos = wd + xPos;
  float yEndPos = wd + yPos;
  fill(color(random(1), 1, 1));
  rect(xPos, yPos, wd, wd);
  while (wd > thr){  //wdがしきい値以上の場合に処理を行う
    itr++;
    if (itr % 2 == 1){
      while (xPos + wd * ratio < xEndPos + 0.1){
        divRect(xPos, yPos, wd * ratio);  //長方形を分割する関数の呼び出し
        xPos += wd * ratio;
      }
      wd = xEndPos - xPos;
    } else {
      while (yPos + wd / ratio < yEndPos + 0.1){
        divRect(xPos, yPos, wd);  //長方形を分割する関数の呼び出し
        yPos += wd / ratio;
      }
      wd = yEndPos - yPos;
    }
  }
}

divRect

//位置(xPos,yPos)にある横幅wdで縦横比がnumA:numBの長方形を正方形によって分割する
void divRect(float xPos, float yPos, float wd){
  int itr = 0;
  float xEndPos = xPos + wd;
  float yEndPos = yPos + wd / ratio;
  fill(color(random(1), 1, 1));
  rect(xPos, yPos, wd, wd / ratio);
  while (wd > thr){   //長方形の幅がしきい値以上の場合に処理を行う
    itr++;
    if (itr % 2 == 0){
      while (xPos + wd < xEndPos + 0.1){
        divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        xPos += wd;
      }
      wd = xEndPos - xPos;
    } else {
      while (yPos + wd < yEndPos + 0.1){
        divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        yPos += wd;
      }
      wd = yEndPos - yPos;
    }
  }
}

マウスクリックでx0,x1,thresholdをランダムに設定して描画する。

void mouseClicked(){
  numA = int(random(1, 20));  //1以上20以下のランダムな整数を代入
  numB = int(random(1, 20));
  while (numA == numB){ //numAとnumBが異なるようにする
    numB = int(random(1, 20));
  }
  thr = int(random(10,300));
  println("numA =", numA, "numB =", numB,"thr =", thr);  //numA,numB,thrの値を表示
  ratio = (float) numA / numB;
  background(0, 0, 1);  //背景を白で消去
  divSquare(0, 0, width);
}
void draw(){} //プログラムを実行している間,繰り返し実行する関数

無理数比の矩形分割

  • 縦横比
    • sqrt(2) 自己相似形。A4,B4サイズの用紙
    • (1 + sqrt(5) ) / 2 黄金比
モンドリアンの再現
float ratio = (sqrt(5) + 1) / 2;  //黄金数
float thr = 40;  //分割する大きさに関するしきい値
float thr2 = 0.5; //確率を決定するしきい値
void setup(){
  size(500, 500);
  colorMode(HSB, 1);
  colorRect(0, 0, width, width);
  divSquare(0, 0, width);
}

モンドリアン風に配色を決める

void colorRect(float xPos, float yPos, float wd, float ht){
  color col;
  float val = random(1);
  if (val < 0.15){  //15%の確率
    col = color(0, 1, 1); //赤
  }else if (val < 0.3){ //15%の確率
    col = color(2.0 / 3, 1, 1); //青
  }else if (val < 0.45){  //15%の確率
    col = color(1.0 / 6, 1, 1); //黄
  }else if (val < 0.5){ //5%の確率
    col = color(0, 1, 0); //黒
  } else if (val < 0.7){  //20%の確率
    col = color(0, 0, 0.9); //灰
  } else {  //30%の確率
    col = color(0, 0, 1); //白
  }
  fill(col);
  strokeWeight(5);  //長方形の枠線の太さ
  rect(xPos, yPos, wd, ht);
}

矩形分割

void divRect(float xPos, float yPos, float wd){  //長方形を分割する関数
  int itr = 0;
  float xEndPos = xPos + wd;  //長方形の横の長さ
  float yEndPos = yPos + wd / ratio;   //長方形の縦の長さ
  while (wd > thr){   //wdがしきい値以上の場合に処理を行う
    itr++;
    if (itr % 2 == 0){
      while (xPos + wd < xEndPos + 0.1){
        colorRect(xPos, yPos, wd, wd);  //正方形を描く
        if (random(1) < thr2){
          divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        }
        xPos += wd;
      }
      wd = xEndPos - xPos;
    } else {
      while (yPos + wd < yEndPos + 0.1){
        colorRect(xPos, yPos, wd, wd);  //正方形を描く
        if (random(1) < thr2){
          divSquare(xPos, yPos, wd);  //正方形を分割する関数の呼び出し
        }
        yPos += wd;
      }
      wd = yEndPos - yPos;
    }
  }
}

void divSquare(float xPos, float yPos, float wd){  //正方形を分割する関数
  int itr = 0;
  float xEndPos = wd + xPos;  //正方形の横の長さ
  float yEndPos = wd + yPos;  //正方形の縦の長さ
  while (wd > thr){  //正方形の幅がしきい値以上の場合に実行
    itr++;
    if (itr % 2 ==1){
      while (xPos + wd * ratio < xEndPos + 0.1){
        colorRect(xPos, yPos, wd * ratio, wd);  //長方形を描く
        if (random(1) < thr2){  //thr2の確率で再分割
          divRect(xPos, yPos, wd * ratio);  //長方形を分割する関数の呼び出し
        }
        xPos += wd * ratio;
      }
      wd = xEndPos - xPos;
    } else {
      while (yPos + wd / ratio < yEndPos + 0.1){
        colorRect(xPos, yPos, wd, wd / ratio);  //長方形を描く
        if (random(1) < thr2){  //thr2の確率で再分割
          divRect(xPos, yPos, wd);  //長方形を分割する関数の呼び出し
        }
        yPos += wd / ratio;
      }
      wd = yEndPos - yPos;
    }
  }
}

マウスクリックでランダムに再構成

void mouseClicked(){
  thr = int(random(10, 50));
  thr2 = random(0,1);
  println("thr =", thr, "thr2 =", thr2);
  colorRect(0, 0, width, width);
  divSquare(0, 0, width);
}
void draw(){}

フィボナッチ数列

  • f(0) = 0, f(1) = 1, f(n) = f(n-1) + f(n-2)
  • 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, …
int num = 40;
int[] fibo = {0,1}; // Initial Fibonacci
int i = 0;
println(i, ":", fibo[i]);
for(i = 1; i < num; i++){
  println(i, ":", fibo[i]);
  fibo = append(fibo, fibo[i-1] + fibo[i]);
}

フィボナッチ数列の可視化

フィボナッチ数を1辺とする正方形を敷き詰めて、長方形を描く。

int[] fibo = {0,1,1};
int[] SGN = {-1, 1, 1, -1};  //敷き詰める方向を決める符号
void setup(){
  size(500, 500);
  colorMode(HSB, 1);
  drawSpiral();
}
void drawSpiral(){
  float xPos = 0;
  float yPos = 0;
  float scalar = (float) width / (2 * fibo[fibo.length - 1]);  //拡大・縮小比率
  background(0, 0, 1);
  translate(width / 2 ,height / 2); //描画ウィンドウ中央に移動
  for(int i = 1; i < fibo.length - 1; i++){
    fill((0.1 * i) % 1, 1, 1);
    //正方形を描く方向を符号の配列に従って変える
    rect(scalar * xPos,
      scalar * yPos,
      scalar * SGN[(i+1) % 4] * fibo[i],  //符号が負の場合,逆方向に正方形を描画
      scalar * SGN[i % 4] * fibo[i]);
    //正方形の位置を符号の配列に従って変える
    if (i % 2 == 1){
      xPos += SGN[i % 4] * (fibo[i] + fibo[i + 1]);
    } else {
      yPos += SGN[i % 4] * (fibo[i] + fibo[i + 1]);
    }
  }
}
void mouseClicked() {
  int nextFibo = fibo[fibo.length-2] + fibo[fibo.length-1];
  fibo = append(fibo, nextFibo);
  drawSpiral();
  println(nextFibo);
}
void draw(){}

フィボナッチらせんを描く関数

void drawSpiral(){
  float xPos = 0;
  float yPos = 0;
  float scalar = (float) width / (2 * fibo[fibo.length-1]);  //拡大・縮小比率
  background(0, 0, 1);
  translate(width / 2 ,height / 2); //描画ウィンドウ中央に移動
  for(int i = 1; i < fibo.length - 1; i++){
    stroke(0, 0, 0);
    rect(scalar * xPos,
      scalar * yPos,
      scalar * SGN[(i+1) % 4] * fibo[i],
      scalar * SGN[i % 4] * fibo[i]);
    stroke(0, 1, 1);
    arc(scalar * (xPos + SGN[(i+1) % 4] * fibo[i]),  //円の中心のx座標
      scalar * (yPos + SGN[i % 4] * fibo[i]),  //円の中心のy座標
      scalar * 2 * fibo[i],  //楕円の縦の直径
      scalar * 2 * fibo[i],  //楕円の横の直径(正円のため縦と同じ)
      (1 + i) * PI / 2,  //円弧の開始位置(ラジアン)
      (2 + i) * PI / 2);  //円弧の終了位置
    if (i % 2 == 1){
      xPos += SGN[i % 4] * (fibo[i] + fibo[i+1]);
    } else {
      yPos += SGN[i % 4] * (fibo[i] + fibo[i+1]);
    }
  }
}

らせん

  • 極座標表示
  • 三角関数
3種のらせん
アルキメデスらせん
フェルマーらせん
対数らせん

らせんの描画

float theta = 0;
float STEP = 2 * PI * 0.01; //曲線の精度
void setup(){
  size(500, 500);
}
void draw(){
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動
  line(rad(theta) * cos(theta),
   rad(theta) * sin(theta),
   rad(theta + STEP) * cos(theta + STEP),
   rad(theta + STEP) * sin(theta + STEP));
  theta += STEP;
}
float rad(float t){ //動径を定める関数
  float r = 5 * t;  //アルキメデスらせん
  //float r = 20 * sqrt(t); //フェルマーらせん
  // float r = pow(1.1, t); //対数らせん
  return(r);
}

自己相似な対数らせん

float STEP = 2 * PI * 0.01; //曲線の精度
void setup(){
  size(500, 500);
  colorMode(HSB, 1);
}
void draw(){
  background(1,0,1);
  drawLogSpiral();  //対数らせんを描画
}

void drawLogSpiral(){
  float theta = 0;
  float scalar = pow(10, (float) mouseX / width) * height / 2;
  //マウスのx座標によって1~10倍に拡大する
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動
  for(int i = 0; i < 2000; i++){
    line(scalar * rad(theta) * cos(theta),
      scalar * rad(theta) * sin(theta),
      scalar * rad(theta + STEP) * cos(theta + STEP),
      scalar * rad(theta + STEP) * sin(theta + STEP));
    theta -= STEP;  //反時計回りに進むほど動径は減少する
  }
}
float rad(float t){ //動径を定める関数
  float r = pow(1.1, t);
  return(r);
}

再帰的な描画と対数らせん

正方形の中に正方形を再帰的に描く

PVector[] vec;  //PVector型の配列を宣言
float gap = 0.01;  //内接する正方形のずれ
void setup(){
  size(500, 500);
  vec = new PVector[4]; //4のベクトルを生成
  vec[0] = new PVector(0, 0); //ウィンドウ左上の角
  vec[1] = new PVector(width, 0); //ウィンドウ右上の角
  vec[2] = new PVector(width, height);  //ウィンドウ右下の角
  vec[3] = new PVector(0, height);  //ウィンドウ左下の角
}
void draw(){
  drawSquare(vec);  //4つのベクトルを頂点とする四角形を描画
  vec = getVector(vec); //ベクトルをgapの分だけずらす
}
void drawSquare(PVector[] v){ 
  for(int i = 0; i < 4; i++){
    line(v[i].x, v[i].y, v[(i + 1) % 4].x, v[(i + 1) % 4].y);
    //ベクトルのxy座標の値を取りだし,線分を描く
  }
}
PVector[] getVector(PVector[] vec){
  PVector[] nextVec = new PVector[4];
  for(int i = 0; i < 4; i++){
    PVector dir = PVector.sub(vec[(i + 1) % 4], vec[i]);  //2頂点間の方向ベクトル
    dir.mult(gap);  //ずれの分を方向ベクトルにかける
    nextVec[i] = PVector.add(vec[i], dir); //元の頂点の位置ベクトルをずらして新たなベクトルを作る
  }
  return(nextVec);
}
void mouseClicked(){
  background(255);
  gap = random(1) / 2;
  println("gap =", gap);
  vec[0] = new PVector(0, 0);
  vec[1] = new PVector(width, 0);
  vec[2] = new PVector(width, height);
  vec[3] = new PVector(0, height);
}

多角形に拡張する。

PVector[] vec;  //PVector型の配列を宣言
float gap = 0.1;  //内接する正多角形のずれ
int gon = 8;  //正多角形の頂点の数
void setup(){
  size(500, 500);
  vec = new PVector[gon];
  for(int i = 0; i < gon; i++){ //正多角形の頂点の位置ベクトル
    vec[i] = PVector.fromAngle(2 * i * PI / gon);
    vec[i].mult(width / 2);
  }
}
void draw(){
  translate(width / 2, height / 2); //描画ウィンドウの中心に移動
  drawPolygon(vec);
  vec = getVector(vec);
}
void drawPolygon(PVector[] v){
  for(int i = 0; i < gon; i++){
    line(v[i].x, v[i].y, v[(i + 1) % gon].x, v[(i + 1) % gon].y);
  }
}
PVector[] getVector(PVector[] v){
  PVector[] nextVec = new PVector[gon];
  for(int i = 0; i < gon; i++){
    PVector dir = PVector.sub(v[(i + 1) % gon], v[i]);
    dir.mult(gap);
    nextVec[i] = PVector.add(v[i], dir);
  }
  return nextVec;
}
void mouseClicked(){
  gap = random(1) / 2;
  gon = int(random(4, 16));
  background(255);
  vec = new PVector[gon];
  for(int i = 0; i < gon; i++){ //正多角形の頂点の位置ベクトル
    vec[i] = PVector.fromAngle(2 * i * PI / gon);
    vec[i].mult(width / 2);
  }
}

フェルマーらせん

離散的らせん
  • 回転角
    • 有理数
      • 17/55
      • 1/2,1/3,1/5,1/10,1/20,1/40
      • /61
      • / 72
      • / 17
      • / 305
      • / 109
      • / 360
    • 無理数
      • sqrt(5)
      • 黄金比
      • 円周率


int itr = 0;  //描画の繰り返し回数
float scalar = 5; //拡大倍率
float rotation;
void setup() {
  size(500, 500);
  background(255);  //背景を白くする
  rotation = 17.0 / 55;
//  rotation = sqrt(5);
//  rotation = (1 + sqrt(5)) / 2;
}
void draw() {
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動
  fill(0);  //点を黒く塗る
  drawFermatSpiral(rotation);  //引数を回転角とするフェルマーらせんの描画
  itr++;
}
void drawFermatSpiral(float rot){
  float theta = 2 * PI * itr * rot; //回転角
  PVector v = PVector.fromAngle(theta);
  v.mult(scalar * sqrt(itr));
  ellipse(v.x, v.y, scalar, scalar); //点を描画
}


分母が共通の複数の離散的らせん

int itr = 0;  //描画の繰り返し回数
float scalar = 5; //拡大倍率
void setup() {
  size(500, 500);
  background(255);
}
void draw() {
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動

  noStroke();
//  fill(255, 0, 0, 127);  //点を赤く塗る
//  drawFermatSpiral(1.0 / 3);
  // drawFermatSpiral(4.0 / 17);
  fill(0, 0, 255, 127);  //点を青く塗る
  drawFermatSpiral(1.0 / 61);
  // drawFermatSpiral(17.0 / 72);
  fill(0, 255, 0, 127);  //点を緑に塗る
  drawFermatSpiral(20.0 / 61);
  // drawFermatSpiral(72.0 / 305);
  itr++;
}
void drawFermatSpiral(float rot){
  float theta = 2 * PI * itr * rot; //回転角
  PVector v = PVector.fromAngle(theta);
  v.mult(scalar * sqrt(itr));
  ellipse(v.x, v.y, scalar, scalar); //点を描画
}


サンプル集

ランダムウォーク

1次元セルオートマトン

ライフゲーム

ラングトンのアリ

Boid

物理エンジン Box2D

自己相似形

リンク

http://gushwell.ifdef.jp/

素数のグラフィック http://www.datapointed.net/visualizations/math/factorization/animated-diagrams/?infinity

個人用ツール
名前空間

変種
操作
案内
ツールボックス