```
///////////////////////////
// Materials //
///////////////////////////
Objekt = (collider, material, draw) => ({
collider: collider,
material: material,
draw: draw
})
line_source = (r0, r1) => () => {
var angle = Math.PI * Math.random()
return Ray(lerp(r0, r1, Math.random()), V2(Math.sin(angle), Math.cos(angle)));
}
shiny_material = (hit, ray) => {
return Ray(hit.pos, reflect(ray.dir, hit.normal));
}
absorbing_material = (hit, ray) => {
return null;
}
line_artist = (r0, r1) => (ctx) => {
ctx.strokeStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(...r0);
ctx.lineTo(...r1);
ctx.stroke();
}
line_collider = (r0, r1) => (ray) => {
var v1 = vsub(ray.pos, r0);
var v2 = vsub(r1, r0);
var v3 = perp(ray.dir);
var t1 = cross(v2, v1) / dot(v2, v3);
var t2 = dot(v1, v3) / dot(v2, v3);
if (0 <= t2 && t2 <= 1 && t1 > 0) {
var normal = normalize(perp(v2));
if (dot(normal, ray.dir) > 0) {
normal = vneg(normal);
}
return Hit(prop(ray, t1).pos, normal, t1);
}
else {
return null;
}
}
shiny_line = (r0, r1) => {
return Objekt(
line_collider(r0, r1),
shiny_material,
line_artist(r0, r1)
)
}
union = (...geoms) => ray => {
var bestHit = null;
geoms.forEach(geom => {
hit = geom(ray);
if (hit != null) {
if (bestHit == null || hit.t < bestHit.t) {
bestHit = hit;
}
}
});
return bestHit;
}
rect_collider = (x, y, w, h) => union(
line_collider(V2(x, y), V2(x + w, y)),
line_collider(V2(x + w, y), V2(x + w, y + h)),
line_collider(V2(x + w, y + h), V2(x, y + h)),
line_collider(V2(x, y + h), V2(x, y))
)
boundary = (x, y, w, h) => Objekt(
rect_collider(x, y, w, h),
absorbing_material,
(ctx) => { }
)
absorbing_rect = (x, y, w, h) => Objekt(
rect_collider(x, y, w, h),
absorbing_material,
(ctx) => {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.stroke();
}
)
```