]> gitweb.ps.run Git - inverse_kinematics/commitdiff
Initial commit main
authorpatrick-scho <patrick.schoenberger@posteo.de>
Thu, 17 Apr 2025 20:16:31 +0000 (22:16 +0200)
committerpatrick-scho <patrick.schoenberger@posteo.de>
Thu, 17 Apr 2025 20:16:31 +0000 (22:16 +0200)
index.html [new file with mode: 0644]

diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..7e63e52
--- /dev/null
@@ -0,0 +1,281 @@
+<!DOCTYPE html>\r
+<html>\r
+\r
+<head>\r
+  <title></title>\r
+\r
+  <style type="text/css">\r
+    body {\r
+      margin: 0;\r
+    }\r
+\r
+    input {\r
+      width: 98%;\r
+    }\r
+  </style>\r
+\r
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/8.1.0/math.js"\r
+    integrity="sha512-ne87j5uORxbrU7+bsqeJJWfWj5in65R9PCjaQL161xtH5cesZgULVbeVAkzAAN7hnYOcrHeBas9Wbd/Lm8gXFA=="\r
+    crossorigin="anonymous"></script>\r
+  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>\r
+  <script type="text/javascript">\r
+    let ctx;\r
+\r
+    class Point {\r
+      constructor(x, y) {\r
+        this.x = x;\r
+        this.y = y;\r
+      }\r
+\r
+      add(that) {\r
+        return new Point(\r
+          this.x + that.x,\r
+          this.y + that.y\r
+        );\r
+      }\r
+\r
+      sub(that) {\r
+        return new Point(\r
+          this.x - that.x,\r
+          this.y - that.y\r
+        );\r
+      }\r
+\r
+      dist(that) {\r
+        let a = this.x - that.x;\r
+        let b = this.y - that.y;\r
+        return Math.sqrt(a * a + b * b)\r
+      }\r
+    }\r
+\r
+    class Segment {\r
+      constructor(length, angle, minAngle, maxAngle) {\r
+        this.length = length;\r
+        this.angle = angle;\r
+        this.minAngle = minAngle;\r
+        this.maxAngle = maxAngle;\r
+      }\r
+\r
+      restrictAngle() {\r
+        if (this.angle < this.minAngle)\r
+          this.angle = this.minAngle;\r
+        if (this.angle > this.maxAngle)\r
+          this.angle = this.maxAngle;\r
+      }\r
+    }\r
+\r
+    class Arm {\r
+      constructor(base, segments) {\r
+        this.base = base;\r
+        this.segments = segments;\r
+      }\r
+\r
+      draw() {\r
+        let currentPoint = this.base;\r
+        drawCircle(currentPoint, 10, '#0000FF');\r
+\r
+        let angleSum = 0;\r
+\r
+        for (let i in this.segments) {\r
+          let segment = this.segments[i];\r
+\r
+          angleSum += segment.angle;\r
+\r
+          let endPoint = currentPoint.add(new Point(\r
+            Math.cos(angleSum * Math.PI / 180) * segment.length,\r
+            Math.sin(angleSum * Math.PI / 180) * segment.length\r
+          ));\r
+\r
+          drawLine(currentPoint, endPoint);\r
+          drawCircle(currentPoint, 10);\r
+\r
+          currentPoint = endPoint;\r
+        }\r
+        drawCircle(currentPoint, 10, '#FF0000');\r
+      }\r
+\r
+      moveTo(targetPoint) {\r
+        const H = 0.00002;\r
+        const EPS = 5;\r
+\r
+        let iteration = 0;\r
+\r
+        while (targetPoint.dist(this.getEndPoint()) > EPS) {\r
+          if (++iteration > 100) break;\r
+          let dO = this.getDeltaOrientation(targetPoint);\r
+          for (let i in this.segments) {\r
+            this.segments[i].angle += dO._data[i] * H;\r
+            if (this.segments[i].angle < -360) this.segments[i].angle = this.segments[i].angle % -360;\r
+            if (this.segments[i].angle > +360) this.segments[i].angle = this.segments[i].angle % +360;\r
+\r
+            this.segments[i].restrictAngle();\r
+          }\r
+        }\r
+      }\r
+\r
+      getDeltaOrientation(targetPoint) {\r
+        let Jt = this.getJacobianTranspose(targetPoint);\r
+        let V = targetPoint.sub(this.getEndPoint());\r
+        return math.multiply(Jt, [V.x, V.y, 1]);\r
+      }\r
+\r
+      getJacobianTranspose(targetPoint) {\r
+        let rows = [];\r
+        for (let i in this.segments) {\r
+          let Ra = [0, 0, 1];\r
+          let E = this.getEndPoint();\r
+          let A = this.getSegmentEndpoint(i);\r
+\r
+          let diff = [E.x - A.x, E.y - A.y, 0];\r
+\r
+          let row = math.cross(Ra, diff);\r
+          rows.push(row);\r
+        }\r
+        let transpose = math.matrix(rows);\r
+        //let jacobi = math.transpose(transpose);\r
+        //let pseudo = math.multiply(math.inv(math.multiply(transpose, jacobi)), transpose);\r
+        //return pseudo;\r
+        return transpose;\r
+      }\r
+\r
+      getEndPoint() {\r
+        return this.getSegmentEndpoint(this.segments.length);\r
+      }\r
+\r
+      getSegmentEndpoint(index) {\r
+        let currentPoint = this.base;\r
+        let angleSum = 0;\r
+\r
+        for (let i in this.segments) {\r
+          if (index == i) break;\r
+\r
+          let segment = this.segments[i];\r
+\r
+          angleSum += segment.angle;\r
+\r
+          let endPoint = currentPoint.add(new Point(\r
+            Math.cos(angleSum * Math.PI / 180) * segment.length,\r
+            Math.sin(angleSum * Math.PI / 180) * segment.length\r
+          ));\r
+\r
+          currentPoint = endPoint;\r
+        }\r
+\r
+        return currentPoint;\r
+      }\r
+    }\r
+\r
+    let mousePos = new Point(0, 0);\r
+\r
+    let arm = new Arm(\r
+      new Point(window.innerWidth / 2, window.innerHeight / 2),\r
+      []\r
+    );\r
+\r
+    for (let i = 0; i < 3; i++)\r
+      AddSegment();\r
+\r
+    function draw(dt) {\r
+      ctx.clearRect(0, 0, w, h);\r
+\r
+      arm.moveTo(mousePos);\r
+      arm.draw();\r
+\r
+      window.requestAnimationFrame(draw);\r
+    }\r
+\r
+    function drawCircle(pos, radius, color) {\r
+      let filled = false;\r
+      if (color) {\r
+        filled = true;\r
+        ctx.fillStyle = color;\r
+      }\r
+      ctx.beginPath();\r
+\r
+      ctx.arc(pos.x, pos.y, radius, 0, 2 * Math.PI);\r
+\r
+      if (filled)\r
+        ctx.fill();\r
+\r
+      ctx.stroke();\r
+    }\r
+\r
+    function drawLine(from, to) {\r
+      ctx.beginPath();\r
+      ctx.moveTo(from.x, from.y);\r
+      ctx.lineTo(to.x, to.y);\r
+      ctx.stroke();\r
+    }\r
+\r
+    function init() {\r
+      let canvas = document.getElementById('canvas');\r
+      let resize = function () {\r
+        w = window.innerWidth;\r
+        h = window.innerHeight - 200;\r
+        canvas.width = w;\r
+        canvas.height = h;\r
+        document.getElementById('inputBaseX').setAttribute('max', w);\r
+        document.getElementById('inputBaseY').setAttribute('max', h);\r
+      }\r
+      window.onresize = resize;\r
+      resize();\r
+      if (canvas.getContext) {\r
+        ctx = canvas.getContext('2d');\r
+        draw(performance.now());\r
+      }\r
+      canvas.onmousemove = (evt) => {\r
+        mousePos.x = evt.clientX;\r
+        mousePos.y = evt.clientY;\r
+      };\r
+    }\r
+\r
+    function AddSegment() {\r
+      arm.segments.push(new Segment(100, 0, -360, 360));\r
+    }\r
+  </script>\r
+</head>\r
+\r
+<body onload="init();">\r
+  <canvas id="canvas"></canvas>\r
+  <div id="app">\r
+    Base X\r
+    <input v-model.number="arm.base.x" type="range" min="0" id="inputBaseX" style="width: 100px;" />\r
+    Base Y\r
+    <input v-model.number="arm.base.y" type="range" min="0" id="inputBaseY" style="width: 100px;" />\r
+    <br />\r
+    <table style="width: 100%;">\r
+      <colgroup>\r
+        <col span="1" style="width: 20%;">\r
+        <col span="1" style="width: 20%;">\r
+        <col span="1" style="width: 20%;">\r
+        <col span="1" style="width: 20%;">\r
+        <col span="1" style="width: 20%;">\r
+      </colgroup>\r
+      <tr>\r
+        <th>Length</th>\r
+        <th>Min. Angle</th>\r
+        <th>Max. Angle</th>\r
+        <th>Angle</th>\r
+      </tr>\r
+      <tr v-for="segment in arm.segments">\r
+        <td><input v-model.number="segment.length" type="range" max="300" /></td>\r
+        <td><input v-model.number="segment.minAngle" min="-360" max="0" type="range" /></td>\r
+        <td><input v-model.number="segment.maxAngle" min="0" max="360" type="range" /></td>\r
+        <td>{{ Number(segment.angle).toFixed(4) }}</td>\r
+        <td><button v-on:click="arm.segments.splice(arm.segments.indexOf(segment), 1)"\r
+            v-if="arm.segments.length > 1">Delete</button></td>\r
+      </tr>\r
+    </table>\r
+    <button onclick="AddSegment()">Add</button>\r
+  </div>\r
+  <script>\r
+    var app = new Vue({\r
+      el: '#app',\r
+      data: {\r
+        arm: arm\r
+      }\r
+    })\r
+  </script>\r
+</body>\r
+\r
+</html>\r