### Light Transport

Some interesting patterns:
- Light Transport (variation)
- Light Transport (variation)
- Light Transport (variation)

```// LL 2021

const rays = 70000; // min=100 max=200000 step=1
const objects = 15; // min=0 max=50 step=1
const object_radius = 10; // min=1 max=30 step=1
const reflection_factor = 0.9; // min=0 max=1 step=0.001

const seed=0; /// min=0 max=100 step=1

Canvas.setpenopacity(-Math.max(0.05, 1/rays));
//Canvas.setpenopacity(-0.1);

const turtle = new Turtle();

const ray_x = 0; // min=-100 max=100 step=1
const ray_y = 0; // min=-100 max=100 step=1
const ray_dir = -1; // min=0 max=1 step=0.001
const ray_spread = 0.01; // min=0 max=1 step=0.001

const origin_x = ray_x;
const origin_y = ray_y;
const origin_dir_x = Math.cos((ray_dir<0?random():ray_dir) * Math.PI * 2);
const origin_dir_y = Math.sin((ray_dir<0?random():ray_dir) * Math.PI * 2);

const draw_objects=1; // min=0 max=1 step=1 (No,Yes)

var whole_scene;
var rng;
var current_ray = null;

console.clear();

function walk(i, t) {
if (i==0) {
rng = new RNG(seed);
whole_scene = new Scene();
}

if (draw_objects) whole_scene.draw();

if (current_ray === null || current_ray.done) {
const dir_x = t<1 ? Math.cos(t * Math.PI * 2) : origin_dir_x;
const dir_y = t<1 ? Math.sin(t * Math.PI * 2) : origin_dir_y;
current_ray = new Ray(whole_scene, origin_x, origin_y, dir_x, dir_y, origin_spread);
}

whole_scene.shoot(current_ray);

return i+1 < rays;
}

class Sphere {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
}

draw() {
turtle.jump(this.x + this.r, this.y);
const step = 1/20;
for (var a=step; a<1+step*0.1; a+=step) {
turtle.goto(this.x + Math.cos(a * Math.PI * 2) * this.r, this.y + Math.sin(a * Math.PI * 2) * this.r);
}
}

getIntersection(ray) {
const fx = ray.x - this.x, fy = ray.y - this.y;
const a = dot(ray.dir_x, ray.dir_y, ray.dir_x, ray.dir_y);
const b = 2 * dot(fx, fy, ray.dir_x, ray.dir_y);
const c = dot(fx, fy, fx, fy) - this.r * this.r;

const discriminant2 = b * b - 4 * a * c;
if (discriminant2 < 0) {
return false;
} else {
const discriminant = Math.sqrt(discriminant2);
const t1 = (-b - discriminant) / (2 * a);
const t2 = (-b + discriminant) / (2 * a);

return (t1 > 0.1) ? t1 : (t2 > 0.1) ? t2 : false;
}
}

getNormalAtPoint(x, y) {
var nx = x - this.x, ny = y - this.y;
const len = Math.hypot(nx, ny);
if (len > 0.01) {
nx /= len;
ny /= len;
}
return [ nx, ny ];
}
}

class Scene {
constructor() {
this.objects = [];

const d = 65;
for (var a=0; a<1; a+=1/objects) {
this.objects.push(
new Sphere(
d * Math.cos(a * Math.PI * 2 - Math.PI / 2),
d * Math.sin(a * Math.PI * 2 - Math.PI / 2),
r)
);
}
}

draw() {
this.objects.forEach(o => o.draw());
}

shoot(ray) {
if (ray.done) return;

var closest_object = null;
var min_t = 1000;

this.objects.forEach(o => {
const t = o.getIntersection(ray);
if (t && (t < min_t)) {
min_t = t;
closest_object = o;
}
});

turtle.jump(ray.x, ray.y);
const new_x = ray.x + ray.dir_x * min_t;
const new_y = ray.y + ray.dir_y * min_t;
turtle.goto(new_x, new_y);

if (closest_object !== null) {
const normal = closest_object.getNormalAtPoint(new_x, new_y);
ray.reflect(new_x, new_y, normal[0], normal[1]);
} else {
ray.done = true;
}
}
}

class Ray {
constructor(scene, x, y, dx, dy, ds) {
this.x = x;
this.y = y;
this.done = false;
this.scene = scene;
const angle = random() * ds - ds / 2;
this.dir_x = rotX(dx, dy, angle);
this.dir_y = rotY(dx, dy, angle);
const len = Math.hypot(this.dir_x, this.dir_y);
if (len < 0.001) this.done = true;
this.dir_x /= len;
this.dir_y /= len;
}

reflect(x, y, nx, ny) {
this.x = x;
this.y = y;

const dir = random() < reflection_factor ? 1 : -1;
const dotDN = dot(this.dir_x, this.dir_y, nx, ny);
const reflect_x = this.dir_x - 2 * dotDN * dir * nx;
const reflect_y = this.dir_y - 2 * dotDN * dir * ny;
this.dir_x = reflect_x;
this.dir_y = reflect_y;

}
}

function dot(v1x, v1y, v2x, v2y) { return v1x * v2x + v1y * v2y; }

function rotX(x, y, a) { return Math.cos(a) * x - Math.sin(a) * y; }
function rotY(x, y, a) { return Math.sin(a) * x + Math.cos(a) * y; }

function random() { return Math.random(); }
//function random() { return rng.nextFloat(); }

// Minified Random Number Generator from https://turtletoy.net/turtle/ab7a7e539e
function RNG(t){return new class{constructor(t){this.m=2147483648,this.a=1103515245,this.c=12345,this.state=t||Math.floor(Math.random()*(this.m-1))}nextFloat(){return this.state=(this.a*this.state+this.c)%this.m,this.state/(this.m-1)}}(t)}
```