Archives

はじまりはProcessingだった

最近ドローンについて調べていく中で、気付いたことがあった。

ドローンの飛行制御パーツ(フライトコントローラ)は、arduinoベースのものが多いのだ。

フライトコントローラも2系統あり、もともとラジコンヘリの中のクアッドコプター向けに作られていたものと、自動制御するために用意されたものとがある。

後者がarduinoベースであり、arduinoを使うことで超音波センサやジャイロ、GPSなどの各種センサ情報を使ってモータ出力をコントロールすることができるのだ。

さらに最近ではraspberry piベースのものも出てきて、Unixの豊富な開発資源を利用しつつドローンの制御を行うことが出来るようになっている。

そうしたフライトコントローラは、フライトとは言ってもより一般化されており、ランドローバーの制御等も行えるようになっている。

要するにGPSや超音波センサなど、リアルワールドで必要とされるセンサ制御とIOをセットにしているため、汎用的なロボットコントローラとして使うことができるのだ。

開発環境も、ROSなどロボティクス全般に応用できる環境を使う。

ロボティクス関連の人がドローンに関心を抱いているのはそのためだ。ホビーとしてのドローンは、マーケットとしての価値しか無い。重要なのは自動制御であり、それを可能にしたのはプロトタイピング可能なハードウェア、つまりarduinoの存在だったということだ。

そしてarduinoはそのIDEを見れば分かる通り、Processingをベースにしている。

「クリエイターがアイデアに集中できる」「間口を広くする」という哲学はProcessingの根幹をなしており、OpenFrameworksやCinder、Arduinoはそれらにインスパイアされて開発された。

僕が最初に感銘を受けたプログラミングのアウトプットは、Processingを使ったMichael Hansmeyerのオブジェクトだった。

僕が最初に覚えたプログラミング言語もProcessingだった。

はじまりに、Processingがあった。

はじまりなのに、すでに進行中(Processing)であるという知的な遊びが、Processingの哲学なのかなと思った。

Logo Beats

Particle Beatsみたいな動画を自分でも作りたいと思って、Processing + minimライブラリで実装してみました。

任意の画像を音楽に合わせてポンポン出すだけです。

ソースコードは以下。

import ddf.minim.analysis.*;
import ddf.minim.*;

int max_logo_index = 300;
int max_particle_index = 500;
int S_a1;
int current_logo_index;
int current_particle_index;

Minim minim;
AudioPlayer player;
FFT fft;

Logo[] logos = new Logo[max_logo_index];
Particle[] particles = new Particle[max_particle_index];

color c_logo = color(220, 220, 255, 100);
  
void setup()
{
  size(1920, 1080);
  stroke(0,0,255);
  frameRate(30);
  
  minim = new Minim(this);
  S_a1 = 30;


  // music
  player = minim.loadFile("music.mp3", 512);
  player.loop();

  // Make FFT Object. bufferSize(): 1024, sampleRate: 44100Hz.
  fft = new FFT(player.bufferSize(), player.sampleRate());
  println("sampling reate is " +player.sampleRate());
  println("spec size is " +fft.specSize());
  println("bandwidth is: " +fft.getBandWidth());

  // construct Logo instances
  PImage img = loadImage("Laughing_man_logo.png");
  for (int i=0; i<max_logo_index; i++) {
    logos[i] = new Logo(img, c_logo);
  }

  // construct Particle instances
  img = loadImage("particle.png");
  for (int i=0; i<max_particle_index; i++) {
    particles[i] = new Particle(img, c_logo);
  }

  current_logo_index = 0;
  current_particle_index = 0;

  imageMode(CENTER);
  ellipseMode(CENTER);

}

