世の中的情報によると、平面に対しては簡単にプロジェクションマッピングっぽい事が出来る、とのことなので、曲面(円柱の横側の部分、さらに言ってしまえばゴミ箱。。。)に対して似たような事が出来ないか試してみました。
VIDEO
実際やった事は、平面を複数用意して、曲面を平面で近似しているだけなので、話自体はそんなに難しい事ではないです。ただこれが(数学的、物理的に?) 正しいプロジェクションなのかは分かりません。
ソースコードは最後に掲載していますが、流れとしては、以下の通りです。プロジェクタを投影対象に向けて、投影対象と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にも置いてみました 。