SyntaxHighlighter

2012-06-21

円柱(ゴミ箱...)に映像を投影する

世の中的情報によると、平面に対しては簡単にプロジェクションマッピングっぽい事が出来る、とのことなので、曲面(円柱の横側の部分、さらに言ってしまえばゴミ箱。。。)に対して似たような事が出来ないか試してみました。






実際やった事は、平面を複数用意して、曲面を平面で近似しているだけなので、話自体はそんなに難しい事ではないです。ただこれが(数学的、物理的に?)正しいプロジェクションなのかは分かりません。


ソースコードは最後に掲載していますが、流れとしては、以下の通りです。プロジェクタを投影対象に向けて、投影対象とPCのモニタを見ながら頑張って位置合わせをしていきます。

  • 映像を表示させたい領域を大まかに選択します
    • 四角形の中に白と灰色の縞模様が現れるけど、個々の縞が曲面を近似する平面になります
  • 四角形の四隅をドラッグして表示領域の隅を決めます
  • aキーを押すと、四角形の周囲にある青い点を上下に動かす事が出来るので、これを使って表示領域を調整します
    • 形状を調整する青い点は、隅になるほど密度を増やしています(点の位置は決めうちのおおざっばです。もっと妥当な間隔にするのは面倒そうです)
  • dを押すと映像が表示されます
  • cを押すと表示領域をクリア出来ます

投影対象を直接見て位置合わせをする考え方はこの前遊んだmapamokを参照してます。角や辺が明確な直方体と違って、円柱は端が明確でないので、精度が微妙になるかと。
また対象が少しでもずれたり、プロジェクタが少しでもずれると残念な事になります。

もっとうまい方法があるんじゃないかなあと、思ったりします、、、
実際に車とか建物に投影する場合はどうやっているのか、大変気になります。カメラとか使用して、画像認識的な手法も使って効率化してるんでしょうか??うーん。

コードはProcessing。だんだん面倒くさくなって数字がベタ書きになっていきます。。。まどろっこしい書き方をしているので無駄に行も長いです。
久しぶりに使ってみると、ProcessingがJavaじゃなくて、PerlやPythonのようなもっとゆるゆるな感じで書けたらなあ、と思わないでもないです。もっとも、Javaを忘れつつあるだけなんですが。

float SPLIT_INTERVAL;
DisplayImageArea dia;

float showWidth;
float showHeight;

DisplayBallon db;
DisplayText dt;

// MODE
static final int MODE_AREA_SELECT   = 0x01;
static final int MODE_CORNER_ADJUST = 0x02;
static final int MODE_FINE_ADJUST   = 0x04;
static final int MODE_SHOW   = 0x08;
int mode;
int showMode = 0;
int NSHOW = 2;

// for AREA SELECT
float startX = -1;
float startY = -1;
float endX = -1;
float endY = -1;

// font
PFont infoFont;
PFont displayFont;

void setup() {
  size(screen.width, screen.height, P3D);
  SPLIT_INTERVAL = width / 40;
  
  // create font
  infoFont = createFont("SansSerif", 16, true); 
  displayFont = createFont("BrushScriptMT", 42, true);
  
  dia = new DisplayImageArea();
  mode = MODE_AREA_SELECT;
  
  
}

void draw() {
  background(0);
  
  switch (mode) {
    case MODE_AREA_SELECT:
      if (endX >=0 && endY >= 0) {
        rectMode(CORNER);
        noStroke();
        fill(255,255,255,128);
        rect(startX, startY, endX-startX, endY-startY);
      }

      break;
    case MODE_CORNER_ADJUST:
    case MODE_FINE_ADJUST:
      dia.drawSplitPattern(color(220), color(100));
      dia.drawFrame();
      dia.drawCornerPoints();

      PGraphics pg = createGraphics((int)showWidth, (int)showHeight, P3D);
      showWire(pg);
      dia.drawImage(pg);
      dia.drawCenterPoints();

      break;
    case MODE_SHOW:
      pg = createGraphics((int)showWidth, (int)showHeight, P3D);
      switch (showMode) {
        case 0:
          db.draws(pg);
          db.move();
          break;
        case 1:
          dt.draws(pg);
          dt.move();
          break;
      }
      dia.drawImage(pg);
  }

  if (mode != MODE_SHOW) {
    PGraphics infopg = createGraphics(width, 100, P2D);
    showInformation(infopg);
    imageMode(CORNER);
    image(infopg, 0, height-100);
  }
}

