{"id":279,"date":"2026-09-14T20:49:29","date_gmt":"2026-09-14T18:49:29","guid":{"rendered":"https:\/\/benlhajlahsen.fr\/?page_id=279"},"modified":"2026-09-14T20:51:51","modified_gmt":"2026-09-14T18:51:51","slug":"prisme","status":"publish","type":"page","link":"https:\/\/benlhajlahsen.fr\/index.php\/prisme\/","title":{"rendered":"Prisme"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Prisme et d\u00e9viation minimale<\/title>\n<style>\n  :root{\n    --bg:#ffffff; --panel:#f7f8fa; --border:#e0e3e8;\n    --text:#1c1f26; --muted:#6b7280; --accent:#c0392b;\n    --glass:#eaf1fb; --curve:#2f7dd1; --marker:#c0392b; --min:#1f9d55;\n  }\n  @media (prefers-color-scheme: dark){\n    :root{\n      --bg:#14161a; --panel:#1c1f26; --border:#2c313a;\n      --text:#e7e9ee; --muted:#9aa2b1; --accent:#ff6b5e;\n      --glass:#16324a; --curve:#5b9bef; --marker:#ff6b5e; --min:#3fd07d;\n    }\n  }\n  *{box-sizing:border-box;}\n  body{\n    margin:0; padding:20px; background:var(--bg); color:var(--text);\n    font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n  }\n  .wrap{max-width:960px; margin:0 auto;}\n  h1{font-size:1.25rem; font-weight:600; margin:0 0 4px;}\n  p.sub{color:var(--muted); font-size:0.92rem; margin:0 0 18px;}\n  .panel{\n    background:var(--panel); border:1px solid var(--border); border-radius:12px;\n    padding:16px; margin-bottom:16px;\n  }\n  .grid{display:grid; grid-template-columns:1.1fr 0.9fr; gap:16px;}\n  @media (max-width:760px){ .grid{grid-template-columns:1fr;} }\n  svg{width:100%; height:auto; display:block;}\n  .controls{display:grid; grid-template-columns:1fr; gap:14px;}\n  .ctrl-row{display:flex; align-items:center; gap:12px;}\n  .ctrl-row label{flex:0 0 230px; font-size:0.92rem;}\n  .ctrl-row input[type=range]{flex:1;}\n  .ctrl-row .val{flex:0 0 70px; text-align:right; font-variant-numeric:tabular-nums; font-weight:600;}\n  .status{display:flex; flex-wrap:wrap; gap:8px 22px; margin-top:14px; font-size:0.92rem;}\n  .status div{display:flex; gap:6px;}\n  .status span.k{color:var(--muted);}\n  .status span.v{font-weight:600; font-variant-numeric:tabular-nums;}\n  .badge{display:inline-block; padding:2px 10px; border-radius:999px; font-size:0.82rem; font-weight:600;}\n  .badge.ok{background:rgba(31,157,85,0.15); color:var(--min);}\n  .badge.no{background:rgba(192,57,43,0.15); color:var(--accent);}\n  footer{color:var(--muted); font-size:0.8rem; text-align:center; margin-top:10px;}\n<\/style>\n<\/head>\n<body>\n<div class=\"wrap\">\n  <h1>Prisme : trajet du rayon et d\u00e9viation minimale<\/h1>\n  <p class=\"sub\">Faites varier l&rsquo;angle d&rsquo;incidence <i>i<\/i> : observez le trajet du rayon et, simultan\u00e9ment, le point correspondant sur la courbe de d\u00e9viation D(i). Le minimum D<sub>m<\/sub> est atteint quand i = i&rsquo;.<\/p>\n\n  <div class=\"grid\">\n    <div class=\"panel\">\n      <svg id=\"scene\" viewBox=\"0 0 500 380\" preserveAspectRatio=\"xMidYMid meet\"><\/svg>\n    <\/div>\n    <div class=\"panel\">\n      <svg id=\"chart\" viewBox=\"0 0 500 380\" preserveAspectRatio=\"xMidYMid meet\"><\/svg>\n    <\/div>\n  <\/div>\n\n  <div class=\"panel\">\n    <div class=\"status\" id=\"status\"><\/div>\n  <\/div>\n\n  <div class=\"panel controls\">\n    <div class=\"ctrl-row\">\n      <label for=\"i\">Angle d&rsquo;incidence <i>i<\/i><\/label>\n      <input type=\"range\" id=\"i\" min=\"0.5\" max=\"89\" step=\"0.5\" value=\"49.5\">\n      <span class=\"val\" id=\"iVal\">49.5\u00b0<\/span>\n    <\/div>\n    <div class=\"ctrl-row\">\n      <label for=\"A\">Angle au sommet <i>A<\/i><\/label>\n      <input type=\"range\" id=\"A\" min=\"30\" max=\"80\" step=\"1\" value=\"60\">\n      <span class=\"val\" id=\"AVal\">60\u00b0<\/span>\n    <\/div>\n    <div class=\"ctrl-row\">\n      <label for=\"n\">Indice du prisme <i>n<\/i><\/label>\n      <input type=\"range\" id=\"n\" min=\"1.30\" max=\"1.80\" step=\"0.005\" value=\"1.52\">\n      <span class=\"val\" id=\"nVal\">1.520<\/span>\n    <\/div>\n  <\/div>\n\n  <footer>S. Benlhajlahsen \u2014 PCSI \u00b7 Trajet d&rsquo;un rayon \u00e0 travers un prisme et recherche de la d\u00e9viation minimale<\/footer>\n<\/div>\n\n<script>\nconst NSns = 'http:\/\/www.w3.org\/2000\/svg';\nfunction el(tag, attrs){\n  const e = document.createElementNS(NSns, tag);\n  for(const k in attrs) e.setAttribute(k, attrs[k]);\n  return e;\n}\nfunction sub(a,b){return [a[0]-b[0], a[1]-b[1]];}\nfunction add(a,b){return [a[0]+b[0], a[1]+b[1]];}\nfunction scl(a,s){return [a[0]*s, a[1]*s];}\nfunction norm(a){return Math.hypot(a[0],a[1]);}\nfunction unit(a){const m=norm(a); return [a[0]\/m, a[1]\/m];}\nfunction dot(a,b){return a[0]*b[0]+a[1]*b[1];}\n\nfunction refract(d_in, n_out, n1, n2){\n  let d = unit(d_in);\n  let n = unit(n_out);\n  if(dot(d,n) > 0) n = scl(n,-1);\n  const cosi = -dot(d,n);\n  const ratio = n1\/n2;\n  const sin2t = ratio*ratio*(1-cosi*cosi);\n  if(sin2t > 1) return null; \/\/ reflexion totale\n  const cost = Math.sqrt(1-sin2t);\n  const t = add(scl(d,ratio), scl(n, ratio*cosi-cost));\n  return unit(t);\n}\nfunction lineIntersect(P0,d,A,B){\n  \/\/ solve P0 + t d = A + s (B-A)\n  const BA = sub(B,A);\n  const Mdet = d[0]*(-BA[1]) - (-BA[0])*d[1];\n  const rhs = sub(A,P0);\n  const t = (rhs[0]*(-BA[1]) - (-BA[0])*rhs[1]) \/ Mdet;\n  const s = (d[0]*rhs[1] - d[1]*rhs[0]) \/ Mdet;\n  return {pt: add(P0, scl(d,t)), t, s};\n}\n\n\/\/ ---- prism geometry ----\n\/\/ IMPORTANT : tous les calculs vectoriels (angles, lois de Descartes, reflets) sont\n\/\/ menes dans un repere MATHEMATIQUE classique (y vers le HAUT, sommet du prisme en\n\/\/ haut = plus grande ordonnee). La conversion vers les coordonnees SVG (y vers le\n\/\/ bas) n'intervient qu'au moment du dessin, via toSvg().\nconst APEX_H = 2.6;      \/\/ hauteur (unites math) du prisme\nconst SCALE = 88;        \/\/ px par unite math\nconst CX = 250, BASE_SVG_Y = 300; \/\/ position de la base du prisme a l'ecran\n\nfunction toSvg(p){\n  return [CX + p[0]*SCALE, BASE_SVG_Y - p[1]*SCALE];\n}\n\nfunction prismGeomMath(A_deg){\n  const A = A_deg*Math.PI\/180;\n  const half = APEX_H*Math.tan(A\/2);\n  const apex = [0, APEX_H];\n  const baseL = [-half, 0];\n  const baseR = [half, 0];\n  return {apex, baseL, baseR};\n}\n\nfunction outwardNormal(P1,P2, side){\n  const d = unit(sub(P2,P1));\n  let n = [d[1], -d[0]];\n  if(side==='L' && n[0]>0) n = scl(n,-1);\n  if(side==='R' && n[0]<0) n = scl(n,-1);\n  return n;\n}\n\nfunction computeRay(i_deg, A_deg, n){\n  const {apex, baseL, baseR} = prismGeomMath(A_deg);\n  const entry = [(apex[0]+baseL[0])\/2, (apex[1]+baseL[1])\/2];\n  const nL = outwardNormal(apex, baseL, 'L');\n  const i = i_deg*Math.PI\/180;\n  const thetaN = Math.atan2(nL[1], nL[0]);\n  const angIn = thetaN + i;\n  const d_in = scl([Math.cos(angIn), Math.sin(angIn)], -1);\n\n  const d_r = refract(d_in, nL, 1.0, n);\n  if(!d_r) return {ok:false, reason:'entry'};\n\n  const hit = lineIntersect(entry, d_r, apex, baseR);\n  if(!(hit.s>=0 && hit.s<=1) || hit.t<=0) return {ok:false, reason:'geom', entry, d_in};\n\n  const nR = outwardNormal(apex, baseR, 'R');\n  const d_out = refract(d_r, nR, n, 1.0);\n  if(!d_out) return {ok:false, reason:'TIR', entry, d_in, hit, d_r};\n\n  const dev = Math.acos(Math.max(-1,Math.min(1,dot(d_in,d_out)))) * 180\/Math.PI;\n\n  \/\/ recover r, r', i' for display\n  const r = Math.asin(Math.min(1, Math.sin(i)\/n));\n  const rp = A_deg*Math.PI\/180 - r;\n  const ip = Math.asin(Math.min(1, n*Math.sin(rp)));\n\n  return {ok:true, entry, d_in, hit, d_r, nR, d_out, dev, r:r*180\/Math.PI, rp:rp*180\/Math.PI, ip:ip*180\/Math.PI};\n}\n\nfunction DofI(i_deg, A_deg, n){\n  const res = computeRay(i_deg, A_deg, n);\n  return res.ok ? res.dev : null;\n}\n\nfunction arrowHead(svg,p1,p2,color){\n  const ang = Math.atan2(p2[1]-p1[1], p2[0]-p1[0]);\n  const len=9, spread=0.45;\n  const p3=[p2[0]-len*Math.cos(ang-spread), p2[1]-len*Math.sin(ang-spread)];\n  const p4=[p2[0]-len*Math.cos(ang+spread), p2[1]-len*Math.sin(ang+spread)];\n  svg.appendChild(el('polygon',{points:`${p2[0]},${p2[1]} ${p3[0]},${p3[1]} ${p4[0]},${p4[1]}`, fill:color}));\n}\n\nfunction drawScene(i_deg, A_deg, n){\n  const svg = document.getElementById('scene');\n  svg.innerHTML='';\n  const {apex, baseL, baseR} = prismGeomMath(A_deg);\n  const apexS = toSvg(apex), baseLS = toSvg(baseL), baseRS = toSvg(baseR);\n\n  svg.appendChild(el('polygon',{points:`${apexS.join(',')} ${baseLS.join(',')} ${baseRS.join(',')}`, fill:'var(--glass)', stroke:'var(--border)', 'stroke-width':1.5}));\n  svg.appendChild(el('text',{x:apexS[0],y:apexS[1]-14,'text-anchor':'middle','font-size':13,fill:'var(--muted)'})).textContent = `A = ${A_deg}\u00b0`;\n\n  const res = computeRay(i_deg, A_deg, n);\n  if(!res.ok){\n    if(res.entry &#038;&#038; res.d_in){\n      const entryS = toSvg(res.entry);\n      \/\/ d_in est en repere MATH (y-haut) : conversion du vecteur direction vers SVG (y inverse)\n      const dSvg = [res.d_in[0], -res.d_in[1]];\n      const start = sub(entryS, scl(dSvg,120));\n      svg.appendChild(el('path',{d:`M${start.join(',')} L${entryS.join(',')}`, stroke:'var(--accent)','stroke-width':2.2, fill:'none'}));\n      arrowHead(svg,start,entryS,'var(--accent)');\n    }\n    const msg = svg.appendChild(el('text',{x:250,y:340,'text-anchor':'middle','font-size':13,fill:'var(--accent)','font-weight':'600'}));\n    msg.textContent = \"R\u00e9flexion totale sur la face de sortie : pas de rayon \u00e9mergent\";\n    return res;\n  }\n\n  const entryS = toSvg(res.entry);\n  const hitS = toSvg(res.hit.pt);\n  const dInSvg = [res.d_in[0], -res.d_in[1]];\n  const dOutSvg = [res.d_out[0], -res.d_out[1]];\n\n  const start = sub(entryS, scl(dInSvg,120));\n  const outEnd = add(hitS, scl(dOutSvg,130));\n\n  svg.appendChild(el('path',{d:`M${start.join(',')} L${entryS.join(',')}`, stroke:'var(--accent)','stroke-width':2.4, fill:'none'}));\n  arrowHead(svg,start,entryS,'var(--accent)');\n\n  svg.appendChild(el('path',{d:`M${entryS.join(',')} L${hitS.join(',')}`, stroke:'var(--accent)','stroke-width':2.4, fill:'none'}));\n\n  svg.appendChild(el('path',{d:`M${hitS.join(',')} L${outEnd.join(',')}`, stroke:'var(--accent)','stroke-width':2.4, fill:'none'}));\n  arrowHead(svg,hitS,outEnd,'var(--accent)');\n\n  \/\/ prolongement pointille du rayon incident (pour visualiser D)\n  const inExt = add(entryS, scl(dInSvg,150));\n  svg.appendChild(el('path',{d:`M${entryS.join(',')} L${inExt.join(',')}`, stroke:'var(--muted)','stroke-width':1,'stroke-dasharray':'3,4', fill:'none'}));\n\n  return res;\n}\n\nlet cachedCurve = null, cachedKey = '';\n\nfunction buildCurve(A_deg, n){\n  const pts = [];\n  for(let id=0.2; id<=89.8; id+=0.2){\n    const d = DofI(id, A_deg, n);\n    if(d!==null) pts.push([id,d]);\n  }\n  return pts;\n}\n\nfunction drawChart(i_deg, A_deg, n){\n  const svg = document.getElementById('chart');\n  svg.innerHTML='';\n  const key = A_deg+'_'+n;\n  if(cachedKey !== key){\n    cachedCurve = buildCurve(A_deg, n);\n    cachedKey = key;\n  }\n  const curve = cachedCurve;\n  if(curve.length===0) return;\n\n  const M = {l:52,r:16,t:20,b:40};\n  const W=500,H=380;\n  const iMin=0, iMax=90;\n  const dMin = Math.min(...curve.map(p=>p[1]));\n  const dMax = Math.max(...curve.map(p=>p[1]));\n  const pad = (dMax-dMin)*0.12 || 5;\n  const yLo = dMin-pad, yHi = dMax+pad;\n\n  function X(i){ return M.l + (i-iMin)\/(iMax-iMin) * (W-M.l-M.r); }\n  function Y(d){ return H-M.b - (d-yLo)\/(yHi-yLo) * (H-M.t-M.b); }\n\n  \/\/ axes\n  svg.appendChild(el('line',{x1:M.l,y1:H-M.b,x2:W-M.r,y2:H-M.b,stroke:'var(--border)','stroke-width':1.3}));\n  svg.appendChild(el('line',{x1:M.l,y1:M.t,x2:M.l,y2:H-M.b,stroke:'var(--border)','stroke-width':1.3}));\n  svg.appendChild(el('text',{x:(W)\/2,y:H-8,'text-anchor':'middle','font-size':12,fill:'var(--muted)'})).textContent = \"angle d'incidence i (\u00b0)\";\n  const ylab = el('text',{x:14,y:H\/2,'text-anchor':'middle','font-size':12,fill:'var(--muted)', transform:`rotate(-90 14 ${H\/2})`});\n  ylab.textContent = 'd\u00e9viation D (\u00b0)'; svg.appendChild(ylab);\n\n  [0,30,60,90].forEach(v=>{\n    svg.appendChild(el('text',{x:X(v),y:H-M.b+16,'text-anchor':'middle','font-size':11,fill:'var(--muted)'})).textContent = v;\n    svg.appendChild(el('line',{x1:X(v),y1:H-M.b,x2:X(v),y2:H-M.b+4,stroke:'var(--border)'}));\n  });\n  [yLo,yHi].forEach(()=>{});\n  const yTicks = 4;\n  for(let k=0;k<=yTicks;k++){\n    const val = yLo + k*(yHi-yLo)\/yTicks;\n    svg.appendChild(el('text',{x:M.l-8,y:Y(val)+4,'text-anchor':'end','font-size':11,fill:'var(--muted)'})).textContent = val.toFixed(0);\n    svg.appendChild(el('line',{x1:M.l-4,y1:Y(val),x2:M.l,y2:Y(val),stroke:'var(--border)'}));\n  }\n\n  \/\/ curve (may have a gap where TIR occurs -> split into contiguous segments)\n  let segs = [[]];\n  let prevI = null;\n  curve.forEach(p=>{\n    if(prevI!==null && p[0]-prevI>0.5) segs.push([]);\n    segs[segs.length-1].push(p);\n    prevI = p[0];\n  });\n  segs.forEach(seg=>{\n    if(seg.length<2) return;\n    const d = seg.map((p,idx)=> (idx===0?'M':'L') + X(p[0]).toFixed(1)+','+Y(p[1]).toFixed(1)).join(' ');\n    svg.appendChild(el('path',{d, fill:'none', stroke:'var(--curve)','stroke-width':2.2}));\n  });\n\n  \/\/ minimum marker\n  let minPt = curve[0];\n  curve.forEach(p=>{ if(p[1]<minPt[1]) minPt = p; });\n  svg.appendChild(el('line',{x1:X(minPt[0]),y1:Y(minPt[1]),x2:X(minPt[0]),y2:H-M.b, stroke:'var(--min)','stroke-width':1,'stroke-dasharray':'3,3'}));\n  svg.appendChild(el('circle',{cx:X(minPt[0]),cy:Y(minPt[1]),r:4,fill:'var(--min)'}));\n  svg.appendChild(el('text',{x:X(minPt[0]),y:Y(minPt[1])-10,'text-anchor':'middle','font-size':11,fill:'var(--min)','font-weight':'600'})).textContent = `Dm\u2248${minPt[1].toFixed(1)}\u00b0`;\n\n  \/\/ current point marker\n  const dCur = DofI(i_deg, A_deg, n);\n  if(dCur!==null){\n    svg.appendChild(el('line',{x1:X(i_deg),y1:Y(dCur),x2:X(i_deg),y2:H-M.b, stroke:'var(--marker)','stroke-width':1,'stroke-dasharray':'2,3'}));\n    svg.appendChild(el('circle',{cx:X(i_deg),cy:Y(dCur),r:5,fill:'var(--marker)'}));\n  }\n}\n\nfunction update(){\n  const i = parseFloat(document.getElementById('i').value);\n  const A = parseFloat(document.getElementById('A').value);\n  const n = parseFloat(document.getElementById('n').value);\n  document.getElementById('iVal').textContent = i.toFixed(1)+'\u00b0';\n  document.getElementById('AVal').textContent = A.toFixed(0)+'\u00b0';\n  document.getElementById('nVal').textContent = n.toFixed(3);\n\n  const res = drawScene(i, A, n);\n  drawChart(i, A, n);\n\n  const st = document.getElementById('status');\n  if(res.ok){\n    const symmetric = Math.abs(res.r*1 - res.rp) < 0.6 || Math.abs(i-res.ip) < 0.6;\n    st.innerHTML = `\n      <div><span class=\"k\">r :<\/span><span class=\"v\">${res.r.toFixed(1)}\u00b0<\/span><\/div>\n      <div><span class=\"k\">r' :<\/span><span class=\"v\">${res.rp.toFixed(1)}\u00b0<\/span><\/div>\n      <div><span class=\"k\">i' :<\/span><span class=\"v\">${res.ip.toFixed(1)}\u00b0<\/span><\/div>\n      <div><span class=\"k\">D = i+i'-A :<\/span><span class=\"v\">${res.dev.toFixed(2)}\u00b0<\/span><\/div>\n      <div><span class=\"badge ${symmetric?'ok':'no'}\">${symmetric ? 'Configuration proche du minimum (i \u2248 i&#39;)' : 'Configuration asym\u00e9trique'}<\/span><\/div>\n    `;\n  } else {\n    st.innerHTML = `<div><span class=\"badge no\">R\u00e9flexion totale sur la face de sortie \u2014 aucun rayon \u00e9mergent pour cette incidence<\/span><\/div>`;\n  }\n}\n\n['i','A','n'].forEach(id=> document.getElementById(id).addEventListener('input', update));\nupdate();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Prisme et d\u00e9viation minimale Prisme : trajet du rayon et d\u00e9viation minimale Faites varier l&rsquo;angle d&rsquo;incidence i : observez le trajet du rayon et, simultan\u00e9ment, le point correspondant sur la courbe de d\u00e9viation D(i). Le minimum Dm est atteint quand i = i&rsquo;. Angle d&rsquo;incidence i 49.5\u00b0 Angle au sommet A 60\u00b0 Indice du prisme &hellip; <a href=\"https:\/\/benlhajlahsen.fr\/index.php\/prisme\/\" class=\"more-link\">Continuer la lecture<span class=\"screen-reader-text\"> de &laquo;&nbsp;Prisme&nbsp;&raquo;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-279","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/pages\/279","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/comments?post=279"}],"version-history":[{"count":1,"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/pages\/279\/revisions"}],"predecessor-version":[{"id":281,"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/pages\/279\/revisions\/281"}],"wp:attachment":[{"href":"https:\/\/benlhajlahsen.fr\/index.php\/wp-json\/wp\/v2\/media?parent=279"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}