yoppa.org


Processingで時間を表現する

「時間」を表現する

前回の授業で取り上げた、時計のプログラムで学んだ内容を生かして、時間を表現するプログラムについて考えてみる

  • 場所 (位置) による時間の表現
  • 色・濃度 による時間の表現
  • 数で時間を表現する …etc.

時間の表現の例

  • NOOKA
    • マシュー・ウォルドマンデザインによるシンプルな図形で時間を表現した腕時計
    • http://nooka.com/
  • Tokyoflash
  • Ora Unica
    • Denis Guidoneデザイン、1本の線で時間を表示する
  • Clocks Series by rhizomatics
    • iPhone向けの時計アプリシリーズ、継続的に、多様でユニークな時計アプリをリリースしている
  • Creative Applicationsで紹介されている様々な時計の試み

Processingで時間を表現してみる

Processingで、シンプルなサンプルを作りながら、いろいろな表現の可能性について考えてみましょう。

  • clock01: 場所による時間の表現
  • clock02: 濃度による時間の表現
  • clock03: 色相による時間の表現
  • clock04: 文字と動きによる時間表現
  • clock05: 2進数による時間表現

時間の表現1:場所による時間の表現

  • 図形の場所によって、時間を表現する
  • 時間、分、秒、ミリ秒の値に対応して、ラインの位置を変化させてみる
int ms, s, m, h;
int lastSecond = 0;

void setup() {
    size(640, 480);
    frameRate(60);
    background(0);
}

void draw() {
    ms = millis() % 1000;
    s = second();
    m = minute();
    h = hour();

    fill(0, 4);
    noStroke();
    rect(0, 0, width, height);

    noFill();

    float ms_pos = width / 1000.0 * ms;
    stroke(255);
    line(ms_pos, 0, ms_pos, height / 4);

    translate(0, height / 4);
    stroke(63);
    line(0, 0, width, 0);

    float s_pos = width / 60.0 * s;
    stroke(255);
    line(s_pos, 0, s_pos, height / 4);

    translate(0, height / 4);
    stroke(63);
    line(0, 0, width, 0);

    float m_pos = width / 60.0 * m;
    stroke(255);
    line(m_pos, 0, m_pos, height / 4);

    translate(0, height / 4);
    stroke(63);
    line(0, 0, width, 0);

    float h_pos = width / 24.0 * h;
    stroke(255);
    line(h_pos, 0, h_pos, height / 4);
}

時間の表現2:濃度による時間の表現

  • 図形の濃度 (アルファ値) によって時間を表現する
  • 4つの正方形を横に並べて、その濃度を、時間、分、秒、ミリ秒に対応させてみる
float ms, s, m, h;

void setup() {
    size(800, 200);
    noStroke();
}

void draw() {
    ms = millis() % 1000;
    s = second();
    m = minute();
    h = hour();

    background(0);

    fill(255, 255 * (h / 24.0));
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(255, 255 * (m / 60.0));
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(255, 255 * (s / 60.0));
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(255, 255 * (ms / 1000.0));
    rect(0, 0, width / 4, height);
}

時間の表現3:色相による時間の表現

  • 同じ図形で、今度は透明度はそのまで、色の色相(Hue)を変化させていく
  • colorModeをCSVにして、色相の変化を計算しやすいように
  • あとは色相を、時間、分、秒、ミリ秒に適用させるだけ
float ms, s, m, h;

void setup() {
    size(800, 200);
    colorMode(HSB, 360, 100, 100);
    noStroke();
}

void draw() {
    ms = millis() % 1000;
    s = second();
    m = minute();
    h = hour();

    background(0);

    fill(360 * (h / 24.0), 50, 100);
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(360 * (m / 60.0), 50, 100);
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(360 * (s / 60.0), 50, 100);
    rect(0, 0, width / 4, height);

    translate(width / 4, 0);
    fill(360 * (ms / 1000.0), 50, 100);
    rect(0, 0, width / 4, height);
}

時間の表現4:文字と動きによる時間表現

  • デジタル時計と、アナログ時計を融合させてみる
  • 時間、分、秒を表示した針が、秒針の角度で回転していく
  • 時間の経過を感じさせるため、余韻を残しながら、消えていくようにする
  • フォントの表示のため、Tools → Create font を利用して、”myFont.vlw” を生成する
PFont font;
int s, m, h;
int lastSecond = 0;

void setup() {
    size(600, 600);

    font = loadFont("myFont.vlw");
    textFont(font);

    frameRate(30);
    smooth();
    background(0);
}

void draw() {
    s = second();
    m = minute();
    h = hour();

    if (lastSecond != s) {
        textAlign(CENTER);
        fill(0, 10);
        noStroke();
        rect(0, 0, width, height);

        fill(255);
        stroke(255);

        pushMatrix();
        translate(width / 2, height / 2);
        rotate(radians(6.0 * s));
        String time = nf(h, 2) + "\n" + nf(m, 2) + "\n" + nf(s, 2);
        text(time, 0, -height / 2.2);
        line(0, 0, 0, -height / 3);
        popMatrix();
    }
    lastSecond = s;
}

時間の表現5:2進数による時間表現

  • 時間の経過を2進数のon/offで表現してみる
  • 現在の時刻をの時間、分、秒をそれぞれ6桁の2進数に変換
    • 例:18:11:49 → 010010001011110001
  • 2進数の0と1を利用して図形を描いていく
  • 時間の経過が蓄積していくように工夫
PFont font;
int s, m, h;
int lastSecond = 0;
int bufferNum = 480;
String[] timeArray = new String[bufferNum];

void setup() {
  size(640, 480);
  frameRate(30);
  background(0);
  noStroke(); h

  for (int i = 0; i < timeArray.length; i++) {
    timeArray[i] = "000000000000000000";
  }
}

void draw() {
  s = second();
  m = minute();
  h = hour();

  if (lastSecond != s) {
    background(0);

    String bs = nf(int(binary(s)), 6);
    String bm = nf(int(binary(m)), 6);
    String bh = nf(int(binary(h)), 6);

    println(nf(h, 2) + ":" + nf(m, 2) + ":" + nf(s, 2) + ", " + bh + bm + bs);

    float rectWidth = width / 18.0;
    float rectHeight = height / float(bufferNum);

    for (int i = timeArray.length; i & gt; 1; i--) {
      timeArray[i - 1] = timeArray[i - 2];
    }
    timeArray[0] = bh + bm + bs;

    for (int j = 0; j < timeArray.length - 1; j++) {
      for (int i = 0; i < 18; i++) {
        if (timeArray[j].charAt(i) == '1') {
          fill(255);
        }
        else {
          fill(0);
        }
        rect(i * rectWidth, j * rectHeight, rectWidth, rectHeight);
      }
    }
  }
  lastSecond = s;
}

サンプルファイルのダウンロード

今回のとりあげた全てのサンプルのソースコードは、下記からダウンロードしてください。


Processingで時計を作る

今日の内容

  • 最終課題のテーマ発表
    • 最終課題に関連した演習
      • 基礎:普通のアナログ時計を作る
        • 時刻の取得
        • 時刻の情報から針の角度を計算する
        • より正確な時計にしていくには?
      • 応用:単純な図形で「時間」を表現する
        • 位置による時間の表現
        • 数による時間の表現
        • 色による時間の表現
        • 形態による時間の表現 …etc

最終課題について

テーマ:Processingを用いて「時間」を表現する

  • Processingを用いて、「時間」を表現するプログラムを作成してください。
    • 時間を表現するといっても、普段身近に接している時計を作成するという意味ではなく、いままで見たことのないような表現手段で「時間」を感じさせる方法を考えてください。
  • 作品の完成度よりは、斬新なアイデアを重視します。
  • 完成したプログラムは下記のWebサーバのアドレスにアップロードすることで提出したこととします。
  • http://www.cuc.ac.jp/~[ログイン名]/proga/final/index.html

時計を作る 1 – アナログ時計を作る

  • 課題の内容と関連して、まずは普通のアナログ時計を作成する
    • 時刻の取得
    • 取得した時刻から、秒針、分針、時針の角度を計算する
  • 時刻を取得するための関数
  • 取得される時刻は、コンピュータのOSに設定された時刻
//秒の取得
second();
//分の取得
minute();
//時の取得
hour();
  • 試しに現在の秒数を取得して、出力してみる
  • println() を利用して、Processingのコンソールのテキストエリアに現在の秒数を表示してみる
void setup() {
  frameRate(30);
}

void draw() {
  int h = hour();
  int m = minute();
  int s = second();
  println("現在の時刻は "+h+":"+m+":"+s);
}
  • 取得した「時」「分」「秒」の値から針の角度を計算する
  • それぞれの1単位あたり、何度針が動くのか計算する (角速度)
    • 時間
      • 12時間で時計を1周する
      • 1時間あたりの移動角度 → 360/12 = 30°
      • 60分で時計を1周する
      • 1分あたりの移動角度 → 360/60 = 6°
      • 60秒で時計を1周する
      • 1秒あたりの移動角度 → 360/60 = 6°
  • 現在の時刻の値にそれぞれの角速度をかけ算すると、現在の角度が算出されるはず
  • 時刻から、針の角度を計算する
    • 例:8時30分20秒
      • 時針:8 x 30 = 240°
      • 分針:30 x 6 = 180°
      • 秒新:20 x 6 = 120°
  • Processingのプロラムで表現してみる
    • 座標系全体を画面の中央に移動 translate(width/2, height/2);
    • 時刻を取得
    • 時間の針
      • pushMatrix();
      • rotate(時間 x 15°);
      • popMatrix();
    • 分の針
      • pushMatrix();
      • rotate(時間 x 6°);
      • popMatrix();
    • 秒の針
      • pushMatrix();
      • rotate(時間 x 6°);
      • popMatrix();
  • Processingの rotate() 関数で用いられる角度の単位に注意する必要あり
  • 角度の単位は「度 (degree)」ではなく「ラジアン (radian)」で指定しなくてはならない
    • Processingでの、degreeからradianへの変換
      • radians( 度 );
    • radianからdegreeへの変換
      • degrees( ラジアン角 );
void setup() {
  size(300,300);
  stroke(255);
  smooth();
  frameRate(30);
}