void changeModeAreaSelect() {
  frameRate(30);
  startX = -1;
  startY = -1;
  endX = -1;
  endY = -1;
  mode = MODE_AREA_SELECT;
}

void changeModeCornerAdjust() {
  frameRate(30);
  mode = MODE_CORNER_ADJUST;
}
void changeModeFineAdjust() {
  frameRate(30);
  mode = MODE_FINE_ADJUST;
}
void changeModeShow() {
  frameRate(10);
  showMode = (showMode + 1) % NSHOW;
  mode = MODE_SHOW;
  switch (showMode) {
    case 0:
      db = new DisplayBallon(10, showWidth, showHeight);
      break;
    case 1:
      dt = new DisplayText(displayFont, "Hello World!", showWidth, showHeight); 
      break;
  }  
}


void mousePressed() {
  
  switch (mode) {
    case MODE_AREA_SELECT:
      startX = mouseX;
      startY = mouseY;
      break;
    case MODE_CORNER_ADJUST:
    case MODE_FINE_ADJUST:
      int select = dia.selectNearCornerPoint(mouseX, mouseY);
      break;    
  }
}

void mouseDragged() {
  switch (mode) {
    case MODE_AREA_SELECT:
      endX = mouseX;
      endY = mouseY;
      break;
    case MODE_CORNER_ADJUST:
      dia.moveSelectedCornerWithPropotional(mouseX, mouseY);
      break;
    case MODE_FINE_ADJUST:
      dia.moveSelectedCornerAdjust(mouseX, mouseY);
      break;
  }
}

void mouseReleased() {
  switch(mode) {
    case MODE_AREA_SELECT:
      if (startX < mouseX) {
        endX = mouseX;
      } else {
        endX = startX;
        startX = mouseX;
      }
      if (startY < mouseY) {
        endY = mouseY;
      } else {
        endY = startY;
        startY = mouseY;
      }
    
      showWidth  = endX - startX;
      showHeight = endY - startY;
      
      dia.setRoughRect(int((endX-startX) / SPLIT_INTERVAL)+1, startX, startY, endX, endY);
//      dia.setRoughRect(1, startX, startY, endX, endY);
      changeModeCornerAdjust();
      break;
    case MODE_CORNER_ADJUST:
    case MODE_FINE_ADJUST:
      dia.clearSelect();
      break;
      
  }
}

void keyPressed() {
  switch (key) {
    case 'a':
      if (mode == MODE_CORNER_ADJUST || mode == MODE_SHOW) {
        changeModeFineAdjust();
      } else if (mode == MODE_FINE_ADJUST) {
        changeModeCornerAdjust();
      }
      break;
    case 'd':
      if (mode == MODE_CORNER_ADJUST || mode == MODE_FINE_ADJUST || mode == MODE_SHOW) {
        changeModeShow();
      }
      break;
    case 'c':
      changeModeAreaSelect();
      break;
  }
}

void showWire(PGraphics pg) {
  pg.beginDraw();
  pg.stroke(255);
  pg.strokeWeight(3);
  pg.line(0,0,showWidth,showHeight);
  pg.line(showWidth,0,0,showHeight);
  pg.endDraw();
}

void showInformation(PGraphics pg) {
  pg.beginDraw();
  pg.background(20);
  pg.textFont(infoFont);
  
  // mode display
  String s = "Current Mode: ";
  float sw = pg.textWidth(s);
  pg.fill(255);
  pg.text(s, 10, 20);

  pg.fill(255,180,180);
  switch (mode) {
    case MODE_AREA_SELECT:
      pg.text("Area Select", 12+sw, 20);
      break;
    case MODE_CORNER_ADJUST:
      pg.text("Corner Adjust", 12+sw, 20);
      break;
    case MODE_FINE_ADJUST:
      pg.text("Fine Adjust", 12+sw, 20);
      break;
    case MODE_SHOW:
      pg.text("Show", 12+sw, 20);
      break;
  }
  
  // key display
  pg.fill(255);
  pg.text("a: toggle adjust mode  d: toggle display mode  c: clear window", 12, 40); 
  
  pg.endDraw();
}

class DisplayImageArea {

  /*
   cornerLT            cornerRT
    0--2--4--6--...--(2*nsplit)
    |  |  |  |          |
    1--3--5--7--...--(2*nsplit+1)
   cornerLB            cornerRB
  */
  private CornerPoint[] CP;
  private CornerPoint[] CenterP;
  private int cornerLT;
  private int cornerLB;
  private int cornerRT;
  private int cornerRB;

