<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js" integrity="sha512-3RlxD1bW34eFKPwj9gUXEWtdSMC59QqIqHnD8O/NoTwSJhgxRizdcFVQhUMFyTp5RwLTDL0Lbcqtl8b7bFAzog==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
body {margin:0px; padding:0px; overflow: hidden}
const maxReflection = 32;
let mirrors;
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
updateMirrors();
}
function updateMirrors() {
mirrors = [];
const w = width;
const r = w / 4;
const l = -w / 4;
const rn = w / 20 * random(1);
const N = 16;
const my = [];
for (let i = 0; i < N + 1 ; i ++) {
my.push(height / 4 + random(-rn, rn));
}
for (let i = 0; i < N; i ++) {
const t0 = i / N;
const t1 = (i + 1) / N;
p0 = createVector(lerp(l, r, t0), my[i]);
p1 = createVector(lerp(l, r, t1), my[i + 1]);
mirrors.push(new LineSegment().fromTwoPoints(p0, p1));
}
}
function hitTest(o, d, lineSegments, ignore) {
let intersection, hitObject, minDistance = Infinity;
const ray = new Line().fromPointAndVector(o, d);
lineSegments.forEach((ls)=>{
if (ls != ignore && ls.intersects(ray)) {
const temp = ls.getIntersectionPoint(ray);
const dot = d.dot(temp.copy().sub(o));
const dist = o.dist(temp);
if (dot >= 0 && dist < minDistance) {
intersection = temp;
minDistance = dist;
hitObject = ls;
}
}
});
return intersection ? {point: intersection, object: hitObject} : null;
}
function reflect(i, n) {
return i.copy().add(n.copy().mult(i.copy().mult(2).dot(n) / pow(n.mag(), 2) * -1));
}
function draw() {
clear();
background(235, 255, 160);
const l = max(width, height);
const angle = frameCount / 60 * PI;
if (frameCount % 120 == 0) { updateMirrors();}
let origin = createVector(0, -height * 0.75);
let target = createVector(sin(frameCount / 120 * PI) * width * 0.24, height * 0.25);
let dir = target.copy().sub(origin).normalize();
const points = [origin];
let currentMirror = null;
for (let i = 0; i < maxReflection; i ++) {
const hitResult = hitTest(origin, dir, mirrors, currentMirror);
if (hitResult) {
points.push(hitResult.point);
currentMirror = hitResult.object;
const p0 = hitResult.object.p0;
const p1 = hitResult.object.p1;
normal = createVector(
-(p0.y - p1.y),
p0.x - p1.x
).normalize();
normal = (normal.dot(dir) > 0) ? normal.mult(-1) : normal;
origin = hitResult.point;
dir = reflect(dir, normal);
} else {
points.push(origin.copy().add(dir.copy().mult(l * 2)));
break;
}
}
push();
translate(width / 2, height / 2);
fill(255); stroke(0); strokeWeight(1);
for (let i = 1; i < points.length; i++) {
line(points[i - 1].x, points[i - 1].y, points[i].x, points[i].y);
}
strokeWeight(3);
mirrors.forEach((m)=>{m.draw();});
fill(0); noStroke(0);
points.forEach((p)=>{drawCircleMarker(p, 4);})
pop();
}
class Line {
constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
fromTwoPoints(p0, p1) {
let dx = p1.x - p0.x;
let dy = p1.y - p0.y;
this.a = dy;
this.b = -dx;
this.c = dx * p0.y - dy * p0.x;
return this;
}
fromPointAndAngle(p0, angle) {
let p1 = {x: p0.x + cos(angle), y: p0.y + sin(angle)};
return this.fromTwoPoints(p0, p1);
}
fromPointAndVector(p0, v) {
let p1 = {x: p0.x + v.x, y: p0.y + v.y};
return this.fromTwoPoints(p0, p1);
}
intersects(o) {
if (o instanceof Line) {
let d = this.a * o.b - o.a * this.b;
return d != 0.0;
} else if (o instanceof LineSegment) {
let t1 = this.a * o.p0.x + this.b * o.p0.y + this.c;
let t2 = this.a * o.p1.x + this.b * o.p1.y + this.c;
return t1 * t2 <= 0;
}
return undefined;
}
getIntersectionPoint(o) {
if (o instanceof Line) {
let d = this.a * o.b - o.a * this.b;
if (d == 0.0) { return undefined; }
let x = (this.b * o.c - o.b * this.c) / d;
let y = (o.a * this.c - this.a * o.c) / d;
return createVector(x, y);
} else if (o instanceof LineSegment) {
if (!this.intersects(o)) { return undefined; }
return this.getIntersectionPoint(o.toLine());
}
return undefined;
}
getAngle() {
return atan2(this.a, -this.b);
}
getPerpendicular(p) {
return new Line(this.b, -this.a, this.a * p.y - this.b * p.x);
}
getParallel(p) {
return new Line(this.a, this.b, -this.a * p.x - this.b * p.y);
}
getNearestPoint(p) {
let l = this.getPerpendicular(p);
return this.getIntersectionPoint(l);
}
draw(rect) {
if (!rect) { rect = {x:0, y:0, w:0, h:0}}
let l0, l1;
if (abs(this.a) > abs(this.b)) {
l0 = new Line().fromTwoPoints({x:rect.x, y:rect.y}, {x:rect.x + width, y:rect.y});
l1 = new Line().fromTwoPoints({x:rect.x, y:rect.y + height}, {x:rect.x + width, y:height});
} else {
l0 = new Line().fromTwoPoints({x:rect.x, y:rect.y}, {x:rect.x, y:height});
l1 = new Line().fromTwoPoints({x:rect.x + width, y:rect.y}, {x:rect.x + width, y:rect.y + height});
}
let p0 = this.getIntersectionPoint(l0);
let p1 = this.getIntersectionPoint(l1);
line(p0.x, p0.y, p1.x, p1.y);
}
}
class LineSegment {
constructor(x0, y0, x1, y1) {
this.p0 = createVector(x0, y0);
this.p1 = createVector(x1, y1);
}
fromTwoPoints(p0, p1) {
this.p0 = p0;
this.p1 = p1;
return this;
}
fromTwoPointsAndLength(p0, p1, length) {
this.p0 = p0;
let n = p1.copy().sub(p0).normalize();
this.p1 = n.mult(length).add(p0);
return this;
}
toLine() {
return new Line().fromTwoPoints(this.p0, this.p1);
}
intersects(o) {
if (o instanceof Line) {
let t0 = o.a * this.p0.x + o.b * this.p0.y + o.c;
let t1 = o.a * this.p1.x + o.b * this.p1.y + o.c;
return t0 * t1 < 0;
} else if (o instanceof LineSegment) {
return this.intersects(o.toLine()) && o.intersects(this.toLine());
}
return undefined;
}
getIntersectionPoint(o) {
if (o instanceof Line) {
if (!this.intersects(o)) { return undefined; }
return o.getIntersectionPoint(this.toLine());
} else if (o instanceof LineSegment) {
if (!this.intersects(o)) { return undefined; }
return o.toLine().getIntersectionPoint(this.toLine());
}
return undefined;
}
getAngle() {
return atan2(this.p1.y - this.p0.y, this.p1.x - this.p0.x);
}
getLength() {
return p0.dist(p1);
}
getNearestPoint(p) {
if (this.p1.copy().sub(this.p0).dot(p.copy().sub(this.p0)) < 0) return this.p0;
if (this.p0.copy().sub(this.p1).dot(p.copy().sub(this.p1)) < 0) return this.p1;
return this.toLine().getNearestPoint(p);
}
getBisection() {
let o = this.getMidPoint();
return this.toLine().getPerpendicular(o);
}
getMidPoint() {
return this.p0.copy().add(this.p1).mult(0.5);
}
getPerpendicular(p) {
return this.toLine().getPerpendicular(p);
}
getParallel(p) {
return this.toLine().getParallel(p);
}
draw() {
line(this.p0.x, this.p0.y, this.p1.x, this.p1.y);
}
}
function drawCircleMarker(p, size) {
ellipse(p.x, p.y, size * 2, size * 2);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.