void draw() {
  background(0);
  float s = second();
  float m = minute();
  float h = hour() % 12;
  translate(width/2, height/2);
  ellipse(0,0,width,height);
  noFill();
  stroke(255);
  //秒針
  pushMatrix();
  rotate(radians(s*(360/60)));
  strokeWeight(1);
  line(0,0,0,-height/2);
  popMatrix();
  //分針
  pushMatrix();
  rotate(radians(m*(360/60)));
  strokeWeight(2);
  line(0,0,0,-height/2);
  popMatrix();
  //時針
  pushMatrix();
  rotate(radians(h*(360/12)));
  strokeWeight(4);
  line(0,0,0,-height/3);
  popMatrix();
}

実際の時計との違い

  • 作成したプログラムの表示と実際のアナログ時計と比べてみる
    • なにか違いはないだろうか?
      • 分が切り替わった瞬間の動き
      • 時間が切り替わった瞬間の動き
  • 実際の時計
    • 時針、分針も常にゆっくりと動いている
    • 時刻が変化した瞬間に動くのではなく、ゆっくりと移動して、気がつくと角度が変化している
  • 実際の分針
    • 秒の影響を受けている
    • 毎秒あたり、1/60の影響 (1分 = 60秒なので)
    • m = m + (s/60);
  • 実際の時針
    • 分の影響を受けている
    • 毎分あたり、1/60の影響 (1時間 = 60分なので)
    • h = h + (m/60);
  • 実際にアナログ時計の動きと同じになるよう、プログラムを改良する
void setup() {
  size(300,300);
  stroke(255);
  smooth();
  frameRate(30);
}

void draw() {
  background(0);
  float s = second();
  float m = minute() + (s/60.0);
  float h = hour()%12 + (m/60.0);
  translate(width/2, height/2);
  ellipse(0,0,width,height);
  noFill();
  stroke(255);
  //秒針
  pushMatrix();
  rotate(radians(s*(360/60)));
  strokeWeight(1);
  line(0,0,0,-height/2);
  popMatrix();
  //分針
  pushMatrix();
  rotate(radians(m*(360/60)));
  strokeWeight(2);
  line(0,0,0,-height/2);
  popMatrix();
  //時針
  pushMatrix();
  rotate(radians(h*(360/12)));
  strokeWeight(4);
  line(0,0,0,-height/3);
  popMatrix();
}
  • 文字盤を追加
    • 角度を変更しながら、一定間隔に目盛を刻んでいく
    • 小さな目盛:6度づつ
    • 大きな目盛:30度づつ
int MARGIN = 20;

void setup() {
  size(300,300);
  stroke(255);
  smooth();
  frameRate(30);
}

void draw() {
  background(0);
  float s = second();
  float m = minute() + (s/60.0);
  float h = hour()%12 + (m/60.0);
  translate(width/2, height/2);
  rotate(radians(180));
  // 文字盤の表示
  pushMatrix();
  fill(128);
  noStroke();
  for(int i=0; i<60; i++){
    rotate(radians(6));
    ellipse(width/2-MARGIN,0,3,3);
  }
  for(int i=0; i<12; i++){
    rotate(radians(30));
    ellipse(width/2-MARGIN,0,10,10);
  }
  popMatrix();
  noFill();
  stroke(255);
  // 秒針
  pushMatrix();
  rotate(radians(s*(360/60)));
  strokeWeight(1);
  line(0,0,0,width/2-MARGIN);
  popMatrix();
  // 分針
  pushMatrix();
  rotate(radians(m*(360/60)));
  strokeWeight(2);
  line(0,0,0,width/2-MARGIN);
  popMatrix();
  // 時針
  pushMatrix();
  rotate(radians(h*(360/12)));
  strokeWeight(4);
  line(0,0,0,width/3-MARGIN);
  popMatrix();
}

アナログ時計 OOP版

int MARGIN = 20;
Clock myClock = new Clock();

void setup() {
  size(300,300);
  stroke(255);
  smooth();
  frameRate(30);
}

void draw() {
  background(0);
  myClock.getTime();
  myClock.draw();
}

class Clock {
  float s, m, h;
  Clock(){
  }
  void getTime(){
    s = second();
    m = minute() + (s/60.0);
    h = hour()%12 + (m/60.0);
  }
  void draw(){
    translate(width/2, height/2);
    rotate(radians(180));
    pushMatrix();
    fill(128);
    noStroke();
    for(int i=0; i<60; i++){
      rotate(radians(6));
      ellipse(width/2-MARGIN,0,3,3);
    }
    for(int i=0; i<12; i++){
      rotate(radians(30));
      ellipse(width/2-MARGIN,0,10,10);
    }
    popMatrix();
    noFill();
    stroke(255);
    pushMatrix();
    rotate(radians(s*(360/60)));
    strokeWeight(1);
    line(0,0,0,width/2-MARGIN);
    popMatrix();
    pushMatrix();
    rotate(radians(m*(360/60)));
    strokeWeight(2);
    line(0,0,0,width/2-MARGIN);
    popMatrix();
    pushMatrix();
    rotate(radians(h*(360/12)));
    strokeWeight(4);
    line(0,0,0,width/3-MARGIN);
    popMatrix();
  }
}

Processingで画像データを扱う

※こちらの内容は既に古い情報となっています。新しく書き直した記事を参照してください。

画像を表示する

  • Processingで画像を表示するためには、画像ファイルをそのスケッチのある階層に「data」フォルダを作成し、その中に画像ファイルを入れる
  • Jepg、GIF、PNG形式に対応している
  • 画像を表示するには、image() 関数を用いる
    • image(画像ファイル名, x, y); – 座標(x, y)を左上にして、画像を表示
    • image(画像ファイル名, x, y, width, height); – 座標(x, y)を左上にして、幅(width)と高さ(height)を指定して画像を表示

画像を表示

PImage img;
void setup() {
  size(640, 426);
  img = loadImage("photo.jpg");
}
void draw() {
  image(img, 0, 0);
}

位置とサイズを指定して画像を表示

PImage img;
void setup() {
  size(640, 426);
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  image(img, width/4, height/4, width/2, height/2);
}

画像の色や透明度を変更する

  • image() で表示した画像の、色や透明度を操作することができる
  • tint() 関数
    • tint(gray) – グレースケール画像のレベルを変更
    • tint(gray, alpha) – グレースケール画像のレベルと透明度を変更
    • tint(red, green, blue) – カラー画像のRGBの値を変更
    • tint(red, green, blue, alpha) – カラー画像のRGBの値と透明度を変更

画像の色を変更してみる

イメージのレベルを変更

PImage img;
void setup() {
  size(640, 426);
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  noTint();
  image(img, 0, 0);
  tint(102);
  image(img, width/2, 0);
}

イメージのカラーを変更

PImage img;
void setup() {
  size(640, 426);
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  noTint();
  image(img, 0, 0);
  tint(31, 127, 255);
  image(img, width/2, 0);
}

イメージの透明度(アルファ)を変更

PImage img;
void setup() {
  size(640, 426);
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  for(int i = 0; i < 8; i++) {
    int xpos = i * width / 8;
    fill(0);
    rect(xpos, 0, width, height);
    tint(255,255,255, i * 32);
    image(img, xpos, 0);
  }
}

ピクセルの情報を取得

  • 表示した画像の情報をピクセル単位取得することができる
  • 取得した画像の情報をもとに、より高度なイメージの操作、イメージの情報をもとにした表現を行うことが可能となる
  • ピクセル情報の取得には、get() 関数を用いる
  • get関数
    • get() – ウィンドウ内のピクセル全てを取得、イメージを返す
    • get(x, y) – 指定した座標、(x, y)のピクセルを取得、色の値を返す
    • get(x, y, width, hegiht) – 指定した座標、(x, y) から幅(width)、高さ(height)を指定してイメージを取得

get()を使用した例

マウスの位置のピクセルの色を取得 – 取得したRGBの値で左上に3つの正方形を描く

PImage img;
void setup() {
  size(640, 426);
  stroke(255, 102);
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  image(img, 0, 0);
  noStroke();
  fill(0);
  rect(20, 20, 60, 20);
  color c = get(mouseX, mouseY);
  fill(red(c), 0, 0);
  rect(20, 20, 20, 20);
  fill(0, green(c), 0);
  rect(40, 20, 20, 20);
  fill(0, 0, blue(c));
  rect(60, 20, 20, 20);
  stroke(255, 102);
  line(mouseX, 0, mouseX, height);
  line(0, mouseY, width, mouseY);
}

画像をモザイク化する

  • loadPixels() – 現在画面に表示されているピクセル情報を記録する
    • loadPixels() で格納したピクセルの色情報は、pixels[] 配列に格納される
  • pixels[] 配列を一定間隔で読みだすことで、画像をモザイク化する
PImage img;
int mosaicWidth = 30;
int mosaicHeight = 20;
void setup() {
  size(640, 426);
  noStroke();
  img = loadImage("photo.jpg");
}
void draw() {
  background(0);
  image(img, 0, 0);
  loadPixels();
  for(int j = 0; j < height; j+=mosaicHeight) {  
    for(int i = 0; i < width; i+=mosaicWidth) {  
      color c = pixels[j * width + i];
      fill(c);
      rect(i, j, mosaicWidth, mosaicHeight);
    }
  }
}
void mouseDragged() {
  mosaicWidth = mouseX / 4 + 10;
  mosaicHeight = mouseY / 4 + 10;
}

画像ファイルをスキャンする

  • loadPixels() を活用して、画像の特定のy座標の色だけをとりだして、バーコード状に並べる
PImage img;
void setup() {
  size(640, 426);
  stroke(255, 102);
  img = loadImage("photo.jpg");
  image(img, 0, 0);
  loadPixels();
}
void draw() {
  for(int i = 0; i < width; i++) {  
    color c = pixels[width * mouseY + i];
    stroke(c);
    line(i, 0, i, height);
  }
  stroke(255, 102);
  line(0, mouseY, width, mouseY);
}

フラクタルフォトモザイク

  • モザイクのひとつひとつが同じ写真になっている
  • モザイク状に配置した写真の明度をtintで変化させている
PImage img;
int mosaicWidth = 30;
int mosaicHeight = 20;
void setup() {
  size(640, 426);
  noStroke();
  background(0);
  img = loadImage("photo.jpg");
  image(img, 0, 0);
  loadPixels();
}
void draw() {
  for(int j = 0; j < height; j+=mosaicHeight) {  
    for(int i = 0; i < width; i+=mosaicWidth) {  
      color c = pixels[j * width + i];
      tint(red(c), green(c), blue(c));
      image(img, i, j, mosaicWidth, mosaicHeight);
    }
  }
}