  private int select;
  private int nsplit;
  
  public DisplayImageArea() {
    CP = null;
    select = -1;
    nsplit = 0;
  }
  
  private float calc_interval(float z) {
    if (z < 0 || z > 1) return z;
    return z*z*(-2*z+3);
  }
  
  public void setRoughRect(int nsplit, float x1, float y1, float x2, float y2) {
    int npoint = 2+2*nsplit;
    CP = new CornerPoint[npoint];
    CenterP = new CornerPoint[nsplit];
    this.nsplit = nsplit;

    float intervalX = (x2-x1) / nsplit;
    float intervalY = y2-y1;

    for (int i=0; i<npoint; i++) {
      if (i % 2 == 0) {
        CP[i] = new CornerPoint(x1 + calc_interval(1.0 * int(i/2) / nsplit) * (x2-x1), y1, i/2 * 1.0/nsplit, 0.0);
      } else {
        CP[i] = new CornerPoint(x1 + calc_interval(1.0 * int(i/2) / nsplit) * (x2-x1), y2, i/2 * 1.0/nsplit, 1.0);
      }
    }
    for (int i=0; i<nsplit; i++) {
      CenterP[i] = new CornerPoint(x1 + calc_interval((i + 0.5) / nsplit) * (x2-x1), y1+intervalY/2, (i + 0.5) * 1.0/nsplit, 0.5);
    }

    cornerLT = 0;
    cornerLB = 1;
    cornerRT = 2*nsplit;
    cornerRB = 2*nsplit+1;
  } 

  public void drawSplitPattern(color color1, color color2) {
    noStroke();
    for (int i=0; i<nsplit; i++) {
      if (i % 2 == 0) {
        fill(color1);
      } else {
        fill(color2);
      }
      beginShape();
      vertex(CP[2*i].getX(), CP[2*i].getY());
      vertex(CP[2*i+1].getX(), CP[2*i+1].getY());
      vertex(CP[2*i+3].getX(), CP[2*i+3].getY());
      vertex(CP[2*i+2].getX(), CP[2*i+2].getY());
      endShape(CLOSE);
    }
  }
  
  public void drawImage(PImage teximage) {
    noStroke();
    textureMode(NORMALIZED);
   
    for (int i=0; i<nsplit; i++) {
      beginShape(TRIANGLE_FAN);
      texture(teximage);
      vertex(CenterP[i].getX(), CenterP[i].getY(), CenterP[i].getS(), CenterP[i].getT());
      vertex(CP[2*i  ].getX(), CP[2*i  ].getY(), CP[2*i  ].getS(), CP[2*i  ].getT());
      vertex(CP[2*i+1].getX(), CP[2*i+1].getY(), CP[2*i+1].getS(), CP[2*i+1].getT());
      vertex(CP[2*i+3].getX(), CP[2*i+3].getY(), CP[2*i+3].getS(), CP[2*i+3].getT());
      vertex(CP[2*i+2].getX(), CP[2*i+2].getY(), CP[2*i+2].getS(), CP[2*i+2].getT());
      vertex(CP[2*i  ].getX(), CP[2*i  ].getY(), CP[2*i  ].getS(), CP[2*i  ].getT());
      endShape();
    }
    
    
  }

  public void drawFrame() {
    stroke(0, 255, 0);
    noFill();
    strokeWeight(3);
    beginShape();
    for (int i=0; i<CP.length; i=i+2) {
      vertex(CP[i].getX(),CP[i].getY());
    }
    for (int i=CP.length-1; i>=0; i=i-2) {
      vertex(CP[i].getX(),CP[i].getY());
    }
   endShape(CLOSE);
  }
  
  public void drawCornerPoints() {
    for (int i=0; i<CP.length; i++) {
      CP[i].drawPoint(i == select);
    }
  }
  public void drawCenterPoints() {
    for (int i=0; i<CenterP.length; i++) {
      CenterP[i].drawPoint(false);
    }
    println(CenterP.length);
  }
  
  public int selectNearCornerPoint(int x, int y) {
    for(int i=0; i<CP.length; i++) {
      if ( CP[i].isNear(x, y)) {
        select = i;
        break;
      }
    }
    return select;
  }
  