void draw()
{
  background(0, 0.9);

  fft.forward(player.mix);

  // logo appear
  if (fft.getBand(1) > S_a1) {

    logos[current_logo_index].activate();
    current_logo_index += 1;

    if (current_logo_index >= max_logo_index) {
      current_logo_index = 0;
    }

  }

  // draw logos
  for (int i=0; i<logos.length; i++) {
      logos[i].move();
  }

  // if collision between logos occurs, generate particle
  for (int i=0; i<logos.length; i++) {
      if (!logos[i].active) continue;

      float x = logos[i].x;
      float y = logos[i].y;
      float size = logos[i].size;
      float min_x = x - size / 2;
      float max_x = x + size / 2;
      float min_y = y - size / 2;
      float max_y = y + size / 2;

      for (int j=0; j<logos.length; j++) {
        if (i == j) continue;
        if (!logos[j].active) continue;

        float obj_x = logos[j].x;
        float obj_y = logos[j].y;

        // if collision, generate particle
        if (obj_x >= min_x && obj_x <= max_x) {
          if (obj_y >= min_y && obj_y <= max_y) {

            int particle_num = int(random(0, 100));
            int max = current_particle_index + particle_num;
            if (max > max_particle_index) {
              current_particle_index = 0;
              max = particle_num;
            }

            for (int k=current_particle_index; k<max; ++k) {
              particles[k].activate(logos[i].x, logos[i].y);
            }

          }
        }

      }
  }

  // draw particles
  for (int i=0; i<particles.length; i++) {
      particles[i].move();
  }

  // for output movie
//  saveFrame("frames/######.tif");
}


void stop()
{
  player.close();
  minim.stop();
  super.stop();
}


class Logo
{

  PImage img;

  // status
  boolean active;
  float x, y;  // position
  float speed;
  float speed_d; // spped decay

  // draw arguments
  color ripple_color;
  float size;
  float ellipse_size;
  float ellipse_alpha;
  float energy;

  // rotation arguments
  boolean right_handed_rotation;
  float r; // distance from (x, y)
  float theta; // angle of rotation

  // lifetime arguments
  int elapsed_time;
  int life_span = 20;

  /////////////////////////////////////////////////////////////////////////////
  // constructor
  /////////////////////////////////////////////////////////////////////////////
  Logo(PImage _logo_img, color _ripple_color)
  {

    img = _logo_img;

    active = false;
    x = random(0, displayWidth);
    y = random(0, displayHeight);
    speed = 0;
    speed_d = 0;

    ripple_color = _ripple_color;
    size = 0;
    ellipse_size = 0;
    ellipse_alpha = 0;
    energy = 0;

    r = random(10,200);
    theta = random(0, 2*PI);

    if (random(0, 1) >= 0.5) {
      right_handed_rotation = true;
    } else {
      right_handed_rotation = false;
    }

  }

  /////////////////////////////////////////////////////////////////////////////
  // initialize parameter before appearance
  /////////////////////////////////////////////////////////////////////////////
  void activate()
  {
    if (active) return;

    active = true;
    speed = random(1, 1.5);
    speed_d = 0.95;

    size = random(0, 200);
    if (size >= 180) {
      size = random(500, 1000);
    }
    ellipse_size = size;
    ellipse_alpha = 200;
    energy = random(0.01, 0.1);

    elapsed_time = 0;
  }