画像をピクセレイト

  • 画像のピクセルの色情報をもとに、画像を再描画する – ピクセレイト

円の大きさと色で、画像を再構成する

PImage img;
int mosaicSize = 12;
void setup() {
  size(640, 426);
  noStroke();
  img = loadImage("photo.jpg");
  image(img, 0, 0);
  loadPixels();
}
void draw() {
  background(0);
  for(int j = 0; j < height; j+=mosaicSize) {  
    for(int i = 0; i < width; i+=mosaicSize) {  
      color c = pixels[j * width + i];
      fill(c, 127);
      ellipse(i, j, brightness(c)/6.0, brightness(c)/6.0);
    }
  }
}

四角形の大きさと角度で、画像を再構成する

PImage img;
int mosaicSize = 6;
void setup() {
  size(640, 426);
  noStroke();
  img = loadImage("photo.jpg");
  image(img, 0, 0);
  loadPixels();
  rectMode(CENTER);
  smooth();
}
void draw() {
  background(0);
  for(int j = 0; j < height; j+=mosaicSize) {  
    for(int i = 0; i < width; i+=mosaicSize) {  
      color c = pixels[j * width + i];
      fill(c, 127);
      pushMatrix();
      translate(i, j);
      rotate(brightness(c));
      rect(0, 0, brightness(c)/6.0, 2);
      popMatrix();
    }
  }
}

画像ファイルのデータを利用した表現

  • Jared Tarbell, box.fitting.imageの紹介
  • ソースファイルを解析してみる
  • 画像ファイルを入れかえてみる

サンプルファイルのダウンロード

今日とりあげた全てのサンプルは下記からダウンロードしてください。


Processingによる3Dプログラミング

今日の内容

Processingで3DCGプログラミング

  • 3Dの座標系
  • OpenGLとは
  • 2Dのアニメーションを3Dに拡張してみる
  • 3D座標で図形を描く
  • 視点の移動
  • 3Dのを用いた高度なアニメーション

コンピュータで3Dを表現するには

コンピュータ画面で3Dを表現したい

  • コンピュータのディスプレイは、2D (縦横に並んだピクセル)
  • 奥行は擬似的に表現するしかない
  • 画面に、立体や空間などの3次元の存在を投影して描画する = 3DCG (3次元グラフィックス)

2次元平面に3次元の存在を投影するには、様々な数学的な知識が必要

  • Processingではこうした演算を自動的に行うことが可能
  • 3次元の座標系をそのまま使用できる
  • 高速な表示のためのライブラリ(OpenGL)も標準で使用可


コンピュータ画面での3Dを表示する

3D空間の座標系

X (幅)、 Y (高さ) に加えて、奥行を表現する座標軸 Z が加わる。


3Dの座標系

3Dプログラミング基本

まず2Dの図形を回転するプログラムを作成してみる

  • translateで画面の中心を座標の原点に
  • 以下の処理を繰り返す
    • 背景描画
    • rotateで回転
    • rectを描画
    • 回転する角度を更新
float rot=0;
void setup(){
    size(400,400);
    frameRate(60);
    smooth();
    fill(63,127,255);
    stroke(255);
    //四角形を中心を原点に描画するモードに
    rectMode(CENTER);
}
void draw(){
    background(0);
    //画面の中心に原点(0,0)を移動する
    translate(width/2,height/2);
    //画面の中心を軸にrotだけ回転
    rotate(rot);
    //四角を描く
    rect(0,0,200,200);
    //角度を更新
    rot += 0.06;
}

現状の回転する四角形のプログラム

  • 2D的な視点では平面上で回転している、これを3D的な視点に変更してみる
  • 現状では、Z軸を中心軸としてXY平面上に置いてある物体が回転している
  • では軸をZ軸ではなく、他の軸(X軸、Y軸)にすると果してどうなるのか

回転する軸を指定してrotateする関数

rotateX(angle);  // X軸を中心に回転
rotateY(angle);  // Y軸を中心に回転軸を中心に回転
rotateZ(angle);  // Z軸を中心に回転軸を中心に回転

3D座標を用いたプログラミングをする際の注意点

レンダラー (描画の際の方式) を指定する必要がある

  1. P3D – Processing専用の3D描画エンジン
  2. OpenGL – 3Dグラフィックスのためのプログラムインターフェイス、高速に3D画像を描画できる

P3Dを使用する場合

  • size関数に以下の指定をする
size (幅, 高さ, P3D); // size関数にP3Dの指定

OpenGLを使用する場合

  • プログラムの先頭でOpenGLのライブラリを読み込み
  • size関数にOPENGLの指定をする
import processing.opengl.*; // OpenGLライブラリの読み込み
size(幅, 高さ, OPENGL);     // size関数にOPENGLの指定

OpenGLを使用した3次元空間での回転

x軸を中心に回転

import processing.opengl.*;
float rot=0;
void setup(){
    size(400,400,OPENGL);
    frameRate(60);
    fill(63,127,255);
    stroke(255);
    rectMode(CENTER);
}
void draw(){
    background(0);
    translate(width/2,height/2);
    //X軸を中心に回転させる
    rotateX(rot);
    rect(0,0,200,200);
    rot += 0.06;
}

x,y,z軸をそれぞれ回転

import processing.opengl.*;
// x, y, z それぞれの軸での回転角度
float rotX, rotY, rotZ;
void setup(){
    size(400,400,OPENGL);
    frameRate(60);
    fill(63,127,255);
    stroke(255);
    rectMode(CENTER);
}
void draw(){
    background(0,0,20);
    translate(width/2,height/2);
    //X軸を中心に回転
    rotateX(rotX);
    //Y軸を中心に回転
    rotateY(rotY);
    //Z軸を中心に回転
    rotateZ(rotZ);
    //四角形を描く
    rect(0,0,200,200);
    //それぞれの軸の回転角度を更新
    rotX += 0.02;
    rotY += 0.03;
    rotZ += 0.05;
}

3D図形の描画デモ

四角形を3D空間にタイル状に敷き詰める

  • for文を2重に入れ子にして、X,Y方向にグリッド状に四角形を敷きつめていく
  • ただし個々の四角形はすこしだけ斜めに傾けて立体感を強調する
  • マウスの位置によって、座標全体を回転してみる
  • まずは単純に描画してみる
import processing.opengl.*;
void setup() {
  size(400, 400, OPENGL);
  noStroke();
  fill(255, 190);
}
void draw() {
  background(0);
  translate(width / 2, height / 2, 0);
  //マウスの位置で座標全体を回転する
  rotateX(mouseX / 200.0);
  rotateY(mouseY / 100.0);
  //四角形描画を中心を原点に
  rectMode(CENTER);
  //敷きつめるタイルの一片の長さ
  int dim = 18;
  //XY平面を正方形でタイリング
  for (int i = -height / 2; i < height / 2; i += dim) {
    for (int j = -width / 2; j < width / 2; j += dim) {
      pushMatrix();
      translate(i, j);
      rotateX(radians(30));
      rotateY(radians(30));
      rect(0, 0, dim, dim);
      popMatrix();
    }
  }
}

ライティング

Processinには様々なライティングの方法を用いることができる

  • ambientLight() – 環境光
  • directionalLight() – 一定方向から差し込む平行光
  • pointLight() – 点光源
  • spotLight() – スポットライト

様々な光を活用しながら、複雑なライティングをしてみる

import processing.opengl.*;
void setup() {
  size(400, 400, OPENGL);
  noStroke();
  fill(255, 190);
}
void draw() {
  //環境光
  ambientLight(63, 31, 31);
  //平行光
  directionalLight(255, 255, 255, -1, 0, 0);
  //点光源
  pointLight(63, 127, 255, mouseX, mouseY, 200);
  //スポットライト
  spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
  background(0);
  translate(width / 2, height / 2, -20);
  rotateX(mouseX / 200.0);
  rotateY(mouseY / 100.0);
  int dim = 18;
  for (int i = -height / 2; i < height / 2; i += dim * 1.4) {
    for (int j = -width / 2; j < width / 2; j += dim * 1.4) {
      pushMatrix();
      translate(i, j);
      rotateX(radians(30));
      rotateY(radians(30));
      box(dim, dim, dim);
      popMatrix();
    }
  }
}

カメラ

Processingでは何も指定していない時にはカメラ(視点)の場所は、中心点(0, 0, 0)。しかし、camera() 関数を使用することで、視点をコントロールすることができる

camera() 関数

  • camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
    • eyeX, eyeY, eyeZ – 視点の位置
    • centerX, centerY, centerZ – 注視する中心位置
    • upX, upY, upZ – カメラの向き

マウスの位置でカメラの位置をコントロールしてみる

import processing.opengl.*;
void setup() {
  size(400, 400, OPENGL);
  noStroke();
  fill(255, 190);
}
void draw() {
  background(0);
  ambientLight(63, 31, 31);
  directionalLight(255, 255, 255, -1, 0, 0);
  pointLight(63, 127, 255, mouseX, mouseY, 200);
  spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
  //カメラを定義、マウスの位置でカメラの位置が変化する
  camera(mouseX, mouseY, 200, width / 2.0, height / 2.0, 0, 0, 1, 0);
  translate(width / 2, height / 2, -20);
  int dim = 18;
  for (int i = -height / 2; i < height / 2; i += dim * 1.4) {
    for (int j = -width / 2; j < width / 2; j += dim * 1.4) {
      pushMatrix();
      translate(i, j);
      rotateX(radians(30));
      rotateY(radians(30));
      box(dim, dim, dim);
      popMatrix();
    }
  }
}

3D空間でのアニメーション

少しずつスピードを変化させながら回転する立方体を作成してみる

import processing.opengl.*;
float a;
int NUM = 128;
float offset = PI / NUM;
color[] colors = new color[NUM];
void setup() {
  size(400, 400, OPENGL);
  noStroke();
  colorMode(HSB, 360, 100, 100, 100);
  frameRate(30);
  for (int i = 0; i < NUM; i++) {
    colors[i] = color(i * 2 + 100, 70, 100, 25);
  }
  lights();
}
void draw() {
  background(0);
  ambientLight(63, 31, 31);
  directionalLight(255, 255, 255, -1, 0, 0);
  pointLight(63, 127, 255, mouseX, mouseY, 200);
  spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
  translate(width / 2, height / 2, -20);
  rotateX(mouseX / 200.0);
  rotateY(mouseY / 100.0);
  for (int i = 0; i < NUM; i++) {
    pushMatrix();
    fill(colors[i]);
    rotateY(a + offset * i);
    rotateX(a / 2 + offset * i);
    rotateZ(a / 3 + offset * i);
    box(width / 2);
    popMatrix();
  }
  a += 0.01;
}

