import React, { useState, useRef, useEffect } from 'react'; import { Lightbulb, MapPin } from 'lucide-react'; const PlaneMirrorSimulator = () => { const [mode, setMode] = useState('rays'); const [rays, setRays] = useState([]); const [isDrawing, setIsDrawing] = useState(false); const [startPoint, setStartPoint] = useState(null); const [endPoint, setEndPoint] = useState(null); const [objectPos, setObjectPos] = useState({ x: 150, y: 250 }); const [isDraggingObject, setIsDraggingObject] = useState(false); const canvasRef = useRef(null); const mirrorX = 400; const canvasWidth = 800; const canvasHeight = 500; const drawCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvasWidth, canvasHeight); // Draw gray area behind mirror ctx.fillStyle = '#e5e7eb'; ctx.fillRect(mirrorX, 0, canvasWidth - mirrorX, canvasHeight); if (mode === 'rays') { // Draw all completed rays rays.forEach(ray => { drawRayWithReflection(ctx, ray.start, ray.end); }); // Draw ray being drawn if (isDrawing && startPoint && endPoint) { drawRayWithReflection(ctx, startPoint, endPoint); } } else { // Object mode drawObjectMode(ctx); } // Draw mirror line on top ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 8; ctx.beginPath(); ctx.moveTo(mirrorX, 0); ctx.lineTo(mirrorX, canvasHeight); ctx.stroke(); }; const drawRayWithReflection = (ctx, start, end) => { // Find where ray hits mirror const dx = end.x - start.x; const dy = end.y - start.y; const t = (mirrorX - start.x) / dx; const hitY = start.y + t * dy; // Calculate reflected ray const remainingX = end.x - mirrorX; const reflectX = mirrorX + Math.abs(remainingX); const reflectY = hitY + (end.y - hitY); // Calculate angle from normal (horizontal line) const angle = Math.atan2(Math.abs(hitY - start.y), Math.abs(mirrorX - start.x)); const angleDeg = Math.round(angle * 180 / Math.PI); // Draw NORMAL (horizontal purple dashed line) ctx.strokeStyle = '#8b5cf6'; ctx.lineWidth = 3; ctx.setLineDash([8, 5]); ctx.beginPath(); ctx.moveTo(mirrorX - 100, hitY); ctx.lineTo(mirrorX + 100, hitY); ctx.stroke(); ctx.setLineDash([]); // NORMAL label ctx.fillStyle = '#8b5cf6'; ctx.font = 'bold 14px Arial'; ctx.fillText('NORMAL', mirrorX - 35, hitY - 18); // Draw INCIDENT RAY (red) ctx.strokeStyle = '#ef4444'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(start.x, start.y); ctx.lineTo(mirrorX, hitY); ctx.stroke(); // Incident label ctx.fillStyle = '#ef4444'; ctx.font = 'bold 12px Arial'; ctx.fillText('Incident', start.x + 20, start.y + 20); // Draw REFLECTED RAY (green) ctx.strokeStyle = '#10b981'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(mirrorX, hitY); ctx.lineTo(reflectX, reflectY); ctx.stroke(); // Reflected label ctx.fillStyle = '#10b981'; ctx.font = 'bold 12px Arial'; ctx.fillText('Reflected', reflectX - 60, reflectY + 20); // Draw incident ANGLE ARC (red) ctx.strokeStyle = '#ef4444'; ctx.lineWidth = 2; ctx.beginPath(); const startAngle = Math.PI; const endAngle = Math.PI - angle; if (start.y < hitY) { ctx.arc(mirrorX, hitY, 50, startAngle, endAngle, true); } else { ctx.arc(mirrorX, hitY, 50, startAngle, Math.PI + angle, false); } ctx.stroke(); // Incident angle label ctx.fillStyle = '#ef4444'; ctx.font = 'bold 16px Arial'; ctx.fillText(`∠i = ${angleDeg}°`, mirrorX - 120, hitY + (start.y < hitY ? -30 : 45)); // Draw reflection ANGLE ARC (green) ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2; ctx.beginPath(); if (reflectY < hitY) { ctx.arc(mirrorX, hitY, 50, 0, angle, false); } else { ctx.arc(mirrorX, hitY, 50, 0, -angle, true); } ctx.stroke(); // Reflection angle label ctx.fillStyle = '#10b981'; ctx.font = 'bold 16px Arial'; ctx.fillText(`∠r = ${angleDeg}°`, mirrorX + 60, hitY + (reflectY < hitY ? -30 : 45)); // Hit point dot ctx.fillStyle = '#f59e0b'; ctx.beginPath(); ctx.arc(mirrorX, hitY, 5, 0, Math.PI * 2); ctx.fill(); }; const drawObjectMode = (ctx) => { const imageX = 2 * mirrorX - objectPos.x; const dist = Math.abs(mirrorX - objectPos.x); // Draw object (red arrow) ctx.strokeStyle = '#ef4444'; ctx.fillStyle = '#ef4444'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(objectPos.x, objectPos.y + 30); ctx.lineTo(objectPos.x, objectPos.y - 30); ctx.stroke(); ctx.beginPath(); ctx.moveTo(objectPos.x, objectPos.y - 30); ctx.lineTo(objectPos.x - 8, objectPos.y - 18); ctx.lineTo(objectPos.x + 8, objectPos.y - 18); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#1f2937'; ctx.font = 'bold 14px Arial'; ctx.fillText('Object', objectPos.x - 25, objectPos.y + 50); // Draw image (green arrow) ctx.strokeStyle = '#10b981'; ctx.fillStyle = '#10b981'; ctx.globalAlpha = 0.6; ctx.beginPath(); ctx.moveTo(imageX, objectPos.y + 30); ctx.lineTo(imageX, objectPos.y - 30); ctx.stroke(); ctx.beginPath(); ctx.moveTo(imageX, objectPos.y - 30); ctx.lineTo(imageX - 8, objectPos.y - 18); ctx.lineTo(imageX + 8, objectPos.y - 18); ctx.closePath(); ctx.fill(); ctx.globalAlpha = 1.0; ctx.fillStyle = '#1f2937'; ctx.fillText('Image', imageX - 20, objectPos.y + 50); // Distance markers ctx.strokeStyle = '#6b7280'; ctx.lineWidth = 1; ctx.setLineDash([3, 3]); ctx.beginPath(); ctx.moveTo(objectPos.x, canvasHeight - 40); ctx.lineTo(mirrorX, canvasHeight - 40); ctx.stroke(); ctx.beginPath(); ctx.moveTo(mirrorX, canvasHeight - 40); ctx.lineTo(imageX, canvasHeight - 40); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = '#1f2937'; ctx.font = '12px Arial'; ctx.fillText(`${Math.round(dist)}px`, (objectPos.x + mirrorX) / 2 - 25, canvasHeight - 45); ctx.fillText(`${Math.round(dist)}px`, (mirrorX + imageX) / 2 - 25, canvasHeight - 45); }; useEffect(() => { drawCanvas(); }, [rays, isDrawing, startPoint, endPoint, mode, objectPos]); const getMousePos = (e) => { const rect = canvasRef.current.getBoundingClientRect(); const scaleX = canvasWidth / rect.width; const scaleY = canvasHeight / rect.height; return { x: (e.clientX - rect.left) * scaleX, y: (e.clientY - rect.top) * scaleY }; }; const handleMouseDown = (e) => { const pos = getMousePos(e); if (mode === 'rays') { if (pos.x < mirrorX - 20) { setIsDrawing(true); setStartPoint(pos); setEndPoint(pos); } } else { const dist = Math.sqrt((pos.x - objectPos.x) ** 2 + (pos.y - objectPos.y) ** 2); if (dist < 40) { setIsDraggingObject(true); } } }; const handleMouseMove = (e) => { const pos = getMousePos(e); if (mode === 'rays' && isDrawing) { setEndPoint(pos); } else if (mode === 'object' && isDraggingObject) { if (pos.x > 50 && pos.x < mirrorX - 50) { setObjectPos({ x: pos.x, y: Math.max(80, Math.min(canvasHeight - 80, pos.y)) }); } } }; const handleMouseUp = (e) => { if (mode === 'rays' && isDrawing && startPoint && endPoint) { if (endPoint.x < mirrorX - 20) { setRays([...rays, { start: startPoint, end: endPoint }]); } setIsDrawing(false); setStartPoint(null); setEndPoint(null); } else if (mode === 'object') { setIsDraggingObject(false); } }; const getCurrentMeasurements = () => { if (mode === 'rays' && rays.length > 0) { const lastRay = rays[rays.length - 1]; const dx = lastRay.end.x - lastRay.start.x; const dy = lastRay.end.y - lastRay.start.y; const t = (mirrorX - lastRay.start.x) / dx; const hitY = lastRay.start.y + t * dy; const angle = Math.atan2(Math.abs(hitY - lastRay.start.y), Math.abs(mirrorX - lastRay.start.x)); const angleDeg = Math.round(angle * 180 / Math.PI); return { incident: `${angleDeg}°`, reflection: `${angleDeg}°`, objectDist: '-', imageDist: '-' }; } else if (mode === 'object') { const dist = Math.abs(mirrorX - objectPos.x); return { incident: '-', reflection: '-', objectDist: `${Math.round(dist)}px`, imageDist: `${Math.round(dist)}px` }; } return { incident: '-', reflection: '-', objectDist: '-', imageDist: '-' }; }; const measurements = getCurrentMeasurements(); return (

🪞 Plane Mirror Simulator

Explore reflection in flat mirrors

{mode === 'rays' ? '💡 Click and drag to draw light rays • Watch them reflect with equal angles!' : '📍 Drag the red object arrow • See the virtual image behind the mirror!'}

📐 Laws of Reflection

  1. The incident ray, reflected ray, and normal all lie in the same plane
  2. Angle of incidence = Angle of reflection

🪞 Plane Mirror Properties

Image Characteristics:

  • Virtual (behind mirror)
  • Upright (same orientation)
  • Same size as object
  • Same distance behind mirror

📊 Current Measurements

Angle of Incidence: {measurements.incident}

Angle of Reflection: {measurements.reflection}

Object Distance: {measurements.objectDist}

Image Distance: {measurements.imageDist}

🎯 Real-World Examples

  • Bathroom mirrors
  • Dressing mirrors
  • Periscopes
  • Kaleidoscopes
); }; export default PlaneMirrorSimulator;