// optics.js — модуль хелперов геометрической оптики для учебника Физика 8 // Экспорт в window.OPTICS = { ... } (function(){ 'use strict'; // === Палитра === const COLOR = { ray: '#fbbf24', rayIncident:'#0891b2', rayReflected:'#10b981', rayRefracted:'#a855f7', normal: '#94a3b8', lensConv: '#22c55e', lensDiv: '#f97316', mirror: '#475569', hatch: '#94a3b8', axis: '#cbd5e1', focus: '#1d4ed8', imageReal: '#7c3aed', imageVirtual:'#a78bfa' }; // === Стрелка-наконечник (внутренний помощник) === function arrowHead(x, y, ux, uy, size, color){ const w = size * 0.55; const px = -uy, py = ux; const bx = x - ux*size, by = y - uy*size; const lx = bx + px*w, ly = by + py*w; const rx = bx - px*w, ry = by - py*w; return ``; } // === Луч (линия + стрелка посередине) === // dashed: true → пунктир (для виртуальных продолжений) function ray(x1, y1, x2, y2, color, dashed){ color = color || COLOR.ray; const dx = x2 - x1, dy = y2 - y1; const len = Math.sqrt(dx*dx + dy*dy); if (len < 1e-6) return ''; const ux = dx/len, uy = dy/len; const dash = dashed ? ' stroke-dasharray="6 4"' : ''; let s = ''; s += ``; // Стрелка-наконечник в точке (x2,y2) if (!dashed) s += arrowHead(x2, y2, ux, uy, 9, color); return s; } // === Падающий, отражённый, преломлённый лучи + нормаль === // На границе двух сред (горизонтальной) с нормалью вверх function refractRay(x0, y0, angleInDeg, n1, n2, len){ // angleInDeg — угол падения от нормали (в градусах) // Возвращает SVG: падающий луч (сверху-слева к (x0,y0)) + нормаль + отражённый + преломлённый len = len || 80; const a1 = angleInDeg * Math.PI / 180; const sinA2 = (n1/n2) * Math.sin(a1); const tir = Math.abs(sinA2) > 1; // полное внутреннее отражение const a2 = tir ? 0 : Math.asin(sinA2); let s = ''; // Граница s += ``; // Нормаль (вверх и вниз пунктиром) s += ``; // Падающий луч (приходит сверху-слева) const xi = x0 - len*Math.sin(a1), yi = y0 - len*Math.cos(a1); s += ray(xi, yi, x0, y0, COLOR.rayIncident); // Отражённый луч (вверх-вправо) const xr = x0 + len*Math.sin(a1), yr = y0 - len*Math.cos(a1); s += ray(x0, y0, xr, yr, COLOR.rayReflected); // Преломлённый луч (вниз) if (!tir){ const xt = x0 + len*Math.sin(a2), yt = y0 + len*Math.cos(a2); s += ray(x0, y0, xt, yt, COLOR.rayRefracted); } return { svg: s, a1: a1, a2: a2, tir: tir }; } // === Отражение от плоского зеркала === // Зеркало горизонтальное на y = y0 function reflectRay(x0, y0, angleInDeg, len){ len = len || 80; const a = angleInDeg * Math.PI / 180; let s = ''; // Зеркало s += ``; // Штриховка с обратной стороны for (let i = -len; i <= len; i += 8){ s += ``; } // Нормаль s += ``; // Падающий const xi = x0 - len*Math.sin(a), yi = y0 - len*Math.cos(a); s += ray(xi, yi, x0, y0, COLOR.rayIncident); // Отражённый const xr = x0 + len*Math.sin(a), yr = y0 - len*Math.cos(a); s += ray(x0, y0, xr, yr, COLOR.rayReflected); return s; } // === Плоское зеркало (отдельный элемент) === function mirrorPlane(x, y, len, angleDeg){ angleDeg = angleDeg || 0; const a = angleDeg * Math.PI / 180; const ca = Math.cos(a), sa = Math.sin(a); const x1 = x - len/2*ca, y1 = y - len/2*sa; const x2 = x + len/2*ca, y2 = y + len/2*sa; const nx = -sa, ny = ca; // нормаль "наружу" let s = ``; // Штриховка с тыльной стороны for (let t = -len/2 + 3; t <= len/2; t += 8){ const bx = x + t*ca, by = y + t*sa; s += ``; } return s; } // === Сферическое зеркало (вогнутое/выпуклое) === // kind: 'concave' (вогнутое — фокусирует) | 'convex' (выпуклое — рассеивает) function mirrorSpherical(cx, cy, R, kind, halfH){ halfH = halfH || R * 0.6; const sign = (kind === 'convex') ? -1 : 1; // Дуга const x1 = cx - sign*R*0.15, y1 = cy - halfH; const x2 = cx - sign*R*0.15, y2 = cy + halfH; const sweep = sign > 0 ? 0 : 1; let s = ``; // Главная оптическая ось s += ``; return s; } // === Тонкая линза === // kind: 'converging' (двусторонне-выпуклая, |) | 'diverging' (двусторонне-вогнутая, |) // Возвращает SVG с осью, фокусами F и 2F function thinLens(cx, cy, halfH, F, kind){ halfH = halfH || 70; F = F || 80; const color = (kind === 'diverging') ? COLOR.lensDiv : COLOR.lensConv; let s = ''; // Главная оптическая ось s += ``; // Линза — вертикальный овал s += ``; if (kind === 'diverging'){ // Стрелки наружу (рассеивающая) s += arrowHead(cx, cy - halfH, 0, -1, 10, color); s += arrowHead(cx, cy + halfH, 0, 1, 10, color); } else { // Стрелки внутрь (собирающая) — вершины на оси s += arrowHead(cx - 6, cy - halfH + 8, -0.6, -0.8, 10, color); s += arrowHead(cx + 6, cy - halfH + 8, 0.6, -0.8, 10, color); s += arrowHead(cx - 6, cy + halfH - 8, -0.6, 0.8, 10, color); s += arrowHead(cx + 6, cy + halfH - 8, 0.6, 0.8, 10, color); } // Фокусы F и 2F s += ``; s += ``; s += `F`; s += `F`; s += ``; s += ``; s += `2F`; s += `2F`; return s; } // === Построение изображения в тонкой линзе === // F — фокусное расстояние (положительное для собирающей, отрицательное для рассеивающей) // d — расстояние от предмета до линзы (положительное) // h — высота предмета (положительная) // Возвращает { f: расстояние до изображения, h2: высота, virtual: bool, kind: 'real'|'virtual' } function buildLensImage(F, d, h){ // 1/F = 1/d + 1/f → f = d·F / (d - F) if (Math.abs(d - F) < 1e-6){ return { f: Infinity, h2: -Infinity, virtual: false, kind: 'infinity' }; } const f = d * F / (d - F); const h2 = -h * f / d; // увеличение: h2/h = -f/d const virtual = (F > 0) ? (d < F) : true; // для рассеивающей всегда мнимое return { f: f, h2: h2, virtual: virtual, kind: virtual ? 'virtual' : 'real' }; } // === Три "золотых" луча для построения изображения === // Возвращает SVG трёх лучей: через центр, параллельно оси, через передний фокус. // objX, objY — координата предмета (вершина стрелки) // lensX, lensY — центр линзы // F (px) — фокусное расстояние (в пикселях) function goldenRays(objX, objY, lensX, lensY, F){ let s = ''; const dx = lensX - objX; const dy = lensY - objY; // Луч 1: параллельно оси → проходит через F с другой стороны const y1At = objY; // приходит на линзу на высоте objY s += ray(objX, y1At, lensX, y1At, COLOR.rayIncident); s += ray(lensX, y1At, lensX + 2.5*F, lensY + (y1At - lensY) * (-2.5*F)/(F * (objY < lensY ? 1 : -1)), COLOR.rayIncident); // Луч 2: через центр — продолжается без преломления s += ray(objX, objY, lensX + dx, lensY + dy, COLOR.rayReflected); // Луч 3: через передний фокус → выходит параллельно оси // (вычислим точку пересечения с линзой) const slope = (lensY - objY) / (objX - (lensX - F)); const yAtLens = lensY - slope * F; // упрощённо s += ray(objX, objY, lensX, yAtLens, COLOR.rayRefracted); s += ray(lensX, yAtLens, lensX + 2*F, yAtLens, COLOR.rayRefracted); return s; } // === Глаз (упрощённая схема) === // accommodation: 0..1, где 0 — расслабленный (дальний предмет), 1 — напряжённый (близкий) function eyeDiagram(cx, cy, R, accommodation){ accommodation = accommodation || 0; let s = ''; // Глазное яблоко s += ``; // Роговица — выпуклость спереди (слева) s += ``; // Хрусталик — эллипс изнутри, форма зависит от accommodation const lensHalfH = R * 0.45; const lensW = R * (0.10 + 0.10 * accommodation); // толще при напряжении s += ``; // Сетчатка — задняя часть s += ``; // Зрительный нерв s += ``; return s; } // === Источник света / предмет === // kind: 'point' (точечный звезда) | 'arrow' (вертикальная стрелка-предмет) function lightObject(x, y, h, kind){ kind = kind || 'arrow'; if (kind === 'point'){ let s = ``; // Лучики for (let i = 0; i < 8; i++){ const a = i * Math.PI / 4; const x1 = x + 8*Math.cos(a), y1 = y + 8*Math.sin(a); const x2 = x + 13*Math.cos(a), y2 = y + 13*Math.sin(a); s += ``; } return s; } // Стрелка-предмет: основание на оси (y), вершина на (y - h) const tipY = y - h; const color = '#0f172a'; let s = ``; s += arrowHead(x, tipY, 0, -1, 9, color); return s; } // === Тень и полутень === // Источник в (sx, sy), непрозрачный объект — отрезок [ox-or .. ox+or] на высоте oy. // Возвращает SVG полупрозрачных треугольников тени/полутени, падающих на экран y = screenY. function shadowTriangle(sx, sy, ox, oy, or, screenY){ // Касательные от источника к краям объекта дают полутень. // Лучи через центр объекта дают полную тень. // Простейшая реализация для точечного источника: только тень. const dy = screenY - sy; const k = dy / (oy - sy); const x1 = sx + (ox - or - sx) * k; const x2 = sx + (ox + or - sx) * k; let s = ''; s += ``; return s; } // === Экспорт === window.OPTICS = { COLOR: COLOR, ray: ray, refractRay: refractRay, reflectRay: reflectRay, mirrorPlane: mirrorPlane, mirrorSpherical: mirrorSpherical, thinLens: thinLens, buildLensImage: buildLensImage, goldenRays: goldenRays, eyeDiagram: eyeDiagram, lightObject: lightObject, shadowTriangle: shadowTriangle }; })();