{"id":290809,"date":"2018-10-15T14:40:02","date_gmt":"2018-10-15T10:40:02","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=290809"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=290809","title":{"rendered":"\u041a\u0430\u043a \u044f \u0441\u0442\u0440\u043e\u0438\u043b \u0433\u0435\u043a\u0441\u0430\u043f\u043e\u0434 \u0432 Space Engineers. \u0427\u0430\u0441\u0442\u044c 1"},"content":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435.<br \/>  \u042f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432 \u0433\u0435\u043a\u0441\u0430\u043f\u043e\u0434\u0435, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u043c \u0432 Space Engineers.<br \/>  \u0417\u0430\u0431\u0435\u0433\u0430\u044f \u0432\u043f\u0435\u0440\u0435\u0434 \u0441\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 Space Engineer, \u0431\u0443\u0434\u0435\u0442 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435. \u0412 \u044d\u0442\u043e\u0439 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u043e\u0431\u0440\u0430\u0442\u043d\u0443\u044e \u043a\u0438\u043d\u0435\u043c\u0430\u0442\u0438\u043a\u0443 \u0438 \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f \u043d\u0430 HTML Canvas \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0437\u0430\u043d\u0438\u043c\u0430\u043b\u0441\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u043e\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xj\/tn\/eo\/xjtneoatofct-nmcczqob0lkioc.png\"><br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<h4>\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438.<\/h4>\n<p>  \u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0441\u043e\u0447\u043b\u0435\u043d\u0435\u043d\u043d\u043e\u0435 \u0448\u0430\u0441\u0441\u0438, \u0430 \u0437\u0430\u0442\u0435\u043c \u043d\u0430 \u043d\u0435\u043c \u043a\u043e\u043f\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u0433\u0440\u0435\u0433\u0430\u0442. \u0422\u0430\u043a\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u043b\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442 \u0432\u0441\u0445 \u043a\u043e\u043b\u0435\u0441 \u0441 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0435\u0440\u043e\u0432\u043d\u043e\u0441\u0442\u044f\u0445, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043f\u0440\u0438 \u0441\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u043d\u0438\u0438<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/624\/606\/4b8\/6246064b8bd63bb3eebe9a5cd53442a6.jpg\" alt=\"image\"><br \/>  \u0412\u0440\u043e\u0434\u0435 \u0442\u0430\u043a\u043e\u0433\u043e<\/p>\n<p>  \u041d\u043e \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u043d\u0435\u0432\u043e\u0437\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0435\u0433\u043e \u0442\u043e\u0447\u043d\u043e \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043d\u0430 \u043c\u0435\u0441\u0442\u043e\u0440\u043e\u0436\u0434\u0435\u043d\u0438\u0438, \u0442\u0430\u043a-\u043a\u0430\u043a \u043a\u043e\u043b\u0435\u0441\u0430 \u0447\u0430\u0441\u0442\u043e \u0441\u043e\u0441\u043a\u0430\u043b\u044c\u0437\u044b\u0432\u0430\u043b\u0438 \u0432\u043d\u0438\u0437 (\u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0444\u0438\u0437\u0438\u043a\u0438 \u2014 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0431\u043b\u043e\u043a\u043e\u0432 (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043a\u043e\u043b\u0435\u0441\u0430) \u0438\u043c\u0435\u044e\u0442 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u0430\u043b\u044b\u0439 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0442\u0440\u0435\u043d\u0438\u044f). \u041a\u043e\u043b\u0435\u0441\u043d\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0441 \u0446\u0435\u043b\u044c\u043d\u043e\u043f\u043e\u0432\u043e\u0440\u043e\u0442\u043d\u044b\u043c\u0438 \u043a\u043e\u043b\u0435\u0441\u043d\u044b\u043c\u0438 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438 \u043e\u043a\u0430\u0437\u0430\u043b\u0430\u0441\u044c \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0433\u0440\u043e\u043c\u043e\u0437\u0434\u043a\u043e\u0439 \u0438 \u0441\u0442\u0440\u0430\u0434\u0430\u043b\u0430 \u043e\u0442 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0445 physics explosion. \u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0431\u044b\u043b\u043e \u0440\u0435\u0448\u0435\u043d\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0448\u0430\u0433\u0430\u044e\u0449\u0435\u0433\u043e \u0440\u043e\u0431\u043e\u0442\u0430 \u2014 \u0430 \u0438\u043c\u0435\u043d\u043d\u043e \u2014 \u0433\u0435\u043a\u0441\u0430\u043f\u043e\u0434, \u043a\u0430\u043a \u0441\u0430\u043c\u0443\u044e \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u0443\u044e \u0448\u0430\u0433\u0430\u044e\u0448\u0443\u044e \u043f\u043b\u0430\u0442\u0444\u0440\u043e\u043c\u0443.<\/p>\n<p>  \u0421 \u0447\u0435\u0433\u043e \u043d\u0430\u0447\u043d\u0435\u0442 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0433\u0435\u043a\u0441\u0430\u043f\u043e\u0434 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0447\u0435\u043b\u043e\u0432\u0435\u043a? \u041d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0437\u0430\u0439\u0434\u0435\u0442 \u0432 \u0438\u0433\u0440\u0443 \u0438 \u043d\u0430\u0447\u043d\u0435\u0442 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u0435\u043b\u043e \u0440\u043e\u0431\u043e\u0442\u0430 \u0441 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0441\u0442\u044f\u043c\u0438, \u0430 \u043f\u043e\u0442\u043e\u043c \u0434\u0443\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0451 \u043e\u0436\u0438\u0432\u043b\u044f\u0442\u044c. \u041d\u043e \u044d\u0442\u043e \u043d\u0435 \u043d\u0430\u0448 \u043c\u0435\u0442\u043e\u0434 (\u0446)<\/p>\n<h4>\u042f \u043d\u0430\u0447\u0430\u043b \u0441 \u0442\u0435\u043e\u0440\u0438\u0438<\/h4>\n<p>  \u0414\u043b\u044f \u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u043d\u043e\u0433\u0438 \u0431\u044b\u043b\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430:<br \/>  Inner joint \u2014 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0441\u0443\u0441\u0442\u0430\u0432, \u043a\u0430\u0447\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u043f\u043e \u043e\u0441\u0438 \u0440\u044b\u0441\u043a\u0430\u043d\u044c\u044f (yaw)<br \/>  Mid joint \u0438 outer joint \u2014 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0441\u0443\u0441\u0442\u0430\u0432\u044b, \u043a\u0430\u0447\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u043f\u043e \u043e\u0441\u0438 \u0442\u0430\u043d\u0433\u0430\u0436\u0430 (pitch). \u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0441\u0447\u0435\u0442\u0430 \u2014 \u043e\u0442 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u043e\u0433\u0438 \u043a \u043a\u043e\u043d\u0446\u0443 \u043d\u043e\u0433\u0438.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gb\/pf\/lk\/gbpflkpwfqchpytnnzuisf8_xss.png\"><br \/>  \u0423\u0433\u043e\u043b 0 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0443\u0441\u0442\u0430\u0432\u043e\u0432 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043d\u043e\u0433\u0430 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u044b\u043f\u0440\u044f\u043c\u043b\u0435\u043d\u0430 (\u043f\u0440\u044f\u043c\u0443\u044e \u043d\u043e\u0433\u0443 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0449\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0432 \u0438\u0433\u0440\u0435).<br \/>  \u0417\u0430\u0434\u0430\u0447\u0430 \u2014 \u043f\u0440\u0438 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0439 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0435 \u043d\u0430\u0439\u0442\u0438 \u0442\u0430\u043a\u0438\u0435 \u0443\u0433\u043b\u044b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0441\u0443\u0441\u0442\u043e\u0432\u043e\u0432, \u0447\u0442\u043e-\u0431\u044b \u043a\u043e\u043d\u0435\u0446 \u043d\u043e\u0433\u0438 \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0435. \u0417\u043d\u0430\u0447\u0438\u0442 \u0432\u0440\u0435\u043c\u044f \u0432\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u0442\u044c \u0442\u0440\u0438\u0433\u043e\u043d\u043e\u043c\u0435\u0442\u0440\u0438\u044e.<br \/>  \u0423\u0433\u043e\u043b \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0441\u0443\u0441\u0442\u0430\u0432\u0430 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 \u0430\u0440\u043a\u0442\u0430\u043d\u0433\u0435\u043d\u0441 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0446\u0435\u043b\u0438.  <\/p>\n<pre><code class=\"javascript\">const yawRad = Math.atan2(esimatedLegPosition.x, esimatedLegPosition.y);<\/code><\/pre>\n<p>  \u0421 \u0434\u0432\u0443\u043c\u044f \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u0443\u0441\u0442\u0430\u0432\u0430\u043c\u0438 \u043f\u043e\u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u043b\u0438\u043d\u0430 \u0432\u0441\u0435\u0445 \u0441\u0443\u0441\u0442\u0430\u0432\u043e\u0432. \u041c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0443\u0433\u043e\u043b \u043a \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0443 \u0438 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0441\u0440\u0435\u0434\u043d\u0438\u043c \u0441\u0443\u0441\u0442\u0430\u0432\u043e\u043c \u0438 \u0437\u0435\u043c\u043b\u0435\u0439, \u0430 \u0442\u0430\u043a-\u0436\u0435 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438. <br \/>  \u0414\u0430\u043b\u044c\u0448\u0435 \u0447\u0435\u0440\u0435\u0437 \u0442\u0435\u043e\u0440\u0435\u043c\u0443 \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u043e\u0432 \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0443\u0433\u043b\u044b \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u043c \u0441\u0442\u043e\u0440\u043e\u043d\u0430\u043c.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/309\/9e4\/26c\/3099e426c99f78fad697fbba0bbcdaac.png\" alt=\"image\"><br \/>  <img decoding=\"async\" src=\"https:\/\/wikimedia.org\/api\/rest_v1\/media\/math\/render\/svg\/73466cc603b04dff114de759f29e794e6e61f185\" alt=\"image\"><br \/>  <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A0%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D1%82%D1%80%D0%B5%D1%83%D0%B3%D0%BE%D0%BB%D1%8C%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2\">\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u043e\u0432<\/a><\/p>\n<p>  \u0422\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435:  <\/p>\n<pre><code class=\"javascript\">\t\tgetLegAngles(esimatedLegPosition) { \t\t\tconst yawRad = Math.atan2(esimatedLegPosition.x, esimatedLegPosition.y);  \t\t\tconst dx = Math.hypot(esimatedLegPosition.x, esimatedLegPosition.y) - this.innerJoint.length; \t\t\tconst dz = this.step.idlePosition.z + esimatedLegPosition.z;  \t\t\tconst hyp = Math.hypot(dx, dz); \t\t\tif (hyp &gt; this.midJoint.length + this.outerJoint.length) {\/\/out of reach \t\t\t\thyp = this.midJoint.length + this.outerJoint.length; \t\t\t}  \t\t\tconst innerAngleRad = Math.acos((this.outerJoint.length * this.outerJoint.length - this.midJoint.length * this.midJoint.length - hyp * hyp) \/ (-2 * this.midJoint.length * hyp)) + Math.atan2(dz, dx); \t\t\tconst outerAngleRad = Math.acos((hyp * hyp - this.midJoint.length * this.midJoint.length - this.outerJoint.length * this.outerJoint.length) \/ (-2 * this.midJoint.length * this.outerJoint.length)) - Math.PI;  \t\t\treturn { yaw: yawRad, midPitch: innerAngleRad, outerPitch: outerAngleRad }; \t\t}<\/code><\/pre>\n<h4>\u0414\u0432\u0438\u0436\u0435\u043d\u0438\u0435<\/h4>\n<p>  \u0414\u0430\u043b\u0435\u0435. \u0420\u043e\u0431\u043e\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0445\u043e\u0434\u0438\u0442\u044c, \u0432\u0435\u0440\u043d\u043e? \u0422\u043e-\u0435\u0441\u0442\u044c \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c N \u0440\u0430\u0437 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0433\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438. \u0421 \u0443\u0447\u0435\u0442\u043e\u043c \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u043d\u043e\u0433 6 \u0438 3 \u0438\u0437 \u043d\u0438\u0445 \u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0444\u0430\u0437\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a-\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u043e. \u041d\u0443\u0436\u043d\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u043d\u043e\u0432\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438.<br \/>  \u0410 \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043c\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c \u0447\u0442\u043e \u043d\u043e\u0433\u0430 \u0434\u0432\u0438\u0436\u0435\u0442\u0441\u044f \u043f\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u0435\u0439 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0443\u0433\u043e\u043b \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u043d\u0430 \u044d\u0442\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438? \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u043c \u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440, \u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0439\u0441\u044f \u0446\u0438\u043a\u043b\u0438\u0447\u043d\u043e. \u0422\u043e\u0433\u0434\u0430 \u0446\u0435\u043b\u0435\u0432\u044b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u044b\u0442\u044b \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0441\u0438\u043d\u0443\u0441 \u0438 \u043a\u043e\u0441\u0438\u043d\u0443\u0441. <br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/q9\/pc\/a1\/q9pca1u_u5aysu0uigf8af-mssc.gif\"><br \/>  \u041f\u043e\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.<\/p>\n<p>  \u041e\u0431\u0434\u0443\u043c\u044b\u0432\u0430\u044f \u043a\u0430\u043a \u0432\u0441\u0451 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u044f \u043f\u043e\u043d\u044f\u043b, \u0447\u0442\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e-\u0431\u044b \u0432\u0441\u0451 \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0430 (\u0441 \u0434\u0435\u0431\u0430\u0433\u043e\u043c \u0432 Space Engineers \u0432\u0441\u0451 \u043f\u043b\u043e\u0445\u043e, \u043d\u043e \u043e\u0431 \u044d\u0442\u043e\u043c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438).<br \/>  \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0440\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440. \u041c\u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0435\u0433\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0431\u0435\u0437 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0438 \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u043e\u0434\u0438\u043d \u043a\u043b\u0438\u043a \u0438 \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044e.<br \/>  \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u044b\u043b \u0432\u044b\u0431\u0440\u0430\u043d JS + HTML Canvas.<br \/>  \u0410 \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0430\u0440\u0438\u0441\u0443\u0435\u043c \u0441\u043e\u0432\u0443.<\/p>\n<p>  \u041a\u043e\u0434:  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u0430<\/b><\/p>\n<div class=\"spoiler_text\">\u0412\u0435\u043a\u0442\u043e\u0440:  <\/p>\n<pre><code class=\"javascript\"> \tclass Vector { \t\tconstructor(x, y, z) { \t\t\tthis.x = x; \t\t\tthis.y = y; \t\t\tthis.z = z; \t\t};  \t\tdistanceTo(vector) { \t\t\treturn Math.sqrt(Math.pow(this.x - vector.x, 2) + Math.pow(this.y - vector.y, 2) + Math.pow(this.z - vector.z, 2)); \t\t}  \t\tdiff(vector) { \t\t\treturn new Vector( \t\t\t\tthis.x - vector.x, \t\t\t\tthis.y - vector.y, \t\t\t\tthis.z - vector.z \t\t\t); \t\t}  \t\tadd(vector) { \t\t\treturn new Vector( \t\t\t\tthis.x + vector.x, \t\t\t\tthis.y + vector.y, \t\t\t\tthis.z + vector.z \t\t\t); \t\t} \t} <\/code><\/pre>\n<p>  \u0421\u0443\u0441\u0442\u0430\u0432:  <\/p>\n<pre><code class=\"javascript\"> \tclass Joint { \t\tconstructor(angle, position, length) { \t\t\tthis.angle = angle; \t\t\tthis.position = position; \t\t\tthis.length = length;  \t\t\tthis.targetAngle = angle; \t\t\tthis.previousAngle = angle; \t\t\tthis.velocity = 0; \t\t};  \t\tsetTargetAngle(targetAngle) { \t\t\tthis.targetAngle = targetAngle; \t\t\tthis.velocity = this.targetAngle - this.normalizeAngle(this.angle); \t\t}  \t\tnormalizeAngle(angle) { \t\t\twhile (angle &lt;= -Math.PI) angle += Math.PI * 2; \t\t\twhile (angle &gt; Math.PI) angle -= Math.PI * 2; \t\t\treturn angle; \t\t}  \t\tgetCurrentVelocity() {\/\/per tick \t\t\treturn this.normalizeAngle(this.angle - this.previousAngle); \t\t}  \t\ttick() { \t\t\tthis.previousAngle = this.angle; \t\t\tthis.angle = this.angle + this.velocity; \t\t} \t} <\/code><\/pre>\n<p>  \u0428\u0430\u0433 \u2014 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0433\u043e\u0439:  <\/p>\n<pre><code class=\"javascript\"> \tclass Step { \t\tconstructor( \t\t\tidlePosition,\/\/vector relative to inner joint \t\t\tangle,\/\/step direction \t\t\tlength,\/\/step length \t\t\theight,\/\/step height \t\t\tphaseShift\/\/ \t\t) { \t\t\tthis.idlePosition = idlePosition; \t\t\tthis.angle = angle;\/\/radians \t\t\tthis.length = length; \t\t\tthis.height = height; \t\t\tthis.phaseShift = phaseShift; \t\t} \t} <\/code><\/pre>\n<p>  \u041d\u043e\u0433\u0430:  <\/p>\n<pre><code class=\"javascript\"> \tclass Leg { \t\tconstructor( \t\t\tvehicleCenter, \t\t\tinnerJoint, \t\t\tmidJoint, \t\t\touterJoint, \t\t\tstep, \t\t\tphaseStep \t\t) { \t\t\tthis.vehicleCenter = vehicleCenter; \t\t\tthis.innerJoint = innerJoint; \t\t\tthis.midJoint = midJoint; \t\t\tthis.outerJoint = outerJoint; \t\t\tthis.step = step; \t\t\tthis.phaseStep = phaseStep;  \t\t\tthis.innerJoint.length = innerJoint.position.distanceTo(midJoint.position);\/\/calculate \t\t\tthis.midJoint.length = midJoint.position.distanceTo(outerJoint.position);\/\/calculate \t\t\t\/\/this.outerJoint.length = 100;  \t\t\tthis.joints = [innerJoint, midJoint, outerJoint];  \t\t\tthis.preCalculateAngles(); \t\t}  \t\tpreCalculateAngles() { \t\t\tthis.angles = {}; \t\t\tfor (let phase = 0; phase &lt; 360; phase += this.phaseStep) { \t\t\t\tthis.angles[phase] = this.getLegAngles(this.getEsimatedLegPosition(phase, this.step.phaseShift)) \t\t\t} \t\t}  \t\tapplyStepHeight(z) { \t\t\tconst idleYawRad = Math.atan2(this.step.idlePosition.x, this.step.idlePosition.y); \t\t\tconst diffHypot = Math.hypot(this.step.idlePosition.x, this.step.idlePosition.y);  \t\t\tconst minZ = Math.abs(this.midJoint.length - this.outerJoint.length); \t\t\tconst maxZ = (this.midJoint.length + this.outerJoint.length) * 0.6;  \t\t\tif (Math.hypot(z, 0) &gt; maxZ) { \t\t\t\tz = z &gt; 0 ? maxZ : -maxZ; \t\t\t}  \t\t\tconst safeY = (this.innerJoint.length + this.midJoint.length * 0.5 + this.outerJoint.length * 0.5) * Math.cos(idleYawRad);  \t\t\tconst vAngle = Math.asin(z \/ safeY); \t\t\tconst y = safeY * Math.cos(vAngle) * Math.cos(idleYawRad); \t\t\tthis.step.idlePosition.z = z; \t\t\tthis.step.idlePosition.y = this.step.idlePosition.y &gt; 0 ? y : -y; \t\t\tthis.preCalculateAngles(); \t\t}  \t\tapplyStepAngle(angle) { \t\t\tthis.step.angle = angle; \t\t\tthis.preCalculateAngles(); \t\t} \t\tapplyPhase(phase\/*0-360*\/) { \t\t\tconst legAngles = this.angles[phase];  \t\t\tthis.innerJoint.setTargetAngle(legAngles.yaw); \t\t\tthis.midJoint.setTargetAngle(legAngles.midPitch); \t\t\tthis.outerJoint.setTargetAngle(legAngles.outerPitch); \t\t}  \t\tgetEsimatedLegPosition(phase, phaseShift) { \t\t\tphase = (phase + phaseShift) % 360;  \t\t\tconst stepX = ((phase &lt; 180 ? phase : 180 - phase % 180) \/ 180 - 0.5) * this.step.length;\/\/linear movement along step direction \t\t\tconst stepZ = Math.max(Math.sin(phase * Math.PI \/ 180), -0.2) * this.step.height \/ 1.2; \t\t\t\/\/const stepZ = Math.max((phase &gt; 180 ? Math.cos(phase * Math.PI \/ 360) + 0.9 : Math.cos((phase - 120) * Math.PI \/ 360)) * .9 - .1, 0) * this.step.height;  \t\t\tconst x = this.step.idlePosition.x + stepX * Math.cos(this.step.angle); \t\t\tconst y = this.step.idlePosition.y + stepX * Math.sin(this.step.angle);  \t\t\treturn new Vector(x, y, stepZ); \t\t}  \t\tgetLegAngles(esimatedLegPosition) { \t\t\tconst yawRad = Math.atan2(esimatedLegPosition.x, esimatedLegPosition.y);  \t\t\tconst dx = Math.hypot(esimatedLegPosition.x, esimatedLegPosition.y) - this.innerJoint.length; \t\t\tconst dz = this.step.idlePosition.z + esimatedLegPosition.z;  \t\t\tconst hyp = Math.hypot(dx, dz); \t\t\tif (hyp &gt; this.midJoint.length + this.outerJoint.length) {\/\/out of reach \t\t\t\thyp = this.midJoint.length + this.outerJoint.length; \t\t\t}  \t\t\tconst innerAngleRad = Math.acos((this.outerJoint.length * this.outerJoint.length - this.midJoint.length * this.midJoint.length - hyp * hyp) \/ (-2 * this.midJoint.length * hyp)) + Math.atan2(dz, dx); \t\t\tconst outerAngleRad = Math.acos((hyp * hyp - this.midJoint.length * this.midJoint.length - this.outerJoint.length * this.outerJoint.length) \/ (-2 * this.midJoint.length * this.outerJoint.length)) - Math.PI;  \t\t\tif (isNaN(yawRad) || isNaN(innerAngleRad) || isNaN(outerAngleRad)) { \t\t\t\tconsole.log(yawRad, innerAngleRad, outerAngleRad); \t\t\t\tconsole.log(dx, dz); \t\t\t\treturn; \t\t\t}  \t\t\treturn { yaw: yawRad, midPitch: innerAngleRad, outerPitch: outerAngleRad }; \t\t}  \t\tgetMaxMinAngles() { \t\t\tconst angles = [0, 90, 180, 270].map((phase) =&gt; { \t\t\t\treturn this.getLegAngles(getEsimatedLegPosition(phase, 0)); \t\t\t}); \t\t\treturn { \t\t\t\tyawMin: Math.min(angles.map((x) =&gt; { return x.yaw })), \t\t\t\tyawMax: Math.max(angles.map((x) =&gt; { return x.yaw })), \t\t\t\tmidPitchMin: Math.min(angles.map((x) =&gt; { return x.midPitch })), \t\t\t\tmidPitchMax: Math.max(angles.map((x) =&gt; { return x.midPitch })), \t\t\t\touterPitchMin: Math.min(angles.map((x) =&gt; { return x.outerPitch })), \t\t\t\touterPitchMax: Math.max(angles.map((x) =&gt; { return x.outerPitch })), \t\t\t} \t\t}  \t\ttick() { \t\t\tthis.joints.forEach(function (joint) { joint.tick(); }); \t\t}  \t\tgetVectors() { \t\t\tconst res = []; \t\t\tconst sinYaw = Math.sin(this.innerJoint.angle); \t\t\tconst cosYaw = Math.cos(this.innerJoint.angle);  \t\t\tlet currentVector = this.vehicleCenter; \t\t\tres.push(currentVector);  \t\t\tcurrentVector = currentVector.add(this.innerJoint.position); \t\t\tres.push(currentVector);  \t\t\tcurrentVector = currentVector.add(new Vector( \t\t\t\tthis.innerJoint.length * sinYaw, \t\t\t\tthis.innerJoint.length * cosYaw, \t\t\t\t0 \t\t\t)); \t\t\tres.push(currentVector);  \t\t\tconst dxMid = Math.cos(this.midJoint.angle) * this.midJoint.length; \t\t\tconst dzMid = Math.sin(this.midJoint.angle) * this.midJoint.length;  \t\t\tcurrentVector = currentVector.add(new Vector( \t\t\t\tdxMid * sinYaw, \t\t\t\tdxMid * cosYaw, \t\t\t\tdzMid \t\t\t)); \t\t\tres.push(currentVector);  \t\t\tconst c = this.midJoint.angle + this.outerJoint.angle; \t\t\tconst dxOuter = Math.cos(c) * this.outerJoint.length; \t\t\tconst dzOuter = Math.sin(c) * this.outerJoint.length; \t\t\tcurrentVector = currentVector.add(new Vector( \t\t\t\tdxOuter * sinYaw, \t\t\t\tdxOuter * cosYaw, \t\t\t\tdzOuter \t\t\t)); \t\t\tres.push(currentVector);  \t\t\treturn res; \t\t} \t} <\/code><\/pre>\n<p>  \u0420\u043e\u0431\u043e\u0442:  <\/p>\n<pre><code class=\"javascript\"> \tclass Hexapod { \t\tconstructor(phaseStep) { \t\t\tthis.idleHeight = -70; \t\t\tthis.stepAngle = 0; \t\t\tthis.turnAngle = 0; \t\t\tthis.stepLength = 70; \t\t\tthis.stepHeight = 30; \t\t\tthis.debugPoints = []; \t\t\tconst vehicleCenter = new Vector(0, 0, 0); \t\t\tthis.legs = [ \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(-70, 10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(-70, 60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(-70, 110, 0), 70), \t\t\t\t\tnew Step(new Vector(-30, 90, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 0), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(-70, -10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(-70, -60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(-70, -110, 0), 70), \t\t\t\t\tnew Step(new Vector(-30, -90, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 180), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(0, 10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(0, 60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(0, 110, 0), 70), \t\t\t\t\tnew Step(new Vector(0, 100, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 180), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(0, -10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(0, -60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(0, -110, 0), 70), \t\t\t\t\tnew Step(new Vector(0, -100, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 0), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(70, 10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(70, 60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(70, 110, 0), 70), \t\t\t\t\tnew Step(new Vector(30, 90, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 0), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t\tnew Leg( \t\t\t\t\tvehicleCenter, \t\t\t\t\tnew Joint(0, new Vector(70, -10, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(70, -60, 0), 50), \t\t\t\t\tnew Joint(0, new Vector(70, -110, 0), 70), \t\t\t\t\tnew Step(new Vector(30, -90, this.idleHeight), this.stepAngle, this.stepLength, this.stepHeight, 180), \t\t\t\t\tphaseStep \t\t\t\t), \t\t\t]; \t\t}  \t\tapplyPhase(phase\/*0-360*\/) { \t\t\tthis.legs.forEach(function (leg) { \t\t\t\tleg.applyPhase(phase); \t\t\t}); \t\t} \t\tchangeHeight(value) { \t\t\tthis.legs.forEach(function (leg) { \t\t\t\tleg.applyStepHeight(this.idleHeight + value); \t\t\t}, this); \t\t} \t\tchangeStepLength(value) { \t\t\tthis.stepLength += value; \t\t\tthis.legs.forEach(function (leg) { \t\t\t\tleg.step.length = this.stepLength; \t\t\t\tleg.preCalculateAngles(); \t\t\t}, this); \t\t} \t\tapplyTurn1(centerX, centerY) { \t\t\tconst angleToAxis = Math.atan2(centerX, centerY); \t\t\tconst distanceToAxis = Math.hypot(centerX, centerY); \t\t\tdistanceToAxis = 1000\/distanceToAxis; \t\t\tthis.legs.forEach(leg =&gt; { \t\t\t\tconst dx = leg.step.idlePosition.x + leg.innerJoint.position.x + Math.sin(angleToAxis)*distanceToAxis || 0; \t\t\t\tconst dy = leg.step.idlePosition.y + leg.innerJoint.position.y + Math.cos(angleToAxis)*distanceToAxis || 0; \t\t\t\t \t\t\t\tconst angle = Math.atan2(dy,dx);  \t\t\t\tconst hypIdle = Math.hypot(dx, dy); \t\t\t\tleg.applyStepAngle(angle+Math.PI\/2); \t\t\t\tleg.step.length = this.stepLength *hypIdle\/ ((distanceToAxis || 0) + 1000); \t\t\t}); \t\t} \t\tapplyTurn(centerX, centerY) { \t\t\tthis.stepAngle = Math.atan2(centerX, centerY); \t\t\tif (this.stepAngle &gt; Math.PI \/ 2) this.stepAngle -= Math.PI; \t\t\tif (this.stepAngle &lt; -Math.PI \/ 2) this.stepAngle += Math.PI; \t\t\tconst mults = this.legs.map(leg =&gt; \t\t\t\tMath.hypot(leg.step.idlePosition.y + leg.innerJoint.position.y, leg.step.idlePosition.x + leg.innerJoint.position.x) \t\t\t\t\/ Math.hypot(leg.step.idlePosition.y + leg.innerJoint.position.y + centerY*.3, leg.step.idlePosition.x + leg.innerJoint.position.x + centerX*.3)); \t\t\tconst minMult = Math.min(...mults); \t\t\tconst maxMult = Math.max(...mults); \t\t\tconst mult = minMult \/ maxMult; \t\t\tconst d = Math.pow(Math.max(...this.legs.map(leg =&gt;Math.hypot(leg.step.idlePosition.y + leg.innerJoint.position.y, leg.step.idlePosition.x + leg.innerJoint.position.x))),2)\/Math.hypot(centerX,centerY); \t\t\tconst a =  Math.atan2(centerX,centerY); \t\t\tthis.legs.forEach(leg =&gt; { \t\t\t\tconst dx = leg.step.idlePosition.x + leg.innerJoint.position.x; \t\t\t\tconst dy = leg.step.idlePosition.y + leg.innerJoint.position.y; \t\t\t\tconst idleAngle = Math.atan2(dx, dy) + this.stepAngle; \t\t\t\tconst turnAngle = Math.atan2(dx + centerX, dy + centerY);  \t\t\t\tconst hypIdle = Math.hypot(dx, dy); \t\t\t\tconst hyp = Math.hypot(dx + centerX, dy + centerY); \t\t\t\tleg.applyStepAngle(turnAngle - idleAngle); \t\t\t\tleg.step.length = this.stepLength * hyp \/ hypIdle * mult; \t\t\t}); \t\t\tthis.debugPoints = [new Vector(Math.sin(a)*-d,Math.cos(a)*-d,0)]; \t\t} \t\ttick() { \t\t\tthis.legs.forEach(function (leg) { \t\t\t\tleg.tick(); \t\t\t}); \t\t} \t\tgetVectors() { \t\t\treturn this.legs.map(function (leg) { return leg.getVectors() }); \t\t} \t} <\/code><\/pre>\n<p>  \u041d\u043e \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0430\u0441\u0441\u043e\u0432:<br \/>  \u041e\u0431\u0435\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 Canvas:  <\/p>\n<pre><code class=\"javascript\"> \tclass Canvas { \t\tconstructor(id, label, axisSelectorX, axisSelectorY) { \t\t\tconst self = this; \t\t\tthis.id = id; \t\t\tthis.label = label; \t\t\tthis.canvas = document.getElementById(id); \t\t\tthis.ctx = this.canvas.getContext('2d'); \t\t\tthis.axisSelectorX = axisSelectorX; \t\t\tthis.axisSelectorY = axisSelectorY;  \t\t\tthis.canvasHeight = this.canvas.offsetHeight; \t\t\tthis.canvasWidth = this.canvas.offsetWidth;  \t\t\tthis.initialY = this.canvasHeight \/ 2; \t\t\tthis.initialX = this.canvasWidth \/ 2;  \t\t\tthis.traceCounter = 0; \t\t\tthis.maxTraces = 50; \t\t\tthis.traces = {};  \t\t\tconst axisSize = 150; \t\t\tthis.axisVectors = [ \t\t\t\t[ \t\t\t\t\tnew Vector(-axisSize, -axisSize, -axisSize), \t\t\t\t\tnew Vector(-axisSize, -axisSize, axisSize) \t\t\t\t], \t\t\t\t[ \t\t\t\t\tnew Vector(-axisSize, -axisSize, -axisSize), \t\t\t\t\tnew Vector(-axisSize, axisSize, -axisSize) \t\t\t\t], \t\t\t\t[ \t\t\t\t\tnew Vector(-axisSize, -axisSize, -axisSize), \t\t\t\t\tnew Vector(axisSize, -axisSize, -axisSize) \t\t\t\t], \t\t\t]  \t\t\tthis.mouseOver = false; \t\t\tthis.mousePos = { x: 0, y: 0 };\/\/relative to center \t\t\tthis.clickPos = { x: 0, y: 0 };\/\/relative to center \t\t\tthis.canvas.addEventListener(\"mouseenter\", function (event) { \t\t\t\tself.mouseOver = true; \t\t\t}, false); \t\t\tthis.canvas.addEventListener(\"mouseleave\", function (event) { \t\t\t\tself.mouseOver = false; \t\t\t}, false); \t\t\tthis.canvas.addEventListener(\"mousemove\", function (event) { \t\t\t\tif (self.mouseOver) { \t\t\t\t\tself.mousePos = { x: event.offsetX - self.initialX, y: event.offsetY - self.initialY }; \t\t\t\t} \t\t\t}, false); \t\t\tthis.canvas.addEventListener(\"mouseup\", function (event) { \t\t\t\tif (self.mouseOver) { \t\t\t\t\tself.clickPos = { x: event.offsetX - self.initialX, y: event.offsetY - self.initialY }; \t\t\t\t} \t\t\t}, false); \t\t}; \t\tclear(drawAxis) { \t\t\tthis.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); \t\t\tthis.ctx.strokeStyle = \"#000000\"; \t\t\tthis.ctx.strokeText(this.label, 10, 10);  \t\t\tif (drawAxis) { \t\t\t\tthis.axisVectors.forEach(function (vectors, i) { \t\t\t\t\tthis.ctx.moveTo(this.initialX, this.initialY); \t\t\t\t\tthis.ctx.beginPath();  \t\t\t\t\tvectors.forEach(function (vector) { \t\t\t\t\t\tthis.ctx.lineTo(this.initialX + this.axisSelectorX(vector), this.initialY - this.axisSelectorY(vector)); \t\t\t\t\t}, this); \t\t\t\t\tthis.ctx.stroke();  \t\t\t\t\tconst lastVector = vectors[vectors.length - 1]; \t\t\t\t\tthis.traces[[this.traceCounter, i]] = lastVector  \t\t\t\t}, this); \t\t\t} \t\t} \t\tdrawVectors(vectors) {\/*2d array*\/ \t\t\tvectors.forEach(function (vectors, i) { \t\t\t\tthis.ctx.moveTo(this.initialX, this.initialY); \t\t\t\tthis.ctx.beginPath();  \t\t\t\tvectors.forEach(function (vector) { \t\t\t\t\tthis.ctx.lineTo(this.initialX + this.axisSelectorX(vector), this.initialY - this.axisSelectorY(vector)); \t\t\t\t}, this); \t\t\t\tthis.ctx.stroke();  \t\t\t\tconst lastVector = vectors[vectors.length - 1]; \t\t\t\tthis.traces[[this.traceCounter, i]] = lastVector  \t\t\t}, this);  \t\t\tfor (const key in this.traces) { \t\t\t\tconst vector = this.traces[key]; \t\t\t\tthis.ctx.fillStyle = \"#FF0000\";\/\/red \t\t\t\tthis.ctx.fillRect(this.initialX + this.axisSelectorX(vector), this.initialY - this.axisSelectorY(vector), 1, 1); \t\t\t}  \t\t\tthis.ctx.strokeStyle = \"#000000\"; \t\t\tthis.ctx.beginPath(); \t\t\tthis.ctx.arc(this.clickPos.x + this.initialX, this.clickPos.y + this.initialY, 5, 0, 2 * Math.PI); \t\t\tthis.ctx.stroke(); \t\t\tif (this.mouseOver) { \t\t\t\tthis.ctx.strokeStyle = \"#00FF00\"; \t\t\t\tthis.ctx.beginPath(); \t\t\t\tthis.ctx.arc(this.mousePos.x + this.initialX, this.mousePos.y + this.initialY, 10, 0, 2 * Math.PI); \t\t\t\tthis.ctx.stroke(); \t\t\t}  \t\t\tthis.traceCounter = (this.traceCounter + 1) % this.maxTraces; \t\t} \t\tdrawPoints(points) {\t\t \t\t\tthis.ctx.fillStyle = \"#00ff00\";\/\/green \t\t\tpoints.forEach(function (point) { \t\t\t\tthis.ctx.fillRect(this.initialX + this.axisSelectorX(point), this.initialY - this.axisSelectorY(point), 3, 3); \t\t\t}, this); \t\t} \t} <\/code><\/pre>\n<p>  \u0412 \u043a\u043b\u0430\u0441\u0441\u0435 Leg \u0435\u0441\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0441\u0443\u0441\u0442\u0430\u0432\u043e\u0432. \u0412\u043e\u0442 \u044d\u0442\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043c\u044b \u0438 \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c.<br \/>  \u0422\u0430\u043a-\u0436\u0435 \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0443 \u0442\u043e\u0447\u0435\u043a, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0430\u0445\u043e\u0434\u0438\u043b\u0430\u0441\u044c \u043d\u043e\u0433\u0430 \u0432 N \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0442\u0438\u043a\u043e\u0432.<\/p>\n<p>  \u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446 Worker, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u0438\u043c\u0443\u043b\u044f\u0446\u0438\u044e:  <\/p>\n<pre><code class=\"javascript\"> \tclass Worker { \t\tconstructor(tickTime) { \t\t\tconst self = this; \t\t\tthis.phaseStep = 5; \t\t\tthis.tickTime = tickTime; \t\t\tconst tan30 = Math.tan(Math.PI \/ 6); \t\t\tconst scale = 0.7; \t\t\tthis.canvases = [ \t\t\t\tnew Canvas('canvasForward', 'yz Forward', function (v) { return v.y }, function (v) { return v.z }), \t\t\t\tnew Canvas('canvasSide', 'xz Side', function (v) { return v.x }, function (v) { return v.z }), \t\t\t\tnew Canvas('canvasTop', 'xy Top', function (v) { return v.x }, function (v) { return -v.y }), \t\t\t\tnew Canvas('canvasIso', 'xyz Iso', function (v) { return v.x * scale + v.y * scale }, function (v) { return v.z * scale + v.x * tan30 * scale - v.y * tan30 * scale }), \t\t\t];  \t\t\tthis.bot = new Hexapod(this.phaseStep);  \t\t\tthis.phase = 0; \t\t\tthis.focus = true;  \t\t\twindow.addEventListener('focus', function () { \t\t\t\tconsole.log('focus'); \t\t\t\tself.focus = true; \t\t\t});  \t\t\twindow.addEventListener('blur', function () { \t\t\t\tconsole.log('blur'); \t\t\t\tself.focus = false; \t\t\t}); \t\t\tthis.start(); \t\t}  \t\ttick(argument) { \t\t\tconst canvasForward = this.canvases[0]; \t\t\tconst bot = this.bot; \t\t\tif (canvasForward.mouseOver) { \t\t\t\tbot.changeHeight(-canvasForward.mousePos.y); \t\t\t} else { \t\t\t\tbot.changeHeight(0); \t\t\t} \t\t\tconst canvasTop = this.canvases[2]; \t\t\tif (canvasTop.mouseOver) { \t\t\t\tbot.applyTurn(-canvasTop.mousePos.x, -canvasTop.mousePos.y); \t\t\t} else { \t\t\t\tbot.applyTurn(0, 0); \t\t\t}    \t\t\tthis.phase = (this.phase + this.phaseStep) % 360; \t\t\tbot.applyPhase(this.phase); \t\t\tbot.tick();  \t\t\tconst vectors = bot.getVectors();  \t\t\tthis.canvases.forEach(function (c) { \t\t\t\tc.clear(false); \t\t\t\tc.drawVectors(vectors); \t\t\t\tc.drawPoints(bot.debugPoints); \t\t\t}); \t\t} \t\tstart() { \t\t\tthis.stop(); \t\t\tthis.interval = setInterval((function (self) { \t\t\t\treturn function () { \t\t\t\t\tif (self.focus) { \t\t\t\t\t\tself.tick(); \t\t\t\t\t} \t\t\t\t} \t\t\t})(this), this.tickTime); \t\t} \t\tstop() { \t\t\tclearInterval(this.interval); \t\t} \t} <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ut\/rv\/2k\/utrv2kzjrrigx19ickf04bduuqm.gif\"><br \/>  \u041f\u0440\u0430\u0432\u0434\u0430 \u043c\u0438\u043b\u0435\u043d\u044c\u043a\u043e?<\/p>\n<p>  \u0417\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u044f \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043d\u043e\u0433 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438. \u0414\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438 \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u0443\u0440\u0435\u0437\u0430\u043d\u043d\u0443\u044e \u0441\u0438\u043d\u0443\u0441\u043e\u0438\u0434\u0443, \u0430 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438 \u043b\u0438\u043d\u0435\u0439\u043d\u043e. \u042d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043d\u0430 \u043d\u043e\u0433\u0438.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0439, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435.<\/p>\n<h4>\u041a\u0430\u043a \u043d\u0430\u0443\u0447\u0438\u0442\u044c \u0440\u043e\u0431\u043e\u0442\u0430 \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c?<\/h4>\n<p>  \u0414\u043b\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b 2 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438: <br \/>  \u0415\u0441\u043b\u0438 \u0440\u043e\u0431\u043e\u0442 \u0441\u0442\u043e\u0438\u0442 \u2014 \u043d\u043e\u0433\u0438 \u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438.<br \/>  \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043d\u043e \u2014 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u043b\u043e-\u0431\u044b \u043a\u043e\u0434 \u0441 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u043e\u0433\u0438 \u0434\u0432\u0438\u0433\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u043a\u0430\u0441\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043a \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>  \u041a\u043e\u0433\u0434\u0430 \u0440\u043e\u0431\u043e\u0442 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0432\u0440\u043e\u0434\u0435 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ackermann_steering_geometry\">Ackermann steering geometry<\/a> \u0441 \u0434\u0438\u0444\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0430\u043b\u043e\u043c. <br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/d72\/b14\/527\/d72b145276aa480e90b63522bb1a40e0.png\" alt=\"image\"><br \/>  \u0422\u043e-\u0435\u0441\u0442\u044c \u0434\u043b\u0438\u043d\u0430 \u0448\u0430\u0433\u0430 \u043d\u043e\u0433, \u0434\u0432\u0438\u0433\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u043f\u043e \u043c\u0435\u043d\u044c\u0448\u0435\u043c\u0443 \u0440\u0430\u0434\u0438\u0443\u0441\u0443, \u2014 \u043c\u0435\u043d\u044c\u0448\u0435. \u0410 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u2014 \u0431\u043e\u043b\u044c\u0448\u0435.<\/p>\n<p>  \u0427\u0442\u043e-\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0433\u0438 \u044f \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c:<br \/>  1) \u0421\u0447\u0438\u0442\u0430\u0435\u043c \u0443\u0433\u043e\u043b \u043e\u0442 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u043e\u0433\u0438 \u043a \u0446\u0435\u043d\u0442\u0440\u0443 \u0440\u043e\u0431\u043e\u0442\u0430  <\/p>\n<pre><code class=\"javascript\">const idleAngle = Math.atan2(dx, dy) + this.stepAngle;<\/code><\/pre>\n<p>  2) \u0421\u0447\u0438\u0442\u0430\u0435\u043c \u0443\u0433\u043e\u043b \u043e\u0442 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u043e\u0433\u0438 \u043a (\u0446\u0435\u043d\u0442\u0440\u0443 \u0440\u043e\u0431\u043e\u0442\u0430 + \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u2014 \u044d\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440)  <\/p>\n<pre><code class=\"javascript\">const turnAngle = Math.atan2(dx + centerX, dy + centerY);<\/code><\/pre>\n<p>  3) \u041f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0448\u0430\u0433 \u043d\u0430 \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u044d\u0442\u0438\u0445 \u0443\u0433\u043b\u043e\u0432  <\/p>\n<pre><code class=\"javascript\">leg.applyStepAngle(turnAngle - idleAngle);<\/code><\/pre>\n<p>  \u041d\u043e \u044d\u0442\u043e \u043d\u0435 \u0432\u0441\u0451. \u0415\u0449\u0435 \u043d\u0443\u0436\u043d\u043e \u0438\u0437\u043c\u043d\u044f\u0442\u044c \u0434\u043b\u0438\u043d\u0443 \u0448\u0430\u0433\u0430. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432 \u043b\u043e\u0431 \u2014 \u0434\u043e\u043c\u043d\u043e\u0436\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u0443 \u0448\u0430\u0433\u0430 \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u2014 \u0438\u043c\u0435\u043b\u043e \u0444\u0430\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u043a \u2014 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u043d\u043e\u0433\u0438 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0448\u0438\u0440\u043e\u043a\u043e \u0448\u0430\u0433\u0430\u043b\u0438 \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u043b\u0438 \u0437\u0430\u0434\u0435\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430.<br \/>  \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e:<br \/>  1) \u0421\u0447\u0438\u0442\u0430\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0433\u0438  <\/p>\n<pre><code class=\"javascript\">const mults = this.legs.map(leg =&gt; \tMath.hypot(leg.step.idlePosition.y + leg.innerJoint.position.y, leg.step.idlePosition.x + leg.innerJoint.position.x) \t\/ Math.hypot(leg.step.idlePosition.y + leg.innerJoint.position.y + centerY*.3, leg.step.idlePosition.x + leg.innerJoint.position.x + centerX*.3));<\/code><\/pre>\n<p>  0.3 \u2014 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0447\u0438\u0441\u043b\u043e<br \/>  2) \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c  <\/p>\n<pre><code class=\"javascript\"> \tconst minMult = Math.min(...mults); \tconst maxMult = Math.max(...mults); \tconst mult = minMult \/ maxMult;<\/code><\/pre>\n<p>  \u042d\u0442\u043e\u0442 \u043c\u043d\u043e\u0436\u0438\u0442\u0435\u043b\u044c \u043e\u0442\u0440\u0430\u0436\u0430\u0435\u0442 \u0440\u0430\u0437\u043d\u0438\u0446\u0443 \u043c\u0435\u0436\u0434\u0443 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043e \u0446\u0435\u043d\u0442\u0440\u0430. \u041e\u043d \u0432\u0441\u0435\u0433\u0434\u0430 \u043c\u0435\u043d\u044c\u0448\u0435 1 \u0438 \u0435\u0441\u043b\u0438 \u043d\u0430 \u043d\u0435\u0433\u043e \u0434\u043e\u043c\u043d\u043e\u0436\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u0443 \u0448\u0430\u0433\u0430 \u2014 \u043e\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u0430\u0436\u0435 \u0434\u043b\u044f \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u043f\u043e \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044e \u043a \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043d\u043e\u0433.  <\/p>\n<pre><code class=\"javascript\"> \tconst hypIdle = Math.hypot(dx, dy); \tconst hyp = Math.hypot(dx + centerX, dy + centerY); \tleg.step.length = this.stepLength * hyp \/ hypIdle * mult; <\/code><\/pre>\n<p>  \u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 (gif 2 \u043c\u0435\u0433\u0430\u0431\u0430\u0439\u0442\u0430):  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">gif 2 \u043c\u0435\u0433\u0430\u0431\u0430\u0439\u0442\u0430<\/b><\/p>\n<div class=\"spoiler_text\">&lt;img src=&#187;<img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/pg\/qt\/ja\/pgqtjaz9vvzddchtveho6hc1x1o.gif\">&#187; alt=\u00abimage\u00bb\/&gt;  <\/div>\n<\/div>\n<p>  \u041f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0442\u0443\u0442:<br \/>  <a href=\"https:\/\/jsfiddle.net\/6cLgsbw0\/2\/\">jsfiddle.net\/6cLgsbw0\/2<\/a><br \/>  \u0414\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0432 html \u0444\u0430\u0439\u043b \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0432 \u043b\u044e\u0431\u0438\u043c\u043e\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435.<\/p>\n<p>  \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u043b \u0432\u0441\u0451 \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 Space Engineers.<br \/>  \u0421\u043f\u043e\u0439\u043b\u0435\u0440: \u0432 Programmable Block \u043c\u043e\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 C# \u043f\u043e\u0447\u0442\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.<\/div>\n<p>        <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d()}}}t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>     <br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/post\/426195\/\"> https:\/\/habr.com\/post\/426195\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435.<br \/>  \u042f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432 \u0433\u0435\u043a\u0441\u0430\u043f\u043e\u0434\u0435, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u043c \u0432 Space Engineers.<br \/>  \u0417\u0430\u0431\u0435\u0433\u0430\u044f \u0432\u043f\u0435\u0440\u0435\u0434 \u0441\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u0432\u0441\u0451, \u0447\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 Space Engineer, \u0431\u0443\u0434\u0435\u0442 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435. \u0412 \u044d\u0442\u043e\u0439 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u043e\u0431\u0440\u0430\u0442\u043d\u0443\u044e \u043a\u0438\u043d\u0435\u043c\u0430\u0442\u0438\u043a\u0443 \u0438 \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f \u043d\u0430 HTML Canvas \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0437\u0430\u043d\u0438\u043c\u0430\u043b\u0441\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u043e\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xj\/tn\/eo\/xjtneoatofct-nmcczqob0lkioc.png\">  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-290809","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/290809","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=290809"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/290809\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=290809"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=290809"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=290809"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}