the home site for me: also iteration 3 or 4 of my site
1<div
2 id="rayTracer"
3 style="display: flex; flex-direction: column; min-height: 40rem"
4>
5 <div class="controls" style="display: flex; flex-direction: column">
6 <div style="display: flex; gap: 20px; align-items: center">
7 <div>
8 <label>Mirror Type:</label>
9 <select id="mirrorType">
10 <option value="concave">Concave Mirror</option>
11 <option value="convex">Convex Mirror</option>
12 </select>
13 </div>
14 <div>
15 <label>Radius of Curvature:</label>
16 <input
17 type="number"
18 id="radius"
19 value="20"
20 min="0.2"
21 step="0.2"
22 />
23 </div>
24 <div>
25 <label>Object Distance:</label>
26 <input
27 type="number"
28 id="objectDist"
29 value="30"
30 min="0.2"
31 step="0.2"
32 />
33 </div>
34 </div>
35 <div style="display: flex; gap: 20px; align-items: center; width: 100%">
36 <div>
37 <label>Object Height:</label>
38 <input
39 type="number"
40 id="objectHeight"
41 value="20"
42 min="0.1"
43 step="0.1"
44 />
45 </div>
46 <div style="flex: 1">
47 <label>Zoom:</label>
48 <input
49 type="range"
50 id="zoom"
51 min="0.01"
52 max="8"
53 step="0.01"
54 value="1"
55 style="width: 100%"
56 />
57 </div>
58 </div>
59 </div>
60 <canvas id="canvas" style="flex: 1; cursor: move"></canvas>
61</div>
62
63<style>
64 #rayTracer {
65 padding: 20px;
66 }
67 .controls {
68 margin-bottom: 20px;
69 }
70 .controls div {
71 margin: 0.2rem 0;
72 }
73 #canvas {
74 border: 1px solid #ccc;
75 width: 100%;
76 }
77</style>
78
79<script>
80 const canvas = document.getElementById("canvas");
81 const ctx = canvas.getContext("2d");
82 const mirrorType = document.getElementById("mirrorType");
83 const radiusInput = document.getElementById("radius");
84 const objectDistInput = document.getElementById("objectDist");
85 const objectHeightInput = document.getElementById("objectHeight");
86 const zoomInput = document.getElementById("zoom");
87
88 let offsetX = 0;
89 let offsetY = 0;
90 let isDragging = false;
91 let lastX = 0;
92 let lastY = 0;
93
94 canvas.addEventListener("mousedown", (e) => {
95 isDragging = true;
96 lastX = e.clientX;
97 lastY = e.clientY;
98 });
99
100 canvas.addEventListener("mousemove", (e) => {
101 if (isDragging) {
102 offsetX += e.clientX - lastX;
103 offsetY += e.clientY - lastY;
104 lastX = e.clientX;
105 lastY = e.clientY;
106 update();
107 }
108 });
109
110 canvas.addEventListener("mouseup", () => {
111 isDragging = false;
112 });
113
114 canvas.addEventListener("mouseleave", () => {
115 isDragging = false;
116 });
117
118 canvas.addEventListener("wheel", (e) => {
119 e.preventDefault();
120 const zoomSpeed = 0.001;
121 const newZoom = parseFloat(zoomInput.value) - e.deltaY * zoomSpeed;
122 zoomInput.value = Math.min(Math.max(newZoom, 0.01), 8);
123 update();
124 });
125
126 function calculateReflectedRay(
127 startX,
128 startY,
129 incidentX,
130 incidentY,
131 centerX,
132 centerY,
133 radius,
134 ) {
135 // Calculate normal vector at intersection point
136 const nx = (incidentX - centerX) / radius;
137 const ny = (incidentY - centerY) / radius;
138
139 // Calculate incident vector
140 const ix = incidentX - startX;
141 const iy = incidentY - startY;
142 const iLen = Math.sqrt(ix * ix + iy * iy);
143 const dirX = ix / iLen;
144 const dirY = iy / iLen;
145
146 // Calculate reflection using r = i - 2(i·n)n
147 const dot = dirX * nx + dirY * ny;
148 const reflectX = dirX - 2 * dot * nx;
149 const reflectY = dirY - 2 * dot * ny;
150
151 // Extend reflected ray to edge of canvas
152 const t = Math.max(
153 Math.abs((0 - incidentX) / reflectX),
154 Math.abs((canvas.width - incidentX) / reflectX),
155 Math.abs((0 - incidentY) / reflectY),
156 Math.abs((canvas.height - incidentY) / reflectY),
157 );
158
159 return {
160 x: incidentX + reflectX * t,
161 y: incidentY + reflectY * t,
162 };
163 }
164
165 function drawMirror(isConcave, R) {
166 const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
167 const centerX = canvas.width / 2 + R * scale * isConcave + offsetX;
168 const centerY = canvas.height / 2 + offsetY;
169
170 ctx.beginPath();
171 ctx.strokeStyle = "black";
172 if (isConcave) {
173 ctx.arc(
174 centerX - R * scale,
175 centerY,
176 R * scale,
177 -Math.PI / 2.75,
178 Math.PI / 2.75,
179 );
180 } else {
181 ctx.arc(
182 centerX + R * scale,
183 centerY,
184 R * scale,
185 -Math.PI / 2.75 + Math.PI,
186 Math.PI / 2.75 + Math.PI,
187 );
188 }
189 ctx.stroke();
190 }
191
192 function drawArrow(x, y, height) {
193 const arrowHeadSize = height * 0.1; // Scale arrow head with height
194 ctx.lineWidth = 1;
195 ctx.beginPath();
196
197 // Draw the main shaft
198 ctx.moveTo(x, y);
199 ctx.lineTo(x, y - height * 0.9);
200
201 // Draw the arrow head
202 ctx.moveTo(x, y - height);
203 ctx.lineTo(x - arrowHeadSize, y - height + arrowHeadSize);
204 ctx.moveTo(x, y - height);
205 ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
206 ctx.moveTo(x - arrowHeadSize, y - height + arrowHeadSize);
207 ctx.lineTo(x + arrowHeadSize, y - height + arrowHeadSize);
208
209 ctx.stroke();
210 }
211
212 function extendRayToCanvasEdge(x1, y1, x2, y2) {
213 const rayDirX = x2 - x1;
214 const rayDirY = y2 - y1;
215 const t = Math.max(
216 Math.abs((0 - x2) / rayDirX),
217 Math.abs((canvas.width - x2) / rayDirX),
218 Math.abs((0 - y2) / rayDirY),
219 Math.abs((canvas.height - y2) / rayDirY),
220 );
221 ctx.lineTo(x2 + rayDirX * t, y2 + rayDirY * t);
222 }
223
224 function findCircleIntersection(radius, x1, h, x3, y3, centerX, centerY) {
225 // Check if the input values are valid
226 if (radius <= 0) {
227 throw new Error("Invalid input values.");
228 }
229
230 // Calculate the slope of the line from (x1, h) to (x3, y3)
231 const m = (y3 - (centerY - h)) / (x3 - x1);
232
233 // Define the line equation: y = h + m * (x - x1)
234 // Substitute into circle equation: (x-centerX)^2 + (y-centerY)^2 = radius^2
235 // y = h + m * (x - x1)
236 // (x-centerX)^2 + (h + m*(x-x1) - centerY)^2 = radius^2
237
238 // Coefficients for the quadratic equation
239 const a = 1 + m * m;
240 const b = -2 * centerX + 2 * m * (centerY - h - centerY - m * x1);
241 const c =
242 centerX * centerX +
243 (centerY - h - centerY - m * x1) *
244 (centerY - h - centerY - m * x1) -
245 radius * radius;
246
247 // Calculate the discriminant
248 const discriminant = b * b - 4 * a * c;
249
250 if (discriminant < 0) {
251 throw new Error("No intersection found.");
252 }
253
254 // Calculate the two possible x values
255 const xIntersect1 = (-b + Math.sqrt(discriminant)) / (2 * a);
256 const xIntersect2 = (-b - Math.sqrt(discriminant)) / (2 * a);
257
258 // Calculate the corresponding y values
259 const yIntersect1 = centerY - h + m * (xIntersect1 - x1);
260 const yIntersect2 = centerY - h + m * (xIntersect2 - x1);
261
262 // Return the intersection points
263 return [
264 { x: xIntersect1, y: yIntersect1 },
265 { x: xIntersect2, y: yIntersect2 },
266 ];
267 }
268
269 function drawRays(isConcave, R, objDist) {
270 const scale = (canvas.width / (R * 6)) * parseFloat(zoomInput.value);
271 const F = R / 2;
272 const h = parseFloat(objectHeightInput.value) * scale;
273 const centerX = canvas.width / 2 + R * scale + offsetX;
274 const centerY = canvas.height / 2 + offsetY;
275 const objX =
276 centerX +
277 objDist * scale * (isConcave ? -1 : -1) -
278 R * scale * !isConcave;
279 const objY = centerY;
280
281 drawArrow(objX, objY, h);
282
283 ctx.beginPath();
284 ctx.moveTo(0, centerY);
285 ctx.lineTo(canvas.width, centerY);
286 ctx.stroke();
287
288 ctx.fillStyle = "red";
289 ctx.beginPath();
290 ctx.arc(centerX - F * scale, centerY, 3, 0, 2 * Math.PI);
291 ctx.fill();
292
293 ctx.fillStyle = "blue";
294 ctx.beginPath();
295 ctx.arc(centerX - R * scale * isConcave, centerY, 3, 0, 2 * Math.PI);
296 ctx.fill();
297
298 const circleCenterX = isConcave
299 ? centerX - R * scale
300 : centerX - R * scale;
301
302 if (isConcave) {
303 // ray that travels from the top of the object towards the mirror and then calculating the bounce angle it goes in that direction
304 ctx.strokeStyle = "green";
305 ctx.beginPath();
306 ctx.lineTo(objX, objY - h);
307 let intersectionX =
308 Math.sqrt((R * scale) ** 2 - h ** 2) + circleCenterX;
309 ctx.lineTo(intersectionX, objY - h);
310 extendRayToCanvasEdge(
311 intersectionX,
312 objY - h,
313 centerX - F * scale,
314 centerY,
315 );
316 ctx.stroke();
317
318 // draw an extension of the ray through the mirror in a slightly opacified color
319 ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
320 ctx.beginPath();
321 ctx.lineTo(intersectionX, objY - h);
322 extendRayToCanvasEdge(
323 centerX - F * scale,
324 centerY,
325 intersectionX,
326 objY - h,
327 );
328 ctx.stroke();
329
330 // draw a point at the intersection of the ray and the mirror
331 ctx.fillStyle = "black";
332 ctx.beginPath();
333 ctx.arc(intersectionX, objY - h, 3, 0, 2 * Math.PI);
334 ctx.fill();
335
336 // 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
337 ctx.strokeStyle = "purple";
338 ctx.beginPath();
339 ctx.lineTo(objX, objY - h);
340 ctx.lineTo(centerX - F * scale, centerY);
341 const extendedRay2 = findCircleIntersection(
342 R * scale,
343 objX,
344 h,
345 centerX - F * scale,
346 centerY,
347 circleCenterX,
348 centerY,
349 );
350 ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
351 ctx.lineTo(0, extendedRay2[0].y);
352 ctx.stroke();
353
354 // draw an extension of the ray through the mirror in a slightly opacified color
355 ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
356 ctx.beginPath();
357 ctx.lineTo(extendedRay2[0].x, extendedRay2[0].y);
358 ctx.lineTo(canvas.width, extendedRay2[0].y);
359 ctx.stroke();
360
361 // draw a point at the intersection of the ray and the mirror
362 ctx.fillStyle = "black";
363 ctx.beginPath();
364 ctx.arc(extendedRay2[0].x, extendedRay2[0].y, 3, 0, 2 * Math.PI);
365 ctx.fill();
366
367 // draw a ray that travels from the top of the object through the radius of curvature of the mirror
368 ctx.strokeStyle = "orange";
369 ctx.beginPath();
370 ctx.lineTo(objX, objY - h);
371 ctx.lineTo(circleCenterX, centerY);
372 const extendedRay3 = findCircleIntersection(
373 R * scale,
374 objX,
375 h,
376 circleCenterX,
377 centerY,
378 circleCenterX,
379 centerY,
380 );
381 ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
382 extendRayToCanvasEdge(
383 extendedRay3[0].x,
384 extendedRay3[0].y,
385 centerX - R * scale,
386 centerY,
387 );
388 ctx.stroke();
389
390 // draw an extension of the ray through the mirror in a slightly opacified color
391 ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
392 ctx.beginPath();
393 ctx.lineTo(extendedRay3[0].x, extendedRay3[0].y);
394 extendRayToCanvasEdge(
395 centerX - R * scale,
396 centerY,
397 extendedRay3[0].x,
398 extendedRay3[0].y,
399 );
400 ctx.stroke();
401
402 // draw a point at the intersection of the ray and the mirror
403 ctx.fillStyle = "black";
404 ctx.beginPath();
405 ctx.arc(extendedRay3[0].x, extendedRay3[0].y, 3, 0, 2 * Math.PI);
406 ctx.fill();
407 } else {
408 // draw a ray that travels from the top of the object horizontally towards the mirror
409 ctx.strokeStyle = "green";
410 ctx.beginPath();
411 ctx.lineTo(objX, objY - h);
412 ctx.lineTo(
413 centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
414 objY - h,
415 );
416 extendRayToCanvasEdge(
417 centerX - F * scale,
418 centerY,
419 centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
420 objY - h,
421 );
422 ctx.stroke();
423
424 // draw an extension of the ray through the mirror in a slightly opacified color
425 ctx.strokeStyle = "rgba(0, 128, 0, 0.5)";
426 ctx.beginPath();
427 ctx.lineTo(
428 centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
429 objY - h,
430 );
431 ctx.lineTo(centerX - F * scale, centerY);
432 ctx.stroke();
433
434 // draw a point at the intersection of the ray and the mirror
435 ctx.fillStyle = "black";
436 ctx.beginPath();
437 ctx.arc(
438 centerX - Math.sqrt((R * scale) ** 2 - h ** 2),
439 objY - h,
440 3,
441 0,
442 2 * Math.PI,
443 );
444 ctx.fill();
445
446 // 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
447 ctx.strokeStyle = "purple";
448 ctx.beginPath();
449 ctx.lineTo(objX, objY - h);
450 const extendedRay2 = findCircleIntersection(
451 R * scale,
452 objX,
453 h,
454 centerX - F * scale,
455 centerY,
456 circleCenterX,
457 centerY,
458 );
459 const extendedRay2Y = centerY - (extendedRay2[0].y - centerY);
460 ctx.lineTo(
461 centerX -
462 Math.sqrt(
463 (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
464 ),
465 centerY - (extendedRay2[0].y - centerY),
466 );
467 ctx.lineTo(0, centerY - (extendedRay2[0].y - centerY));
468 ctx.stroke();
469
470 // draw an extension of the ray through the mirror in a slightly opacified color
471 ctx.strokeStyle = "rgba(128, 0, 128, 0.5)";
472 ctx.beginPath();
473 ctx.lineTo(
474 centerX -
475 Math.sqrt(
476 (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
477 ),
478 centerY - (extendedRay2[0].y - centerY),
479 );
480 ctx.lineTo(canvas.width, centerY - (extendedRay2[0].y - centerY));
481 ctx.stroke();
482
483 ctx.fillStyle = "black";
484 ctx.beginPath();
485 ctx.arc(
486 centerX -
487 Math.sqrt(
488 (R * scale) ** 2 - (centerY - extendedRay2Y) ** 2,
489 ),
490 centerY - (extendedRay2[0].y - centerY),
491 3,
492 0,
493 2 * Math.PI,
494 );
495 ctx.fill();
496
497 // draw a ray that travels from the top of the object through the radius of curvature of the mirror
498 ctx.strokeStyle = "orange";
499 ctx.beginPath();
500 ctx.lineTo(objX, objY - h);
501 // ctx.lineTo(centerX, centerY);
502 const extendedRay3ScaleFactor =
503 (R * scale) / Math.abs(objX - centerX);
504 ctx.lineTo(
505 centerX -
506 Math.sqrt(
507 (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
508 ),
509 centerY - h * extendedRay3ScaleFactor,
510 );
511 extendRayToCanvasEdge(
512 centerX -
513 Math.sqrt(
514 (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
515 ),
516 centerY - h * extendedRay3ScaleFactor,
517 objX,
518 objY - h,
519 );
520 ctx.stroke();
521
522 // draw an extension of the ray through the mirror in a slightly opacified color
523 ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
524 ctx.beginPath();
525 ctx.lineTo(
526 centerX -
527 Math.sqrt(
528 (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
529 ),
530 centerY - h * extendedRay3ScaleFactor,
531 );
532 extendRayToCanvasEdge(
533 centerX -
534 Math.sqrt(
535 (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
536 ),
537 centerY - h * extendedRay3ScaleFactor,
538 centerX,
539 centerY,
540 );
541 ctx.stroke();
542
543 // draw a point at the intersection of the ray and the mirror
544
545 ctx.fillStyle = "black";
546 ctx.beginPath();
547 ctx.arc(
548 centerX -
549 Math.sqrt(
550 (R * scale) ** 2 - (h * extendedRay3ScaleFactor) ** 2,
551 ),
552 centerY - h * extendedRay3ScaleFactor,
553 3,
554 0,
555 2 * Math.PI,
556 );
557 ctx.fill();
558
559 // draw an extension of the ray through the mirror in a slightly opacified color
560 ctx.strokeStyle = "rgba(255, 165, 0, 0.5)";
561 ctx.beginPath();
562 ctx.lineTo(
563 centerX -
564 Math.sqrt(
565 (R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
566 ),
567 centerY - (extendedRay3[0].y - centerY),
568 );
569 extendRayToCanvasEdge(
570 centerX - R * scale,
571 centerY,
572 centerX -
573 Math.sqrt(
574 (R * scale) ** 2 - (centerY - extendedRay3Y) ** 2,
575 ),
576 centerY - (extendedRay3[0].y - centerY),
577 );
578 ctx.stroke();
579 }
580 }
581
582 function update() {
583 canvas.width = canvas.offsetWidth;
584 canvas.height = canvas.offsetHeight;
585 ctx.clearRect(0, 0, canvas.width, canvas.height);
586 ctx.fillStyle = "#f0f0f0";
587 ctx.fillRect(0, 0, canvas.width, canvas.height);
588 const isConcave = mirrorType.value === "concave";
589 const R = parseFloat(radiusInput.value);
590 const objDist = parseFloat(objectDistInput.value);
591
592 drawMirror(isConcave, R);
593 drawRays(isConcave, R, objDist);
594 }
595
596 function resetHeight() {
597 objectHeightInput.value = Math.max(
598 parseFloat(((radiusInput.value * 2) / 3).toFixed(2)),
599 0.1,
600 );
601 }
602
603 mirrorType.addEventListener("change", update);
604 radiusInput.addEventListener("input", () => {
605 resetHeight();
606 update();
607 });
608 objectDistInput.addEventListener("input", update);
609 objectHeightInput.addEventListener("input", update);
610 zoomInput.addEventListener("input", update);
611 window.addEventListener("resize", update);
612
613 resetHeight();
614 update();
615
616 let isCanvasHovered = false;
617
618 canvas.addEventListener("mouseenter", () => {
619 isCanvasHovered = true;
620 });
621
622 canvas.addEventListener("mouseleave", () => {
623 isCanvasHovered = false;
624 });
625
626 document.addEventListener("keydown", (e) => {
627 if (!isCanvasHovered) return;
628 if (e.key === "+" || e.key === "=") {
629 zoomInput.value = Math.min(parseFloat(zoomInput.value) + 0.1, 8);
630 update();
631 }
632 if (e.key === "-" || e.key === "_") {
633 zoomInput.value = Math.max(parseFloat(zoomInput.value) - 0.1, 0.1);
634 update();
635 }
636
637 // translate the canvas
638 if (e.key === "ArrowUp") {
639 offsetY -= 25;
640 update();
641 }
642 if (e.key === "ArrowDown") {
643 offsetY += 25;
644 update();
645 }
646 if (e.key === "ArrowLeft") {
647 offsetX -= 25;
648 update();
649 }
650 if (e.key === "ArrowRight") {
651 offsetX += 25;
652 update();
653 }
654 });
655</script>