Взлом физики и JavaScript (Часть 2). Решение треугольников.

Перевод статьиHack Physics and JavaScript (part 2) :: solving triangles = profit
Автор статьи: Rachel Smith.

Если вы не знакомы с лирическим отступлением о том как я обнаружила, что большинство моих школьных знаний по физике, которые весьма пригодились бы при создании анимации на Canvas, уже давно позабыты, то можете обратиться к первой статье из этой серии. Ко всему прочему оказалось, что тригонометрии мне тоже не вспомнить, что весьма печально, поскольку эти знания также очень бы пригодились в Сanvas анимации, да и вообще для креативного кодирования. Ладно, давайте попробуем исправить ситуацию.

Не все так печально, как кажется на первый взгляд, ведь у нас есть Google, с помощью которого я смогла освежить свои знания по решению задач с треугольником, ознакомившись с такой замечательной статьей как "Math is Fun" (*«Забавная математика»).

Иногда (особенно это касается начинающих свою деятельность в креативном кодировании) для нас не совсем понятно, как и, собственно, зачем мы должны применять эту область математики при создании анимационных эффектов. Сегодня мне хочется показать вам пример, в котором анимация строится на основе тригонометрии совместно с использованием хаккинга физики, рассмотренного в в предыдущей статье.

Здесь мы будем создавать нечто вроде рычага или катапульты, с помощью которой, пользователь, путем оттягивания ее вниз мышью, сможет запускать в воздух маленьких пингвинов. Так и хочется попробовать, правда? Я специально выбрала такой забавный способ обучения, благодаря которому вам будет намного легче пройти все математические этапы нашего проекта.

Как поет в своей песне Тина Тёрнер: «А причем здесь любовь треугольник?»

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

function drawBase() {
ctx.beginPath();
ctx.arc(leverPosition.x, leverPosition.y, 40, Math.PI, 0, false);
ctx.closePath();
ctx.lineWidth = 5;
ctx.fillStyle = '#c1c0c8';
ctx.fill();
}

Теперь нам надо нарисовать реальный рычаг, который по нашей задумке должен быть 200 пикселей в длину с уклоном в 70° относительно воображаемой «земли». Для того, чтобы нарисовать на канве линию нам нужно две точки, начальную (x1, x1) и конечную (x2, y2). На данный момент у нас есть только начальная точка (в основе), а координаты пространства х/у второй точки нам пока неизвестны.

xy

Глядя на приведенную выше диаграмму с так называемым рычагом, с расстоянием по оси Х и с расстоянием по оси Y, легко вообразить себе стороны треугольника. Нам известна длина рычага и угол его наклона при основании, но нужно найти стороны Х и Y. Здесь и начинается тригонометрия.

А вот снова наш рычаг, но уже представленный с тремя углами: A, B, C
и сторонами a, b, c.

angles

Первое тригонометрическое правило, которое необходимо нам для решения задачи с нашим треугольником, гласит: «сумма углов треугольника равна 180°»

A + B + C = 180

В данном случае зная значение угла С (70°) и угла В (он прямой, т.е. 90°), легко найти третий угол А.

A = 180 — B — C

Что ж, имея величины всех трех углов мы можем попытаться найти стороны треугольника x и y. Эту задачу можно решить с помощью «закона синусов» (или «правила синусов»).

a / sin(A) = b / sin(B) = c / sin(C)

Нам известна длина стороны b и величины прилегающих к ней углов С и В. Таким образом мы можем определить размер стороны с.

c / sin(C) = b / sin(B) c = b / sin(B) * sin(C)

И в конце концов находим сторону а.

a / sin(A) = c / sin(C) a = c / sin(C) * sin(A)

Ниже приведена JavaScript функция, определяющая стороны x и y для нашего рычага при известном угле С. И не забудьте, что тригонометрические функции JavaScript, как собственно и функция Math.sin работают только с углами, выраженными в радианах, хотя мы привыкли к их представлению в градусах. Я предпочитаю иметь маленькую функцию-помощника, отвечающую за такое конвертирование.

function calculateTriangleXY(C) {
var B = 90;
// solve for A
var A = 180 — C — B;
// solve c and a
var c = (leverLength * Math.sin(toRadians(C))) / Math.sin(toRadians(B));
var a = (c * Math.sin(toRadians(A))) / Math.sin(toRadians(C));
// return x/y sides
return {x: a, y: c}
}

