343 lines
12 KiB
HTML
343 lines
12 KiB
HTML
<!--
|
|
This work is licensed under CC BY-NC-ND 4.0
|
|
Link to license: http://creativecommons.org/licenses/by-nc-nd/4.0/
|
|
Attribute to Russell Georgi
|
|
-->
|
|
<html>
|
|
<head>
|
|
<title>
|
|
Waves: Rainbow 2
|
|
</title>
|
|
<style>
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
margin: 0px;
|
|
border: 0;
|
|
overflow: hidden;
|
|
display: block;
|
|
}
|
|
|
|
canvas {
|
|
position: absolute;
|
|
}
|
|
|
|
form {
|
|
position: relative;
|
|
}
|
|
|
|
.slideContainer {
|
|
position: relative;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="myCanvas" width="1" height="1" style="border:1px solid #ffffff;">
|
|
Your browser does not support the HTML5 canvas tag.</canvas>
|
|
<form>
|
|
<label for="red">Red</label>
|
|
<input type="checkbox" id="red" onclick="redClick()">
|
|
<br>
|
|
<label for="blue">Blue</label>
|
|
<input type="checkbox" id="blue" onclick="blueClick()">
|
|
</form>
|
|
<div class="slidecontainer">
|
|
<t>Ray density</t>
|
|
<input type="range" min="0.09" max="0.2" value="0.14" step = "0.003" id="slider">
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
|
class Vector2 {
|
|
constructor (x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
scale(n)
|
|
{
|
|
this.x *= n;
|
|
this.y *= n;
|
|
}
|
|
|
|
get len()
|
|
{
|
|
return this.calcLen();
|
|
}
|
|
|
|
calcLen()
|
|
{
|
|
return (Math.sqrt(this.x * this.x + this.y * this.y));
|
|
}
|
|
}
|
|
|
|
class Circle {
|
|
constructor (x, y, r)
|
|
{
|
|
this.x = x;
|
|
this.y = y;
|
|
this.r = r;
|
|
}
|
|
}
|
|
|
|
var c = document.getElementById("myCanvas");
|
|
var ctx = c.getContext("2d");
|
|
ctx.canvas.width = window.innerWidth;
|
|
ctx.canvas.height = window.innerHeight;
|
|
|
|
var nn = 1.35;
|
|
var r = 25;
|
|
var cl4 = "#ee0000";
|
|
var cl17 = "#9999ff";
|
|
var cl15 = "#000000";
|
|
var red = false;
|
|
var blue = false;
|
|
var dtog = 0;
|
|
var x0 = 0;
|
|
var y0 = 60;
|
|
var x1 = 0;
|
|
var y1 = -60;
|
|
var dy = 10;
|
|
var nnn = 50;
|
|
var maxSteps = 18000;
|
|
var reflections = 0;
|
|
var maxReflections = 1;
|
|
var step = r / 10;
|
|
var xPos = 0;
|
|
var yPos = 0;
|
|
var red = false;
|
|
var blue = false;
|
|
var density = 0.14;
|
|
var circles = [];
|
|
|
|
slider.oninput = function()
|
|
{
|
|
density = parseFloat(this.value);
|
|
console.log(density);
|
|
Update();
|
|
}
|
|
|
|
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
|
|
ctx.scale(1.5, -1.5);
|
|
|
|
function Clear(ctx)
|
|
{
|
|
ctx.clearRect(-c.width, -c.height, c.width * 2, c.height * 2);
|
|
}
|
|
|
|
function Update()
|
|
{
|
|
Clear(ctx);
|
|
ctx.strokeStyle = "#000000";
|
|
ctx.beginPath();
|
|
ctx.arc(xPos + x0, yPos + y0, r, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
circles.push(new Circle(x0, y0, r));
|
|
ctx.beginPath();
|
|
ctx.arc(xPos + x1, yPos + y1, r, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
ctx.scale(1, -1);
|
|
ctx.fillText("Light from one reflection", -150, -30);
|
|
ctx.fillText("Light from two reflections", -50, 130);
|
|
ctx.fillText("Dark band", -155, 140);
|
|
ctx.scale(1, -1);
|
|
circles.push(new Circle(x1, y1, r));
|
|
for (h2 = 0; h2 <= 1; h2 += density)
|
|
{
|
|
if (h2 != 0 && h2 < 0.99999)
|
|
{
|
|
if (red)
|
|
{
|
|
maxReflections = 1;
|
|
Ray(new Vector2(1 - c.width, h2 * r + y0), new Vector2(1, 0), 1, 1.332, "#ee0000");
|
|
maxReflections = 2;
|
|
Ray(new Vector2(1 - c.width, - h2 * r + y1), new Vector2(1, 0), 1, 1.332, "#ee0000");
|
|
}
|
|
if (blue)
|
|
{
|
|
maxReflections = 1;
|
|
Ray(new Vector2(1 - c.width, h2 * r + y0), new Vector2(1, 0), 1, 1.35, "#9999ff");
|
|
maxReflections = 2;
|
|
Ray(new Vector2(1 - c.width, - h2 * r + y1), new Vector2(1, 0), 1, 1.35, "#9999ff");
|
|
}
|
|
}
|
|
}
|
|
/*Ray(new Vector2(1 - c.width, 2 * r / 3 + y0), new Vector2(1, 0.00), 1, 1.332, "#ee0000");
|
|
//Ray(new Vector2(r / 2, c.height - 1), new Vector2(-0.03, -1), 0.1, 1.332, "#ee0000");
|
|
Ray(new Vector2(1 - c.width, 2 * r / 3 + y0), new Vector2(1, 0.00), 1, 1.35, "#9999ff");
|
|
maxReflections = 2;
|
|
Ray(new Vector2(1 - c.width, - 15 * r / 16 + y1), new Vector2(1, 0.00), 1, 1.332, "#ee0000");
|
|
//Ray(new Vector2(r / 2, c.height - 1), new Vector2(-0.03, -1), 0.1, 1.332, "#ee0000");
|
|
Ray(new Vector2(1 - c.width, - 15 * r / 16 + y1), new Vector2(1, 0.00), 1, 1.35, "#9999ff");
|
|
//setTimeout(Update, 1000/60);*/
|
|
}
|
|
|
|
function Ray(startPos, startDir, distStep, nCircle, color)
|
|
{
|
|
pos = startPos;
|
|
dir = startDir;
|
|
n = 1;
|
|
nLastStep = 1;
|
|
nSteps = 0;
|
|
reflections = 0;
|
|
circlePos = new Vector2(0, 0);
|
|
for (i = 0; i < circles.length; i++)
|
|
{
|
|
dist = Math.sqrt(Math.pow(pos.x - circles[i].x, 2) + Math.pow(pos.y - circles[i].y, 2));
|
|
if (dist < r)
|
|
{
|
|
n = nCircle;
|
|
circlePos = new Vector2(circles[i].x, circles[i].y);
|
|
}
|
|
}
|
|
while (Math.abs(pos.x) <= c.width && Math.abs(pos.y) <= c.height && nSteps <= maxSteps)
|
|
{
|
|
nSteps += 1;
|
|
|
|
nCurrent = 1;
|
|
for (i = 0; i < circles.length; i++)
|
|
{
|
|
dist = Math.sqrt(Math.pow(pos.x - circles[i].x, 2) + Math.pow(pos.y - circles[i].y, 2));
|
|
if (dist < r)
|
|
{
|
|
nCurrent = nCircle;
|
|
circlePos = new Vector2(circles[i].x, circles[i].y);
|
|
}
|
|
}
|
|
if (nCurrent != n && nCurrent != nLastStep)
|
|
{
|
|
if (nCurrent < n && reflections < maxReflections)
|
|
{
|
|
dir = calculateReflectedDir(dir, pos, n, nCurrent, circlePos);
|
|
reflections += 1;
|
|
} else
|
|
{
|
|
dir = calculateRefractedDir(dir, pos, n, nCurrent, circlePos);
|
|
}
|
|
//console.log(dir, nCurrent, nSteps);
|
|
}
|
|
nLastStep = n;
|
|
n = nCurrent;
|
|
ctx.strokeStyle = color;
|
|
ctx.beginPath();
|
|
ctx.moveTo(pos.x, pos.y);
|
|
ctx.lineTo(pos.x + dir.x * distStep, pos.y + dir.y * distStep);
|
|
ctx.stroke();
|
|
pos.x += dir.x * distStep;
|
|
pos.y += dir.y * distStep;
|
|
}
|
|
}
|
|
|
|
function calculateRefractedDir(dir, pos, n1, n2, cPos)
|
|
{
|
|
//console.log("recalc");
|
|
posToCircle = new Vector2(pos.x - cPos.x, pos.y - cPos.y);
|
|
circleNormal = new Vector2(-posToCircle.x / posToCircle.len, -posToCircle.y / posToCircle.len);
|
|
/*ctx.strokeStyle = "#000000";
|
|
ctx.beginPath();
|
|
ctx.moveTo(pos.x + circleNormal.x * 10, pos.y + circleNormal.y * 10);
|
|
ctx.lineTo(pos.x - circleNormal.x * 10, pos.y - circleNormal.y * 10);
|
|
ctx.stroke();*/
|
|
if (n1 > n2)
|
|
{
|
|
circleNormal.x *= -1;
|
|
circleNormal.y *= -1;
|
|
}
|
|
theta1 = Math.acos((dir.x * circleNormal.x + dir.y * circleNormal.y) / (dir.len * circleNormal.len));
|
|
theta2 = Math.asin(n1 * Math.sin(theta1) / n2);
|
|
angle = theta2 - theta1;
|
|
angleDiff = polarCoords(dir).y - polarCoords(new Vector2(circleNormal.x, circleNormal.y)).y;
|
|
//console.log("calculate", angleDiff);
|
|
if (Math.abs(angleDiff) > 90)
|
|
{
|
|
angleDiff *= -1;
|
|
}
|
|
if (angleDiff < 0)
|
|
{
|
|
//console.log("flipr")
|
|
angle *= -1;
|
|
}
|
|
//console.log(angle, dir.x, dir.y, posToCircle.x, posToCircle.y, theta1, theta2);
|
|
return new Vector2(dir.x * Math.cos(angle) - dir.y * Math.sin(angle), dir.x * Math.sin(angle) + dir.y * Math.cos(angle));
|
|
}
|
|
|
|
function calculateReflectedDir(dir, pos, n1, n2, cPos)
|
|
{
|
|
//console.log("reflect");
|
|
posToCircle = new Vector2(pos.x - cPos.x, pos.y - cPos.y);
|
|
circleNormal = new Vector2(-posToCircle.x / posToCircle.len, -posToCircle.y / posToCircle.len);
|
|
ctx.strokeStyle = "#000000";
|
|
/*ctx.beginPath();
|
|
ctx.moveTo(pos.x + circleNormal.x * 10, pos.y + circleNormal.y * 10);
|
|
ctx.lineTo(pos.x - circleNormal.x * 10, pos.y - circleNormal.y * 10);
|
|
ctx.stroke();*/
|
|
if (n1 > n2)
|
|
{
|
|
circleNormal.x *= -1;
|
|
circleNormal.y *= -1;
|
|
}
|
|
theta1 = Math.acos((dir.x * circleNormal.x + dir.y * circleNormal.y) / (dir.len * circleNormal.len));
|
|
angle = Math.PI - 2 * theta1
|
|
angleDiff = polarCoords(dir).y - polarCoords(new Vector2(circleNormal.x, circleNormal.y)).y;
|
|
//console.log("calculate", angleDiff);
|
|
if (Math.abs(angleDiff) > 90)
|
|
{
|
|
angleDiff *= -1;
|
|
}
|
|
if (angleDiff < 0)
|
|
{
|
|
//console.log("flipr")
|
|
angle *= -1;
|
|
}
|
|
//console.log(angle, dir.x, dir.y, posToCircle.x, posToCircle.y);
|
|
return new Vector2(dir.x * Math.cos(angle) - dir.y * Math.sin(angle), dir.x * Math.sin(angle) + dir.y * Math.cos(angle));
|
|
}
|
|
|
|
window.addEventListener('resize', function(event) {
|
|
c.width = window.innerWidth;
|
|
c.height = window.innerHeight;
|
|
ctx.translate(c.width / 2, c.height / 2);
|
|
ctx.scale(1.5, -1.5);
|
|
Update();
|
|
}, true);
|
|
|
|
function polarCoords(pos)
|
|
{
|
|
theta = Math.atan(pos.y / pos.x);
|
|
if (pos.x < 0)
|
|
{
|
|
theta += Math.PI;
|
|
}
|
|
if (theta < 0)
|
|
{
|
|
theta += Math.PI * 2;
|
|
}
|
|
return new Vector2(pos.len, theta * 180 / Math.PI);
|
|
}
|
|
|
|
function redClick()
|
|
{
|
|
checkBox = document.getElementById("red");
|
|
red = checkBox.checked;
|
|
Clear(ctx);
|
|
Update();
|
|
}
|
|
|
|
function blueClick()
|
|
{
|
|
checkBox = document.getElementById("blue");
|
|
blue = checkBox.checked;
|
|
Clear(ctx);
|
|
Update();
|
|
}
|
|
|
|
Update();
|
|
|
|
</script>
|
|
</body>
|
|
<p xmlns:cc="http://creativecommons.org/ns#" style="font-size: 1vw; bottom: 0px; position: absolute;">
|
|
This work is licensed under
|
|
<a href="http://creativecommons.org/licenses/by-nc-nd/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-ND 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nd.svg?ref=chooser-v1"></a></p>
|
|
</html> |