···
+
style="display: flex; flex-direction: column; min-height: 30rem"
+
<div class="controls" style="display: flex; flex-direction: column">
+
<div style="display: flex; gap: 20px; align-items: center">
+
<label>Mirror Type:</label>
+
<select id="mirrorType">
+
<option value="concave">Concave Mirror</option>
+
<option value="convex">Convex Mirror</option>
+
<label>Radius of Curvature:</label>
+
<input type="number" id="radius" value="20" min="1" />
+
<label>Object Distance:</label>
+
<input type="number" id="objectDist" value="30" min="1" />
+
<canvas id="canvas" style="flex: 1; cursor: move"></canvas>
+
border: 1px solid #ccc;
+
const canvas = document.getElementById("canvas");
+
const ctx = canvas.getContext("2d");
+
const mirrorType = document.getElementById("mirrorType");
+
const radiusInput = document.getElementById("radius");
+
const objectDistInput = document.getElementById("objectDist");
+
const zoomInput = document.getElementById("zoom");
+
let isDragging = false;
+
canvas.addEventListener("mousedown", (e) => {
+
canvas.addEventListener("mousemove", (e) => {
+
offsetX += e.clientX - lastX;
+
offsetY += e.clientY - lastY;
+
canvas.addEventListener("mouseup", () => {
+
canvas.addEventListener("mouseleave", () => {
+
canvas.addEventListener("wheel", (e) => {
+
const zoomSpeed = 0.001;
+
const newZoom = parseFloat(zoomInput.value) - e.deltaY * zoomSpeed;
+
zoomInput.value = Math.min(Math.max(newZoom, 0.01), 8);
+
function calculateReflectedRay(
+
// Calculate normal vector at intersection point
+
const nx = (incidentX - centerX) / radius;
+
const ny = (incidentY - centerY) / radius;
+
// Calculate incident vector
+
const ix = incidentX - startX;
+
const iy = incidentY - startY;
+
const iLen = Math.sqrt(ix * ix + iy * iy);
+
const dirX = ix / iLen;
+
const dirY = iy / iLen;
+
// Calculate reflection using r = i - 2(i·n)n
+
const dot = dirX * nx + dirY * ny;
+
const reflectX = dirX - 2 * dot * nx;
+
const reflectY = dirY - 2 * dot * ny;
+
// Extend reflected ray to edge of canvas
+
Math.abs((0 - incidentX) / reflectX),
+
Math.abs((canvas.width - incidentX) / reflectX),
+
Math.abs((0 - incidentY) / reflectY),
+
Math.abs((canvas.height - incidentY) / reflectY),
+
x: incidentX + reflectX * t,
+
y: incidentY + reflectY * t,
+
function drawMirror(isConcave, R) {
+
const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
+
const centerX = canvas.width / 2 + R * scale * isConcave + offsetX;
+
const centerY = canvas.height / 2 + offsetY;
+
ctx.strokeStyle = "black";
+
function drawArrow(x, y, height) {
+
const arrowHeadSize = height * 0.1; // Scale arrow head with height
+
ctx.lineTo(x, y - height * 0.9);
+
ctx.moveTo(x, y - height);
+
ctx.lineTo(x - arrowHeadSize, y - height + arrowHeadSize);
+
ctx.moveTo(x, y - height);
+
ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
+
ctx.moveTo(x - arrowHeadSize, y - height + arrowHeadSize);
+
ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
+
function extendRayToCanvasEdge(x1, y1, x2, y2) {
+
const rayDirX = x2 - x1;
+
const rayDirY = y2 - y1;
+
Math.abs((0 - x2) / rayDirX),
+
Math.abs((canvas.width - x2) / rayDirX),
+
Math.abs((0 - y2) / rayDirY),
+
Math.abs((canvas.height - y2) / rayDirY),
+
ctx.lineTo(x2 + rayDirX * t, y2 + rayDirY * t);
+
function findCircleIntersection(radius, x1, h, x3, y3, centerX, centerY) {
+
// Check if the input values are valid
+
throw new Error("Invalid input values.");
+
// Calculate the slope of the line from (x1, h) to (x3, y3)
+
const m = (y3 - (centerY - h)) / (x3 - x1);
+
// Define the line equation: y = h + m * (x - x1)
+
// Substitute into circle equation: (x-centerX)^2 + (y-centerY)^2 = radius^2
+
// y = h + m * (x - x1)
+
// (x-centerX)^2 + (h + m*(x-x1) - centerY)^2 = radius^2
+
// Coefficients for the quadratic equation
+
const b = -2 * centerX + 2 * m * (centerY - h - centerY - m * x1);
+
(centerY - h - centerY - m * x1) *
+
(centerY - h - centerY - m * x1) -
+
// Calculate the discriminant
+
const discriminant = b * b - 4 * a * c;
+
if (discriminant < 0) {
+
throw new Error("No intersection found.");
+
// Calculate the two possible x values
+
const xIntersect1 = (-b + Math.sqrt(discriminant)) / (2 * a);
+
const xIntersect2 = (-b - Math.sqrt(discriminant)) / (2 * a);
+
// Calculate the corresponding y values
+
const yIntersect1 = centerY - h + m * (xIntersect1 - x1);
+
const yIntersect2 = centerY - h + m * (xIntersect2 - x1);
+
// Return the intersection points
+
{ x: xIntersect1, y: yIntersect1 },
+
{ x: xIntersect2, y: yIntersect2 },
+
function drawRays(isConcave, R, objDist) {
+
const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
+
const h = (R * scale) / 3;
+
const centerX = canvas.width / 2 + R * scale + offsetX;
+
const centerY = canvas.height / 2 + offsetY;
+
objDist * scale * (isConcave ? -1 : -1) -
+
R * scale * !isConcave;
+
drawArrow(objX, objY, h);
+
ctx.moveTo(0, centerY);
+
ctx.lineTo(canvas.width, centerY);
+
ctx.arc(centerX - F * scale, centerY, 3, 0, 2 * Math.PI);
+
ctx.fillStyle = "blue";
+
ctx.arc(centerX - R * scale * isConcave, centerY, 3, 0, 2 * Math.PI);
+
const circleCenterX = isConcave
+
// ray that travels from the top of the object towards the mirror and then calculating the bounce angle it goes in that direction
+
ctx.strokeStyle = "green";
+
ctx.lineTo(objX, objY - h);
+
Math.sqrt((R * scale) ** 2 - h ** 2) + circleCenterX;
+
ctx.lineTo(intersectionX, objY - h);
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
+
ctx.lineTo(intersectionX, objY - h);
+
// draw a point at the intersection of the ray and the mirror
+
ctx.fillStyle = "black";
+
ctx.arc(intersectionX, objY - h, 3, 0, 2 * Math.PI);
+
// draw a ray that travels from the top of the object towards the focal point of the mirror and through the focal point till it reaches the mirror
+
ctx.strokeStyle = "purple";
+
ctx.lineTo(objX, objY - h);
+
ctx.lineTo(centerX - F * scale, centerY);
+
const extendedRay2 = findCircleIntersection(
+
ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
+
ctx.lineTo(0, extendedRay2[0].y);
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
+
ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
+
ctx.lineTo(canvas.width, extendedRay2[0].y);
+
// draw a point at the intersection of the ray and the mirror
+
ctx.fillStyle = "black";
+
ctx.arc(extendedRay2[0].x, extendedRay2[0].y, 3, 0, 2 * Math.PI);
+
// draw a ray that travels from the top of the object through the radius of curvature of the mirror
+
ctx.strokeStyle = "orange";
+
ctx.lineTo(objX, objY - h);
+
ctx.lineTo(circleCenterX, centerY);
+
const extendedRay3 = findCircleIntersection(
+
ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
+
ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
+
// draw a point at the intersection of the ray and the mirror
+
ctx.fillStyle = "black";
+
ctx.arc(extendedRay3[0].x, extendedRay3[0].y, 3, 0, 2 * Math.PI);
+
// draw a ray that travels from the top of the object horizontally towards the mirror
+
ctx.strokeStyle = "green";
+
ctx.lineTo(objX, objY - h);
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
+
ctx.lineTo(centerX - F * scale, centerY);
+
// draw a point at the intersection of the ray and the mirror
+
ctx.fillStyle = "black";
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
+
// draw a ray that travels from the top of the object towards the focal point of the mirror and through the focal point till it reaches the mirror
+
ctx.strokeStyle = "purple";
+
ctx.lineTo(objX, objY - h);
+
const extendedRay2 = findCircleIntersection(
+
const extendedRay2Y = centerY - (extendedRay2[0].y - centerY);
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
+
centerY - (extendedRay2[0].y - centerY),
+
ctx.lineTo(0, centerY - (extendedRay2[0].y - centerY));
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
+
centerY - (extendedRay2[0].y - centerY),
+
ctx.lineTo(canvas.width, centerY - (extendedRay2[0].y - centerY));
+
ctx.fillStyle = "black";
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
+
centerY - (extendedRay2[0].y - centerY),
+
// draw a ray that travels from the top of the object through the radius of curvature of the mirror
+
ctx.strokeStyle = "orange";
+
ctx.lineTo(objX, objY - h);
+
// ctx.lineTo(centerX, centerY);
+
const extendedRay3ScaleFactor =
+
(R * scale) / Math.abs(objX - centerX);
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
+
centerY - h * extendedRay3ScaleFactor,
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
+
centerY - h * extendedRay3ScaleFactor,
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
+
centerY - h * extendedRay3ScaleFactor,
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
+
centerY - h * extendedRay3ScaleFactor,
+
// draw a point at the intersection of the ray and the mirror
+
ctx.fillStyle = "black";
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
+
centerY - h * extendedRay3ScaleFactor,
+
// draw an extension of the ray through the mirror in a slightly opacified color
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
+
(R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
+
centerY - (extendedRay3[0].y - centerY),
+
(R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
+
centerY - (extendedRay3[0].y - centerY),
+
canvas.width = canvas.offsetWidth;
+
canvas.height = canvas.offsetHeight * 0.8;
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
+
ctx.fillStyle = "#f0f0f0";
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
+
const isConcave = mirrorType.value === "concave";
+
const R = parseFloat(radiusInput.value);
+
const objDist = parseFloat(objectDistInput.value);
+
drawMirror(isConcave, R);
+
drawRays(isConcave, R, objDist);
+
mirrorType.addEventListener("change", update);
+
radiusInput.addEventListener("input", update);
+
objectDistInput.addEventListener("input", update);
+
zoomInput.addEventListener("input", update);
+
window.addEventListener("resize", update);
+
let isCanvasHovered = false;
+
canvas.addEventListener("mouseenter", () => {
+
isCanvasHovered = true;
+
canvas.addEventListener("mouseleave", () => {
+
isCanvasHovered = false;
+
document.addEventListener("keydown", (e) => {
+
if (!isCanvasHovered) return;
+
if (e.key === "+" || e.key === "=") {
+
zoomInput.value = Math.min(parseFloat(zoomInput.value) + 0.1, 8);
+
if (e.key === "-" || e.key === "_") {
+
zoomInput.value = Math.max(parseFloat(zoomInput.value) - 0.1, 0.1);
+
// translate the canvas
+
if (e.key === "ArrowUp") {
+
if (e.key === "ArrowDown") {
+
if (e.key === "ArrowLeft") {
+
if (e.key === "ArrowRight") {