···
3
+
style="display: flex; flex-direction: column; min-height: 30rem"
5
+
<div class="controls" style="display: flex; flex-direction: column">
6
+
<div style="display: flex; gap: 20px; align-items: center">
8
+
<label>Mirror Type:</label>
9
+
<select id="mirrorType">
10
+
<option value="concave">Concave Mirror</option>
11
+
<option value="convex">Convex Mirror</option>
15
+
<label>Radius of Curvature:</label>
16
+
<input type="number" id="radius" value="20" min="1" />
19
+
<label>Object Distance:</label>
20
+
<input type="number" id="objectDist" value="30" min="1" />
24
+
<label>Zoom:</label>
36
+
<canvas id="canvas" style="flex: 1; cursor: move"></canvas>
44
+
margin-bottom: 20px;
50
+
border: 1px solid #ccc;
56
+
const canvas = document.getElementById("canvas");
57
+
const ctx = canvas.getContext("2d");
58
+
const mirrorType = document.getElementById("mirrorType");
59
+
const radiusInput = document.getElementById("radius");
60
+
const objectDistInput = document.getElementById("objectDist");
61
+
const zoomInput = document.getElementById("zoom");
65
+
let isDragging = false;
69
+
canvas.addEventListener("mousedown", (e) => {
75
+
canvas.addEventListener("mousemove", (e) => {
77
+
offsetX += e.clientX - lastX;
78
+
offsetY += e.clientY - lastY;
85
+
canvas.addEventListener("mouseup", () => {
89
+
canvas.addEventListener("mouseleave", () => {
93
+
canvas.addEventListener("wheel", (e) => {
95
+
const zoomSpeed = 0.001;
96
+
const newZoom = parseFloat(zoomInput.value) - e.deltaY * zoomSpeed;
97
+
zoomInput.value = Math.min(Math.max(newZoom, 0.01), 8);
101
+
function calculateReflectedRay(
110
+
// Calculate normal vector at intersection point
111
+
const nx = (incidentX - centerX) / radius;
112
+
const ny = (incidentY - centerY) / radius;
114
+
// Calculate incident vector
115
+
const ix = incidentX - startX;
116
+
const iy = incidentY - startY;
117
+
const iLen = Math.sqrt(ix * ix + iy * iy);
118
+
const dirX = ix / iLen;
119
+
const dirY = iy / iLen;
121
+
// Calculate reflection using r = i - 2(i·n)n
122
+
const dot = dirX * nx + dirY * ny;
123
+
const reflectX = dirX - 2 * dot * nx;
124
+
const reflectY = dirY - 2 * dot * ny;
126
+
// Extend reflected ray to edge of canvas
127
+
const t = Math.max(
128
+
Math.abs((0 - incidentX) / reflectX),
129
+
Math.abs((canvas.width - incidentX) / reflectX),
130
+
Math.abs((0 - incidentY) / reflectY),
131
+
Math.abs((canvas.height - incidentY) / reflectY),
135
+
x: incidentX + reflectX * t,
136
+
y: incidentY + reflectY * t,
140
+
function drawMirror(isConcave, R) {
141
+
const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
142
+
const centerX = canvas.width / 2 + R * scale * isConcave + offsetX;
143
+
const centerY = canvas.height / 2 + offsetY;
146
+
ctx.strokeStyle = "black";
149
+
centerX - R * scale,
157
+
centerX + R * scale,
167
+
function drawArrow(x, y, height) {
168
+
const arrowHeadSize = height * 0.1; // Scale arrow head with height
172
+
// Draw the main shaft
174
+
ctx.lineTo(x, y - height * 0.9);
176
+
// Draw the arrow head
177
+
ctx.moveTo(x, y - height);
178
+
ctx.lineTo(x - arrowHeadSize, y - height + arrowHeadSize);
179
+
ctx.moveTo(x, y - height);
180
+
ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
181
+
ctx.moveTo(x - arrowHeadSize, y - height + arrowHeadSize);
182
+
ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
187
+
function extendRayToCanvasEdge(x1, y1, x2, y2) {
188
+
const rayDirX = x2 - x1;
189
+
const rayDirY = y2 - y1;
190
+
const t = Math.max(
191
+
Math.abs((0 - x2) / rayDirX),
192
+
Math.abs((canvas.width - x2) / rayDirX),
193
+
Math.abs((0 - y2) / rayDirY),
194
+
Math.abs((canvas.height - y2) / rayDirY),
196
+
ctx.lineTo(x2 + rayDirX * t, y2 + rayDirY * t);
199
+
function findCircleIntersection(radius, x1, h, x3, y3, centerX, centerY) {
200
+
// Check if the input values are valid
202
+
throw new Error("Invalid input values.");
205
+
// Calculate the slope of the line from (x1, h) to (x3, y3)
206
+
const m = (y3 - (centerY - h)) / (x3 - x1);
208
+
// Define the line equation: y = h + m * (x - x1)
209
+
// Substitute into circle equation: (x-centerX)^2 + (y-centerY)^2 = radius^2
210
+
// y = h + m * (x - x1)
211
+
// (x-centerX)^2 + (h + m*(x-x1) - centerY)^2 = radius^2
213
+
// Coefficients for the quadratic equation
214
+
const a = 1 + m * m;
215
+
const b = -2 * centerX + 2 * m * (centerY - h - centerY - m * x1);
217
+
centerX * centerX +
218
+
(centerY - h - centerY - m * x1) *
219
+
(centerY - h - centerY - m * x1) -
222
+
// Calculate the discriminant
223
+
const discriminant = b * b - 4 * a * c;
225
+
if (discriminant < 0) {
226
+
throw new Error("No intersection found.");
229
+
// Calculate the two possible x values
230
+
const xIntersect1 = (-b + Math.sqrt(discriminant)) / (2 * a);
231
+
const xIntersect2 = (-b - Math.sqrt(discriminant)) / (2 * a);
233
+
// Calculate the corresponding y values
234
+
const yIntersect1 = centerY - h + m * (xIntersect1 - x1);
235
+
const yIntersect2 = centerY - h + m * (xIntersect2 - x1);
237
+
// Return the intersection points
239
+
{ x: xIntersect1, y: yIntersect1 },
240
+
{ x: xIntersect2, y: yIntersect2 },
244
+
function drawRays(isConcave, R, objDist) {
245
+
const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
247
+
const h = (R * scale) / 3;
248
+
const centerX = canvas.width / 2 + R * scale + offsetX;
249
+
const centerY = canvas.height / 2 + offsetY;
252
+
objDist * scale * (isConcave ? -1 : -1) -
253
+
R * scale * !isConcave;
254
+
const objY = centerY;
256
+
drawArrow(objX, objY, h);
259
+
ctx.moveTo(0, centerY);
260
+
ctx.lineTo(canvas.width, centerY);
263
+
ctx.fillStyle = "red";
265
+
ctx.arc(centerX - F * scale, centerY, 3, 0, 2 * Math.PI);
268
+
ctx.fillStyle = "blue";
270
+
ctx.arc(centerX - R * scale * isConcave, centerY, 3, 0, 2 * Math.PI);
273
+
const circleCenterX = isConcave
274
+
? centerX - R * scale
275
+
: centerX - R * scale;
278
+
// ray that travels from the top of the object towards the mirror and then calculating the bounce angle it goes in that direction
279
+
ctx.strokeStyle = "green";
281
+
ctx.lineTo(objX, objY - h);
282
+
let intersectionX =
283
+
Math.sqrt((R * scale) ** 2 - h ** 2) + circleCenterX;
284
+
ctx.lineTo(intersectionX, objY - h);
285
+
extendRayToCanvasEdge(
288
+
centerX - F * scale,
293
+
// draw an extension of the ray through the mirror in a slightly opacified color
294
+
ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
296
+
ctx.lineTo(intersectionX, objY - h);
297
+
extendRayToCanvasEdge(
298
+
centerX - F * scale,
305
+
// draw a point at the intersection of the ray and the mirror
306
+
ctx.fillStyle = "black";
308
+
ctx.arc(intersectionX, objY - h, 3, 0, 2 * Math.PI);
311
+
// 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
312
+
ctx.strokeStyle = "purple";
314
+
ctx.lineTo(objX, objY - h);
315
+
ctx.lineTo(centerX - F * scale, centerY);
316
+
const extendedRay2 = findCircleIntersection(
320
+
centerX - F * scale,
325
+
ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
326
+
ctx.lineTo(0, extendedRay2[0].y);
329
+
// draw an extension of the ray through the mirror in a slightly opacified color
330
+
ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
332
+
ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
333
+
ctx.lineTo(canvas.width, extendedRay2[0].y);
336
+
// draw a point at the intersection of the ray and the mirror
337
+
ctx.fillStyle = "black";
339
+
ctx.arc(extendedRay2[0].x, extendedRay2[0].y, 3, 0, 2 * Math.PI);
342
+
// draw a ray that travels from the top of the object through the radius of curvature of the mirror
343
+
ctx.strokeStyle = "orange";
345
+
ctx.lineTo(objX, objY - h);
346
+
ctx.lineTo(circleCenterX, centerY);
347
+
const extendedRay3 = findCircleIntersection(
356
+
ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
357
+
extendRayToCanvasEdge(
360
+
centerX - R * scale,
365
+
// draw an extension of the ray through the mirror in a slightly opacified color
366
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
368
+
ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
369
+
extendRayToCanvasEdge(
370
+
centerX - R * scale,
377
+
// draw a point at the intersection of the ray and the mirror
378
+
ctx.fillStyle = "black";
380
+
ctx.arc(extendedRay3[0].x, extendedRay3[0].y, 3, 0, 2 * Math.PI);
383
+
// draw a ray that travels from the top of the object horizontally towards the mirror
384
+
ctx.strokeStyle = "green";
386
+
ctx.lineTo(objX, objY - h);
388
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
391
+
extendRayToCanvasEdge(
392
+
centerX - F * scale,
394
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
399
+
// draw an extension of the ray through the mirror in a slightly opacified color
400
+
ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
403
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
406
+
ctx.lineTo(centerX - F * scale, centerY);
409
+
// draw a point at the intersection of the ray and the mirror
410
+
ctx.fillStyle = "black";
413
+
centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
421
+
// 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
422
+
ctx.strokeStyle = "purple";
424
+
ctx.lineTo(objX, objY - h);
425
+
const extendedRay2 = findCircleIntersection(
429
+
centerX - F * scale,
434
+
const extendedRay2Y = centerY - (extendedRay2[0].y - centerY);
438
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
440
+
centerY - (extendedRay2[0].y - centerY),
442
+
ctx.lineTo(0, centerY - (extendedRay2[0].y - centerY));
445
+
// draw an extension of the ray through the mirror in a slightly opacified color
446
+
ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
451
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
453
+
centerY - (extendedRay2[0].y - centerY),
455
+
ctx.lineTo(canvas.width, centerY - (extendedRay2[0].y - centerY));
458
+
ctx.fillStyle = "black";
463
+
(R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
465
+
centerY - (extendedRay2[0].y - centerY),
472
+
// draw a ray that travels from the top of the object through the radius of curvature of the mirror
473
+
ctx.strokeStyle = "orange";
475
+
ctx.lineTo(objX, objY - h);
476
+
// ctx.lineTo(centerX, centerY);
477
+
const extendedRay3ScaleFactor =
478
+
(R * scale) / Math.abs(objX - centerX);
482
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
484
+
centerY - h * extendedRay3ScaleFactor,
486
+
extendRayToCanvasEdge(
489
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
491
+
centerY - h * extendedRay3ScaleFactor,
497
+
// draw an extension of the ray through the mirror in a slightly opacified color
498
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
503
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
505
+
centerY - h * extendedRay3ScaleFactor,
507
+
extendRayToCanvasEdge(
510
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
512
+
centerY - h * extendedRay3ScaleFactor,
518
+
// draw a point at the intersection of the ray and the mirror
520
+
ctx.fillStyle = "black";
525
+
(R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
527
+
centerY - h * extendedRay3ScaleFactor,
534
+
// draw an extension of the ray through the mirror in a slightly opacified color
535
+
ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
540
+
(R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
542
+
centerY - (extendedRay3[0].y - centerY),
544
+
extendRayToCanvasEdge(
545
+
centerX - R * scale,
549
+
(R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
551
+
centerY - (extendedRay3[0].y - centerY),
557
+
function update() {
558
+
canvas.width = canvas.offsetWidth;
559
+
canvas.height = canvas.offsetHeight * 0.8;
560
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
561
+
ctx.fillStyle = "#f0f0f0";
562
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
563
+
const isConcave = mirrorType.value === "concave";
564
+
const R = parseFloat(radiusInput.value);
565
+
const objDist = parseFloat(objectDistInput.value);
567
+
drawMirror(isConcave, R);
568
+
drawRays(isConcave, R, objDist);
571
+
mirrorType.addEventListener("change", update);
572
+
radiusInput.addEventListener("input", update);
573
+
objectDistInput.addEventListener("input", update);
574
+
zoomInput.addEventListener("input", update);
575
+
window.addEventListener("resize", update);
579
+
let isCanvasHovered = false;
581
+
canvas.addEventListener("mouseenter", () => {
582
+
isCanvasHovered = true;
585
+
canvas.addEventListener("mouseleave", () => {
586
+
isCanvasHovered = false;
589
+
document.addEventListener("keydown", (e) => {
590
+
if (!isCanvasHovered) return;
591
+
if (e.key === "+" || e.key === "=") {
592
+
zoomInput.value = Math.min(parseFloat(zoomInput.value) + 0.1, 8);
595
+
if (e.key === "-" || e.key === "_") {
596
+
zoomInput.value = Math.max(parseFloat(zoomInput.value) - 0.1, 0.1);
600
+
// translate the canvas
601
+
if (e.key === "ArrowUp") {
605
+
if (e.key === "ArrowDown") {
609
+
if (e.key === "ArrowLeft") {
613
+
if (e.key === "ArrowRight") {