Рисование эллипса под произвольным углом в canvas на JavaScript

В процессе разработки одного приложения столкнулся с необходимостью рисования эллипсов под произвольным углом в canvas на JavaScript. Пользоваться какими-либо фреймворками в столь простом проекте не хотелось, так что я отправился на поиски статьи-мануала на эту тему. Поиски не увенчались успехом, так что пришлось разбираться с задачей самостоятельно, и я решил поделиться с вами полученным опытом.Формализуем задачу. Нам требуется функция drawEllipse (coords, sizes, vector), где: coords — координаты центра эллипса — массив [x, y] sizes — длины большой и малой полуосей эллипса — массив [a, b] В качестве основного средства для решения задачи были выбраны кривые Безье. Для построения такой кривой требуются четыре точки: начальная, конечная и две контрольные.201a8d52d60b44cc968a9e0d90e342b2.gif

Наш искомый эллипс будет состоять из двух таких кривых, причем нетрудно догадаться, что вышеупомянутые точки у каждой из них будут вершинами прямоугольника. Попробуем построить наш эллипс.

6cda380987f746078519dd7cbb73d013.png

Имеем некоторый вектор 2888b2bea4da4d79bf16395940521209.pngНайдем единичный вектор 6edbd9b4964347dead6b9a3f93c2f603.pngf323cf7d72e34434aafe24595ae084b5.pnga105232b15b34759ab1cc59d734c99e2.pngНайдем единичный вектор c56df824c9c143b89d0574716eb6eb0b.pngДля этого вспомним свойство скалярного произведения векторов обращаться в ноль в случае, если они перпендикулярны: 512e6116362f44509e2aec3efbc3124c.pngТаким образом: ff9f2e1e7c9e43a89b610caa20f231ce.png Найдем векторы 40824db0db9a4eea96e7a3d74eac5b84.png, точки A1, A2, B1, B280ddba631c5541e0bd297837efe342e2.pngeda54e6ab6c540cb94977f54741cf988.pnga1f574e3ea634ebdb59e39f6ad915e39.png2edf8375ae1f4e6eb0bd415b100a7ac6.png85a0a91a60b1476aa2ee76f7eb17bc76.png84f275d1db4a46c99baf5fafc6118f96.png Найдем векторы 3babac38ed08471b978be5e8e2036b21.png, точки C1, C2, C3, C44237c6373fea4874a9cad1ffeb83d4f6.png78ec8fba37ef453bb04a9b5aa5b7ea8b.pngd0d32e5529e046f5b648d8aa720c2704.png5bc4d741ab44444c9c97a479d59e3226.png03b5661bc4b84a1b87a15d5e0fccabf9.pngb9f3348afb4843d692a362df6f7d1f1c.png Вспомним, что для рисования эллипса нам нужны две кривые Безье:1-я имеет начальную точку B1, конечную B2, проходит через точку A1 2-я имеет начальную точку B2, конечную B1, проходит через точку A2 Вспомним также, что для построения кривых Безье нам требуются контрольные точки. Недолго думая, я сначала подставил в качестве таковых вершины прямоугольника, в который вписан эллипс. Это решение оказалось ошибкой, ведь если мы рассмотрим построение кривой Безье, то обнаружим, что она не касается отрезка, соединяющего две контрольные точки.Изобразим момент построения кривой Безье в точке, в которой она (кривая) будет наиболее близка к отрезку между контрольными точками. В нашем случае это будет выглядеть так: f80f837d830e44b0b72146c7d1f5d46f.png

Из рисунка очевидно, что расстояние от этой точки (A1) до отрезка между контрольными точками (C1, C2) будет составлять четверть от расстояния между центром искомого эллипса (O) и тем же отрезком (C1, C2), то есть: 2c9a34ed498e4d808e96b6562de236e2.png

Решим уравнение 7468c6e48e7542feb99311fbbeeafcf3.png243d3322d9834d24a58143c7501fd290.png789e5427a0cf46cdb0dcd1fc9bdcf408.pngТаким образом, для получения эллипса с нужными параметрами нам необходимо умножить вектор 7e29d8618514486c9eedbdca56b5a5d4.png на параметр cf1e1d7d28374264b1cfff20f98cc0e6.png, после чего вернуться к вычислениям, описанным в пунктах 1–4. В результате получаем наборы точек (B1, C1, C2, B2 и B2, C3, C4, B1) для построения двух кривых Безье, вместе представляющих искомую фигуру. Собственно демо и код: function drawEllipse (ctx, coords, sizes, vector) { var vLen = Math.sqrt (vector[0]*vector[0]+vector[1]*vector[1]); // вычисляем длину вектора var e = [vector[0]/vLen, vector[1]/vLen]; // единичный верктор e || vector var p = 4/3; // параметр

var a = [e[0]*sizes[0]*p, e[1]*sizes[0]*p]; // находим вектор a, используя параметр var b = [e[1]*sizes[1], -e[0]*sizes[1]]; // находм вектор b // находим точки A1, B1, A2, B2 var dotA1 = [coords[0]+a[0], coords[1]+a[1]]; var dotB1 = [coords[0]+b[0], coords[1]+b[1]]; var dotA2 = [coords[0]-a[0], coords[1]-a[1]]; var dotB2 = [coords[0]-b[0], coords[1]-b[1]];

// находим вектора c1, c2 var c1 = [a[0]+b[0], a[1]+b[1]]; var c2 = [a[0]-b[0], a[1]-b[1]]; // находим точки C1, C2, C3, C4 var dotC1 = [coords[0]+c1[0], coords[1]+c1[1]]; var dotC2 = [coords[0]+c2[0], coords[1]+c2[1]]; var dotC3 = [coords[0]-c1[0], coords[1]-c1[1]]; var dotC4 = [coords[0]-c2[0], coords[1]-c2[1]];

// рисуем наш эллипс ctx.strokeStyle = 'black'; ctx.beginPath (); ctx.moveTo (dotB1[0], dotB1[1]); // начальная точка ctx.bezierCurveTo (dotC1[0], dotC1[1], dotC2[0], dotC2[1], dotB2[0], dotB2[1]); // рисуем кривую Безье ctx.bezierCurveTo (dotC3[0], dotC3[1], dotC4[0], dotC4[1], dotB1[0], dotB1[1]); // и вторую из точки, где закончили рисовать первую ctx.stroke (); ctx.closePath ();

// возвращаем вектору a изначальную длину var a = [e[0]*sizes[0], e[1]*sizes[0]];

// отрисовываем красным большую и малую оси эллипса, чтобы проверить, правильно ли мы отобразили запрошенный эллипс ctx.beginPath (); ctx.moveTo (coords[0]+a[0], coords[1]+a[1]); ctx.lineTo (coords[0]-a[0], coords[1]-a[1]); ctx.moveTo (coords[0]+b[0], coords[1]+b[1]); ctx.lineTo (coords[0]-b[0], coords[1]-b[1]); ctx.strokeStyle = 'red'; ctx.stroke (); ctx.closePath (); }

de19504464d54cab9858c3c01769af73.png

© Habrahabr.ru