محرك البصريات
src/lib/optics.js · 206 سطرًا
سنيل، فرينل، العدسة الرقيقة، المرايا، المنشور، شقّان.
1
/**2
* محرك البصريات — دوال فيزيائية خالصة (بدون أي قيم ثابتة مزيفة).3
* جميع الوحدات: الزوايا بالدرجات عند الحدود، وأطوال الموجة بالنانومتر،4
* والأطوال بالمتر ما لم يُذكر خلاف ذلك.5
*/6
7
export const C_LIGHT = 299792458; // m/s8
export const DEG2RAD = Math.PI / 180;9
export const RAD2DEG = 180 / Math.PI;10
11
export const clamp = (v, a, b) => Math.min(b, Math.max(a, v));12
13
export const dot = (a, b) => a.x * b.x + a.y * b.y;14
15
export function normalize(v) {16
const L = Math.hypot(v.x, v.y) || 1;17
return { x: v.x / L, y: v.y / L };18
}19
20
/** الزاوية الحرجة (درجات) — تُعرَّف فقط عند الانتقال من وسط أكثر كثافة ضوئية إلى أقل. */21
export function criticalAngle(n1, n2) {22
if (!(n1 > 0) || !(n2 > 0)) return null;23
if (!(n1 > n2)) return null;24
const s = n2 / n1;25
if (s >= 1) return null;26
return Math.asin(s) * RAD2DEG;27
}28
29
/** قانون سنيل: n1·sinθ1 = n2·sinθ2 */30
export function refraction(n1, n2, theta1Deg) {31
const theta1 = theta1Deg * DEG2RAD;32
const sin1 = Math.sin(theta1);33
const sin2 = (n1 / n2) * sin1;34
const tc = criticalAngle(n1, n2);35
if (Math.abs(sin2) > 1) {36
return { tir: true, theta1Deg, theta1, sin1, sin2, theta2: null, theta2Deg: null, criticalAngleDeg: tc };37
}38
const theta2 = Math.asin(sin2);39
return {40
tir: false,41
theta1Deg,42
theta1,43
sin1,44
sin2,45
theta2,46
theta2Deg: theta2 * RAD2DEG,47
criticalAngleDeg: tc,48
};49
}50
51
/** معاملات فرينل لضوء غير مستقطب + التحقق من حفظ الطاقة */52
export function fresnel(n1, n2, theta1Deg) {53
const t1 = theta1Deg * DEG2RAD;54
const r = refraction(n1, n2, theta1Deg);55
if (r.tir) return { R: 1, T: 0, Rs: 1, Rp: 1, tir: true, theta2Deg: null };56
const c1 = Math.cos(t1);57
const c2 = Math.cos(r.theta2);58
const rs = (n1 * c1 - n2 * c2) / (n1 * c1 + n2 * c2);59
const rp = (n2 * c1 - n1 * c2) / (n2 * c1 + n1 * c2);60
const Rs = rs * rs;61
const Rp = rp * rp;62
return { Rs, Rp, R: (Rs + Rp) / 2, T: 1 - (Rs + Rp) / 2, tir: false, theta2Deg: r.theta2Deg };63
}64
65
/** معادلة العدسة الرقيقة: 1/f = 1/do + 1/di ، والتكبير M = -di/do */66
export function thinLens(doCm, fCm) {67
if (!Number.isFinite(doCm) || !Number.isFinite(fCm) || doCm <= 0 || fCm === 0) {68
return { di: null, m: null, type: 'undefined', atFocus: false, f: fCm };69
}70
const inv = 1 / fCm - 1 / doCm;71
if (Math.abs(inv) < 1e-12) {72
return { di: Infinity, m: null, type: 'at-focus', atFocus: true, f: fCm };73
}74
const di = 1 / inv;75
const m = -di / doCm;76
return { di, m, type: di > 0 ? 'real' : 'virtual', atFocus: false, f: fCm };77
}78
79
/** معادلة المرآة الكروية: 1/f = 1/do + 1/di مع f = R/2 (R>0 مقعرة، R<0 محدبة) */80
export function sphericalMirror(doCm, RCm) {81
const f = Number.isFinite(RCm) ? RCm / 2 : Infinity;82
if (!Number.isFinite(doCm) || doCm <= 0) return { di: null, m: null, type: 'undefined', f, atFocus: false };83
const inv = (f === Infinity ? 0 : 1 / f) - 1 / doCm;84
if (Math.abs(inv) < 1e-12) return { di: Infinity, m: null, type: 'at-focus', f, atFocus: true };85
const di = 1 / inv;86
return { di, m: -di / doCm, type: di > 0 ? 'real' : 'virtual', f, atFocus: false };87
}88
89
/** معاملات كوشي التقريبية: n(λ) = A + B/λ² ، λ بالميكرومتر */90
export const GLASSES = {91
bk7: { id: 'bk7', name: 'زجاج بوروسيليكات BK7', A: 1.5046, B: 0.0042 },92
crown: { id: 'crown', name: 'زجاج كراون', A: 1.522, B: 0.0046 },93
flint: { id: 'flint', name: 'زجاج فلينت', A: 1.6034, B: 0.00895 },94
acrylic: { id: 'acrylic', name: 'أكريليك', A: 1.4831, B: 0.0032 },95
diamond: { id: 'diamond', name: 'ألماس', A: 2.38, B: 0.0106 },96
};97
98
export function cauchyN(lambdaNm, glass) {99
const g = glass || GLASSES.bk7;100
const um = lambdaNm / 1000;101
return g.A + g.B / (um * um);102
}103
104
/** انحراف المنشور: r1 = sin⁻¹(sin i1 / n) ، r2 = A − r1 ، i2 = sin⁻¹(n·sin r2) ، δ = i1 + i2 − A */105
export function prismDeviation(apexDeg, incidenceDeg, n) {106
const A = apexDeg * DEG2RAD;107
const i1 = incidenceDeg * DEG2RAD;108
const s1 = Math.sin(i1) / n;109
if (Math.abs(s1) > 1) {110
return { valid: false, tir: false, i1Deg: incidenceDeg, r1Deg: null, r2Deg: null, i2Deg: null, deltaDeg: null };111
}112
const r1 = Math.asin(s1);113
const r2 = A - r1;114
const s2 = n * Math.sin(r2);115
const base = { valid: true, i1Deg: incidenceDeg, r1Deg: r1 * RAD2DEG, r2Deg: r2 * RAD2DEG };116
if (Math.abs(s2) > 1) {117
return { ...base, tir: true, i2Deg: null, deltaDeg: null };118
}119
const i2 = Math.asin(s2);120
const delta = i1 + i2 - A;121
return { ...base, tir: false, i2Deg: i2 * RAD2DEG, deltaDeg: delta * RAD2DEG };122
}123
124
/** زاوية الانحراف الصغرى: δmin = 2·sin⁻¹(n·sin(A/2)) − A */125
export function minDeviation(apexDeg, n) {126
const A = apexDeg * DEG2RAD;127
const s = n * Math.sin(A / 2);128
if (s > 1) return null;129
return (2 * Math.asin(s) - A) * RAD2DEG;130
}131
132
/** تجربة يونغ: تباعد الأهداب Δy = λL/d */133
export function fringeSpacing({ lambdaNm, slitMm, screenM }) {134
const lambda = lambdaNm * 1e-9;135
const d = slitMm * 1e-3;136
const L = screenM;137
const dyM = (lambda * L) / d;138
return {139
lambda,140
d,141
L,142
dyM,143
dyMm: dyM * 1000,144
firstOrderAngleDeg: Math.asin(clamp(lambda / d, 0, 1)) * RAD2DEG,145
maxOrder: Math.floor(d / lambda),146
};147
}148
149
/** شدة التداخل لشقين (إهمال عرض الشق): I = cos²(π·d·sinθ/λ) */150
export function twoSlitIntensity(yMm, { lambdaNm, slitMm, screenM }) {151
const lambda = lambdaNm * 1e-9;152
const d = slitMm * 1e-3;153
const y = yMm * 1e-3;154
const theta = Math.atan2(y, screenM);155
const delta = d * Math.sin(theta);156
return Math.cos((Math.PI * delta) / lambda) ** 2;157
}158
159
export const mediumSpeed = (n) => C_LIGHT / n;160
export const waveFrequency = (lambdaNm) => C_LIGHT / (lambdaNm * 1e-9);161
export const wavelengthInMedium = (lambdaNm, n) => lambdaNm / n;162
163
/** انكسار متجهي: I اتجاه ساقط، N عمودي على السطح نحو جهة السقوط. يعيد null عند الانعكاس الكلي. */164
export function refractVector(I, N, n1, n2) {165
const eta = n1 / n2;166
const cosi = -dot(I, N);167
const k = 1 - eta * eta * (1 - cosi * cosi);168
if (k < 0) return null;169
const s = eta * cosi - Math.sqrt(k);170
return normalize({ x: eta * I.x + s * N.x, y: eta * I.y + s * N.y });171
}172
173
export function reflectVector(I, N) {174
const d = dot(I, N);175
return normalize({ x: I.x - 2 * d * N.x, y: I.y - 2 * d * N.y });176
}177
178
/** تقريب لون الطول الموجي (Bruton) لعرض الطيف */179
export function wavelengthToRGB(nm) {180
let r = 0;181
let g = 0;182
let b = 0;183
if (nm >= 380 && nm < 440) {184
r = -(nm - 440) / 60;185
b = 1;186
} else if (nm >= 440 && nm < 490) {187
g = (nm - 440) / 50;188
b = 1;189
} else if (nm >= 490 && nm < 510) {190
g = 1;191
b = -(nm - 510) / 20;192
} else if (nm >= 510 && nm < 580) {193
r = (nm - 510) / 70;194
g = 1;195
} else if (nm >= 580 && nm < 645) {196
r = 1;197
g = -(nm - 645) / 65;198
} else if (nm >= 645) {199
r = 1;200
}201
let f = 1;202
if (nm >= 380 && nm < 420) f = 0.35 + (0.65 * (nm - 380)) / 40;203
else if (nm > 700) f = 0.35 + (0.65 * (780 - nm)) / 80;204
const c = (v) => Math.round(255 * Math.pow(clamp(v * f, 0, 1), 0.8));205
return `rgb(${c(r)}, ${c(g)}, ${c(b)})`;206
}الأكواد معروضة كما هي في المشروع تمامًا، وتتحدث تلقائيًا مع أي تعديل لاحق.