  public void clearSelect() {
    select = -1;
  }
  
  public void moveSelectedCornerPoint(int x, int y) {
    if (select >= 0) {
      CP[select].setXY(x, y);
      moveCenterPoint(select);
    }
  }
  public void moveSelectedCornerWithPropotional(int x, int y) {
    if (select == cornerLT || select == cornerRT) { // upper corner
      CP[select].setXY(x, y);
      moveCenterPoint(select);
      
      float tanx = CP[cornerRT].getX() - CP[cornerLT].getX();
      float tany = CP[cornerRT].getY() - CP[cornerLT].getY();
      for (int i=1; i<nsplit; i++) {
        CP[2*i].setXY(CP[cornerLT].getX() + tanx * calc_interval(1.0 * i / nsplit), CP[cornerLT].getY() + (tany*i)/nsplit);
        moveCenterPoint(2*i);
      }
    } else if (select == cornerLB || select == cornerRB) {
      CP[select].setXY(x, y);
      moveCenterPoint(select);
      
      float tanx = CP[cornerRB].getX() - CP[cornerLB].getX();
      float tany = CP[cornerRB].getY() - CP[cornerLB].getY();
      for (int i=1; i<nsplit; i++) {
        CP[2*i+1].setXY(CP[cornerLB].getX() + tanx * calc_interval(1.0 * i / nsplit), CP[cornerLB].getY() + (tany*i)/nsplit);        
        moveCenterPoint(2*i+1);
      }
    }
  }
  public void moveSelectedCornerAdjust(int x, int y) {
    float x1 = CP[select].getX();
    float y1 = CP[select].getY();
    float x2, y2;
    if (select % 2 == 0) {
      x2 = CP[select+1].getX();
      y2 = CP[select+1].getY();
    } else {
      x2 = CP[select-1].getX();
      y2 = CP[select-1].getY();
    }
    if (abs(x2-x1) < 0.0001) {
      CP[select].setXY(x1, y);
      moveCenterPoint(select);
    } else {
      float adjx = (x2-x1)/(y2-y1) * (y -y1) + x1;
      CP[select].setXY(adjx, y);
      moveCenterPoint(select);
    }
  }
  
  private void moveCenterPoint(int centerindex) {
    int index = centerindex/2;
    if (index - 1 >= 0) {
      int i = index - 1;
      CenterP[i].setX((CP[2*i].getX() + CP[2*i+1].getX() + CP[2*i+2].getX() + CP[2*i+3].getX())/4.0);
      CenterP[i].setY((CP[2*i].getY() + CP[2*i+1].getY() + CP[2*i+2].getY() + CP[2*i+3].getY())/4.0);
    }
    if (index < nsplit) {
      int i = index;
      CenterP[i].setX((CP[2*i].getX() + CP[2*i+1].getX() + CP[2*i+2].getX() + CP[2*i+3].getX())/4.0);
      CenterP[i].setY((CP[2*i].getY() + CP[2*i+1].getY() + CP[2*i+2].getY() + CP[2*i+3].getY())/4.0);
    }
  }

  public class CornerPoint {

    static final private float NEARSIZE = 10;
    // Position
    private float x;
    private float y;
    // Texture Coord
    private float s;
    private float t;

    public CornerPoint(float x, float y) {
      this.x = x;
      this.y = y;
      this.s = 0;
      this.y = 0;
    }
    public CornerPoint(float x, float y, float s, float t) {
      this.x = x;
      this.y = y;
      this.s = s;
      this.t = t;
    }

    public float getX() {return x;}
    public void  setX(float x) {this.x = x;}
    public float getY() {return y;}
    public void  setY(float y) {this.y = y;}
    public void setXY(float x, float y) {this.x = x; this.y = y;}
    public float getS() {return s;}
    public void  setS(float s) {this.s = s;}
    public float getT() {return t;}
    public void  setT(float t) {this.t = t;}

    public boolean isNear(int x, int y) {
      return abs(this.x - x) < NEARSIZE && abs(this.y - y) < NEARSIZE;
    }

    public void drawPoint(boolean select) {
      noStroke();
      if (select) {
        fill(255, 100, 100);
      } 
      else {
        fill(100, 100, 255);
      }
      rectMode(CENTER);
      rect(x, y, NEARSIZE, NEARSIZE);
    }
  }
}

class DisplayBallon {

  private Ballon[] blist;
  private float sizeW;
  private float sizeH;
  
