// 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 };
}
// === Анимация конвекции — тороидальный поток частиц ===
// Используется в §4 Физики 8 (главный визуал «конвекция в жидкости/газе»).
// opts: { N — число частиц (default 24), w, h — размеры области, speed (отн. ед., default 1),
// tHot, tCold — температуры для окраски (default 100, 20) }.
function createConvectionSim(opts) {
opts = opts || {};
const N = opts.N || 24;
const w = opts.w || 220;
const h = opts.h || 140;
const speed = opts.speed != null ? opts.speed : 1;
const tHot = opts.tHot != null ? opts.tHot : 100;
const tCold = opts.tCold != null ? opts.tCold : 20;
// Каждая частица движется по фазе вдоль контура прямоугольника:
// правая сторона — вверх (нагрев / подъём), верхняя — влево, левая — вниз (охлаждение), нижняя — вправо.
const parts = new Array(N);
for (let i = 0; i < N; i++) parts[i] = { phase: i / N };
return {
N: N, w: w, h: h, speed: speed,
_tHot: tHot, _tCold: tCold,
setSpeed(v) { this.speed = v; },
setHot(v) { this._tHot = v; },
setCold(v) { this._tCold = v; },
step(dt) {
const dPhase = 0.18 * this.speed * dt;
for (let i = 0; i < this.N; i++) {
parts[i].phase = (parts[i].phase + dPhase) % 1;
if (parts[i].phase < 0) parts[i].phase += 1;
}
},
// Возвращает координаты частицы (cx, cy) и температуру по её положению.
// phase ∈ [0,1): 0..0.25 — правая (подъём, t→tHot), 0.25..0.5 — верх (t=tHot),
// 0.5..0.75 — левая (опускание, t→tCold), 0.75..1 — низ (t=tCold).
_xy(phase, x, y) {
const margin = 12;
const W = this.w - 2 * margin;
const H = this.h - 2 * margin;
let lx, ly, t;
if (phase < 0.25) {
const k = phase / 0.25;
lx = W; ly = H * (1 - k);
t = this._tCold + (this._tHot - this._tCold) * k;
} else if (phase < 0.5) {
const k = (phase - 0.25) / 0.25;
lx = W * (1 - k); ly = 0;
t = this._tHot;
} else if (phase < 0.75) {
const k = (phase - 0.5) / 0.25;
lx = 0; ly = H * k;
t = this._tHot - (this._tHot - this._tCold) * k;
} else {
const k = (phase - 0.75) / 0.25;
lx = W * k; ly = H;
t = this._tCold;
}
return { cx: x + margin + lx, cy: y + margin + ly, t: t };
},
render(x, y) {
const tMin = Math.min(this._tCold, this._tHot);
const tMax = Math.max(this._tCold, this._tHot);
let s = '';
// Контур сосуда
s += ``;
// Нагреватель снизу (красная полоса)
s += ``;
// Частицы
for (let i = 0; i < this.N; i++) {
const p = this._xy(parts[i].phase, x, y);
const c = tempColor(p.t, tMin, tMax);
s += ``;
}
return s;
}
};
}
// === Электронные хелперы для электрических задач ===
// Параллельное и последовательное сопротивление
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;
}
// ======================================================================
// PHYSICS 7 HELPERS — силы, давление, гидростатика, барометры, энергия.
// Все размеры в SVG-пикселях; физические величины в СИ.
// ======================================================================
function forceVector(x, y, F, angleDeg, color, label, scale) {
scale = scale || 1.5;
const a = -angleDeg * Math.PI / 180;
const x2 = x + F * scale * Math.cos(a);
const y2 = y + F * scale * Math.sin(a);
let s = drawArrow(x, y, x2, y2, color || '#0f172a', 2.5, 9);
if (label) {
const lx = x2 + 8 * Math.cos(a);
const ly = y2 + 8 * Math.sin(a) + 4;
s += `${label}`;
}
return s;
}
function dynamometer(x, y, h, Fmax, Fcurr) {
const w = 30;
const springTop = y + 14, springBot = y + h - 28;
const stretch = Math.max(0, Math.min(1, Fcurr / Fmax)) * (springBot - springTop);
const pointerY = springTop + stretch;
let s = ``;
const coils = 8;
const coilH = (pointerY - springTop) / coils;
let path = `M ${x + w/2} ${springTop}`;
for (let i = 0; i < coils; i++) {
path += ` L ${x + (i%2 ? w-6 : 6)} ${springTop + (i+0.5)*coilH}`;
}
path += ` L ${x + w/2} ${pointerY}`;
s += ``;
const ticks = 10;
for (let i = 0; i <= ticks; i++) {
const ty = springTop + (springBot - springTop) * i / ticks;
s += ``;
if (i % 2 === 0) {
s += `${(Fmax * i / ticks).toFixed(Fmax < 5 ? 1 : 0)}`;
}
}
s += ``;
s += ``;
s += `F = ${Fcurr.toFixed(1)} Н`;
return s;
}
function blockOnSurface(x, y, w, h, label, weights) {
let s = ``;
if (label) s += `${label}`;
if (weights && weights > 0) {
const wH = 10, wW = w * 0.7;
for (let i = 0; i < weights; i++) {
const wy = y - (i + 1) * (wH + 2);
s += ``;
}
}
return s;
}
function connectedVessels(x, y, kindA, kindB, levelY, fluidColor) {
fluidColor = fluidColor || '#60a5fa';
const widths = { cylinder: 60, wide: 100, narrow: 35 };
const wA = widths[kindA] || 60, wB = widths[kindB] || 60;
const h = 140, gap = 50;
const xA = x, xB = x + wA + gap;
let s = '';
s += ``;
s += ``;
s += ``;
s += ``;
s += ``;
s += ``;
s += ``;
s += ``;
const fillH = (y + h) - levelY;
s += ``;
s += ``;
s += ``;
s += ``;
return s;
}
function hydraulicPress(x, y, sSmall, sLarge, fSmall, fluidColor) {
fluidColor = fluidColor || '#60a5fa';
const fLarge = fSmall * sLarge / sSmall;
const scale = 6;
const wSmall = Math.max(20, Math.sqrt(sSmall) * scale);
const wLarge = Math.max(40, Math.sqrt(sLarge) * scale);
const cylH = 60, baseY = y + 110, gap = 40;
const xS = x, xL = x + wSmall + gap;
let s = '';
s += ``;
s += ``;
s += ``;
s += ``;
s += ``;
s += drawArrow(xS + wSmall/2, y + 20, xS + wSmall/2, y + 40, '#dc2626', 2.5, 8);
s += drawArrow(xL + wLarge/2, y + 20, xL + wLarge/2, y + 40, '#10b981', 2.5, 8);
s += `F_1 = ${fSmall.toFixed(0)} Н`;
s += `F_2 = ${fLarge.toFixed(0)} Н`;
s += `S_1 = ${sSmall} см²`;
s += `S_2 = ${sLarge} см²`;
return s;
}
function mercuryBarometer(x, y, hMm) {
hMm = hMm == null ? 760 : hMm;
const maxMm = 800, scale = 0.4;
const tubeH = maxMm * scale, tubeW = 14;
const cupY = y + tubeH + 10, cupW = 70, cupH = 26;
const colH = hMm * scale, colTop = y + tubeH - colH;
let s = '';
s += ``;
s += ``;
s += ``;
s += ``;
s += `вакуум`;
for (let mm = 0; mm <= maxMm; mm += 50) {
const ty = y + tubeH - mm * scale;
s += ``;
if (mm % 100 === 0) s += `${mm}`;
}
s += ``;
s += `${hMm} мм`;
return s;
}
function aneroidBarometer(cx, cy, r, pressurePa) {
const pMm = pressurePa / 133.322;
const pMin = 720, pMax = 800;
const angDeg = -210 + 240 * Math.max(0, Math.min(1, (pMm - pMin) / (pMax - pMin)));
const angRad = angDeg * Math.PI / 180;
let s = '';
s += ``;
s += ``;
for (let p = pMin; p <= pMax; p += 10) {
const a = (-210 + 240 * (p - pMin) / (pMax - pMin)) * Math.PI / 180;
const r1 = r - 4, r2 = r - 12;
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);
s += ``;
if (p % 20 === 0) {
const rT = r - 22;
const xT = cx + rT * Math.cos(a), yT = cy + rT * Math.sin(a) + 3;
s += `${p}`;
}
}
const tipX = cx + (r - 18) * Math.cos(angRad), tipY = cy + (r - 18) * Math.sin(angRad);
s += ``;
s += ``;
s += `${pMm.toFixed(0)} мм рт. ст.`;
return s;
}
function uManometer(x, y, w, h, deltaH, fluidColor) {
fluidColor = fluidColor || '#0891b2';
const tube = 14;
const xL = x, xR = x + w - tube;
const baseY = y + h;
let s = ``;
s += ``;
s += ``;
const lLevel = y + h * 0.45 + deltaH / 2;
const rLevel = y + h * 0.45 - deltaH / 2;
s += ``;
s += ``;
if (Math.abs(deltaH) > 1) {
s += ``;
s += `Δh`;
}
return s;
}
function rulerWithError(x, y, lenCm, mmPerDiv) {
mmPerDiv = mmPerDiv || 1;
const pxPerCm = 30;
const w = lenCm * pxPerCm, h = 28;
let s = ``;
const totalDivs = (lenCm * 10) / mmPerDiv;
for (let i = 0; i <= totalDivs; i++) {
const tx = x + (i * mmPerDiv / 10) * pxPerCm;
const mm = i * mmPerDiv;
const isCm = mm % 10 === 0, isHalfCm = mm % 5 === 0;
const tickH = isCm ? 12 : (isHalfCm ? 8 : 5);
s += ``;
if (isCm) s += `${mm/10}`;
}
s += `см`;
return s;
}
function bimetal(x, y, w, h, deltaT) {
const ang = Math.max(-30, Math.min(30, deltaT * 0.3));
let s = ``;
s += ``;
s += ``;
s += `ΔT = ${deltaT.toFixed(0)} °C`;
s += ``;
return s;
}
function expandingRod(x, y, l0, alpha, deltaT) {
const dl = l0 * alpha * deltaT * 1000;
const len = l0 + dl;
const h = 16;
const color = tempColor(deltaT, 0, 100);
let s = ``;
s += ``;
if (dl > 1) {
s += drawArrow(x + l0, y + h + 14, x + len, y + h + 14, '#dc2626', 1.8, 6);
s += `Δl = ${dl.toFixed(1)} px`;
}
return s;
}
// === HillSlideSim — тележка скатывается с горки (для §42, закон сохранения) ===
class HillSlideSim {
constructor(opts) {
opts = opts || {};
this.x0 = opts.x0 || 30;
this.y0 = opts.y0 || 200;
this.hStart = opts.hStart != null ? opts.hStart : 5;
this.m = opts.mass || 1;
this.g = opts.g || 9.8;
this.friction = opts.friction || 0;
this.scale = opts.scale || 30;
this.reset();
}
reset() { this.t = 0; this.x = 0; this.v = 0; this.h = this.hStart; }
step(dt) {
const gEff = this.g * (1 - this.friction);
this.t += dt;
const dropped = this.hStart - this.h;
if (this.h <= 0) { this.h = 0; this.v = Math.sqrt(2 * gEff * this.hStart); return; }
this.v = Math.sqrt(2 * gEff * Math.max(0, dropped));
this.x += this.v * dt;
const L = this.hStart * 4;
const xRel = Math.min(this.x, L) / L;
this.h = this.hStart * Math.pow(1 - xRel, 2);
}
getEnergies() {
return {
Ek: 0.5 * this.m * this.v * this.v,
Ep: this.m * this.g * this.h,
Etot: 0.5 * this.m * this.v * this.v + this.m * this.g * this.h
};
}
renderProfile() {
const L = this.hStart * 4;
const pxL = L * this.scale, pxH = this.hStart * this.scale;
const baseY = this.y0;
let path = `M ${this.x0} ${baseY - pxH}`;
const N = 40;
for (let i = 1; i <= N; i++) {
const xRel = i / N;
const yRel = Math.pow(1 - xRel, 2);
path += ` L ${(this.x0 + pxL * xRel).toFixed(1)} ${(baseY - pxH * yRel).toFixed(1)}`;
}
let s = '';
s += ``;
s += ``;
const cartX = this.x0 + this.x * this.scale;
const cartY = baseY - this.h * this.scale;
s += ``;
s += ``;
s += ``;
return s;
}
}
// === PendulumSim — математический маятник (для §42) ===
class PendulumSim {
constructor(opts) {
opts = opts || {};
this.cx = opts.cx || 150;
this.cy = opts.cy || 40;
this.L = opts.length || 1.2;
this.m = opts.mass || 0.5;
this.g = opts.g || 9.8;
this.phi0 = (opts.angleDeg || 25) * Math.PI / 180;
this.scale = opts.scale || 80;
this.reset();
}
reset() { this.t = 0; this.phi = this.phi0; this.omega = 0; }
step(dt) {
const alpha = -(this.g / this.L) * Math.sin(this.phi);
this.omega += alpha * dt;
this.phi += this.omega * dt;
this.t += dt;
}
getEnergies() {
const h = this.L * (1 - Math.cos(this.phi));
const v = Math.abs(this.omega) * this.L;
return {
Ek: 0.5 * this.m * v * v,
Ep: this.m * this.g * h,
Etot: 0.5 * this.m * v * v + this.m * this.g * h
};
}
render() {
const pxL = this.L * this.scale;
const bx = this.cx + pxL * Math.sin(this.phi);
const by = this.cy + pxL * Math.cos(this.phi);
let s = '';
s += ``;
s += ``;
s += ``;
s += ``;
return s;
}
}
// === Экспорт ===
window.PHYS = {
tempColor: tempColor,
thermometer: thermometer,
calorimeter: calorimeter,
createHeatBar: createHeatBar,
createConvectionSim: createConvectionSim,
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,
// Physics 7 — Phase 0
forceVector: forceVector,
dynamometer: dynamometer,
blockOnSurface: blockOnSurface,
connectedVessels: connectedVessels,
hydraulicPress: hydraulicPress,
mercuryBarometer: mercuryBarometer,
aneroidBarometer: aneroidBarometer,
uManometer: uManometer,
rulerWithError: rulerWithError,
bimetal: bimetal,
expandingRod: expandingRod,
HillSlideSim: HillSlideSim,
PendulumSim: PendulumSim
};
})();