実際やった事は、平面を複数用意して、曲面を平面で近似しているだけなので、話自体はそんなに難しい事ではないです。ただこれが(数学的、物理的に?)正しいプロジェクションなのかは分かりません。
ソースコードは最後に掲載していますが、流れとしては、以下の通りです。プロジェクタを投影対象に向けて、投影対象とPCのモニタを見ながら頑張って位置合わせをしていきます。
- 映像を表示させたい領域を大まかに選択します
- 四角形の中に白と灰色の縞模様が現れるけど、個々の縞が曲面を近似する平面になります
- 四角形の四隅をドラッグして表示領域の隅を決めます
- aキーを押すと、四角形の周囲にある青い点を上下に動かす事が出来るので、これを使って表示領域を調整します
- 形状を調整する青い点は、隅になるほど密度を増やしています(点の位置は決めうちのおおざっばです。もっと妥当な間隔にするのは面倒そうです)
- dを押すと映像が表示されます
- cを押すと表示領域をクリア出来ます
投影対象を直接見て位置合わせをする考え方はこの前遊んだmapamokを参照してます。角や辺が明確な直方体と違って、円柱は端が明確でないので、精度が微妙になるかと。
また対象が少しでもずれたり、プロジェクタが少しでもずれると残念な事になります。
もっとうまい方法があるんじゃないかなあと、思ったりします、、、
実際に車とか建物に投影する場合はどうやっているのか、大変気になります。カメラとか使用して、画像認識的な手法も使って効率化してるんでしょうか??うーん。
また対象が少しでもずれたり、プロジェクタが少しでもずれると残念な事になります。
もっとうまい方法があるんじゃないかなあと、思ったりします、、、
実際に車とか建物に投影する場合はどうやっているのか、大変気になります。カメラとか使用して、画像認識的な手法も使って効率化してるんでしょうか??うーん。
コードはProcessing。だんだん面倒くさくなって数字がベタ書きになっていきます。。。まどろっこしい書き方をしているので無駄に行も長いです。
久しぶりに使ってみると、ProcessingがJavaじゃなくて、PerlやPythonのようなもっとゆるゆるな感じで書けたらなあ、と思わないでもないです。もっとも、Javaを忘れつつあるだけなんですが。
久しぶりに使ってみると、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にも置いてみました。
0 件のコメント:
コメントを投稿