Edit in JSFiddle

<!--
倒立振子の制御シミュレーション
参考:
https://developer.mozilla.org/ja/docs/Web/API/Canvas_API
https://qiita.com/kyrieleison/items/a3ebf7c55295c3e7d8f0
https://sbfl.net/blog/2017/08/21/javascript-canvas-animation/#i
https://myenigma.hatenablog.com/entry/2017/06/26/113719
 -->


<!DOCTYPE html>
<html lang="jp" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Inversed Pendulum</title>
    <script src="https://cdn.jsdelivr.net/gh/nicolaspanel/[email protected]/dist/numjs.min.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      const canvas_width = 640;
      const canvas_height = 400;
      const canvas = document.createElement('canvas');
      canvas.width = canvas_width;
      canvas.height = canvas_height;
      var context = canvas.getContext('2d');
      document.body.appendChild(canvas);

      function setup() {
        context.fillRect(0, canvas_height-10, canvas_width, 10);
        drawGrid();
      }
      
      function drawTruck(){
      	context.beginPath();
        context.fillStyle = '#808080';
        context.arc(-20, canvas_height-30, circle.r, 0, 2 * Math.PI);
        context.arc( 20, canvas_height-30, circle.r, 0, 2 * Math.PI);
        context.fill();
        context.fillStyle = 'rgb(255, 0, 0)';
        context.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
      }
      
      function drawArm(state){
      	//state.get(0,2) = 30*Math.PI/180;
      	dx_theta = 120*Math.sin(state.get(0,2));
        dy_theta = 120*Math.cos(state.get(0,2));
        context.translate(dx_theta, -70+canvas_height-dy_theta);
        context.rotate( state.get(0,2));

        context.beginPath();
        context.fillStyle = 'rgb(0, 255, 0)';
        context.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI);
        context.fill();
        context.fillStyle = 'rgb(0, 0, 255)';
        context.fillRect(arm.x, arm.y, arm.width, arm.height);
        context.rotate( -state.get(0,2));
      }
      
      function drawGrid(){
      	for (var i = 1;  i < 10;  i++){
        	var x = canvas_width / 10 * i;
        	if(i==5){ //中心線だけ太くする
          	context.lineWidth = 1.0;
          } else {
          	context.lineWidth = 0.1;
          }
        	
        	context.beginPath();
          context.moveTo(x,0);
          context.lineTo(x,canvas_height);
          context.closePath();
          context.stroke();
        }
      }

      var circle = {x:0, y:0, r:20};
      var rectangle = {x:-50, y:canvas_height-80, width:100, height:60};

      var arm = {x:-10, y:20, width:20, height:100};

      const l_bar = 2.0
      const M = 1.0
      const m = 0.3
      const g = 9.8

      var A = nj.array([
        [0.0, 1.0, 0.0, 0.0],
        [0.0, 0.0, -m * g / (M+2*m), 0.0],
        [0.0, 0.0, 0.0, 1.0],
        [0.0, 0.0, g * (M + m) / (l_bar * (M+2*m)), 0.0]
      ]);
      var B = nj.array([
        [0.0],
        [1.0 / (M+2*m)],
        [0.0],
        [1.0 / (l_bar * (M+2*m))]
      ]);
      var Bu = nj.array([[0.0], [0.0], [0.0], [0.0]]);
      var Ax = nj.array([[0.0], [0.0], [0.0], [0.0]]);
      var state = nj.array([[0.0], [0.0], [0.2], [0.0]]);
      var x_dot = nj.array([[0.0], [0.0], [0.0], [0.0]]);
      
      var integrated = nj.array([[0.0], [0.0], [0.0], [0.0]]);

      function loop(timestamp) {

				// 制御入力とシステム更新
        integrated = integrated.add(state);
        const u =
        	+1.0 * state.get(0,0)
          +0.2 * state.get(0,1)
          -100.0 * state.get(0,2) 
          -10.0 * state.get(0,3);
        console.log(u);
        Bu = B.multiply(u);
        Ax = A.dot(state);
        x_dot = Ax.add(Bu);
        state = state.add(x_dot.divide(60));

				// canvasの初期化
        context.clearRect(0, 0, canvas_width, canvas_height);

        setup();
        context.font = "italic bold 20px sans-serif";
        txtinfo = (timestamp/1000).toFixed( 2 ) + '[s]';
        context.fillText(txtinfo, 10, 50);
        txtinfo = state.get(0,0).toFixed( 2 ) + '[m]';
        context.fillText(txtinfo, 10, 70);
        txtinfo = state.get(0,1).toFixed( 2 ) + '[m/s]';
        context.fillText(txtinfo, 10, 90);
        txtinfo = (state.get(0,2) * 180 / Math.PI).toFixed( 2 ) + '[deg]';
        context.fillText(txtinfo, 10, 110);
        txtinfo = (state.get(0,3) * 180 / Math.PI).toFixed( 2 ) + '[deg/s]';
        context.fillText(txtinfo, 10, 130);
        

        context.translate(320+state.get(0,0)*canvas_width/10, 0);
        
        drawTruck();
        drawArm(state);

        context.translate(-320 -state.get(0,0)*canvas_width/10 -dx_theta, +70-(canvas_height-dy_theta));
        window.requestAnimationFrame((ts) => loop(ts));
      }

      setup();
      window.requestAnimationFrame((ts) => loop(ts));
    </script>
  </body>
</html>