サンプルファイルのダウンロード

今日とりあげた全てのサンプルは下記からダウンロードしてください。


Processingによる高度なモーション:バネの動きを作る

今日の内容

  • 高度なアニメーション表現を身につける
  • 物理法則を用いたアニメーションの表現
    • ばね
    • 万有引力

「ばね」を定義する

「ばね」を数学的に定義する

  • 「ばね」を数学的に定義する
  • フックの法則
    • バネの伸びが x のとき、それによって生じる力を F とすると
    • F = -kx ( k:ばね定数)


「ばね」の公式

  • フックの定理は以下のように書き直すことができる
    • springForce = -stiffness * stretch
    • バネの力 = -バネの硬さ x バネの伸び
  • さらに「バネの伸び」の部分を解釈する
    • springForce = -stiffness * (position – restPosition)
    • バネの力 = -バネの硬さ x (現在位置 – ばねの静止位置)
  • 式を整理
    • springForce = stiffness * (restPositon – position)
    • バネの力 = バネの硬さ x (静止位置 – 現在位置)
  • この時、ばねの移動速度は下記のように表現される
    • velocity = friction * (velocity + springFroce)
    • 速度 = 摩擦 * (速度 + バネの力)
  • この式を利用して、簡単なアニメーションをProcessingで作成してみる

ばね1:シンプルな「ばね」

  • 「ばね」の公式(フックの法則)を利用して、シンプルな「ばね」の動きをプログラミングしてみる
float stiffness = 0.1; //ばねの強度
float damping = 0.9; //摩擦
float velocity = 0.0; //速度
float targetY; //目標位置
float y; //現在位置

void setup() {
    size(400, 400);
    noStroke();
}

void draw() {
    fill(0, 12);
    rect(0, 0, width, height);
    fill(255);
    float force = stiffness * (targetY - y); //フックの法則 f = -kx
    velocity = damping * (velocity + force); //速度を計算
    y += velocity; //速度分を座標に
    rect(10, y, width - 20, 10); //四角を描画
    targetY = mouseY; //マウスのY座標を目標位置に
}

ばね2:重さ(mass)を付加

  • もうひとつの要素を付加する – バネに付いている物体の重さ(mass)
  • 重さと力の関係はニュートンの運動の第二法則から導きだされる
    • F = ma
    • F:力、m:質量、a:加速度
  • 式を変換
    • a = F / m
  • これを「ばね」にあてはめると、下記のように解釈できる
    • acceleration = springForce / mass
    • 加速 = バネの力 / 重さ
  • 先程のプログラムに重さの要素を付加する
  • 重さの概念を追加した「ばね」
//2つの重さの異なる物体で、ばね運動を作成
float y1, y2; //現在位置
float velocity1, velocity2; //速度
float mass1 = 1.0; //物体1の重さ
float mass2 = 6.0; //物体2の重さ
float stiffness = 0.1; //バネの剛性
float damping = 0.9; //摩擦係数

void setup() {
    size(400, 400);
    noStroke();
}

void draw() {
    fill(0, 12);
    rect(0, 0, width, height);
    fill(255);
    float targetY = mouseY; //目標位置
    //物体1
    float forceA = stiffness * (targetY - y1); //フックの法則
    float accelerationY1 = forceA / mass1; //重さから加速度を算出
    velocity1 = damping * (velocity1 + accelerationY1); //加速度を加味して速度を計算
    y1 += velocity1; //位置を算出
    rect(0, y1, width/2, 15);
    //物体2   
    float forceB = stiffness * (targetY - y2); //フックの法則
    float accelerationY2 = forceB / mass2; //重さから加速度を算出
    velocity2 = damping * (velocity2 + accelerationY2);//加速度を加味して速度を計算
    y2 += velocity2; //位置を算出
    rect(width/2, y2, width/2, 15);
}

ばね3:2次元座標に展開、重力を付加

  • Y軸方向だけでなくX軸方向にも動けるように
    • 重力(gravity)の要素も付加
    • 常に一定の力をY軸方向に付加する
float stiffness = 0.05;
float damping = 0.9;
float mass = 3.0;
float gravity = 0.0;
float velocityX = 0.0, velocityY = 0.0;
float targetX, targetY;
float x, y;

void setup() {
    size(600, 600);
    smooth();

}

void draw() {
    background(0);

    float forceX = stiffness * (targetX - x);
    float accelerationX = forceX / mass;
    velocityX = damping * (velocityX + accelerationX);
    x += velocityX;

    float forceY = stiffness * (targetY - y);
    forceY += gravity;
    float accelerationY = forceY / mass;
    velocityY = damping * (velocityY + accelerationY);
    y += velocityY;

    noStroke();
    fill(255);
    ellipse(x, y, 40, 40);

    stroke(127);
    noFill();
    line(mouseX, mouseY, x, y);

    targetX = mouseX;
    targetY = mouseY;
}

ばね4:「ばね」をクラスとして定義する

  • ばねの動きを汎用的に使えるようにクラスとして定義する
    • Spring2Dクラス
  • メインプログラム
//Spring2Dクラスを利用して、ばねの動きを生成
Spring2D s1, s2;
float gravity = 15.0;
float mass = 2.0;

void setup() {
    size(400, 400);
    smooth();
    fill(0);
    //入力:x座標, y座標, 摩擦, 重力
    s1 = new Spring2D(0.0, width / 2, mass, gravity);
    s2 = new Spring2D(0.0, width / 2, mass, gravity);
}

void draw() {
    background(204);
    s1.update(mouseX, mouseY);
    s1.display(mouseX, mouseY);
    s2.update(s1.x, s1.y);
    s2.display(s1.x, s1.y);
}
  • 「ばね」を定義するクラス:Spring2D
class Spring2D {
    float vx, vy;
    float x, y;
    float gravity;
    float mass;
    float radius = 10;
    float stiffness = 0.2;
    float damping = 0.7;
    //コンストラクタ
    Spring2D(float xpos, float ypos, float m, float g) {
    x = xpos;
    y = ypos;
    mass = m;
    gravity = g;
    }
    //位置を計算
    void update(float targetX, float targetY) {
    float forceX = (targetX - x) * stiffness;
    float ax = forceX / mass;
    vx = damping * (vx + ax);
    x += vx;
    float forceY = (targetY - y) * stiffness;
    forceY += gravity;
    float ay = forceY / mass;
    vy = damping * (vy + ay);
    y += vy;
    }
    //表示
    void display(float nx, float ny) {
    noStroke();
    ellipse(x, y, radius*2, radius*2);
    stroke(255);
    line(x, y, nx, ny);
    }
}

ばね5:ばねを数珠繋ぎにしてみる

  • Spring2Dクラスで定義した「ばね」を直列に繋いでいく
int numSprings = 10;
Spring2D[] s = new Spring2D[numSprings];
float gravity = 5.0;
float mass = 5.0;
float stiffness = 0.2;
float damping = 0.8;

void setup() {
    size(600, 600);
    smooth();
    fill(0);
    for (int i = 0; i < numSprings; i++) {
    s[i] = new Spring2D(width / 2, i*(height / numSprings), mass, gravity, stiffness, damping);
    }
}

void draw() {
    background(204);
    s[0].update(mouseX, mouseY);
    s[0].display(mouseX, mouseY);
    for (int i = 1; i < numSprings; i++) {
    s[i].update(s[i-1].x, s[i-1].y);
    s[i].display(s[i-1].x, s[i-1].y);
    }
}


Processingでオブジェクト指向プログラミング (2)

Processingでオブジェクト指向プログラミング (2)

前回の復習

  • オブジェクト指向プログラミング (= OOP)のポイント

オブジェクト指向プログラムのポイント:その1

  • 「もの (= オブジェクト)」という単位でプログラムを構成する
  • オブジェクトは、プロパティー(性質)とメソッド(動作・ふるまい)から構成

オブジェクト指向プログラムのポイント:その2

  • オブジェクト同士がメッセージを送りあう

オブジェクト指向プログラムのポイント:その3

  • オブジェクトはメッセージを受け取り、それに応じた処理を行う
  • メッセージの処理方法は、オブジェクト自身が知っていて、その処理はオブジェクトによって異なる (ポリモーフィズム)

オブジェクト指向プログラムのポイント:その4

  • 必要のない情報は隠す (カプセル化)
  • プログラムの実装全てを知る必要はない
  • 必要なインターフェイス(接点)だけ見せて、あとは隠す

オブジェクト指向プログラムのポイント:その5

  • オブジェクトから新たなオブジェクトを派生させることができる (継承)

オブジェクト指向プログラムのポイント:その6

  • クラスとは:オブジェクトの「型紙」
  • クラスをインスタンス化 (実体化) することでインスタンス(オブジェクト)となる

先週の内容の確認

  • 円を描くクラス Spot を作成
  • クラスを宣言、クラスをインスタンス化
  • 円のプロパティを定義 – x, y, diameter
  • 円を描画するメソッドを定義 – display()
  • コンストラクタを定義 – Spot(float x, float y, float diameter)
  • 動きを追加する – move();
  • 複数のインスタンスの生成 – sp1, sp2, sp3 …
  • インスタンスの配列を生成 – Spot[] spots

  • 今日の授業は先週のつづきから始めます
  • 先週の最終的なプログラムをダウンロード

[code language=”java”]
int numSpots = 400;

Spot[] spots = new Spot[numSpots];

void setup() {
size(800, 600);
noStroke();
smooth();
frameRate(60);
for (int i = 0; i < spots.length; i++) {
spots[i] = new Spot(random(0,width), height/2, random(5,40), random(-4,4));
}
background(0);
}

void draw() {
fill(0,0,0,10);
rect(0, 0, width, height);
fill(255);
for (int i = 0; i < spots.length; i++) {
spots[i].move();
spots[i].display();
}
}

class Spot {
float x, y;
float diameter;
float speed;

//コンストラクター
Spot(float _x, float _y, float _diameter, float _speed) {
x = _x;
y = _y;
diameter = _diameter;
speed = _speed;
}

void move() {
y += speed;
if ((y > (height – diameter / 2)) || (y < diameter / 2)) {
speed *= -1;
}
}

void display() {
fill(31,127,255,63);
ellipse(x, y, diameter, diameter);
}
}
[/code]

