Запусти своего пингвина! (Банальная физика в JavaScript Ч.2)

от автора

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

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

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

Как когда-то сказала Тина Тернер, на что способна любовь способен треугольник?

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

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,y1) и конечную точку (x2,y2). На данном этапе мы знаем нашу начальную точку (основание), но не знаем конечную точку в пространстве x/y.

img

Глядя на схему выше, мы видим, что наш рычаг, расстояние x и расстояние y до нашей второй точки составляют стороны треугольника. Мы знаем расстояние рычага и угол, но нам нужно вычислить стороны x и y. Пришло время тригонометрии!

Вот опять наш рычаг, разбитый на три угла: A, B и C и три стороны: a, b и c:

img

Первый тригонометрический факт, который нам нужно знать для решения нашего треугольника, это то, что три угла в треугольнике всегда в сумме дают 180°

A + B + C = 180

В данном случае нам известен угол C (70°) и угол B (правый угол — 90°), поэтому мы можем найти угол A:

A = 180 — B — C

Теперь у нас есть все три угла, и мы можем попробовать найти наши стороны a (x) и c (y). Для этого мы можем воспользоваться теоремой синусов:

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

Нам известны значения угла C, угла B и стороны b, поэтому мы можем найти сторону c:

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

Наконец, мы можем найти сторону a:

a / sin(A) = c / sin© a = c / sin© * 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(); } 

Рычаг у нас уже есть, теперь нужно сделать так, чтобы пользователь мог с ним взаимодействовать. Мы можем это сделать, записав движение мышки, и проверив, что мышь привязывается к «области запуска» рычага. При этом «область запуска» представляет собой длинный тонкий треугольник под углом, а потому это не так просто, как отметить положение мышки внутри нормальной рамки x/y.

img

Мы можем использовать тригонометрию для решения и этой проблемы! Когда пользователь держит мышку в области запуска рычага, он создает еще один треугольник с углом 70°. Так как мы знаем положение y мышки и угол рычага, мы можем проверить, каким должно быть положение x, если мышь находится в области запуска. Если x нашей мыши находится рядом с x треугольника, тогда мы можем предположить, что она находится в области запуска. Давайте обновим нашу функцию calculateTriangleXY, чтобы мы могли сообщить ей положение y, а также угол, чтобы получить положение х.

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 в значение «истина».

// 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; } 

Теперь мы знаем, когда пользователь наводит мышь на область запуска! Чудесно! Это значит, что когда пользователь кликнет мышью, мы сможем начать «движение» рычага. Когда пользователь кликнет и потянет, нам нужно сделать так, чтобы рычаг соответствовал положению мыши. Чтобы это сделать, нам нужно определить угол рычага при имеющемся положении x/y мыши. Найти этот угол мы можем с помощью, как вы уже догадались – тригонометрии!

В этом примере мы знаем стороны a и c благодаря позиции x/y мыши, и угол B (90°).

img

Чтобы найти угол C, нам сначала нужно найти сторону b. Для этого мы можем использовать теорию косинусов.

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

Как только мы найдем сторону b, мы можем найти угол С.

Вот функция для этого:

unction 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; 

Наконец, мы можем добавить функцию для «высвобождения» рычага, либо когда пользователь отпускает, либо когда дотягивает рычаг до самого конца. Помните, в 1 части этого блога я объясняла, что вы можете задать скорость в направлении x и y? Вы также можете задать скорость вращения, и именно это мы здесь и сделаем. Мы зададим скорость vr относительно того, насколько далеко был оттянут рычаг, так чтобы он отправлялся обратно до нашего целевого угла в 70°.

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

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

Давайте заставим пингвинов летать

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

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; } 

Вы наверняка заметили, что мы установили свойство penguin.latched в значение «true». Это для того, чтобы функция обновления обновляла вращение пингвина по мере вращения рычага, создавая ощущение, что пингвин «привязан» к рычагу. Нам нужно опустить пингвина, когда рычаг вернется в наш изначальный угол после запуска. Мы можем поставить отметку для этого момента в нашу функцию updateRotation и запустить пингвина.

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

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

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; } 

Зацените готовое демо

ссылка на оригинал статьи http://habrahabr.ru/post/262209/


Комментарии

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

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