Panorama warping cylinder

Hello, i would like to create this warp


(source: http://paulbourke.net/stereographics/cylinder/)

i Just found a way to use opencv but i am not super clear

do you know any way to create in real time the cylinder warping image?
I want to project in a cylinder structure

Thanks!

There are several ways to approach this. I think you want to use the Shapes3D library – perhaps the Tube type:

http://lagers.org.uk/s3d4p/index.html

If you want to use shaders, the Shaders tutorial also shows several examples of images on cylinders. The example uses texture on a PShape.

https://processing.org/tutorials/pshader/

1 Like

Thanks for your answer. My question is, for example, if I project in the cylinder through a texture a straight line, will it look curved or can I keep it straight?

Do you mean you are trying to project onto a real physical wall, and use processing to warp the projection (projection map) so it appears as if projected on a flat surface?

Is there only one audience member with a fixed point of view, or a distant audience? Because I think that optical illusion possible, but only from one perspective – moving would break the illusion.

1 Like

Yes, Cylinder phisical wall projectors inside the cylinder.
The cylinder will be in a theatre then the audience only could see one part of the cylinder.

No – for a large theater audience, you cannot have a cylinder on stage with theater seating and project a shape onto it that will look like a true flat rectangle (perspective illusion) to everyone. It will only look right to a few audience members – unless the cylinder is small and far away. The top and bottom edge will look warped for any audience members not sitting in the sweet spot.

To test – make a paper tube, project a square on it, and draw it. Now prop it up and see what angles the illusion works from.

However, you CAN project from an angle and correct it to look like a wrapped label – eg a soup can label. That will should look “the same” to everyone – they will all see a different wrapping, but it will look normal, like a soup can, curved in z but straight in xy.

1 Like

thanks Jeremy, then , what its the best way to do this?.

If your only goal is to have the top and bottom borders of your image “parralel” to the top and bottom borders of the cylinder you are projecting on (i.e. the borders of your image will always be at the same distance to the borders of the cylinder) and the same for the right and left edges then yes it would work even for a large audience.

To acheive that effect, you could probably ray-trace with real life measurement (a bit like it is done with the first pic you posted) to project the transformation on a plane.

Once you have those points on the projected plane, you have a displacement grid that you can use on normal image to deform it the correct way.

Hope it helps.

1 Like

Yes, I think that i need this. Then I need to create a cylinder mesh in Processing, that ray-trace to the plane like this
/http://paulbourke.net/stereographics/cylinder/ but in a 3D space

to move a ball or a square in polar coordinates, 4 example.
But i don’t know how to do this :frowning:

I have made something similar in the past with plenty of helper functions to find intersection between line and plane, between line and line etc…

I don’t have it know, but I can post it here this evening (evening for me at least ^^)

It should help you do it.

1 Like

Okay, so it’s a bit of a mess since it was something I was doing simply to get my head around something.

Basically the Line class and the Plan class are the one that you should aim for. They get a bunch of features that can be really helpful for you.

The Film class is there to draw the cylinder shape that you can see. The mirror is not shown but is basically a plan at 45° angle on which I compute bouncing light rays. The reference class is just there to draw The X, Y and Z axis.

Here you go:

import peasy.*;

PeasyCam cam;
Film film;
Reference ref;
Plan mirror, screen;
Line[] sourceBeams, reflectionBeams, displaySourceBeams, displayReflectionBeams;
PVector[] mirrorHits, screenHits;
float[] pointQuality;
float focalLength;

public void settings() {
  //size(800, 600, P3D);
  fullScreen(P3D);
}

public void setup() {  
  cam = new PeasyCam(this, 400);

  focalLength = 0.3*0.3*750;
  film = new Film(120, 60, focalLength, 1, 4);
  ref = new Reference(400);
  mirror = new Plan(0, -1, 1, new PVector(0, 20, 10));
  screen = new Plan(0, 0, 1, new PVector(0, 0, 35));

  PVector[][] filmVertices = film.getVertices();
  sourceBeams = new Line[filmVertices.length * filmVertices[0].length];
  displaySourceBeams = new Line[filmVertices.length * filmVertices[0].length];
  mirrorHits = new PVector[filmVertices.length * filmVertices[0].length];
  reflectionBeams = new Line[filmVertices.length * filmVertices[0].length];
  displayReflectionBeams = new Line[filmVertices.length * filmVertices[0].length];
  screenHits = new PVector[filmVertices.length * filmVertices[0].length];
  pointQuality = new float[filmVertices.length * filmVertices[0].length];

  for (int j = 0; j < filmVertices[0].length; j++) {
    for (int i = 0; i < filmVertices.length; i++) {
      sourceBeams[j * filmVertices.length + i] = new Line("POINT", new PVector(0, 0, 0), filmVertices[i][j].copy());
    }
  }

  computeRays();
}

void draw() {
  background(20);
  //scale(-1, 1, 1);
  lights();
  ref.show();
  film.show();
  screen.show();
  //mirror.show();
  //sourceBeams[8].show();
  //reflectionBeams[8].show();
  for (int i = 0; i < sourceBeams.length; i++) {
    displaySourceBeams[i].show();
    drawPoint(mirrorHits[i], color(200, 20, 200));
    displayReflectionBeams[i].show();
    drawPoint(screenHits[i], getQualityColor(pointQuality[i]));
  }
  //displaySourceBeams[0].show();
  //drawPoint(mirrorHits[0], color(200, 20, 200));
  //displayReflectionBeams[0].show();
  //drawPoint(screenHits[0], getQualityColor(pointQuality[0]));
}

void drawPoint(PVector vect, color col) {
  stroke(col);    
  fill(col);

  pushMatrix();
  translate(vect.x, vect.y, vect.z);
  sphere(0.5);
  popMatrix();
}

void computeRays() {
  PVector[][] filmVertices = film.getVertices();
  for (int j = 0; j < filmVertices[0].length; j++) {
    for (int i = 0; i < filmVertices.length; i++) {
      mirrorHits[j * filmVertices.length + i] = sourceBeams[j * filmVertices.length + i].getIntersectionPointWith(mirror);
      displaySourceBeams[j * filmVertices.length + i] = new Line("POINT", new PVector(0, 0, 0), mirrorHits[j * filmVertices.length + i].copy());
      reflectionBeams[j * filmVertices.length + i] = sourceBeams[j * filmVertices.length + i].getReflexionLineWih(mirror);
      screenHits[j * filmVertices.length + i] = reflectionBeams[j * filmVertices.length + i].getIntersectionPointWith(screen);
      displayReflectionBeams[j * filmVertices.length + i] = new Line("POINT", mirrorHits[j * filmVertices.length + i].copy(), screenHits[j * filmVertices.length + i].copy());
      pointQuality[j * filmVertices.length + i] = getDeltaFromFocus(mirrorHits[j * filmVertices.length + i], screenHits[j * filmVertices.length + i]);
    }
  }
}

void keyPressed() {
  if (keyCode == 129) {
    screen.movePlan(0, 0, -1);
  } else if (keyCode == 135) {
    screen.movePlan(0, 0, 1);
  } else if (keyCode == 131) {
    mirror.movePlan(0, 0, -1);
  } else if (keyCode == 137) {
    mirror.movePlan(0, 0, 1);
  }
  computeRays();
}

float getDeltaFromFocus(PVector mirrorPt, PVector screenPt) {
  float d, d1, d2;

  d1 = dist(0, 0, 0, mirrorPt.x, mirrorPt.y, mirrorPt.z);
  d2 = dist(mirrorPt.x, mirrorPt.y, mirrorPt.z, screenPt.x, screenPt.y, screenPt.z);
  d = d1 + d2;

  return abs(d - focalLength);
}

color getQualityColor(float delta) {
  float value = constrain(delta, 0, 20);
  value /= 20;

  return lerpColor(color(0, 255, 0), color(255, 0, 0), value);
}


class Film {
  private float w, h, d;
  private int heightSubLevel, widthSubLevel;
  //private ArrayList<PVector> vertices;
  private PVector[][] vertices;

  public Film(float w, float h, float d, int heightSubLevel, int widthSubLevel) {
    setupFilm(w, h, d, heightSubLevel, widthSubLevel);
  }

  private void setupFilm(float w, float h, float d, int heightSubLevel, int widthSubLevel) {
    this.w = w;
    this.h = h;
    this.d = d;
    this.heightSubLevel = heightSubLevel;
    this.widthSubLevel = widthSubLevel;
    computVertices();
  }

  private void computVertices() {
    int heighSub = (int)(pow(2, heightSubLevel));
    int widthSub = (int)(pow(2, widthSubLevel));

    PVector[] horSlice = new PVector[widthSub + 1];

    float filmOpeningAngle = w / d;
    float angleStep = filmOpeningAngle / widthSub;    
    float zStep = h / heighSub;

    float alpha = HALF_PI + (filmOpeningAngle / 2);
    for (int i = 0; i < horSlice.length; i++) {
      horSlice[i] = new PVector(d * cos(alpha), d * sin(alpha));
      alpha -= angleStep;
    }

    vertices = new PVector[widthSub + 1][heighSub + 1];
    float z = h / 2;
    for (int j = 0; j < heighSub + 1; j++) {
      for (int i = 0; i < horSlice.length; i++) {
        vertices[i][j] = new PVector(horSlice[i].x, horSlice[i].y, z);
      }
      z -= zStep;
    }
  }

  public void show() {
    fill(200);
    stroke(0);
    strokeWeight(1);
    for (int j = 0; j < vertices[0].length - 1; j++) { 
      for (int i = 0; i < vertices.length - 1; i++) {
        beginShape(QUADS);
        vertex(vertices[i][j].x, vertices[i][j].y, vertices[i][j].z);
        vertex(vertices[i][j+1].x, vertices[i][j+1].y, vertices[i][j+1].z);
        vertex(vertices[i+1][j+1].x, vertices[i+1][j+1].y, vertices[i+1][j+1].z);
        vertex(vertices[i+1][j].x, vertices[i+1][j].y, vertices[i+1][j].z);
        endShape();
      }
    }
    endShape();
  }
  
  public PVector[][] getVertices() {
    PVector[][] result = new PVector[vertices.length][vertices[0].length];
    for (int i = 0; i < vertices.length; i++) {
      for (int j = 0; j < vertices[0].length; j++) {
        result[i][j] = vertices[i][j].copy();
      }
    }
    return result;
  }
}


class Line {
  private PVector P0, P1, dir;

  public Line(String initType, PVector P0, PVector P1) {
    this.P0 = P0;

    if (initType == "POINT") {
      this.dir = P1.copy().sub(P0.copy()).normalize();
      this.P1 = P1.copy();
    } else if (initType == "DIR") {
      this.dir = P1.copy();
      this.P1 = new PVector(P0.x + 300*dir.x, P0.y + 300*dir.y, P0.z + 300*dir.z);
    } else {
      println("Error!");
    }
  }

  public void show() {
    stroke(81, 255, 162);
    strokeWeight(1);
    line(P0.x, P0.y, P0.z, P1.x, P1.y, P1.z);
  }

  public PVector getIntersectionPointWith(Plan plan) {
    float a = plan.getCoef()[0];
    float b = plan.getCoef()[1];
    float c = plan.getCoef()[2];
    float d = plan.getCoef()[3];

    float lambda = - (a * P0.x + b * P0.y + c * P0.z + d) / (a * dir.x + b * dir.y + c * dir.z);

    float x = P0.x + lambda * dir.x;
    float y = P0.y + lambda * dir.y;
    float z = P0.z + lambda * dir.z;

    return new PVector(x, y, z);
  }
  
  public PVector getDir() {
    return dir.copy();
  }
  
  //public Line getProjectionLineOn(Plan plan) {
  //  PVector proj1, proj2;
  //  Line line1, line2;
    
  //  //Get the axis of rotation
  //  float a = plan.getCoef()[0];
  //  float b = plan.getCoef()[1];
  //  float c = plan.getCoef()[2];
  //  PVector planNormal = new PVector(a, b, c);
    
  //  line1 = new Line("DIR", this.P0, planNormal);
  //  line2 = new Line("DIR", this.P1, planNormal);
    
  //  proj1 = line1.getIntersectionPointWith(plan);
  //  proj2 = line2.getIntersectionPointWith(plan);
    
  //  return new Line("POINT", proj1, proj2);
  //}
  
  public Line getReflexionLineWih(Plan plan) {
    // Get the point of intersection with the plan
    PVector I = this.getIntersectionPointWith(plan);
    
    //Get the plan normal
    float a = plan.getCoef()[0];
    float b = plan.getCoef()[1];
    float c = plan.getCoef()[2];
    PVector planNormal = new PVector(a, b, c);
    
    //Get line projection on plan direction
    //Line beamProjectionLine = this.getProjectionLineOn(mirror);
    
    // Get rotation axis
    PVector rotAxis = this.dir.copy().cross(planNormal).normalize();
    
    // Get the angle of rotation
    float theta = acos(dir.copy().dot(planNormal) / (dir.mag() * planNormal.mag()));
    theta = degrees(theta) - 90;
    float rotAngle = HALF_PI + radians(theta);
    
    // Get the matrix of rotation
    float[][] rotMatrix = new float[3][3];
    rotMatrix[0][0] = rotAxis.x * rotAxis.x * (1 - cos(rotAngle)) + cos(rotAngle);
    rotMatrix[1][0] = rotAxis.x * rotAxis.y * (1 - cos(rotAngle)) + rotAxis.z * sin(rotAngle);
    rotMatrix[2][0] = rotAxis.x * rotAxis.z * (1 - cos(rotAngle)) - rotAxis.y * sin(rotAngle);
    
    rotMatrix[0][1] = rotAxis.x * rotAxis.y * (1 - cos(rotAngle)) - rotAxis.z * sin(rotAngle);
    rotMatrix[1][1] = rotAxis.y * rotAxis.y * (1 - cos(rotAngle)) + cos(rotAngle);
    rotMatrix[2][1] = rotAxis.y * rotAxis.z * (1 - cos(rotAngle)) + rotAxis.x * sin(rotAngle);
    
    rotMatrix[0][2] = rotAxis.x * rotAxis.z * (1 - cos(rotAngle)) + rotAxis.y * sin(rotAngle);
    rotMatrix[1][2] = rotAxis.y * rotAxis.z * (1 - cos(rotAngle)) - rotAxis.x * sin(rotAngle);
    rotMatrix[2][2] = rotAxis.z * rotAxis.z * (1 - cos(rotAngle)) + cos(rotAngle);
    
    // Get the direction of the reflexion beam
    float x, y, z;
    x = rotMatrix[0][0] * planNormal.x + rotMatrix[0][1] * planNormal.y + rotMatrix[0][2] * planNormal.z;
    y = rotMatrix[1][0] * planNormal.x + rotMatrix[1][1] * planNormal.y + rotMatrix[1][2] * planNormal.z;
    z = rotMatrix[2][0] * planNormal.x + rotMatrix[2][1] * planNormal.y + rotMatrix[2][2] * planNormal.z;
    
    PVector reflexionDir = new PVector(-x, -y, -z);
    
    return new Line("DIR", I, reflexionDir);
  }
}


class Mirror extends Plan {
  private float w, h;
  private PVector[] vertices;
  private PVector center;
  
  public Mirror(float a, float b, float c, PVector P, float w, float h) {
    super(a, b, c, P);
    this.w = w;
    this.h = h;
    this.center = P;
    
    computeVertices();
  }
  
  private void computeVertices() {
    PVector U, V, N, up;
    
    N = new PVector(super.a, super.b, super.c);
    N.normalize();
    up = new PVector(0, 0, 1);
    U = up.cross(N);
    U.normalize();
    V = U.cross(N);
    V.normalize();
    
    vertices = new PVector[4];
    vertices[0] = U.copy().mult(-w/2).add(V.copy().mult(h/2)).add(center);
    vertices[1] = U.copy().mult(w/2).add(V.copy().mult(h/2)).add(center);
    vertices[2] = U.copy().mult(w/2).add(V.copy().mult(-h/2)).add(center);
    vertices[3] = U.copy().mult(-w/2).add(V.copy().mult(-h/2)).add(center);
  }
  
  public void show() {
    fill(40, 145, 237);
    stroke(0);
    strokeWeight(1);
    
    beginShape(QUADS);
    vertex(vertices[0].x, vertices[0].y, vertices[0].z);
    vertex(vertices[1].x, vertices[1].y, vertices[1].z);
    vertex(vertices[2].x, vertices[2].y, vertices[2].z);
    vertex(vertices[3].x, vertices[3].y, vertices[3].z);
    endShape();
  }
}


class Plan {
  // Plan of equation ax + by + cz + d = 0;
  private float a, b, c, d;
  private PVector[] vertices;
  private PVector P;

  public Plan(float a, float b, float c, PVector P) {
    this.a = a;
    this.b = b;
    this.c = c;    
    this.P = P.copy();

    d = - (a * P.x + b * P.y + c * P.z);
    vertices = new PVector[4];
    computeVertices();
  }

  private void computeVertices() {
    if (a == 0 && b == 0) {
      vertices[0] = new PVector(-200, 200, -d / c);
      vertices[1] = new PVector(200, 200, -d / c);
      vertices[2] = new PVector(200, -200, -d / c);
      vertices[3] = new PVector(-200, -200, -d / c);
    } else {
      vertices[0] = new PVector(-200, -(a * -200 + c * 200 + d) / b, 200);
      vertices[1] = new PVector(200, -(a * 200 + c * 200 + d) / b, 200);
      vertices[2] = new PVector(200, -(a * 200 + c * -200 + d) / b, -200);
      vertices[3] = new PVector(-200, -(a * -200 + c * -200 + d) / b, -200);
    }
  }
  
  public void movePlan(float dx, float dy, float dz) {
    P = new PVector(P.x + dx, P.y + dy, P.z + dz);
    d = - (a * P.x + b * P.y + c * P.z);
    computeVertices();
  }

  public void show() {
    fill(40, 145, 237, 0);
    stroke(0);
    strokeWeight(1);

    beginShape(QUADS);
    vertex(vertices[0].x, vertices[0].y, vertices[0].z);
    vertex(vertices[1].x, vertices[1].y, vertices[1].z);
    vertex(vertices[2].x, vertices[2].y, vertices[2].z);
    vertex(vertices[3].x, vertices[3].y, vertices[3].z);
    endShape();
  }

  public float[] getCoef() {
    float[] result = new float[4];
    result[0] = a;
    result[1] = b;
    result[2] = c;
    result[3] = d;

    return result;
  }
}


class Reference {
  private float size;
  
  public Reference(float size) {
    this.size = size;
  }
  
  public void show() {
    strokeWeight(1);
    
    // x in red
    stroke(200, 20, 20);    
    fill(200, 20, 20);
    
    line(-size/2,0,0,size/2,0,0);
    
    pushMatrix();
    translate(size/2, 0, 0);
    sphere(3);
    popMatrix();
    
    // y in green
    stroke(20, 200, 20);    
    fill(20, 200, 20);
    
    line(0,-size/2,0,0,size/2,0);
    
    pushMatrix();
    translate(0, size/2, 0);
    sphere(3);
    popMatrix();
    
    
    // z in blue
    stroke(20, 20, 200);    
    fill(20, 20, 200);
    
    line(0,0,-size/2,0,0,size/2);
    
    pushMatrix();
    translate(0, 0, size/2);
    sphere(3);
    popMatrix();
  }
}
1 Like

Thnks 4 the Code & The explanation.
I Have some questions:
How can I use this in my case? for example to rotate a Cube around the Cylinder and and project it .I am going to use 4 projectors. And I want to pass after to MadMapper for the Blending.
I Don’t know where can i Draw my 3d scene in your system.
THanks!

I thought you were projecting onto a physical cylinder in a theater space from a fixed projector. What is your “3D scene”? Do you mean the position and angle of the projector?

This is great!

What are the keyCodes you map in keyPressed()? It would be helpful to comment them with their keyboard equivalents, or to add a comment to the top about what the UI options are. It looks like two of them move the projector, and two of them move the projection surface.

I will project with 4 projectors. The projectors (beamers) will be inside the cylinder.
The 3d scene i mean in digital way. To have for example a shape moving around the circle .
46

How are you mapping sketch output to the four projectors? Are you running four synchronized sketches, each controlling one projector and each drawing part of the surface? Or are you running one sketch drawing to one stretched video output in a long rectangle, with each projector showing part of that rectangle?

I am going to mapping with Spout > madmapper or this above tool
I am going to use one 3d sketch , then i need to put the 3d surface in a long rectangle and each projector showing part of that

The first idea was to use this


but its for equirectangular projections.
I would need the same but for Cylindrical projection

One approach would be to use SurfaceMapper – and possibly try using the SurfaceMapperGUI (may need to be updated). The problem with an algorithmic solution in this case is that very tiny variations in your cylinder surface or in the angle of your projection mounts will cause a pure geometric approach to create distortion. An alternative is to create a segmented, bezier-curve controlled surface that you can tweak at installation time and save the settings, like this:

The GUI might need to be updated for Processing 3 – I haven’t tried it.

Another old (deprecated) mapping library for Processing that supports curves – MappingTools

http://www.patricksaintdenis.com/mappingtools/

Check out in particular BezierWarp and ClusterBezierWarp.

A more up-to-date projection mapping library is Keystone, but it lacks the curve warping support that you will need. Perhaps the curve code from SurfaceMapper could be ported to KeyStone…

https://fh-potsdam.github.io/doing-projection-mapping/processing-keystone/