より複雑な動きを作成する

  • Spotクラスの再設計
  • 円は上下だけでなく、左右にも移動するように変更

  • Spotクラスの再設計
  • 必要となる属性
    • x座標、y座標 → float x, y;
    • x方向の速度、y方向の速度 → float vx, vy;
    • 大きさ → float diameter
  • 必要となるメソッド
    • コンストラクタ → Spot
    • 座標の更新 → update
    • 画面に表示 → display
  • Spotクラス、クラス図を作成

– 設計したクラスをもとにプログラムを作成

[code language=”java”]
int numSpots = 400;

Spot[] spots = new Spot[numSpots];

void setup() {
size(800, 600);
noStroke();
smooth();
frameRate(60);
for (int i = 0; i < spots.length; i++) {
color col = color(random(255),random(255),random(255),63);
spots[i] = new Spot(random(0,width), height/2, random(5,40), random(-4,4),col);
}
background(0);
}

void draw() {
fill(0,0,0,10);
rect(0, 0, width, height);
fill(255);
for (int i = 0; i < spots.length; i++) {
spots[i].move();
spots[i].display();
}
}

class Spot {
float x, y;
float diameter;
float speed;
color col;

Spot(float _x, float _y, float _diameter, float _speed, color _col) {
x = _x;
y = _y;
diameter = _diameter;
speed = _speed;
col = _col;
}

void move() {
y += speed;
if ((y > (height – diameter / 2)) || (y < diameter / 2)) {
speed *= -1;
}
}

void display() {
fill(col);
ellipse(x, y, diameter, diameter);
}
}
[/code]

  • 完成した動き

クラスの継承:重力を付加

  • さらにリアルな動きを追求してみる
  • 重力の概念を適用してみる
  • 移動中は、常に重力の影響を受けつづける

  • Spotクラスはそのままで、継承を使用してクラスを拡張する
  • 親クラスの性質を引き継いで、別の性質を付加した子クラスを生成
  • Spotクラス (親クラス)
    • 上下左右に移動する円
    • 画面の端でバウンドする
  • GravitySpot (子クラス)
    • Spotクラスを継承
    • つねに一定の重力の影響を受け続ける – 重力 → float gravity
  • クラスを継承する際の、クラス定義のやりかた
    • 何のクラスを継承したかを記述する
    • class 子クラス extends 親クラス {….}
    • 例:GravitySpotクラスが、Spotクラスを継承する場合
      • class GravitySpot extends Spot {…}
  • 子クラスから、親クラスのコンストラクタを呼びだす方法
    • super(引数1, 引数2, 引数3…);
    • 例:GravitySpotからSpotのコンストラクタを設定
      • super(_x, _y, _vx, _vy, _diameter);
  • 子クラスから親クラスのメソッドを呼び出す
    • super.メソッド名(引数1, 引数2, 引数3…);
    • 例:GravitySpotからSpotのmove()を呼び出す
      • super.move();

  • 設計したクラスをもとにプログラムを作成

[code language=”java”]
int numSpots = 400;

GravitySpot[] spots = new GravitySpot[numSpots];

void setup() {
size(800, 600);
noStroke();
smooth();
frameRate(60);
for (int i = 0; i < spots.length; i++) {
spots[i] = new GravitySpot(width/2,height/4,random(-4,4),random(-4,4),random(2,20));
}
background(0);
}

void draw() {
fill(0,0,0,10);
rect(0, 0, width, height);
fill(51,127,255,63);
for (int i = 0; i < spots.length; i++) {
spots[i].move();
spots[i].display();
}
}

class Spot {
float x, y;
float diameter;
float vx;
float vy;

Spot(float _x, float _y, float _vx, float _vy, float _diameter) {
x = _x;
y = _y;
vx = _vx;
vy = _vy;
diameter = _diameter;
}

void move() {
x += vx;
if ((x > (width – diameter / 2)) || (x < diameter / 2)) {
vx *= -1;
}
x = constrain(x, diameter/2, width – diameter/2);

y += vy;
if ((y > (height – diameter / 2)) || (y < diameter / 2)) {
vy *= -1;
}
y = constrain(y, diameter/2, height – diameter/2);

}

void display() {
ellipse(x, y, diameter, diameter);
}
}

class GravitySpot extends Spot{
float gravity = 0.1;
GravitySpot(float _x, float _y, float _vx, float _vy, float _diameter){
super(_x, _y, _vx, _vy, _diameter);
}

void move(){
vy += gravity;
super.move();
}

void display(){
super.display();
}
}
[/code]

クラスの継承2:摩擦力を付加

  • 重力の概念に加えて、摩擦力の概念を加える
    • 摩擦 → 移動方向と反対方向にかかる力、空気抵抗など
    • 移動中は、重力に加えて常に摩擦力が働くようにする

  • 改良版、GravitySpot
    • 重力 (gravity) に加えて、摩擦力 (friction) を付加
    • x, y方向のスピードそれぞれに一定の値をかけ算する
    • frictoin ≦ 1.0;
    • frictionが1.0のときは摩擦なし
    • 値が小さくなるほど、大きな摩擦力が働く
  • 摩擦(friction)をGravitySpotのプロパティに追加

  • 設計したクラスをもとにプログラムを作成

[code language=”java”]
int numSpots = 400;

GravitySpot[] spots = new GravitySpot[numSpots];

void setup() {
size(800, 600);
noStroke();
smooth();
frameRate(60);
for (int i = 0; i < spots.length; i++) {
spots[i] = new GravitySpot(width/2,height/4,random(-20,20),random(-20,20),random(2,20));
}
background(0);
}

void draw() {
fill(0,0,0,10);
rect(0, 0, width, height);
fill(51,127,255,63);
for (int i = 0; i < spots.length; i++) {
spots[i].move();
spots[i].display();
}
}

class Spot {
float x, y;
float diameter;
float vx;
float vy;

//コンストラクター
Spot(float _x, float _y, float _vx, float _vy, float _diameter) {
x = _x;
y = _y;
vx = _vx;
vy = _vy;
diameter = _diameter;
}

void move() {
x += vx;
if ((x > (width – diameter / 2)) || (x < diameter / 2)) {
vx *= -1;
}
x = constrain(x, diameter/2, width – diameter/2);

y += vy;
if ((y > (height – diameter / 2)) || (y < diameter / 2)) {
vy *= -1;
}
y = constrain(y, diameter/2, height – diameter/2);

}
void display() {
ellipse(x, y, diameter, diameter);
}
}

class GravitySpot extends Spot{
float gravity = 0.1;
float friction = 0.995;
GravitySpot(float _x, float _y, float _vx, float _vy, float _diameter){
super(_x, _y, _vx, _vy, _diameter);
}

void move(){
vy += gravity;
vx *= friction;
vy *= friction;
super.move();
}

void display(){
super.display();
}
}
[/code]

重力と摩擦力を付加した動き (摩擦力 = 0.995)

クラスの継承3:色を指定

  • さらに色を指定できるようにする
  • GravitySpotのプロパティーに色の情報を追加 → color col

  • 設計したクラスをもとにプログラムを作成

[code language=”java”]
int numSpots = 400;
GravitySpot[] spots = new GravitySpot[numSpots];

void setup() {
size(800, 600);
colorMode(HSB, 360, 100, 100, 100);
noStroke();
smooth();
frameRate(60);
for (int i = 0; i < spots.length; i++) {
color col = color(random(120,240),80,80,50);
spots[i] = new GravitySpot(width/2,height/4,random(-10,10),random(-10,0),random(2,20),col);
}
background(0);
}

void draw() {
fill(0,0,0,10);
rect(0, 0, width, height);
fill(51,127,255,63);
for (int i = 0; i < spots.length; i++) {
spots[i].move();
spots[i].display();
}
}

class Spot {
float x, y;
float diameter;
float vx;
float vy;

Spot(float _x, float _y, float _vx, float _vy, float _diameter) {
x = _x;
y = _y;
vx = _vx;
vy = _vy;
diameter = _diameter;
}

void move() {
x += vx;
if ((x > (width – diameter / 2)) || (x < diameter / 2)) {
vx *= -1;
}
x = constrain(x, diameter/2, width – diameter/2);

y += vy;
if ((y > (height – diameter / 2))) {
vy *= -1;
}
y = constrain(y, -height, height – diameter/2);

}
void display() {
ellipse(x, y, diameter, diameter);
}
}

class GravitySpot extends Spot{
float gravity = 0.1;
float friction = 0.998;
color col;
GravitySpot(float _x, float _y, float _vx, float _vy, float _diameter, color _col){
super(_x, _y, _vx, _vy, _diameter);
col = _col;
}

void move(){
vy += gravity;
vx *= friction;
vy *= friction;
super.move();
}

void display(){
fill(col);
super.display();
}
}
[/code]

  • 色を指定したバージョン


Processingでオブジェクト指向プログラミング (1)

今日の内容

  • オブジェクト指向でProcessingのプログラムを作る
    • そもそもオブジェクト指向とは?
    • 簡単なプログラムを、オブジェクト指向で書いてみる
    • クラスの定義
    • クラスの呼びだし

オブジェクト指向プログラミング (= OOP) について

OOP理解のポイント

  • OOP = 難しい?
  • OOPの難しげな用語たち
    • オブジェクト
    • メソッド
    • メッセージ
    • 継承
    • 抽象化
    • カプセル化
    • ポリモーフィズム (多態性)
    • クラス
    • インスタンス化
  • 言葉だけだとかなり難しそう…
  • 本質を理解すると、実はやっていることは単純

プログラミング・パラダイムの変遷

  • OOP以前:その1
    • 非構造化ログラミング
    • 全てのコードが一つの連続したブロックに含まれている
    • アセンブリ言語・COBOL・BASICなど
    • goto文による制御
    • デバッグが難しい
    • 「スパゲティプログラム」
  • OOP以前:その2
    • 手続型プログラミング
    • 手続を呼び出す
    • サブルーチン、関数
    • C言語・FORTRAN・Pascal など
    • 3つの基本制御パターン
      • 逐次処理
      • 分岐
      • 繰り返し

機能中心のソフトウェア開発の問題点

  • 機能、手続きを中心に構成されたプログラムの問題点
    • プログラムの規模が大きくなるにつれて全体像の把握が難しい
    • 機能の追加・拡張・変更が難しい
    • コードの再利用が難しい – 規模が大きくなるにつれ、プログラミングにかかるコストが膨大に
  • これらの欠点を改良するため、新たなパラダイムが考案される
  • オブジェクト指向プログラミング (OOP)
    • Simula, Smalltalk, C++, Objective-C, Java, Python, Ruby, JavaScript, C# …
    • 「もの ( = オブジェクト)」の集まりとしてプログラムを構成する

