xxxxxxxxxx
262
// Light source position
let lx;
let ly;
let lc;
let count;
let reflectors;
class Seg {
constructor(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
const d = dist(x1, y1, x2, y2);
this.nx = (y1 - y2) / d;
this.ny = (x2 - x1) / d;
}
intersect(x, y, dx, dy) {
const x3 = x + dx;
const y3 = y + dy;
const den = dy * (this.x2 - this.x1) - dx * (this.y2 - this.y1);
const numA = dx * (this.y1 - y3) - dy * (this.x1 - x3);
const numB =
(this.x2 - this.x1) * (this.y1 - y3) -
(this.y2 - this.y1) * (this.x1 - x3);
//Coincident? - If true, displays intersection in center of line segment
if (abs(numA) == 0 && abs(numB) == 0 && abs(den) == 0) {
return null;
}
//Parallel? - No intersection
if (abs(den) == 0) {
return null;
}
//Intersection?
const uA = numA / den;
const uB = numB / den;
if (uA < 0 || uA > 1 || uB < 0) {
return null;
}
++count;
return [
lerp(this.x1, this.x2, uA),
lerp(this.y1, this.y2, uA),
this.nx,
this.ny,
];
}
draw() {
stroke(255, 0, 0);
line(this.x1, this.y1, this.x2, this.y2);
}
}
class Arc {
constructor(cx, cy, r, ang1, ang2) {
this.cx = cx;
this.cy = cy;
this.r = r;
this.ang1 = ang1;
this.ang2 = ang2;
}
intersect(x, y, dx, dy) {
const pmcx = x - this.cx;
const pmcy = y - this.cy;
const A = 1.0;
const B = 2 * (pmcx * dx + pmcy * dy);
const C = sq(pmcx) + sq(pmcy) - sq(this.r);
const disc = B * B - 4 * A * C;
if (disc <= 0.0) {
return null;
}
const rs = [(-B + sqrt(disc)) / (2 * A), (-B - sqrt(disc)) / (2 * A)];
let bestx;
let besty;
let best_dist = 1e9;
for (let r of rs) {
if (r < 0.0) {
continue;
}
const px = x + r * dx;
const py = y + r * dy;
let ang = atan2(py - this.cy, px - this.cx);
while (ang < this.ang1) {
ang += TWO_PI;
}
if (ang < this.ang2) {
// Valid intersection!
if (r < best_dist) {
best_dist = r;
bestx = px;
besty = py;
}
}
}
if (best_dist < 1e9) {
++count;
return [
bestx,
besty,
(bestx - this.cx) / this.r,
(besty - this.cy) / this.r,
];
} else {
return null;
}
}
draw() {
noFill();
arc(this.cx, this.cy, 2 * this.r, 2 * this.r, this.ang1, this.ang2);
}
}
function randX() {
return width * random(0, 1);
}
function randY() {
return height * random(0, 1);
}
function createWorld() {
lx = width * random(-0.25, 1.25);
ly = height * random(-0.25, 1.25);
const r1 = random(TWO_PI);
reflectors = [
new Seg(randX(), randY(), randX(), randY()),
new Arc(
randX(),
randY(),
random(40, 200),
r1,
r1 + random(0.25 * PI, 1.5 * PI)
),
];
colorMode(HSB, 255);
lc = color(random(255), random(50, 100), 255);
background(random(255), random(20, 100), 50);
colorMode(RGB, 255);
count = 0;
}
function randRay() {
const ang = random(TWO_PI);
return [lx, ly, cos(ang), sin(ang)];
// return [lx,random(height), cos(ang),sin(ang)];
}
// If first is true, don't render the primary ray, only reflected rays.
function traceRay(first) {
let ray = randRay();
let alpha = 40.0;
for (let idx = 0; idx < 100; ++idx) {
let best;
let besti;
let best_dist = 1e9;
for (let r of reflectors) {
let isect = r.intersect(ray[0], ray[1], ray[2], ray[3]);
if (isect != null) {
const d = dist(ray[0], ray[1], isect[0], isect[1]);
if (d < best_dist) {
best = r;
best_dist = d;
besti = isect;
}
}
}
if (best != null) {
if (!first) {
stroke(red(lc), green(lc), blue(lc), alpha);
line(ray[0], ray[1], besti[0], besti[1]);
}
// Weighted random angle of reflection to simulate diffuse material
const xi = random(1);
let si = 2.0 * xi - 1.0;
let ci = sqrt(1.0 - si * si);
let nx = besti[2];
let ny = besti[3];
if (nx * (ray[0] - besti[0]) + ny * (ray[1] - besti[1]) < 0) {
nx = -nx;
ny = -ny;
}
let nrx = nx * ci - ny * si;
let nry = nx * si + ny * ci;
ray[0] = besti[0] + 0.001 * nrx;
ray[1] = besti[1] + 0.001 * nry;
ray[2] = nrx;
ray[3] = nry;
alpha *= 0.9;
} else {
if (!first) {
stroke(red(lc), green(lc), blue(lc), alpha);
line(ray[0], ray[1], ray[0] + 1000 * ray[2], ray[1] + 1000 * ray[3]);
}
}
if (alpha < 0.01 || best == null) {
break;
}
first = false;
}
}
function setup() {
createCanvas(512,512);
createWorld();
}
function draw() {
strokeWeight(0.25);
if (count < 10000) {
for (let idx = 0; idx < 50; ++idx) {
traceRay(true);
}
}
/*
fill(0);
rect( 0, 0, 100, 100 );
fill(255);
text( "" + count, 10, 50 );
// noLoop();
stroke(255,0,0);
for (let r of reflectors) {
r.draw();
}
*/
}
function keyPressed() {
if (key == " ") {
createWorld();
} else if( key == 's' ) {
save( 'output.png' );
}
}