  public DisplayBallon(int n, float sizeW, float sizeH) {
    blist = new Ballon[n];
    color cfrom = color(255, 200,  20);
    color cto   = color( 30, 200, 255);
    for (int i=0; i<n; i++) {
      blist[i] = new Ballon(random(sizeW), sizeH + random(100), random(0.4,1.8), lerpColor(cfrom, cto, i*1.0/(n-1)));
    }
    this.sizeW = sizeW;
    this.sizeH = sizeH;
  }
  
  public void move() {
    
    Arrays.sort(blist);
    for (int i=0; i<blist.length; i++) {
      blist[i].move();
      if (!blist[i].isLower(sizeW, sizeH)) {
        blist[i].setInit(random(sizeW), sizeH + random(100), random(0.4,1.8));
        println("reset " + i);
      }
    }
  }
    
  public void draws(PGraphics pg) {
    pg.beginDraw();
    pg.background(0);
    for (int i=0; i<blist.length; i++) {
      blist[i].drawImage(pg);
    }
    pg.endDraw();
  }
  
  class Ballon implements Comparable {
    private float x;
    private float y;
    private float z;
    private color c;
        
    private static final float ballonW = 20;
    private static final float ballonH = 30;
    private static final float ballonString = 30;
    
    public Ballon(float x, float y, float z, color c) {
      this.x = x;
      this.y = y;
      this.z = z;
      this.c = c;      
    }
    
    public float getZ() {
      return z;
    }
    
    public boolean isLower(float W, float H) {
      if (0 <= y + ballonH + ballonString) {
        return true;
      }
      return false;
    }

    public void setInit(float x, float y, float z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }      
      
    
    public void move() {
      y -= 5.0/z;
    }
    
    public void drawImage(PGraphics pg) {
      pg.pushMatrix();
      pg.translate(x,y);
      pg.scale(1.0/z);

      pg.noStroke();

      pg.fill(255);
      pg.rect(-2, 0, 2, ballonH+ballonString);

      pg.fill(c);
      pg.ellipseMode(CENTER);
      pg.ellipse(0, 0, ballonW*2, ballonH*2);

      pg.popMatrix();
    }
    
    public int compareTo(Object obj) {
      return new Float(-this.z).compareTo(new Float(-((Ballon) obj).getZ()));
    }
  };
}

class DisplayText {

  private PFont font;
  private String showtext;
  private float x;
  private float y;
  private float sizeW;
  private float sizeH;
  
  public DisplayText(PFont font, String s, float sizeW, float sizeH) {
    this.font = font;
    this.showtext = s;
    this.sizeW = sizeW;
    this.sizeH = sizeH;
    this.x = 0;
    this.y = sizeH / 2; 
  }
  
  public void draws(PGraphics pg) {
    pg.beginDraw();
    pg.background(0);
    pg.textFont(font);
  
    pg.fill(255);
    pg.text(showtext, x, y);

    pg.endDraw();
  }
  
  public void move() {
    x += 5;
    if (x > sizeW) {
      x = random(-100,0);
    }
  }
}

bitbucketにも置いてみました

2012-06-02

ヤマハのアンサンブルフェスティバル

ヤマハの音楽教室(バイオリン)に通い始めて2年半ほどになるのだけど、今日、アンサンブルフェスティバルっていう発表会に初めて参加してきました。
2年に一度開かれているイベントらしく、1グループ100人近く集まって演奏するものです。
自分は、今回、バイオリンアンサンブルに参加して、「カッチーニのアヴェ・マリア」「愛の悲しみ」「愛の喜び」を弾きました。
弾いたと言っても、愛の喜びが難しくてちゃんとは弾けてない。。バイオリン歴2年そこその若輩者が弾く曲じゃない気がします。

全部合わせて15分に満たないのだけど、緊張している間にあっという間に終わった感がするのが、正直なところです。観客にはどんな風に聞こえていたのだろうかと気になります。

ホールが大きくてとっても響いたので、遠くの方で弾いている音が遅れて聞こえてきて、自分が遅れているか不安になりながら弾いてました。指揮者を見ている余裕はとても無い。。。

全部で10分少々の曲を弾くので精一杯で、(プロのコンサートだと標準的と思われる)2時間分弾くってのは半端ない努力と体力が必要なのだなあと改めて感じます。

次回がもしあったら、もっとうまく弾けるようになっているといいな

Related Posts Plugin for WordPress, Blogger...