{"id":9959,"date":"2025-09-13T18:36:52","date_gmt":"2025-09-13T16:36:52","guid":{"rendered":"https:\/\/spgoo.org\/?page_id=9959"},"modified":"2025-09-13T18:36:52","modified_gmt":"2025-09-13T16:36:52","slug":"pendule-simple","status":"publish","type":"page","link":"https:\/\/spgoo.org\/?page_id=9959","title":{"rendered":"Pendule simple"},"content":{"rendered":"  <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/jsxgraph\/distrib\/jsxgraph.css\" \/>\r\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jsxgraph\/distrib\/jsxgraphcore.js\"><\/script>\r\n  <link rel=\"stylesheet\" type=\"text\/css\" href=\"https:\/\/cdn.jsdelivr.net\/gh\/bitmaks\/cm-web-fonts@latest\/fonts.css\">\r\n\r\n  <!-- MathJax -->\r\n  <script>\r\n    window.MathJax = {\r\n      tex: { inlineMath: [['\\\\(', '\\\\)'], ['$', '$']] },\r\n      svg: { fontCache: 'global' }\r\n    };\r\n  <\/script>\r\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/mathjax@3\/es5\/tex-svg.js\"><\/script>\r\n\r\n  <style>\r\n    h2 { margin: 0 0 8px; font-size: 18px; }\r\n    .jxgbox {\r\n      width: 95%;\r\n      height: 420px;\r\n      background: #fff;\r\n      border: 1px solid #dcdcdc;\r\n      border-radius: 10px;\r\n      margin: 12px 0 24px;\r\n    }\r\n    .hint { font-size: 12px; color: #666; margin-top: -16px;color:white }\r\n\t .JXGtext {\r\n      font-family: \"Computer Modern Serif\", \"CMU Serif\", serif !important;\r\n      fill: #000;\r\n    }\r\n  <\/style>\r\n  <div id=\"board1\" class=\"jxgbox\"><\/div>\r\n  <div class=\"hint\">Astuce: mettre en pause, ajuster \u03b8(0), puis red\u00e9marrer. On peux aussi faire glisser le bob \u00e0 l\u2019arr\u00eat.<\/div>\r\n  <div id=\"board2\" class=\"jxgbox\"><\/div>\r\n\r\n  <script>\r\n    \/\/ Physique de base\r\n    const g = 9.81;\r\n    let L = 1.5;                 \/\/ longueur (m)\r\n    let theta = 30 * Math.PI\/180;\/\/ angle (rad) (0 = vertical bas)\r\n    let omega = 0;               \/\/ vitesse angulaire (rad\/s)\r\n    let c = 0.02;                \/\/ amortissement lin\u00e9aire (s^-1)\r\n    let running = false;\r\n    let t = 0;\r\n\r\n    \/\/ Trajectoire\r\n    const path = [];\r\n    const maxPath = 4000;\r\n\r\nJXG.Options.text.useMathJax = true;\t\r\n\r\n    \/\/ Board 1 \u2014 pendule + UI\r\n    const b1 = JXG.JSXGraph.initBoard('board1', {\r\n      boundingbox: [-9,2., 3.2, -5.2],\r\n      keepaspectratio: true,\r\n      axis: false,\r\n      showNavigation: false,\r\n      showZoom: false,\r\n      showCopyright: false\r\n    });\r\n\r\nb1.renderer.container.style.backgroundColor='#C3ADC3';\r\n\r\n\r\nb1.create('text',[-10,1.2,'\\\\(\\\\textrm{Le pendule}\\\\)'],{color:'#AE181E',fontSize:26,highlight:false,fixed:true})\r\n\r\n\r\n    \/\/ Sliders int\u00e9gr\u00e9s (gauche->droite)\r\n    const sL = b1.create('slider', [[-9.0, -1.8], [-7.2, -1.8], [0.5, 1.5, 3]], {\r\n      name: '\\\\(L\\\\)',\r\n      suffixLabel: '\\\\(L = \\\\)',\r\n      snapWidth: 0.1,\r\n\t  baseline: { strokeColor: 'cadetblue'},\r\n      highline: { strokeColor: '#AE181E'},\r\n      fillColor: 'goldenrod',\r\n\t  useMathJax:true\r\n    });\r\n\t\r\n\tb1.create('text',[-9,-1.4,'\\\\(\\\\textrm{Longueur de la corde}\\\\)'],{highlight:false,fixed:true});\r\n\tsL.on('drag',function(){MathJax.typesetPromise(); b1.update();})\r\n\r\n\t\r\n    const sTheta0 = b1.create('slider', [[-9.0, -2.5], [-7.2, -2.5], [-80, 30, 80]], {\r\n      name: '\u03b8\u2080',\r\n\t  withLabel:true,\r\n      suffixLabel: '\\\\(\\\\theta(0) = \\\\)',\r\n      snapWidth: 1,\r\n\t  baseline: { strokeColor: 'cadetblue'},\r\n      highline: { strokeColor: '#AE181E'},\r\n      fillColor: 'darkviolet',\r\n\t  useMathJax:true\r\n    });\r\n\t\t\r\n    b1.create('text',[-9,-2.2,\"\\\\(\\\\textrm{Angle initial du pendule}\\\\)\"],{highlight:false,fixed:true});\r\n\tsTheta0.on('drag',function(){MathJax.typesetPromise(); b1.update();})\r\n\t\r\n\tsTheta0.on('drag', function() {\r\n  if (!running) {\r\n    theta = sTheta0.Value() * Math.PI \/ 180;\r\n    omega = 0;\r\n    t = 0;\r\n    b1.update();\r\n  }\r\n});\r\n\r\n\t\r\n    const sDamp = b1.create('slider', [[-9.0, -3.2], [-7.2, -3.2], [0, 0.02, 0.2]], {\r\n      name: '\\\\(c\\\\)',\r\n      snapWidth: 0.005,\r\n\t  baseline: { strokeColor: 'cadetblue'},\r\n      highline: { strokeColor: '#AE181E'},\r\n      fillColor: 'seagreen',\r\n\t  \t  useMathJax:true\r\n    });\r\n\tb1.create('text',[-9,-2.9,\"\\\\(\\\\textrm{Coefficient d'amortissement}\\\\)\"],{highlight:false,fixed:true});\r\n\tsDamp.on('drag',function(){MathJax.typesetPromise(); b1.update();})\r\n\t\/\/checkbox\r\n\tconst showVectors = b1.create('checkbox', [-9.0, -3.7, '\\\\(\\\\textrm{Afficher vecteurs}\\\\)'], {\r\n  checked: false\r\n});\r\n\r\nconst im = b1.create('image', ['.\/personnalisation\/images\/hommependule2.png', [-2.75,-8.], [10,10]],{visible:true,fixed:true});\r\n\r\nconst showimage = b1.create('checkbox', [-7.0, -3.7, '\\\\(\\\\textrm{ Masquer image}\\\\)'], {\r\n  checked: false\r\n});\r\n\r\n\/\/ Link checkbox to image visibility\r\nshowimage.on('up', () => {\r\n  im.setAttribute({ visible: showimage.Value() });\r\n  b1.update(); \/\/ refresh the board\r\n});\r\n\r\n    \/\/ Boutons (int\u00e9gr\u00e9s au board)\r\n    const btnStart = b1.create('button', [-9.0, 0.0, 'D\u00e9marrer \/ Pause', () => {\r\n      running = !running;\r\n      if (running) {\r\n        \/\/ Si on d\u00e9marre depuis l\u2019arr\u00eat, on (re)prend l\u2019angle du slider comme condition initiale\r\n        if (t === 0) {\r\n          theta = sTheta0.Value() * Math.PI \/ 180;\r\n          omega = 0;\r\n          path.length = 0;\r\n        }\r\n        lastStamp = null;\r\n        requestAnimationFrame(loop);\r\n      }\r\n      b1.update();\r\n    }]);\r\n    const btnReset = b1.create('button', [-9, -0.8, '\\\\(\\\\textrm{R\u00e9initialiser}\\\\)', () => {\r\n      running = false;\r\n      t = 0;\r\n      L = sL.Value();\r\n      c = sDamp.Value();\r\n      theta = sTheta0.Value() * Math.PI \/ 180;\r\n      omega = 0;\r\n      path.length = 0;\r\n\t  strokeColor:\"#AE181E\",\r\n      b1.update();\r\n      b2.update();\r\n    }]);\r\n\r\n    \/\/ Pivot, bob, tige\r\n    const pivot = b1.create('point', [0, 0], {fixed: true, name: '', size: 3, fillColor: '#333', strokeColor: '#333',shadow:true,highlight:false});\r\n    const bob = b1.create('point',\r\n      [\r\n        () => L * Math.sin(theta),\r\n        () => -L * Math.cos(theta)\r\n      ],\r\n      { name: '', size: 4, fillColor: '#1976d2', strokeColor: '#0d47a1' ,shadow:true,highlight:false}\r\n    );\r\n    const rod = b1.create('segment', [pivot, bob], { strokeWidth: 3, strokeColor: '#555',shadow:true,highlight:false });\r\n    const arcRef = b1.create('circle', [pivot, () => [L, 0]], { strokeColor: '#e0e0e0', dash: 2, fixed: true,visible:false });\r\n\r\nconst vecVitesse = b1.create('arrow', [\r\n  () => [L * Math.sin(theta), -L * Math.cos(theta)],\r\n  () => {\r\n    const vx = L * omega * Math.cos(theta);\r\n    const vy = L * omega * Math.sin(theta);\r\n    return [L * Math.sin(theta) + vx, -L * Math.cos(theta) + vy];\r\n  }\r\n], {\r\n  strokeColor: '#e53935',\r\n  strokeWidth: 2,\r\n  visible: () => showVectors.Value(),\r\n  shadow:true\r\n});\r\n\r\nconst vecAccel = b1.create('arrow', [\r\n  () => [L * Math.sin(theta), -L * Math.cos(theta)],\r\n  () => {\r\n    const alpha = - (g \/ L) * Math.sin(theta) - c * omega;\r\n    const ax = L * alpha * Math.cos(theta) - L * omega * omega * Math.sin(theta);\r\n    const ay = L * alpha * Math.sin(theta) + L * omega * omega * Math.cos(theta);\r\n    return [L * Math.sin(theta) + ax, -L * Math.cos(theta) + ay];\r\n  }\r\n], {\r\n  strokeColor: '#43a047',\r\n  strokeWidth: 2,\r\n  visible: () => showVectors.Value(),\r\n  shadow:true\r\n});\r\n\r\n    \/\/ Permettre d\u2019ajuster \u03b8\u2080 et L en d\u00e9pla\u00e7ant le bob quand on est \u00e0 l\u2019arr\u00eat\r\n    bob.on('drag', () => {\r\n      if (!running) {\r\n        const x = bob.X(), y = bob.Y();\r\n        const r = Math.hypot(x, y);\r\n        if (r > 1e-6) {\r\n          L = r;\r\n          theta = Math.atan2(x, -y);\r\n          \/\/ Sync sliders\r\n          sL.setValue(L);\r\n          sTheta0.setValue(theta * 180 \/ Math.PI);\r\n          t = 0;\r\n          omega = 0;\r\n          b1.update();\r\n        }\r\n      }\r\n    });\r\n\r\n    \/\/ Board 2 \u2014 trajectoire\r\n    const b2 = JXG.JSXGraph.initBoard('board2', {\r\n      boundingbox: [-2.5, 2, 2.5, -6.2],\r\n      keepaspectratio: false,\r\n      axis: true,\r\n      showNavigation: false,\r\n      showZoom: false,\r\n      showCopyright: false,\r\n\t   defaultAxes: {\r\n    x : {\r\n    name: 't',\r\n    withLabel: true,\r\n    label: {\r\n        position: 'rt',\r\n      offset: [-10, -15]\r\n    }\r\n  },\r\n  y : {\r\n    withLabel:true,\r\n    name: '\\\\(\\\\theta(t)\\\\)',\r\n      label: {\r\n        position: 'rt',\r\n        offset: [-20, -10]\r\n      }\r\n    }\r\n  }\r\n});\r\nb2.renderer.container.style.backgroundColor='#C3ADC3';\r\nb1.addChild(b2);\r\n\r\n\r\n    const tracer = b2.create('point',\r\n      [\r\n        () => L * Math.sin(theta),\r\n        () => -L * Math.cos(theta)\r\n      ],\r\n      { name: '', size: 3, face: 'x', strokeColor: 'cadetblue' ,shadow:true}\r\n    );\r\n\r\n    const curve = b2.create('curve', [[NaN], [NaN]], { strokeColor: '#AE181E', strokeWidth: 2,shadow:true });\r\n\r\n    function updateCurve() {\r\n      if (path.length === 0) {\r\n        curve.dataX = [NaN];\r\n        curve.dataY = [NaN];\r\n      } else {\r\n        curve.dataX = path.map(p => p[0]);\r\n        curve.dataY = path.map(p => p[1]);\r\n      }\r\n      curve.updateDataArray();\r\n    }\r\n\r\nconst eq0 = b2.create('text', [-2,.4,'\\\\(\\\\ddot{\\\\theta}+\\\\frac{g}{L}\\\\,\\\\sin(\\\\theta) + c \\\\, \\\\dot{\\\\theta} = 0\\\\)'],{color:'#AE181E',fontSize:20});\r\nconst eq1 = b2.create('text', [1, .4, function() {\r\n  return '\\\\(\\\\ddot{\\\\theta} + ' + (9.81 \/ sL.Value()).toFixed(2) +\r\n         '\\\\,\\\\sin(\\\\theta) + ' + sDamp.Value().toFixed(2) +\r\n         '\\\\, \\\\dot{\\\\theta} = 0\\\\)';\r\n}], {\r\n  color: '#AE181E',\r\n  fontSize: 20\r\n});\r\nb2.update();\r\n    \/\/ Int\u00e9grateur RK4 pour \u03b8'' + (g\/L) sin\u03b8 + c \u03b8' = 0\r\n    function rk4_step(h) {\r\n      const th = theta;\r\n      const om = omega;\r\n      const a = (tt, oo) => - (g \/ L) * Math.sin(tt) - c * oo;\r\n\r\n      const k1_th = om;\r\n      const k1_om = a(th, om);\r\n\r\n      const k2_th = om + 0.5*h*k1_om;\r\n      const k2_om = a(th + 0.5*h*k1_th, om + 0.5*h*k1_om);\r\n\r\n      const k3_th = om + 0.5*h*k2_om;\r\n      const k3_om = a(th + 0.5*h*k2_th, om + 0.5*h*k2_om);\r\n\r\n      const k4_th = om + h*k3_om;\r\n      const k4_om = a(th + h*k3_th, om + h*k3_om);\r\n\r\n      theta = th + (h\/6)*(k1_th + 2*k2_th + 2*k3_th + k4_th);\r\n      omega = om + (h\/6)*(k1_om + 2*k2_om + 2*k3_om + k4_om);\r\n      t += h;\r\n    }\r\n\r\n    \/\/ Boucle d\u2019animation (pas fixe, rAF avec accumulateur)\r\n    const dtFixed = 1\/240;\r\n    let lastStamp = null;\r\n    function loop(stamp) {\r\n      if (!running) return;\r\n      if (lastStamp == null) lastStamp = stamp;\r\n      let acc = Math.min(0.05, (stamp - lastStamp) \/ 1000); \/\/ max 50 ms\r\n      lastStamp = stamp;\r\n\r\n      \/\/ Mettre \u00e0 jour param\u00e8tres depuis sliders une fois par frame\r\n      L = sL.Value();\r\n      c = sDamp.Value();\r\n      while (acc > 1e-9) {\r\n      const h = Math.min(dtFixed, acc);\r\n      rk4_step(h);\r\n      acc -= h;\r\n\r\n    if ((t \/ dtFixed) % 3 < 1) {\r\n      const x = L * Math.sin(theta);\r\n      const y = -L * Math.cos(theta);\r\n      path.push([x, y]);\r\n      if (path.length > maxPath) path.shift();\r\n    }\r\n  }\r\n      while (acc > 1e-9) {\r\n        const h = Math.min(dtFixed, acc);\r\n        rk4_step(h);\r\n        acc -= h;\r\n\r\n        \/\/ \u00c9chantillonnage de la trajectoire\r\n        if ((t \/ dtFixed) % 3 < 1) {\r\n          const x = L * Math.sin(theta);\r\n          const y = -L * Math.cos(theta);\r\n          path.push([x, y]);\r\n          if (path.length > maxPath) path.shift();\r\n        }\r\n      }\r\n\r\n      updateCurve();\r\n      b1.update();\r\n      b2.update();\r\n      requestAnimationFrame(loop);\r\n    }\r\n\r\n    \/\/ Init affichage\r\n    updateCurve();\r\n    b1.update();\r\n    b2.update();\r\n  <\/script>\r\n\r\n\r\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-9959","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/9959","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9959"}],"version-history":[{"count":1,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/9959\/revisions"}],"predecessor-version":[{"id":9960,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/9959\/revisions\/9960"}],"wp:attachment":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9959"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}