{"id":360531,"date":"2024-05-21T00:55:10","date_gmt":"2024-05-21T00:55:10","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=360531"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=360531","title":{"rendered":"<span>\u041a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u043a \u0432 Figma-\u0435<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0445\u0430\u0431\u0440. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u043d\u0430\u043f\u043e\u0434\u043e\u0431\u0438\u0435 \u0442\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430\u0445 figma, adobe illustrator \u0438 \u0432\u043e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435 \u0434\u0440\u0443\u0433\u0438\u0445 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430\u0445. \u0412 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430. \u041e\u043d\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430, \u0435\u0433\u043e \u043c\u0430\u0441\u0448\u0442\u0430\u0431 \u0438 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430.<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/914\/39d\/80d\/91439d80df93a586b003d394dde9b427.jpg\" alt=\"\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\" title=\"\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\" width=\"2742\" height=\"1456\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/914\/39d\/80d\/91439d80df93a586b003d394dde9b427.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/213\/0ec\/7e2\/2130ec7e27a71de32b5d6546b2628817.png\" alt=\"\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 Figma. \u0413\u043e\u043b\u0443\u0431\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a - \u044d\u0442\u043e \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\" title=\"\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 Figma. \u0413\u043e\u043b\u0443\u0431\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a - \u044d\u0442\u043e \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\" width=\"443\" height=\"427\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/213\/0ec\/7e2\/2130ec7e27a71de32b5d6546b2628817.png\"\/><\/p>\n<div><figcaption>\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 Figma. \u0413\u043e\u043b\u0443\u0431\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a &#8212; \u044d\u0442\u043e \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438 \u043d\u0430 \u0442\u0435\u043e\u0440\u0438\u044e \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443. \u0412 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u042f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e\u0441\u043d\u043e\u0432\u044b \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0430\u043b\u0433\u0435\u0431\u0440\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043c\u0430\u0442. \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0435\u0441\u044f \u043a \u0440\u0430\u043c\u043a\u0435. \u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0434\u0430\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u044f\u0437\u044b\u043a\u0435 javascript, \u043e\u043f\u0438\u0441\u0430\u043d\u044b \u043c\u0430\u0442. \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u0434 \u0440\u0430\u043c\u043a\u043e\u0439 \u0438 \u0435\u0433\u043e \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439, \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u044b \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043a\u0430\u043a \u043f\u043e\u043b\u0438\u0433\u043e\u043d, \u0441\u043c\u0430\u0439\u043b\u0438\u043a \u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u044d\u0442\u0438\u0445 \u0444\u0438\u0433\u0443\u0440, \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<h2>\u0422\u0435\u043e\u0440\u0438\u044f<\/h2>\n<h2>\u0413\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.<\/h2>\n<p>\u0421\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0444\u043e\u0440\u043c\u0443\u043b\u043e\u0439:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/442\/2ff\/a0b\/4422ffa0bd548f5ee02abb55d5b55f12.jpg\" width=\"185\" height=\"24\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/442\/2ff\/a0b\/4422ffa0bd548f5ee02abb55d5b55f12.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u0444\u043e\u0440\u043c\u0443\u043b\u0430 \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043c, \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440. \u041f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B &#8212; \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0438\u0441\u043b\u043e, \u0440\u0430\u0432\u043d\u043e\u0435 \u0434\u043b\u0438\u043d\u0435 \u043f\u0440\u043e\u0435\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u043f\u043e\u0434 \u0443\u0433\u043b\u043e\u043c 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0434 \u201c\u0440\u0430\u0431\u043e\u0447\u0435-\u043a\u0440\u0435\u0441\u0442\u044c\u044f\u043d\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\u201d, \u0442\u043e \u0432\u044b\u0439\u0434\u0435\u0442, \u0447\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B &#8212; \u044d\u0442\u043e \u0434\u043b\u0438\u043d\u0430 \u0442\u0435\u043d\u0438, \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c\u0430\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u0410 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u043f\u043e\u0434 \u0443\u0433\u043b\u043e\u043c 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0413\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/9b2\/78c\/41f\/9b278c41f13329a58549f4e395ad82b1.jpg\" width=\"698\" height=\"458\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9b2\/78c\/41f\/9b278c41f13329a58549f4e395ad82b1.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0421\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0442\u0435\u0441\u043d\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0435\u0439 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432. \u0422\u0430\u043a \u043a\u0430\u043a \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 A \u0438 B\u00a0 &#8212; \u044d\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 B, \u0438\u043b\u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 B \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 A \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A:\u00a0<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/8fd\/b3c\/834\/8fdb3c834322bf318163af0294d533cd.jpeg\" width=\"698\" height=\"458\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8fd\/b3c\/834\/8fdb3c834322bf318163af0294d533cd.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u0438\u043c\u0435\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0440\u0430\u0432\u043d\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0435, \u0442\u043e \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0440\u0430\u0432\u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/672\/cb8\/5a9\/672cb85a977b2fc05a9bb47cdbe2dabc.jpeg\" width=\"698\" height=\"458\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/672\/cb8\/5a9\/672cb85a977b2fc05a9bb47cdbe2dabc.jpeg\" data-blurred=\"true\"\/><\/figure>\n<h2>\u041c\u0430\u0442\u0440\u0438\u0446\u044b<\/h2>\n<p>\u041c\u0430\u0442\u0440\u0438\u0446\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438, \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u043a\u0443\u0440\u0441\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u043d\u043e\u0439 \u0433\u0440\u0430\u0444\u0438\u043a\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/326\/2aa\/f9a\/3262aaf9a0a07989c524920a5417bafb.jpg\" width=\"989\" height=\"807\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/326\/2aa\/f9a\/3262aaf9a0a07989c524920a5417bafb.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0443\u044e \u0444\u0438\u0433\u0443\u0440\u0443 \u0441 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0441\u043e\u0432\u043e\u043a\u0443\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u0442\u043e\u0447\u0435\u043a, \u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 \u043d\u0435 \u0432\u044b\u0439\u0434\u0443\u0442 \u0437\u0430 \u043a\u0440\u0430\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u0438 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u044b \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u201c\u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439\u201d \u0444\u0438\u0433\u0443\u0440\u043e\u0439. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0443\u0441\u0442\u044c \u0444\u0438\u0433\u0443\u0440\u0430 &#8212; \u044d\u0442\u043e \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0430 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u0441\u043e\u0432\u043e\u043a\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0442\u043e\u0447\u0435\u043a \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0442\u043e\u0433\u0434\u0430 \u0443\u043c\u043d\u043e\u0436\u0430\u044f \u0432\u0441\u0435 \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u0432\u044b\u0448\u0435\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u043c\u0430\u0442\u0440\u0438\u0446\u044b, \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/2ca\/532\/812\/2ca532812d63c5b3ce64716c6756b02b.jpeg\" width=\"1006\" height=\"679\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2ca\/532\/812\/2ca532812d63c5b3ce64716c6756b02b.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0433\u0434\u0435 \u043f\u043e\u0434 pi \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0444\u0438\u0433\u0443\u0440\u044b.<\/p>\n<h2>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u0430\u0442\u0440\u0438\u0446 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447<\/h2>\n<p>\u0414\u043b\u044f \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043c\u0430\u0442\u0440\u0438\u0446\u0430\u043c\u0438 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0442\u0430\u043a\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443. \u0415\u0441\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/f6c\/a3d\/e16\/f6ca3de1683670b89fd0d64c6bfbabfa.jpeg\" width=\"698\" height=\"517\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f6c\/a3d\/e16\/f6ca3de1683670b89fd0d64c6bfbabfa.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0432 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430 \u0438 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435 \u0432 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0435\u0433\u043e \u0442\u043e\u0447\u043a\u0438 t. \u0417\u0430\u0434\u0430\u0447\u0430 \u0440\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u0440\u0438 \u044d\u0442\u0430\u043f\u0430. \u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u0442\u043e\u0447\u043a\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u00a0 -t . \u041d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430 \u0440\u0430\u043c\u043a\u0438. \u0418 \u0432 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u0440\u0430\u043c\u043a\u0443 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0432 \u0432\u0441\u0435 \u0435\u0433\u043e \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 t. \u0423\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/5e6\/ca1\/e7a\/5e6ca1e7ad4822e62a8d2b704a8501be.jpeg\" width=\"1279\" height=\"224\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5e6\/ca1\/e7a\/5e6ca1e7ad4822e62a8d2b704a8501be.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0413\u0434\u0435 \u0442\u043e\u0447\u043a\u0430 pi &#8212; \u044d\u0442\u043e \u0442\u043e\u0447\u043a\u0430 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c i.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0430\u043f\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438, \u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 -t<\/p>\n<\/li>\n<\/ul>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 -t<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/a7f\/6c8\/89d\/a7f6c889d130f19a46964bc175bd0a87.jpeg\" alt=\"\" title=\"\" width=\"698\" height=\"517\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a7f\/6c8\/89d\/a7f6c889d130f19a46964bc175bd0a87.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0432 2 \u0440\u0430\u0437\u0430 \u0438 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435 \u0432 3 \u0440\u0430\u0437\u0430<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/07d\/fa5\/15e\/07dfa515ea38748f7a5f675340cef48b.jpeg\" alt=\"\" title=\"\" width=\"852\" height=\"517\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/07d\/fa5\/15e\/07dfa515ea38748f7a5f675340cef48b.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 t<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/a59\/1d7\/3ae\/a591d73aeecb2c40da75e66065fe7a39.jpeg\" alt=\"\" title=\"\" width=\"1057\" height=\"517\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a59\/1d7\/3ae\/a591d73aeecb2c40da75e66065fe7a39.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041a\u0430\u043a \u0412\u044b \u0443\u0436\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u044d\u0442\u0430\u043f\u044b \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043f\u0440\u0430\u0432\u043e-\u043d\u0430\u043b\u0435\u0432\u043e. \u0418 \u0435\u0441\u043b\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u044d\u0442\u0430\u043f \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0432\u043e\u043a\u0440\u0443\u0433 \u043d\u0430\u0447\u0430\u043b\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0442\u043e \u043c\u0430\u0442\u0440\u0438\u0446\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c\u0441\u044f \u0441\u043b\u0435\u0432\u0430 \u043e\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/a89\/6a9\/a4b\/a896a9a4be26b88b8582208c3586f485.jpeg\" width=\"1524\" height=\"869\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a89\/6a9\/a4b\/a896a9a4be26b88b8582208c3586f485.jpeg\" data-blurred=\"true\"\/><\/figure>\n<h2>\u041f\u0440\u0430\u043a\u0442\u0438\u043a\u0430<\/h2>\n<p>\u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0440\u0430\u043c\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0438\u043b\u0438 \u0443\u0433\u043e\u043b\u043a\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0446\u0435\u043d\u0442\u0440\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043a\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0438\u0433\u0443\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442:<\/p>\n<ul>\n<li>\n<p>\u041f\u043e\u043b\u0438\u0433\u043e\u043d;<\/p>\n<\/li>\n<li>\n<p>\u0421\u043c\u0430\u0439\u043b\u0438\u043a;<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 javascript \u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u043f\u0443\u043d\u043a\u0442\u0430\u043c. \u0422\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043c\u0435\u0442\u043e\u0434\u044b \u0438 \u0447\u043b\u0435\u043d\u044b \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0440\u0430\u0437\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0439 \u0432 \u0438\u0445 \u0440\u0430\u0431\u043e\u0442\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e. \u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u0434, \u043f\u0440\u043e\u043b\u0438\u0441\u0442\u0430\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u044c\u044e \u0434\u043e \u043a\u043e\u043d\u0446\u0430.<\/p>\n<h2>\u041b\u0438\u043d\u0435\u0439\u043d\u0430\u044f \u0430\u043b\u0433\u0435\u0431\u0440\u0430<\/h2>\n<h2>\u041a\u043b\u0430\u0441\u0441 \u0432\u0435\u043a\u0442\u043e\u0440<\/h2>\n<details class=\"spoiler\">\n<summary>\u043a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Vector {     constructor(x, y) {         this.x = x; this.y = y     }     static getVector(p2, p1) { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e \u0434\u0432\u0443\u043c \u0442\u043e\u0447\u043a\u0430\u043c         return new Vector(p2.x - p1.x,  p2.y - p1.y)     }     static getNormal(v) { \/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u044c \u0432\u0435\u043a\u0442\u043e\u0440\u0430         return new Vector( v.y, -v.x )     }     static getRotationAngle(v) {  \/\/ \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u043b\u044c\u043d\u043e \u043e\u0441\u0438 X         const axisY = {x: 0, y: 1}         const dot = Vector.dotProduct(v, axisY)         let angle = Math.acos(v.x \/ v.length())         angle = dot > 0 ? angle : -angle         return angle     }     length() {         return Math.sqrt(this.x * this.x + this.y * this.y)     }     normalize() { \/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0440\u0430\u0432\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435         const len = Math.sqrt(this.x * this.x + this.y * this.y)         this.x = this.x \/ len          this.y = this.y \/ len     }     negative() { \/\/ \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432         this.x = -this.x         this.y = -this.y     }     multi(k) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0435\u043d\u0442         this.x = this.x * k; this.y = this.y * k     }     static dotProduct(v1, v2) { \/\/ \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435         return v1.x * v2.x + v1.y * v2.y     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u0430\u0441\u0441 Vector, \u0438\u043c\u0435\u0435\u0442 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 getRotationAngle. \u041e\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0438 x:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/e3f\/50e\/cd6\/e3f50ecd6fe19328c15fc0d1259b0472.jpeg\" width=\"698\" height=\"326\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e3f\/50e\/cd6\/e3f50ecd6fe19328c15fc0d1259b0472.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b \u0447\u0435\u0440\u0435\u0437 \u0430\u0440\u043a\u043a\u043e\u0441\u0438\u043d\u0443\u0441 \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v:  <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/c23\/967\/792\/c2396779272bf9fd5a323806631b6cf6.jpeg\" width=\"129\" height=\"41\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c23\/967\/792\/c2396779272bf9fd5a323806631b6cf6.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u0410\u0440\u043a\u043a\u043e\u0441\u0438\u043d\u0443\u0441 \u0443\u0433\u043b\u0430 \u0438\u043c\u0435\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 0 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u042d\u0442\u043e\u0442 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u043d\u0435 \u0434\u0430\u0451\u0442 \u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430, \u0442.\u043a. \u043f\u043e\u043b\u043d\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 &#8212; \u044d\u0442\u043e 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0437\u043d\u0430\u043a \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v \u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 axisY, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u043e\u0441\u0438 Y. \u0415\u0441\u043b\u0438 \u0437\u043d\u0430\u043a \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 axisY. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 1-\u043e\u0439 \u0438\u043b\u0438 \u0432\u043e 2-\u043e\u0439 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0438 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 0 \u0434\u043e 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432:  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/422\/3f8\/c32\/4223f8c328cb391119a49a5ed024dff0.jpg\" width=\"1055\" height=\"353\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/422\/3f8\/c32\/4223f8c328cb391119a49a5ed024dff0.jpg\" data-blurred=\"true\"\/><\/figure>\n<\/p>\n<p>\u0435\u0441\u043b\u0438 \u0437\u043d\u0430\u043a \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043f\u0440\u043e\u0435\u0446\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u043e\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 axisY. \u0418 \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 3-\u0435\u0439 \u0438\u043b\u0438 4-\u043e\u0439 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 0 \u0434\u043e -180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/c60\/e3c\/541\/c60e3c541401db9c618d84ba3bf70933.jpg\" width=\"1084\" height=\"326\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c60\/e3c\/541\/c60e3c541401db9c618d84ba3bf70933.jpg\" data-blurred=\"true\"\/><\/figure>\n<\/p>\n<\/li>\n<\/ul>\n<h2>\u041a\u043b\u0430\u0441\u0441 \u043c\u0430\u0442\u0440\u0438\u0446\u0430<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Matrix {     constructor(a, b, c, d, e, f) {         this.a = a; this.c = c; this.e = e         this.b = b; this.d = d; this.f = f     }     multiMatrix(m) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443         const a = this.a * m.a + this.c * m.b         const b = this.b * m.a + this.d * m.b         const c = this.a * m.c + this.c * m.d         const d = this.b * m.c + this.d * m.d         const e = this.a * m.e + this.c * m.f + this.e         const f = this.b * m.e + this.d * m.f + this.f         return new Matrix(a, b, c, d, e, f)     }     multiVector(p) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 \u0438\u043b\u0438 \u0442\u043e\u0447\u043a\u0443         return {             x: this.a * p.x + this.c * p.y + this.e,             y: this.b * p.x + this.d * p.y + this.f         }     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 \u043c\u0430\u0442\u0440\u0438\u0446\u044b         return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f)     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 clone \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u043e\u043f\u0438\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 Matrix. \u041d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u043e\u0434\u0430 clone \u043b\u044e\u0431\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u043e\u043f\u0438\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<h2>\u0424\u0438\u0433\u0443\u0440\u044b<\/h2>\n<p>\u0423 \u0432\u0441\u0435\u0445 \u0444\u0438\u0433\u0443\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044e \u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0438\u043b\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0440\u0430\u043c\u043a\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443, \u043d\u0435 \u0437\u043d\u0430\u044f \u043e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439 \u0444\u0438\u0433\u0443\u0440. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043e\u0431\u0449\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043a\u043b\u0430\u0441\u0441\u043e\u043c AbstractFigure. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043e\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 AbstractFigure, \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u043c \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0443 \u043a\u043b\u0430\u0441\u0441\u043e\u0432:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/5e0\/280\/cea\/5e0280cea395f27501cc6ee3e6436644.jpeg\" width=\"1600\" height=\"775\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5e0\/280\/cea\/5e0280cea395f27501cc6ee3e6436644.jpeg\" data-blurred=\"true\"\/><\/figure>\n<h2>\u041a\u043b\u0430\u0441\u0441 AbstractFigure<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class AbstractFigure {     clockwise = false     constructor() { }      clone() {          return AbstractFigure()     }     setClockwise(val) {         this.clockwise = val     }     clockwise() {         return this.clockwise     }     prop() {         return { x: 0, y: 0, width: 0, height: 0 }      }     multiMatrix(mat) { }     draw() { } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e clockwise \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u042d\u0442\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u0433\u0443\u0440\u0443 \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438.<\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 prop \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0444\u0438\u0433\u0443\u0440\u044b &#8212; \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c x, y, \u0434\u043b\u0438\u043d\u0443 \u0438 \u0448\u0438\u0440\u0438\u043d\u0443. \u041f\u043e\u0437\u0438\u0446\u0438\u044f x \u0438 y \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043b\u0435\u0432\u043e\u0433\u043e \u043d\u0438\u0436\u043d\u0435\u0433\u043e \u0443\u0433\u043b\u0430.<\/p>\n<h2>\u041a\u043b\u0430\u0441\u0441 Polygon<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Polygon extends AbstractFigure {     constructor(points = []) {         super()         this.points = Object.assign(points)     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 Polygon         const polygon = new Polygon(this.points)         return polygon     }     prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Polygon: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430         let minX = Number.MAX_VALUE; let maxX = Number.MIN_VALUE         let minY = Number.MAX_VALUE; let maxY = Number.MIN_VALUE         this.points.forEach(p => {             if (p.x > maxX) { maxX = p.x }             if (p.x &lt; minX) { minX = p.x }             if (p.y > maxY) { maxY = p.y }             if (p.y &lt; minY) { minY = p.y }         })         return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }      }     multiMatrix(mat) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443         this.points = this.points.map(( p ) => mat.multiVector( p ))     }     draw() {         ctx.save()         ctx.beginPath()         this.points.forEach((p, ind) => {             if (ind === 0) {                 ctx.moveTo(p.x, p.y)             } else {                 ctx.lineTo(p.x, p.y)             }         })         ctx.stroke()         ctx.restore()     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u0430\u0441\u0441 \u0445\u0440\u0430\u043d\u0438\u0442 \u0442\u043e\u0447\u043a\u0438 points, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434 draw. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434 multiMatrix. \u0412 \u043c\u0435\u0442\u043e\u0434\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 mat \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e points.<\/p>\n<h2>\u041a\u043b\u0430\u0441\u0441 Ellipse<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Ellipse extends AbstractFigure {     constructor(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {         super()         this.x = x; this.y = y; \/\/ center         this.radiusX = radiusX         this.radiusY = radiusY         this.rotation = rotation \/\/ \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u044d\u043b\u0438\u043f\u0441\u0430         this.startAngle = startAngle         this.endAngle = endAngle         super.setClockwise(!counterclockwise)     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 Ellipse         const ellipse = new Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.endAngle, !super.clockwise())         return ellipse     }     prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Ellipse: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430          return {              x: this.x - this.radiusX,              y: this.y - this.radiusY,              width: this.radiusX * 2,              height: this.radiusY * 2          }      }     multiMatrix(mat) {         const sin = Math.sin( this.rotation )         const cos = Math.cos( this.rotation )         const newCenter = mat.multiVector( { x: this.x, y: this.y } )         \/\/ \u043e\u0441\u044c x         let vectorX = (new Matrix(cos, sin, -sin, cos, 0, 0)).multiVector({ x: this.radiusX, y: 0})          let extremeX = { x: this.x + vectorX.x, y: this.y + vectorX.y }         let newExtremeX = mat.multiVector(extremeX)         const newRadiusX = Vector.getVector(newExtremeX, newCenter).length()         \/\/ \u043e\u0441\u044c y         let vectorY = (new Matrix(cos, sin, -sin, cos, 0, 0)).multiVector({ x: 0, y: this.radiusY})          let extremeY = { x: this.x + vectorY.x, y: this.y + vectorY.y }         let newExtremeY = mat.multiVector(extremeY)         const newRadiusY = Vector.getVector(newExtremeY, newCenter).length()         \/\/ \u043f\u043e\u0434\u0441\u0447\u0435\u0442 rotation         const newRotation = Vector.getRotationAngle(Vector.getVector(newExtremeX, newCenter))         \/\/ \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442         this.x = newCenter.x         this.y = newCenter.y         this.radiusX = newRadiusX          this.radiusY = newRadiusY         this.rotation = newRotation     }     draw() {         ctx.save()         ctx.beginPath()         ctx.ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.endAngle, !super.clockwise())         ctx.stroke()         ctx.restore()     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 Ellipse \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f ellipse \u0432 canvas:\u00a0<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/b3d\/409\/30e\/b3d40930e80af2fe2787bc51f76590f4.jpg\" width=\"719\" height=\"720\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b3d\/409\/30e\/b3d40930e80af2fe2787bc51f76590f4.jpg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>radiusX, radiusY &#8212; \u0440\u0430\u0434\u0438\u0443\u0441\u044b \u043f\u043e \u043e\u0441\u044f\u043c X, Y;<\/p>\n<\/li>\n<li>\n<p>rotation &#8212; \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u044d\u043b\u043b\u0438\u043f\u0441\u0430, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 \u0440\u0430\u0434\u0438\u0430\u043d\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>startAngle &#8212; \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043f\u043e \u043e\u0441\u0438 X, \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u044e\u0449\u0430\u044f\u00a0 \u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>endAngle &#8212; \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043f\u043e \u043e\u0441\u0438 X, \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u044e\u0449\u0430\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>counterclockwise &#8212; \u0431\u0443\u043b\u0435\u0432\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u0435\u0441\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 true \u0440\u0438\u0441\u0443\u0435\u043c \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438, \u0435\u0441\u043b\u0438 false \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043b\u0430\u0441\u0441 Ellipse \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 multiMatrix \u043f\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443:<\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043f\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 Ellipse \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0445 \u0440\u0430\u0441\u0447\u0435\u0442\u043e\u0432:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/507\/373\/36a\/50737336ab35c06dce4971cdfb407a91.jpg\" width=\"579\" height=\"308\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/507\/373\/36a\/50737336ab35c06dce4971cdfb407a91.jpg\" data-blurred=\"true\"\/><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/a71\/907\/77a\/a7190777a32afa6129ebdd975f7be4fe.jpeg\" width=\"698\" height=\"326\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a71\/907\/77a\/a7190777a32afa6129ebdd975f7be4fe.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443 mat:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/420\/098\/c5f\/420098c5ffdb711a2ba3b63906984352.jpg\" width=\"331\" height=\"229\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/420\/098\/c5f\/420098c5ffdb711a2ba3b63906984352.jpg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u0437 \u0440\u0430\u043d\u043d\u0435\u0435 \u043f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u043a\u043b\u0430\u0441\u0441\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/8a7\/f46\/da8\/8a7f46da81477af0f0f2965bcc1a5b6b.jpg\" width=\"434\" height=\"236\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8a7\/f46\/da8\/8a7f46da81477af0f0f2965bcc1a5b6b.jpg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0441 \u043f\u043e\u043c\u0435\u0442\u043a\u043e\u0439 \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 new. <\/p>\n<\/li>\n<\/ul>\n<h2>\u041a\u043b\u0430\u0441\u0441 PaintedImage<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class PaintedImage extends AbstractFigure {     constructor(img, x, y, width, height) {         super()         this.mat = new Matrix(1, 0, 0, 1, 0, 0)         this.img = img         this.x = x; this.y = y;         this.width = width; this.height = height     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 PaintedImage         const paintedImage = new PaintedImage(this.img, this.x, this.y, this.width, this.height)         paintedImage.mat = this.mat.clone()         return paintedImage     }     prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 PaintedImage: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430          return {              x: this.x,              y: this.y,              width: this.width,              height: this.height          }      }     multiMatrix(mat) {         this.mat = mat.multiMatrix( this.mat ).clone()     }     draw() {         ctx.beginPath()         ctx.save()         const mat = this.mat         ctx.transform(mat.a, mat.b, mat.c, mat.d, mat.e, mat.f)         ctx.drawImage(this.img, this.x, this.y, this.width, this.height)         ctx.restore()     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412 \u043a\u043b\u0430\u0441\u0441\u0435 PaintedImage \u043c\u0435\u0442\u043e\u0434 multiMatrix \u043f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u043c\u0430\u0442\u0440\u0438\u0446\u044b, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u0439 \u0432 this.mat, \u043d\u0430 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 mat. \u0418 \u043f\u0440\u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u043c draw, \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u0430\u044f \u043c\u0430\u0442\u0440\u0438\u0446\u0430 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 canvas.  <\/p>\n<h2>\u041a\u043b\u0430\u0441\u0441 Container<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Container extends AbstractFigure {     constructor(figures = []) {         super()         this.figures = figures     }     clone() {         const figureClone = this.figures.map((figure) => figure.clone())         return new Container(figureClone)     }     setClockwise(val) {         this.figures.forEach((figure) => {             figure.setClockwise( super.clockwise() == val ? figure.clockwise : !figure.clockwise )          })         super.setClockwise(val)     }     prop() {         let minX = Number.MAX_VALUE; let minY = Number.MAX_VALUE;         let maxWidth = Number.MIN_VALUE; let maxHeight = Number.MIN_VALUE         this.figures.forEach(figure => {             const prop = figure.prop()             if (prop.x &lt; minX) { minX = prop.x }             if (prop.y &lt; minY) { minY = prop.y }             if (prop.width > maxWidth) { maxWidth = prop.width }             if (prop.height > maxHeight) { maxHeight = prop.height }         })         return { x: minX, y: minY, width: maxWidth, height: maxHeight }      }     multiMatrix(mat) {         this.figures.forEach(( figure ) => {             figure.multiMatrix( mat )         })     }     draw() {         this.figures.forEach(( figure ) => {             figure.draw()         })     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u0430\u0441\u0441 Container &#8212; \u044d\u0442\u043e \u043a\u043b\u0430\u0441\u0441, \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0449\u0438\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u0440\u0430\u043d\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u043c\u0430\u0441\u0441\u0438\u0432 \u0444\u0438\u0433\u0443\u0440. \u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043a\u043b\u0430\u0441\u0441 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430. \u0421\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0434\u043b\u044f \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430 &#8212; \u044d\u0442\u043e \u044d\u043b\u043b\u0438\u043f\u0441\u044b \u0438 \u043f\u043e\u043b\u0443\u044d\u043b\u043b\u0438\u043f\u0441\u044b.<\/p>\n<h2>\u041a\u043b\u0430\u0441\u0441 Frame &#8212; \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/h2>\n<details class=\"spoiler\">\n<summary>\u0418\u043d\u0446\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     constructor(width = 0, height = 0) {         this.width = width;         this.height = height;         this.points = [             {x: 0, y: 0},              {x: width, y: 0},              {x: width, y: height},              {x: 0, y: height}         ]     }      clockwise() {         let indexByMinX = 0         for (let i = 0; i &lt; this.points.length; ++i) { \/\/ \u0423\u0437\u043d\u0430\u0435\u043c \u0438\u043d\u0434\u0435\u043a\u0441 \u0442\u043e\u0447\u043a\u0438 c \u043d\u0430\u0438\u043c\u0435\u043d\u044c\u0448\u0438\u043c x             if(this.points[indexByMinX].x > this.points[i].x) {                 indexByMinX = i             }         }         const beginIndex = (indexByMinX + 3) % this.points.length         const middleIndex = indexByMinX         const lastIndex = (indexByMinX + 1) % this.points.length         const v1 = Vector.getVector( this.points[middleIndex], this.points[beginIndex] )         const v2 = Vector.getVector( this.points[lastIndex], this.points[middleIndex] )         const multiVec = v1.x * v2.y - v2.x * v1.y \/\/ \u0412\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435         return multiVec &lt; 0     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 Frame \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0440\u0430\u043c\u043a\u0438. \u042d\u0442\u043e \u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u044b width, height, \u0438 \u0435\u0433\u043e \u0442\u043e\u0447\u043a\u0438 points, \u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0449\u0438\u0435 \u043f\u0440\u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a. \u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0442\u043e\u0447\u0435\u043a points \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 \u043e\u0431\u0445\u043e\u0434 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430. \u041e\u0431\u0445\u043e\u0434 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e \u0438\u043b\u0438 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438. \u041c\u0435\u0442\u043e\u0434 clockwise \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u044d\u0442\u043e.<\/p>\n<details class=\"spoiler\">\n<summary>\u0414\u043e\u0447\u0435\u0440\u043d\u044f\u044f \u0444\u0438\u0433\u0443\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     figure = new Polygon() \/\/ \u0444\u0438\u0433\u0443\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0434\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f     ...      adjustToFigure() { \/\/ \u043e\u0431\u0432\u0430\u043b\u0430\u043a\u0438\u0432\u0430\u0435\u0442 \u0444\u0438\u0433\u0443\u0440\u0443 \u0440\u0430\u043c\u043a\u043e\u0439         const prop = this.figure.prop()         this.width = prop.width         this.height = prop.height         this.points = [             {x: prop.x, y: prop.y},              {x: prop.x + this.width, y: prop.y},              {x: prop.x + this.width, y: prop.y + this.height},              {x: prop.x, y: prop.y + this.height}         ]     }      setFigure(figure) {         this.figure = figure.clone()         this.adjustToFigure()     }      transition(v) {         ...         this.figure.multiMatrix( mat )     }      rotation(downMouse, upMouse) {         ...         this.figure.multiMatrix( mat )     }      multiBySide(index, v, shift = false) {         ...         this.figure.multiMatrix( mat )         this.figure.setClockwise( this.clockwise() )     }      draw( mouse = {x: -1, y: -1} ) {         this.figure.draw()       ...     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0424\u0438\u0433\u0443\u0440\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0434\u043e\u043b\u0436\u043d\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u043c\u043a\u0430, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c setFigure. \u041f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u0443\u044e \u0444\u0438\u0433\u0443\u0440\u0443, \u043a\u043b\u0430\u0441\u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 Frame \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0438, \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 adjustToFigure, \u043f\u043e\u0434\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 prop \u0444\u0438\u0433\u0443\u0440\u044b.<\/p>\n<p>\u0412\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0435 \u043c\u0430\u0442\u0440\u0438\u0446, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u0443 multiMatrix \u043e\u0431\u044a\u0435\u043a\u0442\u0430 figure.<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u043c\u043a\u0438<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">\/\/ \u0420\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f class Frame {     \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 painted \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 draw     paintedCircles = new Array(4).fill().map(() => (new Path2D())) \/\/ \u043a\u0440\u0443\u0433\u0438  \u0434\u043b\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u043c\u043a\u0438     paintedSides = new Array(4).fill().map(() => new Path2D())     paintedRect = new Path2D() \/\/ \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438     cornerWidth = 7.5 \/\/ \u0448\u0438\u0440\u0438\u043d\u0430 \u0443\u0433\u043e\u043b\u043a\u0430     paintedCorners = new Array(4)      ...     containedInRect(mouse) {         return ctx.isPointInPath( this.paintedRect, mouse.x, mouse.y )     }      containedInCorner(mouse) {         for (let i = 0; i &lt; this.paintedCorners.length; ++i) {             if (ctx.isPointInPath( this.paintedCorners[i], mouse.x, mouse.y )) {                 return i             }         }         return -1     }      containedInCircle(mouse) {         for (let i = 0; i &lt; this.paintedCircles.length; ++i) {             if (ctx.isPointInPath( this.paintedCircles[i], mouse.x, mouse.y )) {                 return true             }         }         return false     }      containedInSide(mouse) {         for (let i = 0; i &lt; this.paintedSides.length; ++i) {             if (ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y )) {                 return i             }         }         return -1     }     ...      draw( mouse = {x: -1, y: -1} ) {         this.figure.draw()          const points = this.clockwise() ? this.points.reverse() : this.points          let selectedMouse = false \/\/ \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u043f\u0435\u0440\u0435\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430          \/\/ \u0443\u0433\u043e\u043b\u043a\u0438         ctx.save()         const center = {             x: ( points[2].x + points[0].x ) \/ 2,              y: ( points[2].y + points[0].y ) \/ 2         }         const width = Vector.getVector( points[1], points[0] ).length()         const height = Vector.getVector( points[2], points[1] ).length()         const rotationAngle = Vector.getRotationAngle(  Vector.getVector( points[1], points[0] ) )         const sin = Math.sin( rotationAngle )         const cos = Math.cos( rotationAngle )         const cornerPos = [              { x: center.x - width \/ 2, y: center.y - height \/ 2 },             { x: center.x + width \/ 2, y: center.y - height \/ 2 },             { x: center.x + width \/ 2, y: center.y + height \/ 2 },             { x: center.x - width \/ 2, y: center.y + height \/ 2 },         ]         cornerPos.forEach((p, ind) => {             const p1 = { x: p.x - this.cornerWidth \/ 2, y: p.y - this.cornerWidth \/ 2 }             const p2 = { x: p1.x + this.cornerWidth, y: p1.y }             const p3 = { x: p2.x, y: p2.y + this.cornerWidth }             const p4 = { x: p3.x - this.cornerWidth, y: p3.y }             const cornerPoints = [p1, p2, p3, p4].map((p) => {                 let mat = new Matrix(1, 0, 0, 1, center.x, center.y)                 mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))                 mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -center.x, -center.y))                 return mat.multiVector( p )             })             this.paintedCorners[ind] = new Path2D()             cornerPoints.forEach((p, index) => {                 if (index == 0) {                     this.paintedCorners[ind].moveTo(p.x, p.y)                 } else {                     this.paintedCorners[ind].lineTo(p.x, p.y)                 }             })             ctx.fillStyle = ctx.isPointInPath( this.paintedCorners[ind], mouse.x, mouse.y ) &amp;&amp; !selectedMouse ? \"#6200EA\" : \"#757575\";             if ( !selectedMouse ) {                 selectedMouse = ctx.isPointInPath( this.paintedCorners[ind], mouse.x, mouse.y )             }             ctx.fill( this.paintedCorners[ind] )         })         ctx.restore()         \/\/ \u0441\u0442\u043e\u0440\u043e\u043d\u044b         ctx.save()         for (let i = 0; i &lt; this.paintedSides.length; ++i) {             const p1 = points[i]             const p2 = points[(i + 1) % 4]             this.paintedSides[i] = new Path2D()             this.paintedSides[i].moveTo(p1.x, p1.y)             this.paintedSides[i].lineTo(p2.x, p2.y)             ctx.strokeStyle = ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y ) &amp;&amp; !selectedMouse ? \"#6200EA\" : \"#75757599\";             if ( !selectedMouse ) {                 selectedMouse = ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y )             }             ctx.stroke( this.paintedSides[i] )         }         ctx.restore()         \/\/ \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a         this.paintedRect.painted = new Path2D()         points.forEach((p, ind) => {             if (ind == 0) {                 this.paintedRect.moveTo(p.x, p.y)             } else {                 this.paintedRect.lineTo(p.x, p.y)             }         })         \/\/ \u043a\u0440\u0443\u0433\u0438         ctx.save()         const d = 5         const angle = Math.PI \/ 4 \/\/ 45 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432         const mat = new Matrix(angle, angle, -angle, angle, 0, 0)         for (let i = 0; i &lt; 4; ++i) {             this.paintedCircles[i] = new Path2D()             const p1 = points[i]             const p2 = points[(i + 1) % 4]             const sideV = Vector.getVector(p2, p1)             sideV.normalize()             sideV.multi(20)             const circleV = mat.multiVector(sideV)             const circleP = {x: p1.x + circleV.x, y: p1.y + circleV.y}             this.paintedCircles[i].arc(circleP.x, circleP.y, d, 0, 2 * Math.PI)             ctx.fillStyle = ctx.isPointInPath( this.paintedCircles[i], mouse.x, mouse.y ) ? \"#6200EA\" : \"#757575\";             ctx.fill( this.paintedCircles[i] )         }         ctx.restore()     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0420\u0430\u043c\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432: \u043a\u0440\u0443\u0433\u0438, \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0443\u0433\u043e\u043b\u043a\u0438 \u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a. \u041a\u0430\u0436\u0434\u0430\u044f \u0438\u0437 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0445 \u0444\u0438\u0433\u0443\u0440 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u043a\u0443\u0440\u0441\u043e\u0440\u043e\u043c \u043c\u044b\u0448\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0440\u0430\u043c\u043a\u0443:<\/p>\n<ul>\n<li>\n<p>\u041a\u0440\u0443\u0433\u0438 \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u044e\u0442 \u0440\u0430\u043c\u043a\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u044e\u0442 \u0440\u0430\u043c\u043a\u0443 \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0438\u043b\u0438 \u0448\u0438\u0440\u0438\u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0438\u043b\u0438 \u0443\u0433\u043b\u043e\u0432 \u0440\u0430\u043c\u043a\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0443\u0433\u043e\u043b\u043a\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u044e\u0442 \u0440\u0430\u043c\u043a\u0443 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0438 \u0448\u0438\u0440\u0438\u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u0440\u0430\u043c\u043a\u0438;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0435\u0442 \u0432\u0441\u0435 \u0442\u043e\u0447\u043a\u0438 \u0440\u0430\u043c\u043a\u0438.\u00a0<\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u0435\u0442\u043e\u0434\u044b, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u0441 contained\u00a0 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0440\u0430\u043c\u043a\u0438. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0440\u0435\u0434\u0438 \u043f\u0440\u043e\u0440\u0438\u0441\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 painted. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0447\u0438\u0441\u043b\u043e \u0431\u043e\u043b\u044c\u0448\u0435\u0435 -1 \u0438\u043b\u0438 \u0431\u0443\u043b\u0435\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 true, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, containedInSide \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0447\u0438\u0441\u043b\u043e, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0440\u0430\u043c\u043a\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0430, \u0442\u043e\u0433\u0434\u0430 \u043a\u0430\u043a \u043c\u0435\u0442\u043e\u0434 containedInRect \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0431\u0443\u043b\u0435\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435.<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u0440\u043e\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 draw, \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0441 painted. \u0412\u0441\u0435 \u043f\u0440\u043e\u0440\u0438\u0441\u043e\u0432\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043c\u0435\u0442\u043e\u0434\u0435, \u043f\u043e \u043c\u043d\u0435\u043d\u0438\u044e \u0430\u0432\u0442\u043e\u0440\u0430, \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u043e\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u0443\u0433\u043e\u043b\u043a\u043e\u0432. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043f\u0440\u043e\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u0443\u0433\u043e\u043b\u043a\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u041e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0440\u0430\u043c\u043a\u0438 \u0447\u0435\u0442\u044b\u0440\u0435 \u0440\u0430\u0432\u043d\u043e\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u043a\u0438 \u043f\u043e x \u043d\u0430 \u0448\u0438\u0440\u0438\u043d\u0443 \u0440\u0430\u043c\u043a\u0438 \u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u043d\u0430 \u0434\u0432\u0430 \u0438 \u043f\u043e y \u043d\u0430 \u0434\u043b\u0438\u043d\u0443 \u0440\u0430\u043c\u043a\u0438 \u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u043d\u0430 \u0434\u0432\u0430:<\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/beb\/1bc\/047\/beb1bc047da989ff7e248e1f0e013a52.jpeg\" width=\"489\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/beb\/1bc\/047\/beb1bc047da989ff7e248e1f0e013a52.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u0421\u0442\u0440\u043e\u0438\u043c \u0443\u0433\u043e\u043b\u043a\u0438 \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0442\u043e\u0447\u043a\u0430\u043c:  <\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/987\/118\/c36\/987118c365180db71b6c1ed8078c56a3.jpeg\" width=\"489\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/987\/118\/c36\/987118c365180db71b6c1ed8078c56a3.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u0411\u0435\u0440\u0435\u043c \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u0440\u0430\u043c\u043a\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435. \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0438\u0437 \u044d\u0442\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0438 X:  <\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/77a\/4b4\/766\/77a4b47662cbc7980e95a47f4ef955ec.jpeg\" width=\"489\" height=\"325\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/77a\/4b4\/766\/77a4b47662cbc7980e95a47f4ef955ec.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u0434\u043b\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0443\u0433\u043e\u043b\u043a\u043e\u0432 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0440\u0430\u043c\u043a\u0438 \u0438 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u0443\u044e \u043c\u0430\u0442\u0440\u0438\u0446\u0443 \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u043d\u0430 \u0442\u043e\u0447\u043a\u0438 \u0443\u0433\u043e\u043b\u043a\u043e\u0432:<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/00f\/5f6\/a9a\/00f5f6a9a6e9db866ab0db5b2de9d78a.jpeg\" width=\"832\" height=\"72\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/00f\/5f6\/a9a\/00f5f6a9a6e9db866ab0db5b2de9d78a.jpeg\" data-blurred=\"true\"\/><\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/f75\/403\/c39\/f75403c39a12a0e0654f49f137f29b25.jpeg\" width=\"489\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f75\/403\/c39\/f75403c39a12a0e0654f49f137f29b25.jpeg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p>\u0440\u0438\u0441\u0443\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<\/li>\n<\/ul>\n<details class=\"spoiler\">\n<summary>transition<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     ...     transition(v) {         const mat = new Matrix(1, 0, 0, 1, v.x, v.y)         this.points = this.points.map(( p ) => mat.multiVector( p ))         this.figure.multiMatrix( mat )     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0440\u0430\u043c\u043a\u0438 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c transition. \u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 v \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 \u043a\u0430\u043a\u043e\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0440\u0430\u043c\u043a\u0443. \u0412 \u0442\u0435\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043c\u0430\u0442\u0440\u0438\u0446\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0442\u043e\u0447\u043a\u0438 \u0440\u0430\u043c\u043a\u0438 \u0438 \u0434\u043e\u0447\u0435\u0440\u043d\u044e\u044e \u0444\u0438\u0433\u0443\u0440\u0443. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f.<\/p>\n<details class=\"spoiler\">\n<summary>rotation<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     ...     rotation(downMouse, upMouse) {         const center = {             x: ( this.points[2].x + this.points[0].x ) \/ 2,              y: (this.points[2].y + this.points[0].y) \/ 2         }         const axisX = Vector.getVector( downMouse, center )         axisX.normalize()         const axisY = Vector.getNormal( axisX )         const rotationV = Vector.getVector(upMouse, center)         const proj = Vector.dotProduct(axisX, rotationV)         let angle = Math.acos( proj \/ rotationV.length())         angle = Vector.dotProduct(axisY, rotationV) > 0 ? -angle : angle          const sin = Math.sin(angle)         const cos = Math.cos(angle)         let mat = new Matrix(1, 0, 0, 1, center.x, center.y)         mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))         mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -center.x, -center.y))         this.points = this.points.map(( p ) => mat.multiVector( p ))         this.figure.multiMatrix( mat )     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 rotation \u043f\u043e\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0440\u0430\u043c\u043a\u0443 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430. \u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u043c\u043a\u0438 \u0432 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0434\u0432\u0435 \u0442\u043e\u0447\u043a\u0438. \u041f\u0435\u0440\u0432\u0430\u044f \u0442\u043e\u0447\u043a\u0430 downMouse \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u041b\u041a\u041c, \u0432\u0442\u043e\u0440\u0430\u044f upMouse \u043f\u043e\u0441\u043b\u0435 \u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u041b\u041a\u041c \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043a\u0443\u0440\u0441\u043e\u0440\u0430 \u043c\u044b\u0448\u0438. \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 rotation: <\/p>\n<ul>\n<li>\n<p>\u041e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0440\u0430\u043c\u043a\u0438 \u043a \u0442\u043e\u0447\u043a\u0435 downMouse \u0432\u0435\u043a\u0442\u043e\u0440 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c vectorX. \u041d\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c vectorX, \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0432\u0435\u043a\u0442\u043e\u0440 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c vectorY. \u0414\u0430\u043d\u043d\u044b\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442:  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/336\/5c7\/f46\/3365c7f46c242b3a5390368289f8c420.jpeg\" width=\"673\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/336\/5c7\/f46\/3365c7f46c242b3a5390368289f8c420.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0440\u0430\u043c\u043a\u0438 \u043a \u0442\u043e\u0447\u043a\u0435 upMouse \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432\u0435\u043a\u0442\u043e\u0440 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c rotationV. \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b angle \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u044c\u044e vectorX \u0438 rotationV \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 \u0438\u0437 \u043a\u043b\u0430\u0441\u0441\u0430 Vector \u043c\u0435\u0442\u043e\u0434\u0430 getRotationAngle: <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/28c\/7ce\/556\/28c7ce556b41c90519ff514b8f2053c7.jpeg\" width=\"675\" height=\"293\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/28c\/7ce\/556\/28c7ce556b41c90519ff514b8f2053c7.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u0434\u043b\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u043c\u043a\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/e3f\/d7b\/be6\/e3fd7bbe6da428d0b83ee468d1c25c60.jpeg\" width=\"671\" height=\"72\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e3f\/d7b\/be6\/e3fd7bbe6da428d0b83ee468d1c25c60.jpeg\" data-blurred=\"true\"\/><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/799\/2e8\/5c5\/7992e85c5e189219a4439c6fbe736411.jpeg\" width=\"1600\" height=\"1415\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/799\/2e8\/5c5\/7992e85c5e189219a4439c6fbe736411.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u0423\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0443\u044e\u0441\u044f \u043c\u0430\u0442\u0440\u0438\u0446\u0443.<\/p>\n<\/li>\n<\/ul>\n<details class=\"spoiler\">\n<summary>multiBySide<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     ...     multiBySide(index, v, shift = false) {         const center = {                 x: ( this.points[2].x + this.points[0].x ) \/ 2,                  y: (this.points[2].y + this.points[0].y) \/ 2         }           const prevSideInterval = 3         const referencePoint = shift ? {x: center.x, y: center.y} : this.points[ (index + prevSideInterval) % this.points.length ]          const sideVector = Vector.getVector(              this.points[index],              this.points[ (index + 1) % this.points.length ]          )         const adjacentVector = Vector.getVector(             this.points[ (index + prevSideInterval) % this.points.length ],             this.points[index]         )         const sideNormal = Vector.getNormal( sideVector )         sideNormal.normalize()         sideNormal.negative()         let dif = shift ? 2 * Vector.dotProduct(v, sideNormal) : Vector.dotProduct(v, sideNormal)         dif = this.clockwise() ? -dif : dif         const angle = Vector.getRotationAngle(adjacentVector)         const width = adjacentVector.length()         let multiX = 1 + dif \/ width         \/\/ \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u043c\u043a\u043a\u0430 \u043f\u0440\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u0435, \u0441\u0442\u0440\u0435\u043c\u044f\u0449\u0438\u043c\u0441\u044f \u043a \u043d\u0443\u043b\u044e, \u043d\u0435 \u043b\u043e\u043c\u0430\u043b\u0430\u0441\u044c \u0438 \u043d\u0435 \u0438\u0441\u0447\u0435\u0437\u0430\u043b\u0430         if (multiX > 0) {             multiX = multiX &lt; 0.001 ?  0.001 : multiX         } else {             multiX = multiX > -0.001 ?  -0.001 : multiX         }         const sin = Math.sin(angle)         const cos = Math.cos(angle)         let mat = new Matrix(1, 0, 0, 1, referencePoint.x, referencePoint.y)         mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))         mat = mat.multiMatrix(new Matrix(multiX, 0, 0, 1, 0, 0))         mat = mat.multiMatrix(new Matrix(cos, -sin, sin, cos, 0, 0))         mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -referencePoint.x, -referencePoint.y))         const newPoints = this.points.map(( p ) => mat.multiVector( p ))         this.points = newPoints         this.figure.multiMatrix( mat )         this.figure.setClockwise( this.clockwise() )     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u0440\u0430\u043c\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c multiBySide. \u0412 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f index &#8212; \u044d\u0442\u043e \u0438\u043d\u0434\u0435\u043a\u0441, \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, v &#8212; \u0432\u0435\u043a\u0442\u043e\u0440, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u043f\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u043b\u0438, shift &#8212; \u044d\u0442\u043e \u0431\u0443\u043b\u0435\u0432\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f\u00a0 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0435\u0433\u043e \u0440\u0430\u043c\u043a\u0430 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 shift \u0440\u0430\u0432\u0435\u043d true, \u0442\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0440\u0430\u043c\u043a\u0438, \u0435\u0441\u043b\u0438 false, \u0442\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u0433\u043b\u0430 \u0440\u0430\u043c\u043a\u0438. \u041c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u044d\u0442\u0430\u043f\u044b \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435:  <\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043e\u043f\u043e\u0440\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443 referencePoint. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f shift \u0440\u0430\u0432\u043d\u0430 true, \u0442\u043e \u043e\u043f\u043e\u0440\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0440\u0430\u0432\u043d\u0430 \u0446\u0435\u043d\u0442\u0440\u0443 \u0440\u0430\u043c\u043a\u0438, \u0435\u0441\u043b\u0438 false, \u0442\u043e \u0442\u043e\u0447\u043a\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0437 \u0443\u0433\u043b\u0430 \u0440\u0430\u043c\u043a\u0438. \u041d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0440\u0430\u043c\u043a\u0438, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u0418\u043d\u0434\u0435\u043a\u0441 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432 \u0443\u0433\u043b\u0430 \u0440\u0430\u043c\u043a\u0438. \u0423\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u043d\u0430 \u0442\u0440\u0438 \u0434\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441 \u043e\u043f\u043e\u0440\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0438:    <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/bf6\/405\/df8\/bf6405df8d13f154d011aa6c8edbff2b.jpeg\" width=\"641\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bf6\/405\/df8\/bf6405df8d13f154d011aa6c8edbff2b.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/p>\n<p>\u0412\u0437\u044f\u0442\u0438\u0435 \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043e\u043f\u043e\u0440\u043d\u043e\u0439, \u0434\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u043f\u043e \u0443\u0433\u043e\u043b\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043d\u043e\u0440\u043c\u0430\u043b\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u044b &#8212; sideNormal. \u041f\u0440\u043e\u0435\u0446\u0438\u0440\u0443\u0435\u043c \u0432\u0435\u043a\u0442\u043e\u0440 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f v \u043d\u0430 \u044d\u0442\u0443 \u043d\u043e\u0440\u043c\u0430\u043b\u044c, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e dif. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0438\u0441\u043b\u043e \u043d\u0430 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0430\u0441\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u0430 \u0440\u0430\u043c\u043a\u0438:   <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/6c9\/949\/fa3\/6c9949fa37446b064b6d464654eadf1d.jpeg\" width=\"489\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6c9\/949\/fa3\/6c9949fa37446b064b6d464654eadf1d.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u0411\u0435\u0440\u0435\u043c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0443\u044e \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0435\u0441\u043b\u0438 \u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0442\u043e\u0447\u043a\u0438 points \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435, \u0438 \u0434\u0435\u043b\u0430\u0435\u043c \u0438\u0437 \u044d\u0442\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0432\u0435\u043a\u0442\u043e\u0440 adjacentVector. \u041d\u0430\u0445\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043d \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0438 X: <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/d1c\/e42\/69e\/d1ce4269e0dfb41b730d3b3741631f64.jpeg\" width=\"629\" height=\"298\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d1c\/e42\/69e\/d1ce4269e0dfb41b730d3b3741631f64.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e multiX. \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f multiX &#8212; \u0447\u0438\u0441\u043b\u043e, \u0432\u043e \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u043d\u043e\u0432\u0430\u044f \u0440\u0430\u043c\u043a\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u0438\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439:\u00a0<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/1ea\/f1c\/65e\/1eaf1c65e6779990cc2917648739f79c.jpeg\" width=\"272\" height=\"62\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1ea\/f1c\/65e\/1eaf1c65e6779990cc2917648739f79c.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u043c \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u0434\u043b\u044f \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438:  <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/5e7\/9c2\/23c\/5e79c223c15599e8d3e9a97acf0bdb4b.jpeg\" width=\"1290\" height=\"72\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5e7\/9c2\/23c\/5e79c223c15599e8d3e9a97acf0bdb4b.jpeg\" data-blurred=\"true\"\/><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/7d1\/03b\/6c5\/7d103b6c5fb0c6a5e50e12b1365653d2.jpeg\" width=\"1600\" height=\"1373\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d1\/03b\/6c5\/7d103b6c5fb0c6a5e50e12b1365653d2.jpeg\" data-blurred=\"true\"\/><\/figure>\n<\/li>\n<\/ul>\n<details class=\"spoiler\">\n<summary>multiByCorner<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     ...     multiByCorner(index, v) {         this.multiBySide(index, v)         const nextSideInterval = 3         this.multiBySide((index + nextSideInterval) % this.paintedSides.length, v)     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 \u0443\u0433\u043e\u043b\u043e\u043a \u0440\u0430\u043c\u043a\u0438 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u0435\u043d \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0447\u0435\u0440\u0435\u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u0443. \u041c\u0435\u0442\u043e\u0434 multiByCorner \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u0443\u0433\u043e\u043b\u043a\u0430 \u0438 \u0432\u0435\u043a\u0442\u043e\u0440, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0443\u0433\u043e\u043b\u043e\u043a. \u0412 \u0442\u0435\u043b\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 multiByCorner \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0434\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 multiBySide. \u041e\u043d\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u044e\u0442 \u0440\u0430\u043c\u043a\u0443 \u043f\u043e \u0442\u0435\u043c \u0441\u0442\u043e\u0440\u043e\u043d\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u043b\u0435\u0433\u0430\u044e\u0442 \u043a \u0443\u0433\u043e\u043b\u043a\u0443 \u0440\u0430\u043c\u043a\u0438.  <\/p>\n<details class=\"spoiler\">\n<summary>\u043c\u0435\u0442\u043e\u0434 clone<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Frame {     ...     clone() {         const frameClone = new Frame()         frameClone.points = Object.assign(this.points)         frameClone.figure = this.figure.clone()         return frameClone     }     ... }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 \u0441lone \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u043e\u043f\u0438\u044e \u0440\u0430\u043c\u043a\u0438. \u041a\u043e\u043f\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u0440\u0430\u043c\u043a\u0438 \u0438 \u0444\u0438\u0433\u0443\u0440\u0430 figure. <\/p>\n<h2>\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b<\/h2>\n<p>\u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f canvas \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f \u0432 2d. \u042d\u0442\u043e\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043c\u0435\u0442\u043e\u0434\u044b draw:  <\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">const canvas = document.querySelector(\"canvas\"); const rect = canvas.parentNode.getBoundingClientRect() canvas.width = rect.width canvas.height = rect.height const ctx = canvas.getContext(\"2d\"); ctx.lineWidth = 3;<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a \u043a\u043d\u043e\u043f\u043a\u0430\u043c \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u201c\u043f\u043e\u043b\u0438\u0433\u043e\u043d\u201d, \u201c\u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c\u201d, \u201c\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430\u201d \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c callback \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u0440\u0430\u043c\u043a\u0443 &#8212; frame \u0438 \u0444\u0438\u0433\u0443\u0440\u0443 &#8212; figure. \u0424\u0438\u0433\u0443\u0440\u0430 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 canvas \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434 setFigure \u043e\u0431\u044a\u0435\u043a\u0442\u0443 frame: <\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">function resetBtn() {     polygonBtn.classList.remove('active')     circleBtn.classList.remove('active')     imageBtn.classList.remove('active') }  let frame = new Frame(200 ,200) \/\/ \u0438\u043d\u0446\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u044f frame polygonBtn.onclick = () => {     savedFrame = []     ctx.clearRect(0, 0, canvas.width, canvas.height);     frame = new Frame(200 ,200)     let points = [                         {x: 75.0, y: 0.0},         {x: 150.0, y: 100.0},         {x: 100.0, y: 200.0},         {x: 50.0, y: 200.0},         {x: 0.0, y: 100.0},         {x: 75.0, y: 0.0}     ]     points = points.map((p) => {         return ({ x: canvas.width \/ 2 + p.x - 75, y: canvas.height \/ 2 + p.y - 100 }) \/\/ \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u043c     })     const polygon = new Polygon(points)     frame.setFigure( polygon )     frame.draw()     resetBtn()     polygonBtn.classList.add('active') }  circleBtn.onclick = () => {     savedFrame = []     ctx.clearRect(0, 0, canvas.width, canvas.height);     frame = new Frame(200 ,200)     let ellipses = [         new Ellipse(50, 50, 50, 50, 0, 0, Math.PI * 2, true),         new Ellipse(50, 50, 35, 35, 0, 0, Math.PI, false),         new Ellipse(35, 40, 5, 5, 0, 0, Math.PI * 2, true),         new Ellipse(65, 40, 5, 5, 0, 0, Math.PI * 2, true),     ]     \/\/ \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u043c     ellipses = ellipses.map((ellipse) => {          const halfWidth = 100 \/ 2         const halfHeight = 100 \/ 2         ellipse.x = ellipse.x + canvas.width \/ 2 - halfWidth         ellipse.y = ellipse.y + canvas.height \/ 2 - halfHeight         return ellipse     })     const container = new Container(ellipses)      frame.setFigure( container )     frame.draw()     resetBtn()     circleBtn.classList.add('active') }  imageBtn.onclick = () => {     savedFrame = []     ctx.clearRect(0, 0, canvas.width, canvas.height);     frame = new Frame(200 ,200)     const img = new Image()     img.src = 'sonic.webp'     const width = 300     const height = 150     const paintedImage = new PaintedImage(img, canvas.width \/ 2 - width \/ 2, canvas.height \/ 2 - height \/ 2, 300, 150)     frame.setFigure( paintedImage )     frame.draw()     resetBtn()     imageBtn.classList.add('active') }  circleBtn.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>callback \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043c\u044b\u0448\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0440\u0430\u043c\u043a\u0438, \u0442\u0438\u043f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0438 \u043a\u0443\u0440\u0441\u043e\u0440\u0430 \u043c\u044b\u0448\u0438, \u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043c\u044b\u0448\u0438. \u0412\u0441\u0435 \u044d\u0442\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 savedProp: <\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">let savedProp = undefined  addEventListener(\"mousedown\", (evt) => {     const rect = canvas.getBoundingClientRect();     const mouse = { x: evt.clientX - rect.left, y: evt.clientY - rect.top }     const cornerInd = frame.containedInCorner(mouse)     if (cornerInd > -1) {         savedFrame.push( frame.clone() )         savedProp = {             type: 'multiByCorner',             cornerInd,             downMouse: mouse,             frame: frame.clone()         }         return     }     const sideInd = frame.containedInSide(mouse)     if (sideInd > -1) {         savedFrame.push( frame.clone() )         savedProp = {             type: 'multiBySide',             sideInd,             downMouse: mouse,             shift: evt.shiftKey,              frame: frame.clone()         }         return     }     if (frame.containedInCircle(mouse)) {         savedFrame.push( frame.clone() )         savedProp = {             type: 'rotation',             downMouse: mouse,              frame: frame.clone()         }         return     }     if (frame.containedInRect(mouse)) {         savedFrame.push( frame.clone() )         savedProp = {             type: 'transition',             downMouse: mouse,              frame: frame.clone()         }     } })<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0440\u0438 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u0438 \u043d\u0430\u0436\u0430\u0442\u043e\u0439 \u043c\u044b\u0448\u0438 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 callback, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 savedProp \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 undefined:<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">let savedProp = undefined  addEventListener(\"mouseup\", (evt) => {     savedProp = undefined })<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043a\u0443\u0440\u0441\u043e\u0440\u0430 \u043c\u044b\u0448\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u043c\u043a\u0438 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 propState \u043d\u0435 undefined. \u0412\u0435\u043a\u0442\u043e\u0440 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0437\u0430\u0436\u0430\u0442\u043e\u0439 \u043c\u044b\u0448\u0438 \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043c\u044b\u0448\u0438 \u043a \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043a\u0443\u0440\u0441\u043e\u0440\u0430 \u043c\u044b\u0448\u0438. \u041e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0435\u0442\u043e\u0434\u044b frame \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">addEventListener(\"mousemove\", (evt) => {     ctx.clearRect(0, 0, canvas.width, canvas.height);     const rect = canvas.getBoundingClientRect();     const mouse = { x: evt.clientX - rect.left, y: evt.clientY - rect.top }     if (savedProp) {         frame = savedProp.frame.clone()         const v = Vector.getVector(mouse, savedProp.downMouse)         if (savedProp.type === 'multiByCorner') {             frame.multiByCorner(savedProp.cornerInd, v)         } else if (savedProp.type === 'multiBySide') {             frame.multiBySide(savedProp.sideInd, v, savedProp.shift)         } else if (savedProp.type === 'transition') {             frame.transition(v)         } else if (savedProp.type === 'rotation') {             frame.rotation( savedProp.downMouse, mouse )         }     }     frame.draw(mouse) })<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0430\u0434 \u0440\u0430\u043c\u043a\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u043f\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044e \u043a\u043d\u043e\u043f\u043a\u0438 \u201c\u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u201d. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">let savedFrame = []  ... polygonBtn.onclick = () => {   savedFrame = [] }  circleBtn.onclick = () => {   savedFrame = [] }  imageBtn.onclick = () => {   savedFrame = [] }  backBtn.onclick = () => {     if (savedFrame.length > 0) {         ctx.clearRect(0, 0, canvas.width, canvas.height);         frame = savedFrame.pop()         frame.draw()     } } addEventListener(\"mousedown\", (evt) => {   ...   savedFrame.push( frame.clone() )   ... }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0443 \u043c\u0435\u043d\u044f \u0432\u0441\u0451. \u041d\u0430\u0434\u0435\u044e\u0441\u044c \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u044b\u043b\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u0438 \u0412\u044b \u043f\u043e\u0434\u0442\u044f\u043d\u0443\u043b\u0438 \u0437\u043d\u0430\u043d\u0438\u044f \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0430\u043b\u0433\u0435\u0431\u0440\u044b \u0438 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u043d\u043e\u0439 \u0433\u0440\u0430\u0444\u0438\u043a\u0438, \u0438 \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0441\u0432\u043e\u0438\u0445 \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">&lt;!DOCTYPE html> &lt;html> &lt;head>     &lt;meta charset='utf-8'>     &lt;meta http-equiv='X-UA-Compatible' content='IE=edge'>     &lt;title>Page Title&lt;\/title>     &lt;meta name='viewport' content='width=device-width, initial-scale=1'>     &lt;!-- &lt;link rel='stylesheet' type='text\/css' media='screen' href='main.css'> -->     &lt;!-- &lt;script src='main.js'>&lt;\/script> -->     &lt;style>         body {             display: flex;             margin: 0;         }         .wrapper-canvas {             width: 80%;             height: 100vh;             background-color: #E0F7FA;         }         .wrapper-btns {             display: flex;             flex-direction: column;             width: 20%;             height: 100vh;             background-color: #00838F;         }         button {             border-color: #B39DDB;             background-color: #6200EA;             color: #fff;             height: 50px;         }         button.active {             background-color: #FFD54F;             color: black;         }         #backBtn {             margin-top: auto;         }     &lt;\/style> &lt;\/head>     &lt;body>         &lt;div class = \"wrapper-canvas\">             &lt;canvas id=\"canvas\">&lt;\/canvas>         &lt;\/div>         &lt;div class = \"wrapper-btns\">             &lt;button id='polygonBtn'>\u041f\u043e\u043b\u0438\u0433\u043e\u043d&lt;\/button>             &lt;button id='circleBtn'>\u0421\u043c\u0430\u0439\u043b\u0438\u043a&lt;\/button>             &lt;button id='imageBtn'>\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430&lt;\/button>             &lt;button id='backBtn'>\u0443\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u043b. \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435&lt;\/button>         &lt;\/div>     &lt;\/body> &lt;script>      \/\/ \u041b\u0438\u043d\u0435\u0439\u043d\u0430\u044f \u0430\u043b\u0433\u0435\u0431\u0440\u0430     class Vector {         constructor(x, y) {             this.x = x; this.y = y         }         static getVector(p2, p1) { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e \u0434\u0432\u0443\u043c \u0442\u043e\u0447\u043a\u0430\u043c             return new Vector(p2.x - p1.x,  p2.y - p1.y)         }         static getNormal(v) { \/\/ \u0432\u043e\u0437\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043d\u043e\u0440\u043c\u0430\u043b\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u0430             return new Vector( v.y, -v.x )         }         static getRotationAngle(v) {  \/\/ \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u043b\u044c\u043d\u043e \u043e\u0441\u0438 X             const axisY = {x: 0, y: 1}             const dot = Vector.dotProduct(v, axisY)             let angle = Math.acos(v.x \/ v.length())             angle = dot > 0 ? angle : -angle             return angle         }         length() {             return Math.sqrt(this.x * this.x + this.y * this.y)         }         normalize() { \/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0440\u0430\u0432\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435             const len = Math.sqrt(this.x * this.x + this.y * this.y)             this.x = this.x \/ len              this.y = this.y \/ len         }         negative() { \/\/ \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432             this.x = -this.x             this.y = -this.y         }         multi(k) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0435\u043d\u0442             this.x = this.x * k; this.y = this.y * k         }         static dotProduct(v1, v2) { \/\/ \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435             return v1.x * v2.x + v1.y * v2.y         }     }      class Matrix {         constructor(a, b, c, d, e, f) {             this.a = a; this.c = c; this.e = e             this.b = b; this.d = d; this.f = f         }         multiMatrix(m) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443             const a = this.a * m.a + this.c * m.b             const b = this.b * m.a + this.d * m.b             const c = this.a * m.c + this.c * m.d             const d = this.b * m.c + this.d * m.d             const e = this.a * m.e + this.c * m.f + this.e             const f = this.b * m.e + this.d * m.f + this.f             return new Matrix(a, b, c, d, e, f)         }         multiVector(p) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 \u0438\u043b\u0438 \u0442\u043e\u0447\u043a\u0443             return {                 x: this.a * p.x + this.c * p.y + this.e,                 y: this.b * p.x + this.d * p.y + this.f             }         }         clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 \u043c\u0430\u0442\u0440\u0438\u0446\u044b             return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f)         }     }      \/\/ \u0418\u043d\u0446\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u044f canvas     const canvas = document.querySelector(\"canvas\");     const rect = canvas.parentNode.getBoundingClientRect()     canvas.width = rect.width     canvas.height = rect.height     const ctx = canvas.getContext(\"2d\");     ctx.lineWidth = 3;          class AbstractFigure {         clockwise = false         constructor() { }          clone() {              return AbstractFigure()         }         setClockwise(val) {             this.clockwise = val         }         clockwise() {             return this.clockwise         }         prop() {             return { x: 0, y: 0, width: 0, height: 0 }          }         multiMatrix(mat) { }         draw() { }     }      \/\/ \u0424\u0438\u0433\u0443\u0440\u044b     class Polygon extends AbstractFigure {         constructor(points = []) {             super()             this.points = Object.assign(points)         }         clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 Polygon             const polygon = new Polygon(this.points)             return polygon         }         prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Polygon: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430             let minX = Number.MAX_VALUE; let maxX = Number.MIN_VALUE             let minY = Number.MAX_VALUE; let maxY = Number.MIN_VALUE             this.points.forEach(p => {                 if (p.x > maxX) { maxX = p.x }                 if (p.x &lt; minX) { minX = p.x }                 if (p.y > maxY) { maxY = p.y }                 if (p.y &lt; minY) { minY = p.y }             })             return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }          }         multiMatrix(mat) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443             this.points = this.points.map(( p ) => mat.multiVector( p ))         }         draw() {             ctx.save()             ctx.beginPath()             this.points.forEach((p, ind) => {                 if (ind === 0) {                     ctx.moveTo(p.x, p.y)                 } else {                     ctx.lineTo(p.x, p.y)                 }             })             ctx.stroke()             ctx.restore()         }     }      class Ellipse extends AbstractFigure {         constructor(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {             super()             this.x = x; this.y = y; \/\/ center             this.radiusX = radiusX             this.radiusY = radiusY             this.rotation = rotation \/\/ \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u044d\u043b\u0438\u043f\u0441\u0430             this.startAngle = startAngle             this.endAngle = endAngle             super.setClockwise(!counterclockwise)         }         clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 Ellipse             const ellipse = new Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.endAngle, !super.clockwise())             return ellipse         }         prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Ellipse: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430              return {                  x: this.x - this.radiusX,                  y: this.y - this.radiusY,                  width: this.radiusX * 2,                  height: this.radiusY * 2              }          }         multiMatrix(mat) {             const sin = Math.sin( this.rotation )             const cos = Math.cos( this.rotation )             const newCenter = mat.multiVector( { x: this.x, y: this.y } )             \/\/ \u043e\u0441\u044c x             let vectorX = (new Matrix(cos, sin, -sin, cos, 0, 0)).multiVector({ x: this.radiusX, y: 0})              let extremeX = { x: this.x + vectorX.x, y: this.y + vectorX.y }             let newExtremeX = mat.multiVector(extremeX)             const newRadiusX = Vector.getVector(newExtremeX, newCenter).length()             \/\/ \u043e\u0441\u044c y             let vectorY = (new Matrix(cos, sin, -sin, cos, 0, 0)).multiVector({ x: 0, y: this.radiusY})              let extremeY = { x: this.x + vectorY.x, y: this.y + vectorY.y }             let newExtremeY = mat.multiVector(extremeY)             const newRadiusY = Vector.getVector(newExtremeY, newCenter).length()             \/\/ \u043f\u043e\u0434\u0441\u0447\u0435\u0442 rotation             const newRotation = Vector.getRotationAngle(Vector.getVector(newExtremeX, newCenter))             \/\/ \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442             this.x = newCenter.x             this.y = newCenter.y             this.radiusX = newRadiusX              this.radiusY = newRadiusY             this.rotation = newRotation         }         draw() {             ctx.save()             ctx.beginPath()             ctx.ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.endAngle, !super.clockwise())             ctx.stroke()             ctx.restore()         }     }      class PaintedImage extends AbstractFigure {         constructor(img, x, y, width, height) {             super()             this.mat = new Matrix(1, 0, 0, 1, 0, 0)             this.img = img             this.x = x; this.y = y;             this.width = width; this.height = height         }         clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 PaintedImage             const paintedImage = new PaintedImage(this.img, this.x, this.y, this.width, this.height)             paintedImage.mat = this.mat.clone()             return paintedImage         }         prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 PaintedImage: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430              return {                  x: this.x,                  y: this.y,                  width: this.width,                  height: this.height              }          }         multiMatrix(mat) {             this.mat = mat.multiMatrix( this.mat ).clone()         }         draw() {             ctx.beginPath()             ctx.save()             const mat = this.mat             ctx.transform(mat.a, mat.b, mat.c, mat.d, mat.e, mat.f)             ctx.drawImage(this.img, this.x, this.y, this.width, this.height)             ctx.restore()         }     }      class Container extends AbstractFigure {         constructor(figures = []) {             super()             this.figures = figures         }         clone() {             const figureClone = this.figures.map((figure) => figure.clone())             return new Container(figureClone)         }         setClockwise(val) {             this.figures.forEach((figure) => {                 figure.setClockwise( super.clockwise() == val ? figure.clockwise : !figure.clockwise )              })             super.setClockwise(val)         }         prop() {             let minX = Number.MAX_VALUE; let minY = Number.MAX_VALUE;             let maxWidth = Number.MIN_VALUE; let maxHeight = Number.MIN_VALUE             this.figures.forEach(figure => {                 const prop = figure.prop()                 if (prop.x &lt; minX) { minX = prop.x }                 if (prop.y &lt; minY) { minY = prop.y }                 if (prop.width > maxWidth) { maxWidth = prop.width }                 if (prop.height > maxHeight) { maxHeight = prop.height }             })             return { x: minX, y: minY, width: maxWidth, height: maxHeight }          }         multiMatrix(mat) {             this.figures.forEach(( figure ) => {                 figure.multiMatrix( mat )             })         }         draw() {             this.figures.forEach(( figure ) => {                 figure.draw()             })         }     }      \/\/ \u0420\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f     class Frame {         figure = new Polygon() \/\/ \u0444\u0438\u0433\u0443\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0434\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f         \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 painted \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 draw         paintedCircles = new Array(4).fill().map(() => (new Path2D())) \/\/ \u043a\u0440\u0443\u0433\u0438  \u0434\u043b\u044f \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u043c\u043a\u0438         paintedSides = new Array(4).fill().map(() => new Path2D())         paintedRect = new Path2D() \/\/ \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438         cornerWidth = 7.5 \/\/ \u0448\u0438\u0440\u0438\u043d\u0430 \u0443\u0433\u043e\u043b\u043a\u0430         paintedCorners = new Array(4)          constructor(width = 0, height = 0) {             this.width = width;             this.height = height;             this.points = [                 {x: 0, y: 0},                  {x: width, y: 0},                  {x: width, y: height},                  {x: 0, y: height}             ]         }          setFigure(figure) {             this.figure = figure.clone()             this.adjustToFigure()         }          clone() {             const frameClone = new Frame()             frameClone.points = Object.assign(this.points)             frameClone.figure = this.figure.clone()             return frameClone         }          adjustToFigure() { \/\/ \u043e\u0431\u0432\u0430\u043b\u0430\u043a\u0438\u0432\u0430\u0435\u0442 \u0444\u0438\u0433\u0443\u0440\u0443 \u0440\u0430\u043c\u043a\u043e\u0439             const prop = this.figure.prop()             this.width = prop.width             this.height = prop.height             this.points = [                 {x: prop.x, y: prop.y},                  {x: prop.x + this.width, y: prop.y},                  {x: prop.x + this.width, y: prop.y + this.height},                  {x: prop.x, y: prop.y + this.height}             ]         }          containedInRect(mouse) {             return ctx.isPointInPath( this.paintedRect, mouse.x, mouse.y )         }          containedInCorner(mouse) {             for (let i = 0; i &lt; this.paintedCorners.length; ++i) {                 if (ctx.isPointInPath( this.paintedCorners[i], mouse.x, mouse.y )) {                     return i                 }             }             return -1         }          containedInCircle(mouse) {             for (let i = 0; i &lt; this.paintedCircles.length; ++i) {                 if (ctx.isPointInPath( this.paintedCircles[i], mouse.x, mouse.y )) {                     return true                 }             }             return false         }          containedInSide(mouse) {             for (let i = 0; i &lt; this.paintedSides.length; ++i) {                 if (ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y )) {                     return i                 }             }             return -1         }          clockwise() {             let indexByMinX = 0             for (let i = 0; i &lt; this.points.length; ++i) { \/\/ \u0423\u0437\u043d\u0430\u0435\u043c \u0438\u043d\u0434\u0435\u043a\u0441 \u0442\u043e\u0447\u043a\u0438 c \u043d\u0430\u0438\u043c\u0435\u043d\u044c\u0448\u0438\u043c x                 if(this.points[indexByMinX].x > this.points[i].x) {                     indexByMinX = i                 }             }             const beginIndex = (indexByMinX + 3) % this.points.length             const middleIndex = indexByMinX             const lastIndex = (indexByMinX + 1) % this.points.length             const v1 = Vector.getVector( this.points[middleIndex], this.points[beginIndex] )             const v2 = Vector.getVector( this.points[lastIndex], this.points[middleIndex] )             const multiVec = v1.x * v2.y - v2.x * v1.y \/\/ \u0412\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435             return multiVec &lt; 0         }          transition(v) {             const mat = new Matrix(1, 0, 0, 1, v.x, v.y)             this.points = this.points.map(( p ) => mat.multiVector( p ))             this.figure.multiMatrix( mat )         }          rotation(downMouse, upMouse) {             const center = {                 x: ( this.points[2].x + this.points[0].x ) \/ 2,                  y: (this.points[2].y + this.points[0].y) \/ 2             }             const axisX = Vector.getVector( downMouse, center )             axisX.normalize()             const axisY = Vector.getNormal( axisX )             const rotationV = Vector.getVector(upMouse, center)             const proj = Vector.dotProduct(axisX, rotationV)             let angle = Math.acos( proj \/ rotationV.length())             angle = Vector.dotProduct(axisY, rotationV) > 0 ? -angle : angle              const sin = Math.sin(angle)             const cos = Math.cos(angle)             let mat = new Matrix(1, 0, 0, 1, center.x, center.y)             mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))             mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -center.x, -center.y))             this.points = this.points.map(( p ) => mat.multiVector( p ))             this.figure.multiMatrix( mat )         }          multiBySide(index, v, shift = false) {             const center = {                     x: ( this.points[2].x + this.points[0].x ) \/ 2,                      y: (this.points[2].y + this.points[0].y) \/ 2             }               const prevSideInterval = 3             const referencePoint = shift ? {x: center.x, y: center.y} : this.points[ (index + prevSideInterval) % this.points.length ]              const sideVector = Vector.getVector(                  this.points[index],                  this.points[ (index + 1) % this.points.length ]              )             const adjacentVector = Vector.getVector(                 this.points[ (index + prevSideInterval) % this.points.length ],                 this.points[index]             )             const sideNormal = Vector.getNormal( sideVector )             sideNormal.normalize()             sideNormal.negative()             let dif = shift ? 2 * Vector.dotProduct(v, sideNormal) : Vector.dotProduct(v, sideNormal)             dif = this.clockwise() ? -dif : dif             const angle = Vector.getRotationAngle(adjacentVector)             const width = adjacentVector.length()             let multiX = 1 + dif \/ width             \/\/ \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u043c\u043a\u043a\u0430 \u043f\u0440\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u0435, \u0441\u0442\u0440\u0435\u043c\u044f\u0449\u0438\u043c\u0441\u044f \u043a \u043d\u0443\u043b\u044e, \u043d\u0435 \u043b\u043e\u043c\u0430\u043b\u0430\u0441\u044c \u0438 \u043d\u0435 \u0438\u0441\u0447\u0435\u0437\u0430\u043b\u0430             if (multiX > 0) {                 multiX = multiX &lt; 0.001 ?  0.001 : multiX             } else {                 multiX = multiX > -0.001 ?  -0.001 : multiX             }             const sin = Math.sin(angle)             const cos = Math.cos(angle)             let mat = new Matrix(1, 0, 0, 1, referencePoint.x, referencePoint.y)             mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))             mat = mat.multiMatrix(new Matrix(multiX, 0, 0, 1, 0, 0))             mat = mat.multiMatrix(new Matrix(cos, -sin, sin, cos, 0, 0))             mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -referencePoint.x, -referencePoint.y))             const newPoints = this.points.map(( p ) => mat.multiVector( p ))             this.points = newPoints             this.figure.multiMatrix( mat )             this.figure.setClockwise( this.clockwise() )         }          multiByCorner(index, v) {             this.multiBySide(index, v)             const nextSideInterval = 3             this.multiBySide((index + nextSideInterval) % this.paintedSides.length, v)         }          draw( mouse = {x: -1, y: -1} ) {             this.figure.draw()              const points = this.clockwise() ? this.points.reverse() : this.points              let selectedMouse = false \/\/ \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u043f\u0435\u0440\u0435\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430              \/\/ \u0443\u0433\u043e\u043b\u043a\u0438             ctx.save()             const center = {                 x: ( points[2].x + points[0].x ) \/ 2,                  y: ( points[2].y + points[0].y ) \/ 2             }             const width = Vector.getVector( points[1], points[0] ).length()             const height = Vector.getVector( points[2], points[1] ).length()             const rotationAngle = Vector.getRotationAngle(  Vector.getVector( points[1], points[0] ) )             const sin = Math.sin( rotationAngle )             const cos = Math.cos( rotationAngle )             const cornerPos = [                  { x: center.x - width \/ 2, y: center.y - height \/ 2 },                 { x: center.x + width \/ 2, y: center.y - height \/ 2 },                 { x: center.x + width \/ 2, y: center.y + height \/ 2 },                 { x: center.x - width \/ 2, y: center.y + height \/ 2 },             ]             cornerPos.forEach((p, ind) => {                 const p1 = { x: p.x - this.cornerWidth \/ 2, y: p.y - this.cornerWidth \/ 2 }                 const p2 = { x: p1.x + this.cornerWidth, y: p1.y }                 const p3 = { x: p2.x, y: p2.y + this.cornerWidth }                 const p4 = { x: p3.x - this.cornerWidth, y: p3.y }                 const cornerPoints = [p1, p2, p3, p4].map((p) => {                     let mat = new Matrix(1, 0, 0, 1, center.x, center.y)                     mat = mat.multiMatrix(new Matrix(cos, sin, -sin, cos, 0, 0))                     mat = mat.multiMatrix(new Matrix(1, 0, 0, 1, -center.x, -center.y))                     return mat.multiVector( p )                 })                 this.paintedCorners[ind] = new Path2D()                 cornerPoints.forEach((p, index) => {                     if (index == 0) {                         this.paintedCorners[ind].moveTo(p.x, p.y)                     } else {                         this.paintedCorners[ind].lineTo(p.x, p.y)                     }                 })                 ctx.fillStyle = ctx.isPointInPath( this.paintedCorners[ind], mouse.x, mouse.y ) &amp;&amp; !selectedMouse ? \"#6200EA\" : \"#757575\";                 if ( !selectedMouse ) {                     selectedMouse = ctx.isPointInPath( this.paintedCorners[ind], mouse.x, mouse.y )                 }                 ctx.fill( this.paintedCorners[ind] )             })             ctx.restore()             \/\/ \u0441\u0442\u043e\u0440\u043e\u043d\u044b             ctx.save()             for (let i = 0; i &lt; this.paintedSides.length; ++i) {                 const p1 = points[i]                 const p2 = points[(i + 1) % 4]                 this.paintedSides[i] = new Path2D()                 this.paintedSides[i].moveTo(p1.x, p1.y)                 this.paintedSides[i].lineTo(p2.x, p2.y)                 ctx.strokeStyle = ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y ) &amp;&amp; !selectedMouse ? \"#6200EA\" : \"#75757599\";                 if ( !selectedMouse ) {                     selectedMouse = ctx.isPointInStroke( this.paintedSides[i], mouse.x, mouse.y )                 }                 ctx.stroke( this.paintedSides[i] )             }             ctx.restore()             \/\/ \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a             this.paintedRect.painted = new Path2D()             points.forEach((p, ind) => {                 if (ind == 0) {                     this.paintedRect.moveTo(p.x, p.y)                 } else {                     this.paintedRect.lineTo(p.x, p.y)                 }             })             \/\/ \u043a\u0440\u0443\u0433\u0438             ctx.save()             const d = 5             const angle = Math.PI \/ 4 \/\/ 45 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432             const mat = new Matrix(angle, angle, -angle, angle, 0, 0)             for (let i = 0; i &lt; 4; ++i) {                 this.paintedCircles[i] = new Path2D()                 const p1 = points[i]                 const p2 = points[(i + 1) % 4]                 const sideV = Vector.getVector(p2, p1)                 sideV.normalize()                 sideV.multi(20)                 const circleV = mat.multiVector(sideV)                 const circleP = {x: p1.x + circleV.x, y: p1.y + circleV.y}                 this.paintedCircles[i].arc(circleP.x, circleP.y, d, 0, 2 * Math.PI)                 ctx.fillStyle = ctx.isPointInPath( this.paintedCircles[i], mouse.x, mouse.y ) ? \"#6200EA\" : \"#757575\";                 ctx.fill( this.paintedCircles[i] )             }             ctx.restore()         }     }      function resetBtn() {         polygonBtn.classList.remove('active')         circleBtn.classList.remove('active')         imageBtn.classList.remove('active')     }      let frame = new Frame(200 ,200)     let savedFrame = []     polygonBtn.onclick = () => {         savedFrame = []         ctx.clearRect(0, 0, canvas.width, canvas.height);         frame = new Frame(200 ,200)         let points = [                             {x: 75.0, y: 0.0},             {x: 150.0, y: 100.0},             {x: 100.0, y: 200.0},             {x: 50.0, y: 200.0},             {x: 0.0, y: 100.0},             {x: 75.0, y: 0.0}         ]         points = points.map((p) => {             return ({ x: canvas.width \/ 2 + p.x - 75, y: canvas.height \/ 2 + p.y - 100 }) \/\/ \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u043c         })         const polygon = new Polygon(points)         frame.setFigure( polygon )         frame.draw()         resetBtn()         polygonBtn.classList.add('active')     }      circleBtn.onclick = () => {         savedFrame = []         ctx.clearRect(0, 0, canvas.width, canvas.height);         frame = new Frame(200 ,200)         let ellipses = [             new Ellipse(50, 50, 50, 50, 0, 0, Math.PI * 2, true),             new Ellipse(50, 50, 35, 35, 0, 0, Math.PI, false),             new Ellipse(35, 40, 5, 5, 0, 0, Math.PI * 2, true),             new Ellipse(65, 40, 5, 5, 0, 0, Math.PI * 2, true),         ]         \/\/ \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u043c         ellipses = ellipses.map((ellipse) => {              const halfWidth = 100 \/ 2             const halfHeight = 100 \/ 2             ellipse.x = ellipse.x + canvas.width \/ 2 - halfWidth             ellipse.y = ellipse.y + canvas.height \/ 2 - halfHeight             return ellipse         })         const container = new Container(ellipses)          frame.setFigure( container )         frame.draw()         resetBtn()         circleBtn.classList.add('active')     }      imageBtn.onclick = () => {         savedFrame = []         ctx.clearRect(0, 0, canvas.width, canvas.height);         frame = new Frame(200 ,200)         const img = new Image()         img.src = 'sonic.webp'         const width = 300         const height = 150         const paintedImage = new PaintedImage(img, canvas.width \/ 2 - width \/ 2, canvas.height \/ 2 - height \/ 2, 300, 150)         frame.setFigure( paintedImage )         frame.draw()         resetBtn()         imageBtn.classList.add('active')     }      backBtn.onclick = () => {         if (savedFrame.length > 0) {             ctx.clearRect(0, 0, canvas.width, canvas.height);             frame = savedFrame.pop()             frame.draw()         }     }      circleBtn.click()      let savedProp = undefined      addEventListener(\"mousemove\", (evt) => {         ctx.clearRect(0, 0, canvas.width, canvas.height);         const rect = canvas.getBoundingClientRect();         const mouse = { x: evt.clientX - rect.left, y: evt.clientY - rect.top }         if (savedProp) {             frame = savedProp.frame.clone()             const v = Vector.getVector(mouse, savedProp.downMouse)             if (savedProp.type === 'multiByCorner') {                 frame.multiByCorner(savedProp.cornerInd, v)             } else if (savedProp.type === 'multiBySide') {                 frame.multiBySide(savedProp.sideInd, v, savedProp.shift)             } else if (savedProp.type === 'transition') {                 frame.transition(v)             } else if (savedProp.type === 'rotation') {                 frame.rotation( savedProp.downMouse, mouse )             }         }         frame.draw(mouse)     });     addEventListener(\"mousedown\", (evt) => {         const rect = canvas.getBoundingClientRect();         const mouse = { x: evt.clientX - rect.left, y: evt.clientY - rect.top }         const cornerInd = frame.containedInCorner(mouse)         if (cornerInd > -1) {             savedFrame.push( frame.clone() )             savedProp = {                 type: 'multiByCorner',                 cornerInd,                 downMouse: mouse,                 frame: frame.clone()             }             return         }         const sideInd = frame.containedInSide(mouse)         if (sideInd > -1) {             savedFrame.push( frame.clone() )             savedProp = {                 type: 'multiBySide',                 sideInd,                 downMouse: mouse,                 shift: evt.shiftKey,                  frame: frame.clone()             }             return         }         if (frame.containedInCircle(mouse)) {             savedFrame.push( frame.clone() )             savedProp = {                 type: 'rotation',                 downMouse: mouse,                  frame: frame.clone()             }             return         }         if (frame.containedInRect(mouse)) {             savedFrame.push( frame.clone() )             savedProp = {                 type: 'transition',                 downMouse: mouse,                  frame: frame.clone()             }         }     })     addEventListener(\"mouseup\", (evt) => {         savedProp = undefined     }) &lt;\/script> &lt;\/html> <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><a href=\"https:\/\/github.com\/svirid132\/frame-editor\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/svirid132\/frame-editor<\/a> &#8212; \u043a\u043e\u0434 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 github-\u0435.<\/p>\n<p><a href=\"https:\/\/svirid132.github.io\/frame-editor\/\" rel=\"noopener noreferrer nofollow\">https:\/\/svirid132.github.io\/frame-editor\/<\/a> &#8212; \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><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\/ru\/articles\/774776\/\"> https:\/\/habr.com\/ru\/articles\/774776\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0445\u0430\u0431\u0440. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u043d\u0430\u043f\u043e\u0434\u043e\u0431\u0438\u0435 \u0442\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430\u0445 figma, adobe illustrator \u0438 \u0432\u043e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435 \u0434\u0440\u0443\u0433\u0438\u0445 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430\u0445. \u0412 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430. \u041e\u043d\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430, \u0435\u0433\u043e \u043c\u0430\u0441\u0448\u0442\u0430\u0431 \u0438 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430.<\/p>\n<figure class=\"bordered full-width\">\n<div><figcaption>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0440\u0430\u043c\u043a\u0443 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<figure class=\"\">\n<div><figcaption>\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 Figma. \u0413\u043e\u043b\u0443\u0431\u043e\u0439 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a &#8212; \u044d\u0442\u043e \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438 \u043d\u0430 \u0442\u0435\u043e\u0440\u0438\u044e \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443. \u0412 \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u042f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e\u0441\u043d\u043e\u0432\u044b \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439 \u0430\u043b\u0433\u0435\u0431\u0440\u044b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043c\u0430\u0442. \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0435\u0441\u044f \u043a \u0440\u0430\u043c\u043a\u0435. \u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0434\u0430\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u044f\u0437\u044b\u043a\u0435 javascript, \u043e\u043f\u0438\u0441\u0430\u043d\u044b \u043c\u0430\u0442. \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u0434 \u0440\u0430\u043c\u043a\u043e\u0439 \u0438 \u0435\u0433\u043e \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439, \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u044b \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u043c\u043a\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043a\u0430\u043a \u043f\u043e\u043b\u0438\u0433\u043e\u043d, \u0441\u043c\u0430\u0439\u043b\u0438\u043a \u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u044d\u0442\u0438\u0445 \u0444\u0438\u0433\u0443\u0440, \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<h2>\u0422\u0435\u043e\u0440\u0438\u044f<\/h2>\n<h2>\u0413\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.<\/h2>\n<p>\u0421\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0444\u043e\u0440\u043c\u0443\u043b\u043e\u0439:<\/p>\n<figure class=\"\"><\/figure>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u0444\u043e\u0440\u043c\u0443\u043b\u0430 \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043c, \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440. \u041f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B &#8212; \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0438\u0441\u043b\u043e, \u0440\u0430\u0432\u043d\u043e\u0435 \u0434\u043b\u0438\u043d\u0435 \u043f\u0440\u043e\u0435\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u043f\u043e\u0434 \u0443\u0433\u043b\u043e\u043c 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0434 \u201c\u0440\u0430\u0431\u043e\u0447\u0435-\u043a\u0440\u0435\u0441\u0442\u044c\u044f\u043d\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\u201d, \u0442\u043e \u0432\u044b\u0439\u0434\u0435\u0442, \u0447\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B &#8212; \u044d\u0442\u043e \u0434\u043b\u0438\u043d\u0430 \u0442\u0435\u043d\u0438, \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c\u0430\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u043c \u0410 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u043f\u043e\u0434 \u0443\u0433\u043b\u043e\u043c 90 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u0413\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0421\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0442\u0435\u0441\u043d\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0435\u0439 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432. \u0422\u0430\u043a \u043a\u0430\u043a \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 A \u0438 B\u00a0 &#8212; \u044d\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 B \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 B, \u0438\u043b\u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 B \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 A \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 A:\u00a0<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u0438\u043c\u0435\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0440\u0430\u0432\u043d\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0435, \u0442\u043e \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0440\u0430\u0432\u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440:<\/p>\n<figure class=\"full-width\"><\/figure>\n<h2>\u041c\u0430\u0442\u0440\u0438\u0446\u044b<\/h2>\n<p>\u041c\u0430\u0442\u0440\u0438\u0446\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438, \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u043a\u0443\u0440\u0441\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u043d\u043e\u0439 \u0433\u0440\u0430\u0444\u0438\u043a\u0438:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u0443\u043c\u043d\u043e\u0436\u0438\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0443\u044e \u0444\u0438\u0433\u0443\u0440\u0443 \u0441 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0441\u043e\u0432\u043e\u043a\u0443\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u0442\u043e\u0447\u0435\u043a, \u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 \u043d\u0435 \u0432\u044b\u0439\u0434\u0443\u0442 \u0437\u0430 \u043a\u0440\u0430\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u0438 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u044b \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u201c\u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439\u201d \u0444\u0438\u0433\u0443\u0440\u043e\u0439. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0443\u0441\u0442\u044c \u0444\u0438\u0433\u0443\u0440\u0430 &#8212; \u044d\u0442\u043e \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0430 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u0441\u043e\u0432\u043e\u043a\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0442\u043e\u0447\u0435\u043a \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0442\u0440\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a, \u0442\u043e\u0433\u0434\u0430 \u0443\u043c\u043d\u043e\u0436\u0430\u044f \u0432\u0441\u0435 \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u0432\u044b\u0448\u0435\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u043c\u0430\u0442\u0440\u0438\u0446\u044b, \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0433\u0434\u0435 \u043f\u043e\u0434 pi \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u0447\u043a\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0444\u0438\u0433\u0443\u0440\u044b.<\/p>\n<h2>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u0430\u0442\u0440\u0438\u0446 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447<\/h2>\n<p>\u0414\u043b\u044f \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043c\u0430\u0442\u0440\u0438\u0446\u0430\u043c\u0438 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0442\u0430\u043a\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443. \u0415\u0441\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0432 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430 \u0438 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435 \u0432 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0435\u0433\u043e \u0442\u043e\u0447\u043a\u0438 t. \u0417\u0430\u0434\u0430\u0447\u0430 \u0440\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u0440\u0438 \u044d\u0442\u0430\u043f\u0430. \u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u0442\u043e\u0447\u043a\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u00a0 -t . \u041d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430 \u0440\u0430\u043c\u043a\u0438. \u0418 \u0432 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u0440\u0430\u043c\u043a\u0443 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0432 \u0432\u0441\u0435 \u0435\u0433\u043e \u0442\u043e\u0447\u043a\u0438 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 t. \u0423\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0413\u0434\u0435 \u0442\u043e\u0447\u043a\u0430 pi &#8212; \u044d\u0442\u043e \u0442\u043e\u0447\u043a\u0430 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c i.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0430\u043f\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u043c\u043a\u0438, \u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u044d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 -t<\/p>\n<\/li>\n<\/ul>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 -t<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><\/figure>\n<ul>\n<li>\n<p>\u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u043e \u0434\u043b\u0438\u043d\u0435 \u0432 2 \u0440\u0430\u0437\u0430 \u0438 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435 \u0432 3 \u0440\u0430\u0437\u0430<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><\/figure>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 t<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><\/figure>\n<p>\u041a\u0430\u043a \u0412\u044b \u0443\u0436\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u044d\u0442\u0430\u043f\u044b \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043f\u0440\u0430\u0432\u043e-\u043d\u0430\u043b\u0435\u0432\u043e. \u0418 \u0435\u0441\u043b\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u044d\u0442\u0430\u043f \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0432\u043e\u043a\u0440\u0443\u0433 \u043d\u0430\u0447\u0430\u043b\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0442\u043e \u043c\u0430\u0442\u0440\u0438\u0446\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c\u0441\u044f \u0441\u043b\u0435\u0432\u0430 \u043e\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0443\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f:<\/p>\n<figure class=\"full-width\"><\/figure>\n<h2>\u041f\u0440\u0430\u043a\u0442\u0438\u043a\u0430<\/h2>\n<p>\u0412 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0440\u0430\u043c\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0446\u0435\u043d\u0442\u0440\u0430 \u0438\u043b\u0438 \u0443\u0433\u043e\u043b\u043a\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0432\u043e\u0440\u043e\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0446\u0435\u043d\u0442\u0440\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043a\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0438\u0433\u0443\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u043c\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442:<\/p>\n<ul>\n<li>\n<p>\u041f\u043e\u043b\u0438\u0433\u043e\u043d;<\/p>\n<\/li>\n<li>\n<p>\u0421\u043c\u0430\u0439\u043b\u0438\u043a;<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 javascript \u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u043f\u0443\u043d\u043a\u0442\u0430\u043c. \u0422\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043c\u0435\u0442\u043e\u0434\u044b \u0438 \u0447\u043b\u0435\u043d\u044b \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0440\u0430\u0437\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0439 \u0432 \u0438\u0445 \u0440\u0430\u0431\u043e\u0442\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e. \u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u0434, \u043f\u0440\u043e\u043b\u0438\u0441\u0442\u0430\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u044c\u044e \u0434\u043e \u043a\u043e\u043d\u0446\u0430.<\/p>\n<h2>\u041b\u0438\u043d\u0435\u0439\u043d\u0430\u044f \u0430\u043b\u0433\u0435\u0431\u0440\u0430<\/h2>\n<h2>\u041a\u043b\u0430\u0441\u0441 \u0432\u0435\u043a\u0442\u043e\u0440<\/h2>\n<details class=\"spoiler\">\n<summary>\u043a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Vector {     constructor(x, y) {         this.x = x; this.y = y     }     static getVector(p2, p1) { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e \u0434\u0432\u0443\u043c \u0442\u043e\u0447\u043a\u0430\u043c         return new Vector(p2.x - p1.x,  p2.y - p1.y)     }     static getNormal(v) { \/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u044c \u0432\u0435\u043a\u0442\u043e\u0440\u0430         return new Vector( v.y, -v.x )     }     static getRotationAngle(v) {  \/\/ \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u043b\u044c\u043d\u043e \u043e\u0441\u0438 X         const axisY = {x: 0, y: 1}         const dot = Vector.dotProduct(v, axisY)         let angle = Math.acos(v.x \/ v.length())         angle = dot > 0 ? angle : -angle         return angle     }     length() {         return Math.sqrt(this.x * this.x + this.y * this.y)     }     normalize() { \/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u043b\u0438\u043d\u0443 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0440\u0430\u0432\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435         const len = Math.sqrt(this.x * this.x + this.y * this.y)         this.x = this.x \/ len          this.y = this.y \/ len     }     negative() { \/\/ \u043f\u043e\u0432\u043e\u0440\u043e\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0430 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432         this.x = -this.x         this.y = -this.y     }     multi(k) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0435\u043d\u0442         this.x = this.x * k; this.y = this.y * k     }     static dotProduct(v1, v2) { \/\/ \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435         return v1.x * v2.x + v1.y * v2.y     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u0430\u0441\u0441 Vector, \u0438\u043c\u0435\u0435\u0442 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 getRotationAngle. \u041e\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0443\u0433\u043e\u043b \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0438 x:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u0433\u043b\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u043c \u0443\u0433\u043e\u043b \u0447\u0435\u0440\u0435\u0437 \u0430\u0440\u043a\u043a\u043e\u0441\u0438\u043d\u0443\u0441 \u043a\u043e\u0441\u0438\u043d\u0443\u0441\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v:  <\/p>\n<figure class=\"\"><\/figure>\n<\/li>\n<li>\n<p>\u0410\u0440\u043a\u043a\u043e\u0441\u0438\u043d\u0443\u0441 \u0443\u0433\u043b\u0430 \u0438\u043c\u0435\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 0 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432 \u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u042d\u0442\u043e\u0442 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u043d\u0435 \u0434\u0430\u0451\u0442 \u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430, \u0442.\u043a. \u043f\u043e\u043b\u043d\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 &#8212; \u044d\u0442\u043e 360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0437\u043d\u0430\u043a \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v \u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440 axisY, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u043e\u0441\u0438 Y. \u0415\u0441\u043b\u0438 \u0437\u043d\u0430\u043a \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 v \u043e\u0442\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 axisY. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 1-\u043e\u0439 \u0438\u043b\u0438 \u0432\u043e 2-\u043e\u0439 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0438 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 0 \u0434\u043e 180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432:  <\/p>\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<p>\u0435\u0441\u043b\u0438 \u0437\u043d\u0430\u043a \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439, \u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043f\u0440\u043e\u0435\u0446\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u043e\u0442\u0438\u0432\u043e\u043f\u043e\u043b\u043e\u0436\u043d\u043e\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u0430 axisY. \u0418 \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u0435\u043a\u0442\u043e\u0440 v \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 3-\u0435\u0439 \u0438\u043b\u0438 4-\u043e\u0439 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442, \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0443\u0433\u043b\u0430 \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 \u043e\u0442 0 \u0434\u043e -180 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432:<\/p>\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/li>\n<\/ul>\n<h2>\u041a\u043b\u0430\u0441\u0441 \u043c\u0430\u0442\u0440\u0438\u0446\u0430<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Matrix {     constructor(a, b, c, d, e, f) {         this.a = a; this.c = c; this.e = e         this.b = b; this.d = d; this.f = f     }     multiMatrix(m) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443         const a = this.a * m.a + this.c * m.b         const b = this.b * m.a + this.d * m.b         const c = this.a * m.c + this.c * m.d         const d = this.b * m.c + this.d * m.d         const e = this.a * m.e + this.c * m.f + this.e         const f = this.b * m.e + this.d * m.f + this.f         return new Matrix(a, b, c, d, e, f)     }     multiVector(p) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0432\u0435\u043a\u0442\u043e\u0440 \u0438\u043b\u0438 \u0442\u043e\u0447\u043a\u0443         return {             x: this.a * p.x + this.c * p.y + this.e,             y: this.b * p.x + this.d * p.y + this.f         }     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 \u043c\u0430\u0442\u0440\u0438\u0446\u044b         return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f)     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 clone \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u043e\u043f\u0438\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 Matrix. \u041d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u043e\u0434\u0430 clone \u043b\u044e\u0431\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u043e\u043f\u0438\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<h2>\u0424\u0438\u0433\u0443\u0440\u044b<\/h2>\n<p>\u0423 \u0432\u0441\u0435\u0445 \u0444\u0438\u0433\u0443\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044e \u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0438\u043b\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0440\u0430\u043c\u043a\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443, \u043d\u0435 \u0437\u043d\u0430\u044f \u043e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0439 \u0444\u0438\u0433\u0443\u0440. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043e\u0431\u0449\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043a\u043b\u0430\u0441\u0441\u043e\u043c AbstractFigure. \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043e\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 AbstractFigure, \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u043c \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0443 \u043a\u043b\u0430\u0441\u0441\u043e\u0432:<\/p>\n<figure class=\"full-width\"><\/figure>\n<h2>\u041a\u043b\u0430\u0441\u0441 AbstractFigure<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class AbstractFigure {     clockwise = false     constructor() { }      clone() {          return AbstractFigure()     }     setClockwise(val) {         this.clockwise = val     }     clockwise() {         return this.clockwise     }     prop() {         return { x: 0, y: 0, width: 0, height: 0 }      }     multiMatrix(mat) { }     draw() { } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e clockwise \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u042d\u0442\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u0433\u0443\u0440\u0443 \u043f\u043e \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0442\u0438\u0432 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u0435\u043b\u043a\u0438.<\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 prop \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0444\u0438\u0433\u0443\u0440\u044b &#8212; \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c x, y, \u0434\u043b\u0438\u043d\u0443 \u0438 \u0448\u0438\u0440\u0438\u043d\u0443. \u041f\u043e\u0437\u0438\u0446\u0438\u044f x \u0438 y \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0444\u0438\u0433\u0443\u0440\u044b \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043b\u0435\u0432\u043e\u0433\u043e \u043d\u0438\u0436\u043d\u0435\u0433\u043e \u0443\u0433\u043b\u0430.<\/p>\n<h2>\u041a\u043b\u0430\u0441\u0441 Polygon<\/h2>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">class Polygon extends AbstractFigure {     constructor(points = []) {         super()         this.points = Object.assign(points)     }     clone() { \/\/ \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 Polygon         const polygon = new Polygon(this.points)         return polygon     }     prop() { \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Polygon: \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u0444\u0438\u0433\u0443\u0440\u044b \u043f\u043e x,y; \u0434\u043b\u0438\u043d\u0430; \u0448\u0438\u0440\u0438\u043d\u0430         let minX = Number.MAX_VALUE; let maxX = Number.MIN_VALUE         let minY = Number.MAX_VALUE; let maxY = Number.MIN_VALUE         this.points.forEach(p => {             if (p.x > maxX) { maxX = p.x }             if (p.x &lt; minX) { minX = p.x }             if (p.y > maxY) { maxY = p.y }             if (p.y &lt; minY) { minY = p.y }         })         return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }      }     multiMatrix(mat) { \/\/ \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043c\u0430\u0442\u0440\u0438\u0446\u0443         this.points = this.points.map(( p ) => mat.multiVector( p ))     }     draw() {         ctx.save()         ctx.beginPath()         this.points.forEach((p, ind) => {             if (ind === 0) {                 ctx.moveTo(p.x, p.y)             } else {                 ctx.lineTo(p.x, p.y)             }         })         ctx.stroke()         ctx.restore()     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u0430\u0441\u0441 \u0445\u0440\u0430\u043d\u0438\u0442 \u0442\u043e\u0447\u043a\u0438 points, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434 draw. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u0435\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\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-360531","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360531","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=360531"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360531\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=360531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=360531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=360531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}