// phys.js — модуль физических хелперов для учебника Физика 10 // Экспорт в window.PHYS = { ... } (function(){ 'use strict'; // === Стрелка вектора (2D) === function drawArrow(x1, y1, x2, y2, color, width, headSize) { width = width || 2; headSize = headSize || 10; 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 px = -uy, py = ux; const h = headSize, w = headSize * 0.6; const bx = x2 - ux*h, by = y2 - uy*h; const lx = bx + px*w, ly = by + py*w; const rx = bx - px*w, ry = by - py*w; return `` + ``; } // === Линии электрического поля от точечного заряда === function fieldLinesPointCharge(cx, cy, sign, scale, numLines) { numLines = numLines || 16; scale = scale || 80; let s = ''; const color = sign > 0 ? '#dc2626' : '#2563eb'; for (let i = 0; i < numLines; i++) { const a = 2 * Math.PI * i / numLines; const r1 = 18, r2 = scale; const x1 = cx + r1*Math.cos(a), y1 = cy + r1*Math.sin(a); const x2 = cx + r2*Math.cos(a), y2 = cy + r2*Math.sin(a); if (sign > 0) s += drawArrow(x1, y1, x2, y2, color, 1.4, 7); else s += drawArrow(x2, y2, x1, y1, color, 1.4, 7); } return s; } // === Обозначение заряда (кружок с +/-) === function chargeMark(cx, cy, sign, r, label) { r = r || 14; const color = sign > 0 ? '#dc2626' : '#2563eb'; const fill = sign > 0 ? '#fecaca' : '#bfdbfe'; let s = ''; s += ``; if (sign > 0) { s += ``; s += ``; } else { s += ``; } if (label) { s += `${label}`; } return s; } // === Магнитное поле сквозь экран (сетка крестиков или точек) === function magneticFieldGrid(x0, y0, w, h, cols, rows, direction) { // direction: 'in' = крест (× — вошло в плоскость), 'out' = точка (• — вышло) let s = ''; const dx = w / (cols - 1), dy = h / (rows - 1); const color = '#7c3aed'; for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { const cx = x0 + i * dx, cy = y0 + j * dy; s += ``; if (direction === 'in') { s += ``; s += ``; } else { s += ``; } } } return s; } // === Молекула газа (частица) === function molecule(x, y, r, color) { r = r || 4; color = color || '#2563eb'; return ``; } // === Симуляция газа (упругое столкновение со стенками) === function createGasSim(opts) { opts = opts || {}; const N = opts.N || 30; const W = opts.W || 320; const H = opts.H || 220; const baseSpeed = opts.speed || 60; // px/s const r = opts.r || 4; const particles = []; for (let i = 0; i < N; i++) { particles.push({ x: r + Math.random() * (W - 2*r), y: r + Math.random() * (H - 2*r), vx: (Math.random() - 0.5) * 2 * baseSpeed, vy: (Math.random() - 0.5) * 2 * baseSpeed }); } return { W: W, H: H, r: r, particles: particles, step(dt) { for (const p of particles) { p.x += p.vx * dt; p.y += p.vy * dt; if (p.x < r) { p.x = r; p.vx = -p.vx; } if (p.x > W - r) { p.x = W - r; p.vx = -p.vx; } if (p.y < r) { p.y = r; p.vy = -p.vy; } if (p.y > H - r) { p.y = H - r; p.vy = -p.vy; } } }, render(color) { color = color || '#2563eb'; return particles.map(p => molecule(p.x, p.y, r, color)).join(''); }, setSpeed(scale) { for (const p of particles) { p.vx *= scale; p.vy *= scale; } } }; } // === Электрические схемы: компоненты === // orientation: 'h' (горизонтально, по умолчанию) или 'v' (вертикально) function batteryEMF(x, y, EMF, orientation) { orientation = orientation || 'h'; let s = ''; if (orientation === 'h') { s += ``; s += ``; s += `+`; s += ``; if (EMF !== undefined) s += `ε = ${EMF}`; } else { s += ``; s += ``; if (EMF !== undefined) s += `ε = ${EMF}`; } return s; } function resistor(x, y, R, orientation) { orientation = orientation || 'h'; let s = ''; if (orientation === 'h') { s += ``; if (R !== undefined) s += `R = ${R}`; } else { s += ``; if (R !== undefined) s += `R = ${R}`; } return s; } function capacitorSymbol(x, y, C, orientation) { orientation = orientation || 'h'; let s = ''; if (orientation === 'h') { s += ``; s += ``; if (C !== undefined) s += `C = ${C}`; } return s; } function ammeterSymbol(x, y, r) { r = r || 14; return `` + `A`; } function voltmeterSymbol(x, y, r) { r = r || 14; return `` + `V`; } function lightbulbSymbol(x, y, r) { r = r || 14; let s = ''; s += ``; s += ``; s += ``; return s; } function inductorSymbol(x, y, L, orientation) { orientation = orientation || 'h'; let s = ''; if (orientation === 'h') { for (let i = 0; i < 4; i++) { const cx = x - 15 + i * 10; s += ``; } if (L !== undefined) s += `L = ${L}`; } return s; } function wire(x1, y1, x2, y2) { return ``; } // === Эталонные константы === const CONST = { k: 9e9, // Кулона e: 1.6e-19, // элементарный заряд eps0: 8.85e-12, // электрическая постоянная kB: 1.38e-23, // Больцмана NA: 6.022e23, // Авогадро R: 8.314, // универсальная газовая c: 3e8, // скорость света g: 9.8, // ускорение свободного падения atm: 101325, // 1 атм в Па T0: 273.15 // ноль Цельсия в К }; // === Конвертеры единиц === function celsiusToKelvin(t) { return t + 273.15; } function kelvinToCelsius(T) { return T - 273.15; } function atmToPa(p) { return p * 101325; } function paToAtm(p) { return p / 101325; } function litersToM3(V) { return V / 1000; } function m3ToLiters(V) { return V * 1000; } // === Расширения для Физики 8 (тепловые явления) === // === Цвет по температуре: 240° (синий) → 0° (красный) === function tempColor(t, tMin, tMax) { const u = Math.max(0, Math.min(1, (t - tMin) / (tMax - tMin))); const hue = 240 * (1 - u); return `hsl(${hue.toFixed(0)},72%,52%)`; } // === Термометр (вертикальная шкала, столбик ртути по температуре) === function thermometer(x, y, h, tMin, tMax, tValue) { // x, y — верх корпуса; h — высота столбика const reservoirR = 11; const tubeW = 8; const u = Math.max(0, Math.min(1, (tValue - tMin) / (tMax - tMin))); const fillH = h * u; const color = tempColor(tValue, tMin, tMax); let s = ''; // Корпус (стекло) s += ``; // Столбик ртути s += ``; // Резервуар s += ``; // Деления (5) for (let i = 0; i <= 5; i++) { const ty = y + h * i / 5; const tv = tMax - (tMax - tMin) * i / 5; s += ``; s += `${tv.toFixed(0)}`; } // Подпись текущего значения s += `${tValue.toFixed(0)} °C`; return s; } // === Калориметр-стакан с жидкостью === function calorimeter(x, y, w, h, fillFrac, liquidColor) { fillFrac = Math.max(0, Math.min(1, fillFrac || 0.6)); liquidColor = liquidColor || '#60a5fa'; const wall = 4; const innerH = h - wall; const liqH = innerH * fillFrac; let s = ''; // Внешний контур s += ``; // Жидкость s += ``; // Мениск s += ``; return s; } // === Симуляция теплопроводности по стержню (одномерное уравнение тепла) === // Возвращает объект с .step(dt), .render(x, y, w, h), .reset(), .setTHot(t), .setTCold(t) function createHeatBar(opts) { opts = opts || {}; const N = opts.N || 30; const tHot = opts.tHot != null ? opts.tHot : 200; const tCold = opts.tCold != null ? opts.tCold : 0; const alpha = opts.alpha || 0.25; // коэф. диффузии (отн. ед.) const T = new Array(N); for (let i = 0; i < N; i++) T[i] = tCold; return { N: N, alpha: alpha, T: T, _tHot: tHot, _tCold: tCold, setTHot(v) { this._tHot = v; }, setTCold(v) { this._tCold = v; }, reset() { for (let i = 0; i < this.N; i++) this.T[i] = this._tCold; }, step(dt) { // Конечно-разностное уравнение теплопроводности с граничными условиями Дирихле. const Tnew = new Array(this.N); Tnew[0] = this._tHot; Tnew[this.N - 1] = this._tCold; for (let i = 1; i < this.N - 1; i++) { Tnew[i] = this.T[i] + this.alpha * dt * (this.T[i-1] - 2*this.T[i] + this.T[i+1]); } this.T = Tnew; }, render(x, y, w, h) { const segW = w / this.N; const tMin = Math.min(this._tCold, this._tHot); const tMax = Math.max(this._tCold, this._tHot); let s = ''; for (let i = 0; i < this.N; i++) { const c = tempColor(this.T[i], tMin, tMax); s += ``; } // Контур стержня s += ``; return s; } }; } // === График фазовых переходов T(t) с горизонтальными плато === // points: массив сегментов [{tStart, tEnd, Tstart, Tend, label?}] // W, H, pad — размеры графика; tMaxAll, TminAll, TmaxAll — диапазоны function phaseGraphTT(W, H, pad, points, tMaxAll, TminAll, TmaxAll) { const toX = t => pad + (W - 2*pad) * t / tMaxAll; const toY = T => H - pad - (H - 2*pad) * (T - TminAll) / (TmaxAll - TminAll); let s = ''; // Оси s += ``; s += ``; s += `t`; s += `T,°C`; // Сегменты let d = ''; let first = true; for (const seg of points) { if (first) { d += `M ${toX(seg.tStart).toFixed(1)} ${toY(seg.Tstart).toFixed(1)} `; first = false; } d += `L ${toX(seg.tEnd).toFixed(1)} ${toY(seg.Tend).toFixed(1)} `; } s += ``; // Подписи сегментов for (const seg of points) { if (seg.label) { const mx = (toX(seg.tStart) + toX(seg.tEnd)) / 2; const my = (toY(seg.Tstart) + toY(seg.Tend)) / 2 - 8; s += `${seg.label}`; } } return { svg: s, toX: toX, toY: toY }; } // === Электронные хелперы для электрических задач === // Параллельное и последовательное сопротивление function Rseries() { let R = 0; for (let i = 0; i < arguments.length; i++) R += arguments[i]; return R; } function Rparallel() { let inv = 0; for (let i = 0; i < arguments.length; i++) { if (arguments[i] > 0) inv += 1 / arguments[i]; } return inv > 0 ? 1 / inv : Infinity; } // === Экспорт === window.PHYS = { tempColor: tempColor, thermometer: thermometer, calorimeter: calorimeter, createHeatBar: createHeatBar, phaseGraphTT: phaseGraphTT, Rseries: Rseries, Rparallel: Rparallel, drawArrow: drawArrow, fieldLinesPointCharge: fieldLinesPointCharge, chargeMark: chargeMark, magneticFieldGrid: magneticFieldGrid, molecule: molecule, createGasSim: createGasSim, batteryEMF: batteryEMF, resistor: resistor, capacitorSymbol: capacitorSymbol, ammeterSymbol: ammeterSymbol, voltmeterSymbol: voltmeterSymbol, lightbulbSymbol: lightbulbSymbol, inductorSymbol: inductorSymbol, wire: wire, CONST: CONST, celsiusToKelvin: celsiusToKelvin, kelvinToCelsius: kelvinToCelsius, atmToPa: atmToPa, paToAtm: paToAtm, litersToM3: litersToM3, m3ToLiters: m3ToLiters }; })();