オブジェクト指向プログラミングの概念

  • オブジェクト指向プログラムのポイント:その1
    • 「もの (= オブジェクト)」という単位でプログラムを構成する
    • オブジェクトは、プロパティー(性質)とメソッド(動作・ふるまい)から構成
  • 例:「りんご」をオブジェクトとして考える
  • オブジェクト指向プログラムのポイント:その2
    • オブジェクト同士がメッセージを送りあう
  • オブジェクト指向プログラムのポイント:その3
    • オブジェクトはメッセージを受け取り、それに応じた処理を行う
    • メッセージの処理方法は、オブジェクト自身が知っていて、その処理はオブジェクトによって異なる (ポリモーフィズム、多態性)
  • オブジェクト指向プログラムのポイント:その4
    • 必要のない情報は隠す (カプセル化)
    • プログラムの実装全てを知る必要はない
    • 必要なインターフェイス(接点)だけ見せて、あとは隠す
  • オブジェクト指向プログラムのポイント:その5
    • オブジェクトから新たなオブジェクトを派生させることができる (継承)

クラス

  • クラスとは:オブジェクトの「型紙」
  • クラスをインスタンス化 (実体化) することでインスタンス(オブジェクト)となる

Processingでオブジェクト指向プログラミング

円を描く:いままでの方法

  • まずはいままでのやり方で円を描いてみる
int x = 150;
int y = 200;
int diameter = 100;

void setup() {
  size(400, 400);
  smooth();
  noStroke();
}

void draw() {
  background(0);
  ellipse(x, y, diameter, diameter);
}

円を描く:クラスを宣言、属性を定義

  • 円を生成するクラスを宣言する
    • クラス名:Spot
    • クラスのプロパティー
    • float x → 円のx座標
    • float y → 円のy座標
    • float diameter → 円の直径
  • UML(統一モデリング言語)のクラス図で上の情報を記述してみる
    • クラス図:クラス、属性、クラス間の関係からシステムの構造を記述する静的な構造図
    • クラスの属性の手前の記号の意味
    • “+”:public クラスの外部から参照可能
    • “-“:private クラスの外部からは隠蔽される(カプセル化)
Spot sp; // オブジェクトを宣言

void setup() {
  size(400, 400);
  smooth();
  noStroke();
  sp = new Spot(); //オブジェクトの生成
  sp.x = 150; // Xの値に150を設定
  sp.y = 200; // Yの値に200を設定
  sp.diameter = 100; // 直径の値を100に設定
}

void draw() {
  background(0);
  ellipse(sp.x, sp.y, sp.diameter, sp.diameter);
}

class Spot {
  float x, y; // X座標とY座標
  float diameter; // 円の直径
}

円を描く:メソッドを定義

  • Spotクラスの「動作 (= メソッド)」を定義する
    • メソッドはクラス内で関数として表現される
    • 必要となるメソッド
    • 円を描く
      • メソッド名 – void display();
Spot sp; //オブジェクトを宣言

void setup() {
  size(400, 400);
  smooth();
  noStroke();
  sp = new Spot(); // オブジェクトを生成
  sp.x = 150;
  sp.y = 200;
  sp.diameter = 100;
}

void draw() {
  background(0);
  sp.display();
}

class Spot {
  float x, y, diameter;
  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

円を描く:コンストラクタを定義

  • コンストラクタ (構築子、Constructor) とは
    • オブジェクトをインスタンス化する際に呼び出されて、内容の初期化などを行う関数
    • クラス名と同一の名前を持つメソッドとして定義される
    • 戻り値は持たない
    • コンストラクタに渡す引数により初期化のバリエーションが定義される
  • 例:Spotクラスのコンストラクタ
    • Spot( 引数1, 引数2, 引数3 …) { }
  • Spotクラスの引数に以下の2つのパラメータを渡せるようにする
    • ofPoint pos;
    • float diameter;
  • コンストラクタをクラス図に追加
Spot sp; // オブジェクトの宣言

void setup() {
  size(400, 400);
  smooth();
  noStroke();
  sp = new Spot(150, 200, 100); // オブジェクトを生成
}

void draw() {
  background(0);
  sp.display();
}

class Spot {
  float x, y, diameter;

  Spot(float xpos, float ypos, float dia) {
    x = xpos; // xに150を設定
    y = ypos; // yに200を設定
    diameter = dia; // 直径に100を設定
  }

  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

円を描く:動きを追加する

  • 円をアニメーションさせてみる
  • メソッドを1つと、プロパティーを1つ追加
  • move() – 円の座標を更新する
  • クラスSpotのプロパティーに円のスピードを追加 – float speed
  • コンストラクタの引数に円のスピードを追加 – float sp
Spot sp; // オブジェクトを宣言

void setup() {
  size(400, 400);
  smooth();
  noStroke();
  sp = new Spot(150, 200, 100, 3); // オブジェクトを生成
}

void draw() {
  fill(0, 15);
  rect(0, 0, width, height);
  fill(255);
  sp.move();
  sp.display();
}

class Spot {
  float x, y;
  float diameter;
  float speed; // 円の動きのスピード
  int direction = 1; // 動きの向き (1 降下, -1 上昇)

  // コンストラクター
  Spot(float xpos, float ypos, float dia, float sp) {
    x = xpos;
    y = ypos;
    diameter = dia;
    speed = sp;
  }

  void move() {
    y += (speed * direction);
    if ((y > (height - diameter / 2)) || (y < diameter / 2)) {
      direction *= -1;
    }
  }

  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

円を描く:複数の物体を動かす 1 – 複数のインスタンスの生成

  • オブジェクトは、変数と同様に名前を変えて宣言することで複数宣言することが可能
  • インスタンス化の際の引数を変化させることで、様々なバリエーションのSpotオブジェクトを同時に表示することができる
  • Spotクラスから3つのオブジェクトを生成
    • sp1
    • sp2
    • sp3
Spot sp1, sp2, sp3; //3つのオブジェクトを宣言
void setup() {
  size(400, 400);
  smooth();
  noStroke();
  sp1 = new Spot(100, 200, 80, 2); //sp1を生成
  sp2 = new Spot(200, 200, 20, 5); //sp2を生成
  sp3 = new Spot(300, 50, 60, 3); //sp3を生成
}

void draw() {
  fill(0, 15);
  rect(0, 0, width, height);
  fill(255);
  sp1.move();
  sp2.move();
  sp3.move();
  sp1.display();
  sp2.display();
  sp3.display();
}

class Spot {
  float x, y;
  float diameter;
  float speed;
  int direction = 1;
  Spot(float xpos, float ypos, float dia, float sp) {
    x = xpos;
    y = ypos;
    diameter = dia;
    speed = sp;
  }
  void move() {
    y += (speed * direction);
    if ((y > (height - diameter / 2)) || (y < diameter / 2)) {
      direction *= -1;
    }
  }
  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

円を描く:複数の物体を動かす 2 – インスタンスの配列を生成

  • インスタンスの命名では、大量のインスタンスを生成することは難しい
    • 例:100コの円を同時に生成
  • インスタンスを配列で管理すると、とても便利
    • 配列を宣言
    • 配列を生成
    • それぞれのインスタンスを配列に格納
int numSpots = 20;

Spot[] spots = new Spot[numSpots];

void setup() {
  size(400, 400);
  smooth();
  noStroke();
  for (int i = 0; i < spots.length; i++) {
    float x = 10 + i * 20;
    float rate = 2.0 + i * 0.1;
    spots[i] = new Spot(x, 50, 16, rate);
  }
}

void draw() {
  fill(0, 12);
  rect(0, 0, width, height);
  fill(255);
  for (int i = 0; i < spots.length; i++) {
    spots[i].move(); // Move each object
    spots[i].display(); // Display each object
  }
}

class Spot {
  float x, y; // X-coordinate, y-coordinate
  float diameter; // Diameter of the circle
  float speed; // Distance moved each frame
  int direction = 1; // Direction of motion (1 is down, -1 is up)

  // Constructor
  Spot(float xpos, float ypos, float dia, float sp) {
    x = xpos;
    y = ypos;
    diameter = dia;
    speed = sp;
  }

  void move() {
    y += (speed * direction);
    if ((y > (height - diameter / 2)) || (y < diameter / 2)) {
      direction *= -1;
    }
  }

  void display() {
    ellipse(x, y, diameter, diameter);
  }
}

サンプルファイルのダウンロード

今日の授業でとりあげた、全てのプログラムは下記からダウンロードできます。


アニメーション、条件分岐

アニメーション

アニメーションをするには

  • プログラミングの構造化が必要となる
  • setup:初期化関数
  • draw:メインループ関数

Processingのプログラム構造のイメージ

setup:初期化関数

  • プログラムの最初に、1回だけ実行される処理を記述、アニメーションの前準備
  • setupの中で行われることの多い処理
  • size:画面のサイズを設定
  • colorMode:カラーモードを設定
  • frameRate:画面の書き換え速度を設定

draw:メインループ関数

  • プログラムが終了するか、noLoop()関数が呼ばれるまでは、プログラムを実行し続ける
  • ループの中で図形の場所や色、形を操作してアニメーションにする
  • 画面の書き換え頻度はsetup()関数内のframerate()関数で設定する

構造化されたプログラムの例:増殖する円

[code language=”java”]
//setup – 初期化関数
void setup()
{
size(480,480);
//書き換え頻度の設定
frameRate(12);
//円弧の描画をなめらかに
smooth();
//背景を最初に描く
background(0);
}

//draw – メインループ
void draw()
{
//円の直径をランダムに設定
float diameter = random(100);
noStroke();
//塗りの色をランダムに
fill(random(255),random(255),random(255));
//円を描画
ellipse(random(width),random(height),diameter,diameter);
}
[/code]

アニメーション作成の考えかた

