// 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
};
})();