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();
}
}