  • setup関数で全体に共通の設定を初期化
  • draw関数を繰り返す
  • 背景の描画
  • 図形を描く
  • パラメータ(場所、色など)を微妙に変更

アニメーションの例1:円を動かしてみる

[code language=”java”]
//グローバル関数の定義 (x座標、y座標)
int x, y;

//初期化関数
void setup(){
//画面サイズの設定
size(640,480);
//フレームレート(書き換え頻度)設定
frameRate(30);
//色の設定
colorMode(HSB,360,100,100,100);
noStroke();
fill(200,80,80);
//初期位置の座標を設定
x = 0;
y = 0;
}

//メインループ
void draw(){
//背景の再描画
background(0);
//円の描画
ellipse(x,y,50,50);
//x座標更新
x = x+1;
//y座標更新
y = y+1;
}
[/code]

円が画面の幅(width)と高さ(height)から、はみ出ないようにするには?

  • 方法:画面の幅 (width) を変化する変数 x の場合
  • x を 画面の幅(width)で割った余りを、円のx軸上の位置にする
  • 画面の幅からはみ出た場合は、また0に戻る
  • y は 画面の高さ(height)の余りをとる
  • よって以下のようにする
  • x座標の位置 = x % width;
  • y座標の位置 = y % height;
  • % : 余りを計算する演算子
  • 例:5 % 2 = 1 (5/4 = 2 余り1 だから)
  • アニメーションの例1:円を動かしてみる – 画面からはみ出さないようにしたバージョン

[code language=”java”]
//グローバル関数の定義 (x座標、y座標)
int x=0, y=0;
//初期化関数
void setup()
{
size(640,480);
frameRate(30);
colorMode(HSB,360,100,100,100);
noStroke();
fill(200,80,80);
}

//メインループ
void draw()
{
background(0);
//画面からはみ出さないように座標を計算
ellipse(x%width,y%height,50,50);
x = x+5;
y = y+3;
}
[/code]

座標以外の値をアニメーションする

  • アニメーションの変化するパラメータは座標だけではない
  • 色をアニメーションすることも可能
  • fillの値を変化させれば、色が徐々に変化していく
  • アニメーションの例2:色相の変化

[code language=”java”]
//グローバル変数 (色相の変化)
int h = 0;

//初期化関数
void setup()
{
size(640,480);
frameRate(30);
colorMode(HSB,360,100,100,100);
smooth();
noStroke();
fill(200,80,80);
}

//メインループ//メインループ
void draw(){
background(0);
//色相を変数「h」で 0〜360の間で変化させている
fill(h%360,80,80);
//設定した塗りの色で円を再描画
ellipse(width/2,height/2,200,200);
//変数の値を更新
h++;
}
[/code]

条件分岐

条件分岐をつかって、円が画面からはみ出ないようにしてみる

  • 剰余演算子「%」を使用した方法意外のやりかたで、画面からはみ出ないようにしてみる
  • 条件分岐を使用して、画面の端に来たら位置を修正するようにしてみる
  • パックマンの画面移動のイメージ

条件分岐の構文

  • 例 – 「もし画面の外にでたら、反対から出現する」
  • もし○○したら、××せよ:条件分岐命令の典型的な例
  • if〜else文:条件分岐をProcessingで記述する

[code language=”java”]
if( [条件式] ){
[条件が正しい場合の処理]
} else {
[条件が誤っている場合の処理]
}
[/code]

条件「もし画面の外に出たら」→ 4つの可能性

  • 画面の右端にはみ出る
  • 条件式:x > width
  • 画面の左端にはみ出る
  • 条件式:x < 0
  • 画面の下端にはみ出る
  • 条件式:y > height
  • 画面の上端にはみ出る
  • 条件式: y < 0

それぞれの条件をif文に書いてみる

  • 画面の右端からはみ出たら、左端に移動
  • if(x > width) { x = 0; }
  • 画面の左端からはみ出たら、右端へ移動
  • if(x < 0) { x = width; }
  • 画面の下端からはみ出たら、上へ移動
  • if(y > height) { y = 0; }
  • 画面の右端からはみ出たら
  • if(y < 0) { y = height; }

条件分岐を付加したプログラム

[code language=”java”]
//x座標、y座標
float x, y;
//x軸方向のスピ-ド, y軸方向のスピ-ド
float xSpeed, ySpeed;

//初期化関数
void setup()
{
size(480,480);
frameRate(30);
colorMode(HSB,360,100,100,100);
noStroke();
fill(180,100,80,80);
//初期位置を画面の範囲内でランダムに決定
x = random(width);
y = random(height);
//スピードも -10〜10 の範囲でランダムに
xSpeed = random(-10,10);
ySpeed = random(-10,10);
}

//メインループ
void draw(){
background(0);
//円の描画
ellipse(x,y,40,40);
//x座標更新
x+=xSpeed;
//y座標更新
y+=ySpeed;

//条件分岐で画面の範囲内にいるかを判定

//条件1.もし画面の右端からはみ出たら
if(x > width){
//画面の左端に移動
x = 0;
}

//条件2.もし画面の左端からはみ出たら
if(x < 0){
//画面の右端に移動
x = width;
}

//条件3.もし画面の下端からはみ出たら
if(y > height){
//画面の上端に移動
y = 0;
}

//条件3.もし画面の上端からはみ出たら
if(y < 0){

//画面の下端に移動
y = height;
}
}
[/code]

壁で跳ね返ってくる動き

  • 現状のプログラムを改造して、画面からはみ出た際に反対側から出現するのではなく、画面の境界を壁にして、跳ね返るように変更してみる
  • ブロック崩しのイメージ
  • バウンドを表現するには?
  • x方向のスピードはどうなるのか?
  • y方向のスピードはどうなるのか?

[code language=”java”]

//グローバル変数
float x, y, xSpeed, ySpeed;

//初期化関数
void setup()
{
size(480,480);
frameRate(30);
colorMode(HSB,360,100,100,100);
noStroke();
fill(180,100,80,80);
x = random(width);
y = random(height);
xSpeed = random(-10,10);
ySpeed = random(-10,10);
}

//メインループ
void draw()
{
background(0);
ellipse(x,y,40,40);
x += xSpeed;
y += ySpeed;

//画面の左端、もしくは右端からはみ出たら
if(x > width || x < 0){
//X軸方向のスピードを反転
xSpeed *= -1;
}

//画面の上端、もしくは下端からはみ出たら
if(y > height || y < 0){
//Y軸方向のスピードを反転
ySpeed *= -1;
}
}
[/code]


変数と繰り返し

変数

変数とは?

  • 一時的に値(文字、文字列、数字など)を記憶しておく場所
  • データを入れておく「箱」のようなもの

データ型

  • 値の種類を「データ型」という

Processingでよく用いられるデータ型

  • int:整数 (-1, 0, 1, 2, 3….)
  • float:少数 (-0.01, 3.14, 21.314)
  • boolean:ブール値、「真」か「偽」か、(true, false)
  • char:1文字分のデータ(a, b, c, d…)
  • color:色の情報を保存

変数の使いかた

  • 宣言:使用する変数の名前の箱を準備する

[code language=”java”]
int hoge;
[/code]

  • 代入:変数の箱に値を入れる

[code language=”java”]
hoge = 0;
[/code]

  • 演算:変数の値を計算する

[code language=”java”]
hoge = hoge + 1;
[/code]

変数の使用例

[code language=”java”]
//変数の宣言
int diameter, x, y;

//初期設定
colorMode(HSB,360,100,100,100);
size(300,300);
background(0);
fill(210,100,100,50);
noStroke();

//変数へ値の代入
diameter = 180;
x = 60;
y = 150;

//円1
ellipse(x,y,diameter,diameter);

//右に移動
x = x+60;

//円2
ellipse(x,y,diameter,diameter);

//右に移動
x = x+60;

//円3
ellipse(x,y,diameter,diameter);

//右に移動
x = x+60;

//円4
ellipse(x,y,diameter,diameter);
[/code]

変数の使用例:その2

[code language=”java”]
//変数の宣言
int diameter,top,left;

//初期設定
size(300,300);
colorMode(HSB,360,100,100,100);
background(0,0,0,100);
noStroke();
fill(210,100,100,20);
diameter = 300;
top = 150;
left = 150;

//同心円を描いて行く
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
ellipse(top,left,diameter,diameter);
diameter = diameter – 40;
[/code]

繰り返し

  • さっきのプログラムに注目
  • 途中から同じ命令のくりかえしになっていないだろうか?

[code language=”java”]
//同心円を描いて行く
ellipse(top,left,ellipse_size, ellipse_size);
ellipse_size = ellipse_size – 40;
ellipse(top,left,ellipse_size, ellipse_size);
ellipse_size = ellipse_size – 40;
ellipse(top,left,ellipse_size, ellipse_size);
ellipse_size = ellipse_size – 40;
….
[/code]

  • 繰り返しの部分を何度も書かずに一気に指定したい

[code language=”java”]
ellipse(top,left,ellipse_size, ellipse_size);
ellipse_size = ellipse_size – 40;
[/code]

  • これを10回くりかえし

繰り返し – for文

  • 繰り返しをするには「for文」を使用する
  • for文の書きかた

[code language=”java”]
for (初期化式; 継続条件式; 再初期化式) {
文;
}
[/code]

  • 初期化式:初期化の際の条件式
  • 継続条件式:繰り返しを継続する条件式
  • 再初期化式:繰り返されるたびに実行される式

for分を用いた、繰り返しの例 1

[code language=”java”]
// 100回「+」の文字を出力する

int i;

for (i=0; i<100; i=i+1){
print("+");
}
[/code]

for分を用いた、繰り返しの例 2

  • 変数 i をfor分の中で使用してみる

[code language=”java”]
/*
0から99までの数を出力する
for文の繰り返しの条件式の中の、変数iを利用する
*/

int i;

for (i=0; i<100; i=i+1){
print(i + ", ");
}
[/code]

同心円をくりかえしを用いて描画

[code language=”java”]
//変数の宣言
int diameter,top,left,i;

//初期条件の設定
size(300,300);
colorMode(HSB,360,100,100,100);
background(0,0,0,100);
noStroke();
fill(210,100,100,10);

//円のパラメータを初期化
diameter = 300;
top = 150;
left = 150;

//同心円を描いていく
for(i=0; i<16; i++){
ellipse(top,left,diameter,diameter);
diameter = diameter – 20;
}
[/code]

虹色のグラデーション

[code language=”java”]
//変数の宣言
int i;

//初期設定
size(300,300);
colorMode(HSB, 360, 100, 100);
noStroke();

//色相を変化させながら四角を描いていく
for(i=0; i<30; i++){
fill(i*10,80,100);
rect(i*10,0,10,300);
}
[/code]

乱数:ランダムな値を出力する

  • 例:0から100の乱数を生成

[code language=”java”]
random(100);
[/code]