  /////////////////////////////////////////////////////////////////////////////
  // update position and speed
  /////////////////////////////////////////////////////////////////////////////
  void move()
  {
    if (!active) size = 0;

    // update position
    if (right_handed_rotation) {
      image(img, x + r*cos(2*PI - theta), y - r*sin(2*PI - theta), size, size);
    } else {
      image(img, x + r*cos(theta), y - r*sin(theta), size, size);
    }

    // draw
    draw_logo();

    // update speed
    speed *= speed_d;
    theta += PI / 100;
    elapsed_time += 1;

    // first increase size, then shrink
    if (elapsed_time <= life_span / 10) {
      size *= 1 + 4 * energy;
    } else {
      size *= 1 - energy;
    }

    if (elapsed_time >= life_span) {
      active = false;
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // draw
  /////////////////////////////////////////////////////////////////////////////
  void draw_logo()
  {
    pushStyle();

    color c_ripple = color(red(ripple_color), blue(ripple_color),
                           green(ripple_color), ellipse_alpha);

    // draw a ripple
    if (active) {

      noFill();
      strokeWeight(10);
      stroke(c_ripple);

      if (right_handed_rotation) {

        ellipse(x + r*cos(2*PI - theta), y - r*sin(2*PI - theta),
                ellipse_size * elapsed_time * 0.2,
                ellipse_size * elapsed_time * 0.2);

      } else {

        ellipse(x + r*cos(theta), y - r*sin(theta),
                ellipse_size * elapsed_time * 0.2,
                ellipse_size * elapsed_time * 0.2);

      }

    }

    ellipse_alpha -= 200 / life_span;

    popStyle();
  }
};


class Particle
{

  PImage img;
  color tint_color;

  // status
  boolean active;
  float x, y;
  float speed_x;
  float speed_y;
  float speed_d; // spped decay

  float size;
  float elapsed_time;

  boolean right_handed_rotation;

  int life_span = 50;

  /////////////////////////////////////////////////////////////////////////////
  // constructor
  /////////////////////////////////////////////////////////////////////////////
  Particle(PImage _logo_img, color _tint_color)
  {
    img = _logo_img;
    tint_color = _tint_color;
    active = false;
  }

  /////////////////////////////////////////////////////////////////////////////
  // initialize parameter before appearance
  /////////////////////////////////////////////////////////////////////////////
  void activate(float x_start, float y_start)
  {
    if (active) return;

    x = x_start;
    y = y_start;

    speed_x = random(0, 10);
    speed_y = random(0, 10);

    if (random(0, 1) >= 0.5) {
      speed_x *= -1;
    }
    if (random(0, 1) >= 0.5) {
      speed_y *= -1;
    }

    speed_d = 0.98;
    active = true;
    size = random(0, 20);
    elapsed_time = 0;
  }

  /////////////////////////////////////////////////////////////////////////////
  // update position and speed
  /////////////////////////////////////////////////////////////////////////////
  void move()
  {
    if (!active) size = 0;

    // update position
    x += speed_x;
    y += speed_y;

    // draw
    pushStyle();
    tint(tint_color);
    image(img, x, y, size, size);
    popStyle();


    // update speed
    speed_x *= speed_d;
    speed_y *= speed_d;

    elapsed_time += 1;
    
    if (elapsed_time >= life_span) {
      active = false;
    }
  }

};

 

arduinoでフォトリフレクタTPR-105を使って得た値をprocessingで読み取る

フォトリフレクタは回路が2つついてて、1つはLED、もう1つはフォトトランジスタ(照度で抵抗値が変化する素子)になっている。電気を流すことで反射光の照度を抵抗値として取得することができる装置である。安くてっちゃいのを秋月電子で探してみるとTPR-105があった。

フォトリフレクタ(反射タイプ) TPR-105: 半導体 秋月電子通商 電子部品 ネット通販

TPR-105_dim

角が欠けてる方がLED。LEDは1から2に電流を流す。フォトトランジスタは3から4に電流を流して電流値を見る。データシートによるとLEDの方は20mAなので5Vから引っ張って抵抗は250Ωがないので220Ωで。フォトトランジスタの方は同じく5Vでやると1MΩで割といい感じになった。

TPR-105F_ブレッドボード

 

これをシリアル通信で送ってprocessingで表示したい。

arduinoの方のコードは超シンプル。

void setup () {
  Serial.begin(9600);
}

void loop () {
  Serial.write(analogRead(A1));
}

processing側。

import processing.serial.*;
Serial port;
PFont ricty;
int[] y;
int   x;
int   val;
int   size;
int   time;

void setup() {
  size(1000, 600);
  port = new Serial(this, "SERIAL_PORT_PATH", 9600);
  size = width/2;
  x = 0;
  y = new int[size];
  val = 0;
  time = 0;
  ricty = loadFont("Ricty-Regular-24.vlw"); // ここはお好みで
  textFont(ricty, 24);
  smooth();
}

void draw() {
  for (int i=0; i<size-1; ++i) y[i] = y[i+1];
  y[size-1] = int(map(val, 0, 255, -height, height));

  // frame
  background(40);
  stroke(#999999);
  strokeWeight(1);
  line(0, height/2, width, height/2);
  line(0, height/4, width, height/4);
  line(0, 3*height/4, width, 3*height/4);
  line(width-time%(width/2), 0, width-time%(width/2), height);
  line(width/2-time%(width/2), 0, width/2-time%(width/2), height);
  if (time % (width/2) == 0) time = 0;
  fill(40, 100);
  rect(0, 0, width, height);

  fill(220);
  text(val, 10, 35);

  stroke(#eebbcc);
  strokeWeight(2);
  for (int i=0; i<size; ++i) {
    point(i, -y[i]/3+height/2);
  }

  ++time;
}

void serialEvent(Serial p) {
  val = port.read();
}

シリアル通信で得た値をオシロスコープ風に点のプロットで 表示する。配列yにsizeまでの数の過去の値を保存しておいて、それを更新していくという感じ。フォトリフレクタに手を近づけたり離したりすると以下のようになる。

スクリーンショット_2014-01-06_3_06_18-4

フォトリフレクタ自体はライントレースとかに使えるようなのでこれからいろいろやってみる。