function toRadians(deg) {
return deg / 180 * Math.PI;
}

Имея функцию, определяющую стороны треугольника x и y, мы можем нарисовать наш рычаг, найдя недостающую точку путем вычитания найденных значений из координат базовой точки у основания рычага.

function drawLever() {
var xy = calculateTriangleXY(currentAngle);
ctx.beginPath();
ctx.moveTo(leverPosition.x, leverPosition.y);
ctx.lineTo(leverPosition.x — xy.x, leverPosition.y — xy.y);
ctx.lineWidth = 4;
ctx.strokeStyle = '#7b4b3d';
ctx.stroke();

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

hit-area
hit-area-mousepos

Эта проблема тоже решается с помощью тригонометрии. При нахождении курсора мыши пользователя в активной области рычага создается другой треугольник с углом 70°. Поскольку нам известна вертикальная координата у позиции мыши и угол наклона рычага, мы можем просчитать, какое значение должна иметь координата х, если курсор мыши находится в активной зоне рычага. То есть, если координата х курсора близка по значению к координате х треугольника, то можно допустить, что курсор находится в активной зоне. Сейчас давайте модифицируем нашу функцию calculateTriangleXY таким образом, чтобы мы могли узнавать координату х, передавая ей в качестве аргументов координату у и угол.

function calculateTriangleXY(C, y) {
var B = 90;
// solve for A
var A = 180 — C — B;
// if we only have angle C we need to solve edge c first
var c = y || (leverLength * Math.sin(toRadians(C))) / Math.sin(toRadians(B));
var a = (c * Math.sin(toRadians(A))) / Math.sin(toRadians(C));
return {x: a, y: c}
}

Теперь у нас есть возможность проверки, находится ли курсор мыши в активной области, и если да, то мы меняем внешний вид курсора на указатель, давая тем самым пользователю знать, что он находится в кликабельной зоне. Кроме того в этом случае функция установит переменную hitting в истинное значение true.

// get x given current angle & y pos
var xy = calculateTriangleXY(currentAngle, leverPosition.y — mousePos.y);
// if mouse x is close to the triangle x, we're in the hit area
if ((mousePos.x > leverPosition.x — xy.x — 20) && (mousePos.x < leverPosition.x — xy.x + 20)) {
canvas.style.cursor = 'pointer';
hitting = true;
} else {
canvas.style.cursor = 'auto';
hitting = false;
}

На данном этапе мы уже в состоянии определить момент, когда пользователь наводит мышь на зону активации рычага. Отлично! А это означает, что при нажатии кнопки пользователем мы можем начать «перетаскивание» рычага. В момент, когда пользователь, нажав кнопку мыши, оттягивает рычаг, нам нужно добиться совпадения положения рычага с позицией курсора. Чтобы реализовать это мы должны определять угол рычага на основе координат х и у курсора мыши. И мы можем решить эту задачу, прибегнув, как вы думаете к чему? Правильно, к тригонометрии.

В данном случае нам известны стороны треугольника а и с (это координаты х и у курсора мыши), а также угол В (прямой 90°).

drag

Прежде чем определить угол С, нам нужно найти сторону b. Эта задача решается применением закона косинусов.

b2 = a2 + c2 − (2 * a * c * cos(B))

Как только мы узнаем величину стороны b, можно сразу же определять угол С. Вот функция, выполняющая необходимые для этого действия:

function calculateTriangleAngle(x,y) {
var A, C;
var B = 90;
var c = y;
var a = x;
var b = Math.sqrt(Math.pow(a, 2) + Math.pow(c, 2) — 2 * a * c * Math.cos(toRadians(B)));
C = Math.asin(Math.sin(toRadians(B)) / b * c);
return C;
}

Теперь мы можем изменить угол наклона рычага, на соответствующий его положению, которое он должен занимать в результате перетаскивания пользователем.

var angle = toDegrees(calculateTriangleAngle(leverPosition.x — mousePos.x, leverPosition.y — mousePos.y));
if (angle < 10) {
release();
return;
}
if (angle > 80) angle = 80;
currentAngle = angle;

И в завершение процесса построения катапульты, мы создаем функцию, реализующую ее «выстреливание», т.е. отпускающую рычаг, когда пользователь отпускает кнопку мыши или оттягивает рычаг в самый низ. Помните, в первой моей публикации на эту тему я говорила о скорости движения по оси х иу? Так вот, существует также скорость вращения, с которой здесь мы и имеем дело. Ниже устанавливаем переменную vr, величина которой зависит от того, насколько далеко был оттянут рычаг. Это позволит нам вернуть его обратно в исходное положение под углом 70°.

function release() {
pulling = false;
vr = (70 — currentAngle) * 0.06;
}

Соединив все это воедино, мы получим рычаг, который можно оттягивать и в нужный момент отпускать.

See the Pen A lever by Rachel Smith (@rachsmith) on CodePen.

Извиняюсь за урезанный вид, вот ссылка на полную картину.

Отправим в полет парочку пингвинов.

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

Для начала нам нужно создать объект Pinguin, который будет использоваться для генерации пингвинов.

function Penguin(x, y, r) {
var _this = this;
var img;
this.x = x;
this.y = y;
this.r = targetAngle;

(function() {
img = new Image();
img.src = $https://s3-us-west-2.amazonaws.com/s.cdpn.io/53148/penguin.png$;
})();

this.update = function() {
// rotating the penguin
ctx.save();
ctx.translate(_this.x, _this.y);
if(_this.latched) ctx.rotate(toRadians(currentAngle));
else ctx.rotate(toRadians(_this.r));
// translating a little so it sits further down the ledge
ctx.translate(24, -16);
ctx.drawImage(img, -20, -20, 40, 40);
ctx.restore();
}
}

Теперь мы можем создать пингвина и разместить его на палке.

function addPenguin() {
var penguin = new Penguin(140,120);
penguin.latched = true;
penguins.push(penguin);
loadedPenguin = penguin;
}

Как вы, вероятно, заметили, пингвину было добавлено свойство latched со значением true. Это сделано для того, чтобы сообщить функции отслеживания о том, что данный объект Pinguin должен перемещаться вместе с рычагом, т.е. создается эффект закрепления пингвина на рычаге. Кроме того, наш пингвин нужно будет запустить в свободный полет по достижению рычагом своего «штатного» положения (угол 70°), после того, как он будет отпущен пользователем. Для этого можно воспользоваться обычной проверкой в функции updateRotationspan, которая будет сообщать, в какой момент пингвин должен улететь.

if (released && currentAngle > targetAngle) {
released = false;
throwPenguin();
}

«Запуск» пингвина сопровождается заданием ему значений скорости по двум осям (vx, vy) и значения степени натяжки самой катапульты (vr). После внесения этих данных в нашу функцию отслеживания движения рычага и добавления в нее некоторых хитростей из области физики мы получаем парящего в свободном полете пингвина.

function throwPenguin() {
loadedPenguin.vx = vr*4;
loadedPenguin.vy = vr*-1.86;
loadedPenguin.vr = 0.5;
loadedPenguin.latched = false;
loadedPenguin = null;
setTimeout(addPenguin, 500);
}
// added to Penguin.update —
// penguin is launched!
if(!_this.latched && _this.vx) {
_this.vy += gravity;
_this.x += _this.vx;
_this.y += _this.vy;
_this.r += _this.vr;
_this.vx *= 0.98;
_this.vy *= 0.98;

// check for hitting edges
if (_this.x + 28 > canvasWidth) {
_this.x = canvasWidth — 28;
_this.vx *= bounce;
_this.vr *= bounce;
}

if (_this.y + 38 > canvasHeight) {
_this.y = canvasHeight — 38;
_this.vy *= bounce;
_this.vr *= bounce;
}

if (Math.abs(_this.vx) < 0.001) _this.vx = 0;
if (Math.abs(_this.vy) < 0.001) _this.vy = 0;
}

Взгляните на уже готовый результат таких действий.

See the Pen A lever with penguins by Rachel Smith (@rachsmith) on CodePen.

Извиняюсь за урезанный вид, вот ссылка на полную картину.

Ну наконец-то! Нам понадобилось немного JavaScript кода и все заработало. Теперь вы знаете, как решать задачи с треугольниками и видите, какие чудные вещи можно творить с помощью JavaScript в Canvas.

* Примечание переводчика.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *