the home site for me: also iteration 3 or 4 of my site
at main 22 kB view raw
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>