  • 例:100から1000の乱数を生成

[code language=”java”]
random(100, 1000);
[/code]

ランダムに色を塗る

[code language=”java”]
//ランダムに色を塗る

//変数の宣言
int i;

//初期設定
size(300,300);
colorMode(HSB, 360, 100, 100);
noStroke();

//ランダムに色を塗る
for(i=0; i<300; i++){
fill(random(360),random(100),random(100));
rect(i*1,0,1,300);
}
[/code]

似たような色相で

[code language=”java”]
//似たような色相で色を塗る

//変数の宣言
int i;

//初期設定
size(300,300);
colorMode(HSB, 360, 100, 100);
noStroke();

//似たような色相で色を塗る
for(i=0; i<300; i++){
fill(random(200,220),random(100),random(100));
rect(i*1,0,1,300);
}
[/code]

似たような明度と彩度で

[code language=”java”]
//似たような明度と彩度で色を塗る

//変数の宣言
int i;

//初期設定
size(300,300);
colorMode(HSB, 360, 100, 100);
noStroke();

//似たような明度と彩度で色を塗る
for(i=0; i<300; i++){
fill(random(360),60,90);
rect(i*1,0,1,300);
}
[/code]

たくさん円を描く

[code language=”java”]
//たくさん円を描く

//変数の宣言
int i;

//初期設定
size(300,300);
background(0);
colorMode(HSB, 360, 100, 100,100);
noStroke();

//たくさんの円を描く
for(i=0; i<100; i++){
float diameter;
fill(random(200,240),random(50,100),random(50,100), 50);
diameter = random(20,80);
ellipse(random(300),random(300),diameter,diameter);
}
[/code]

たくさん四角形を描く

[code language=”java”]
//たくさんの四角形を描く

//変数の宣言
int i;

//初期設定
size(300,300);
background(0);
colorMode(HSB, 360, 100, 100,100);
rectMode(CENTER);
noStroke();

//たくさんの四角形を描く
for(i=0; i<100; i++){
fill(random(0,40),random(50,100),random(50,100), 50);
rect(random(300),random(300),random(5,100),random(5,100));
}
[/code]

次回までの課題

  • くりかえしを使用したスケッチの作成
  • 連休明けの授業で提出
  • 以下の場所にアップロードしておくこと
  • http://www.cuc.ac.jp/~[ログイン名]/proga/assignment2/

色について

コンピュータの画面の復習

  • コンピュータの画面を拡大していくと…
  • 縦横に並んだ点の集合 → ピクセル (Pixel)
  • 一つのピクセルは、赤、緑、青の三原色から成り立っている


液晶画面のピクセル

コンピュータで絵を描くということ

  • コンピュータ画面は、縦横沢山のピクセルから構成された巨大なエクセルの表のようなもの
  • 例:1024 x 768 の液晶画面
  • 横に1024列縦に768行ならんだ巨大な表
  • それぞれのセルにR,G,B,A(アルファ値)が格納されている


RGBAプレーン

色の指定

  • 色を指定するには?
  • R(赤) G(緑) B(青)の三原色で指定する
  • 加法混色 (光の三原色であることに注意) ←→ 色料の三原色


光と色料の三原色

色の階調

  • R : 0 〜 255 → 2進数だと:11111111 = 8bit
  • G : 0 〜 255 → 2進数だと:11111111 = 8bit
  • B : 0 〜 255 → 2進数だと:11111111 = 8bit

どのくらいの色を再現できるのか?

  • 256(R) x 256(G) x 256(B) = 16,777,216 (24bit)
  • これに加えて透明度を255階調加えたものが、32bitカラー。最近のPCのほとんどは、32bitカラーを表現可能。
  • 24ビットカラー (24-bit color)、および32ビットカラー (32-bit color) は「トゥルーカラー」(Truecolor) と呼ばれる

色の指定(復習)

3つの色の属性

  • 背景色 background関数
  • bacground(<Rの値>, <Gの値>, <Bの値>);
  • 線に色をつける stroke関数
  • stroke(<Rの値>, <Gの値>, <Bの値>);
  • 塗りの色をつける fill関数
  • fill(<Rの値>, <Gの値>, <Bの値>);
  • 線を描きたくないとき
  • noStroke();
  • 塗り潰しの色を付けたくないとき
  • noFill();

[code language=”java”]
size(400,300);
background(255);

strokeWeight(12);
smooth();
noFill();

stroke(0,133,199);
ellipse(70,120,106,106);

stroke(244,195,0);
ellipse(135,173,106,106);

stroke(0);
ellipse(200,120,106,106);

stroke(0,159,61);
ellipse(265,173,106,106);

stroke(233,0,36);
ellipse(330,120,106,106);
[/code]


オリンピック!!

HSBによる色の指定

  • RGB以外の色の指定方法
  • HSB色空間、HSV色空間とも言う
  • 色相 (Hue):色の種類
  • 彩度 (Saturation):色の鮮かさ
  • 明度 (Brightness, Value):色の明かるさ
  • プログラミングしながら色を指定する場合は、HSBの方が直感的に望みの色を指定し易いことも多い
  • HSBモデルのあり方が人間が色を知覚する方法と類似している
  • HSB色空間の視覚イメージ
  • 円錐のイメージ
  • 色相:外環の角度、彩度:中心点からの距離、明度:高さ


錐体のHSB色空間

HSB (HSV) 色空間への切替方法

  • colorMode関数を使用する、それぞれのパラメータの範囲も同時に指定
  • 例:色相:360階調(°), 彩度:100階調(%), 明度:100階調(%)

colorMode(HSB, 360, 100, 100);

演習:色彩のハーモニー

色を美しく配置する演習

  • 正方形(600 x 600)のキャンバスを縦に3つ、横に3つ、合計9つの正方形に分割し、それぞれを別の色で塗り分けてみる
  • 自分のなかでバランスが取れていると思える配色・配置に
  • 配色ができたらタイトルを付けてみる

例1

[code language=”java”]
size(300,300);
colorMode(HSB,360,100,100);
noStroke();

//1行目
fill(210,89,64);
rect(0,0,100,100);
fill(2,77,80);
rect(100,0,100,100);
fill(232,13,47);
rect(200,0,100,100);

//2行目
fill(183,68,62);
rect(0,100,100,100);
fill(31,98,85);
rect(100,100,100,100);
fill(256,52,43);
rect(200,100,100,100);

//3行目
fill(169,68,29);
rect(0,200,100,100);
fill(193,98,55);
rect(100,200,100,100);
fill(250,8,28);
rect(200,200,100,100);
[/code]


マーブルチョコ?

例2

[code language=”java”]
size(300,300);
colorMode(HSB,360,100,100);
noStroke();

//1行目
fill(36,42,100);
rect(0,0,100,100);
fill(72,34,100);
rect(100,0,100,100);
fill(60,30,70);
rect(200,0,100,100);

//2行目
fill(170,30,80);
rect(0,100,100,100);
fill(160,30,70);
rect(100,100,100,100);
fill(350,20,100);
rect(200,100,100,100);

//3行目
fill(240,10,70);
rect(0,200,100,100);
fill(240,4,80);
rect(100,200,100,100);
fill(0,10,70);
rect(200,200,100,100);
[/code]


パステル

配色のヒント1

  • HSBの要素を一つだけを連続的に変化させる

彩度の変化

[code language=”java”]
size(300,300);
colorMode(HSB,360,100,100);
noStroke();

//1行目
fill(20,100,100);
rect(0,0,100,100);
fill(20,70,100);
rect(100,0,100,100);
fill(20,40,100);
rect(200,0,100,100);

//2行目
fill(120,100,100);
rect(0,100,100,100);
fill(120,70,100);
rect(100,100,100,100);
fill(120,40,100);
rect(200,100,100,100);

//3行目
fill(200,100,100);
rect(0,200,100,100);
fill(200,70,100);
rect(100,200,100,100);
fill(200,40,100);
rect(200,200,100,100);
[/code]


彩度の連続的な変化

明度の変化

[code language=”java”]
size(300,300);
colorMode(HSB,360,100,100);
noStroke();

//1行目
fill(20,100,100);
rect(0,0,100,100);
fill(20,100,70);
rect(100,0,100,100);
fill(20,100,40);
rect(200,0,100,100);

//2行目
fill(120,100,100);
rect(0,100,100,100);
fill(120,100,70);
rect(100,100,100,100);
fill(120,100,40);
rect(200,100,100,100);

//3行目
fill(200,100,100);
rect(0,200,100,100);
fill(200,100,70);
rect(100,200,100,100);
fill(200,100,40);
rect(200,200,100,100);
[/code]


明度の連続的な変化

色相の変化

[code language=”java”]
size(300,300);
colorMode(HSB,360,100,100);
noStroke();

//1行目
fill(0,70,100);
rect(0,0,100,100);
fill(20,70,100);
rect(100,0,100,100);
fill(40,70,100);
rect(200,0,100,100);

//2行目
fill(60,70,100);
rect(0,100,100,100);
fill(80,70,100);
rect(100,100,100,100);
fill(100,70,100);
rect(200,100,100,100);

//3行目
fill(120,70,100);
rect(0,200,100,100);
fill(140,70,100);
rect(100,200,100,100);
fill(160,70,100);
rect(200,200,100,100);
[/code]


色相の連続的な変化

配色のヒント2

  • 写真から色をピックアップする
  • 有名な絵画からピックアップする
  • 配色の綺麗なWebサイトから色をピックアップする


「モナリザ」レオナルド・ダビンチ


「睡蓮」モネ

次回授業までの課題

  • 自分自身の観点から、美しいと思う配色を作成する
  • 300x300pixelの画面を9分割した正方形に配色すること
  • 作成の方法は自由 (イメージからピックアップ、HSBの数値を工夫するなど)
  • 作成した配色は、エクスポートして、アプレットを以下にアップロードすること
  • http://www.cuc.ac.jp/~学籍番号/proga/assignment1/

映像の視聴

DVD「colorcalm by Design

  • Art Barcodes – 映像×音楽:イルマ・ブーム×マイケル・ナイマン
  • Food Coloring – 映像×音楽:ジョン前田×坂本龍一
  • Colour Wheel – 映像×音楽:ピーター・サヴィル×ニュー・オーダー/テラノヴァ
  • Blue to Red Elegia – 映像×音楽:ピーター・サヴィル×ニュー・オーダー


「Art Barcodes」より

参考:Art Barcodesを自動生成してくれるWebサイト