Наверное, вы помните из первой части, что я говорила о том, что забыла школьную программу физики, которая могла бы мне помочь создавать анимацию с помощью canvas. Как оказалось, я забыла еще и тригонометрию. Также оказалось, что тригонометрия крайне полезна в создании анимации и креативного кодинга в целом.
Иногда, не совсем понятно, как и зачем использовать этот вид математики для создания анимации. Сегодня я хочу показать вам пример, как мы можем использовать тригонометрию в сочетании с нашей банальной физикой для создания забавной анимации.
Мы сделаем рычаг или катапульту, которую пользователь сможет оттянуть мышкой и запустить в воздух маленького пингвинчика.
Как когда-то сказала Тина Тернер, на что
Первый элемент, требуемый для нашей анимации – это рычаг. Базовая часть довольно проста, мы можем нарисовать полукруг, чтобы создать следующее.
Теперь нам нужно нарисовать сам рычаг. Мы знаем, что нам нужен рычаг длиной 200 пикселей, и нам нужно его направить под углом 70° от «основания». Чтобы нарисовать линию, вам нужно знать начальную (x1,y1) и конечную точку (x2,y2). На данном этапе мы знаем нашу начальную точку (основание), но не знаем конечную точку в пространстве x/y.
Глядя на схему выше, мы видим, что наш рычаг, расстояние x и расстояние y до нашей второй точки составляют стороны треугольника. Мы знаем расстояние рычага и угол, но нам нужно вычислить стороны x и y. Пришло время тригонометрии!
Вот опять наш рычаг, разбитый на три угла: A, B и C и три стороны: a, b и c:
Первый тригонометрический факт, который нам нужно знать для решения нашего треугольника, это то, что три угла в треугольнике всегда в сумме дают 180°
A + B + C = 180
В данном случае нам известен угол C (70°) и угол B (правый угол — 90°), поэтому мы можем найти угол A:
A = 180 — B — C
Теперь у нас есть все три угла, и мы можем попробовать найти наши стороны a (x) и c (y). Для этого мы можем воспользоваться теоремой синусов:
Нам известны значения угла C, угла B и стороны b, поэтому мы можем найти сторону c:
Наконец, мы можем найти сторону a:
Вот функция JavaScript, которая может найти стороны x/y треугольника нашего рычага при известном угле С. Не забывайте, что для тригонометрических функций JavaScript, таких как Math.sin, углы должны быть указаны в радианах, а мы их считали в градусах. Я люблю использовать простую функцию помощника, которая помогает отслеживать конвертацию.
Теперь у нас есть функция, способная найти стороны x/y треугольника, и мы можем нарисовать наш рычаг за счет вычитания этих значений из нашей базовой точки, чтобы получить конечную точку линии.
Рычаг у нас уже есть, теперь нужно сделать так, чтобы пользователь мог с ним взаимодействовать. Мы можем это сделать, записав движение мышки, и проверив, что мышь привязывается к «области запуска» рычага. При этом «область запуска» представляет собой длинный тонкий треугольник под углом, а потому это не так просто, как отметить положение мышки внутри нормальной рамки x/y.
Мы можем использовать тригонометрию для решения и этой проблемы! Когда пользователь держит мышку в области запуска рычага, он создает еще один треугольник с углом 70°. Так как мы знаем положение y мышки и угол рычага, мы можем проверить, каким должно быть положение x, если мышь находится в области запуска. Если x нашей мыши находится рядом с x треугольника, тогда мы можем предположить, что она находится в области запуска. Давайте обновим нашу функцию calculateTriangleXY, чтобы мы могли сообщить ей положение y, а также угол, чтобы получить положение х.
Теперь мы можем проверить положение мыши, и если она находится в области запуска, мы изменим курсор на указатель, чтобы пользователь понимал, что объект кликабелен, и установим переменную hitting в значение «истина».
Теперь мы знаем, когда пользователь наводит мышь на область запуска! Чудесно! Это значит, что когда пользователь кликнет мышью, мы сможем начать «движение» рычага. Когда пользователь кликнет и потянет, нам нужно сделать так, чтобы рычаг соответствовал положению мыши. Чтобы это сделать, нам нужно определить угол рычага при имеющемся положении x/y мыши. Найти этот угол мы можем с помощью, как вы уже догадались – тригонометрии!
В этом примере мы знаем стороны a и c благодаря позиции x/y мыши, и угол B (90°).
Чтобы найти угол C, нам сначала нужно найти сторону b. Для этого мы можем использовать теорию косинусов.
b2 = a2 + c2 ? (2 * a * c * cos(B))
Как только мы найдем сторону b, мы можем найти угол С.
После этого мы можем установить соответствующий угол рычага, что приведет к движению с помощью мыши!
Наконец, мы можем добавить функцию для «высвобождения» рычага, либо когда пользователь отпускает, либо когда дотягивает рычаг до самого конца. Помните, в 1 части этого блога я объясняла, что вы можете задать скорость в направлении x и y? Вы также можете задать скорость вращения, и именно это мы здесь и сделаем. Мы зададим скорость vr относительно того, насколько далеко был оттянут рычаг, так чтобы он отправлялся обратно до нашего целевого угла в 70°.
Собрав все это вместе, мы получим рычаг, который можно натягивать и отпускать!
Сначала нам нужно создать объект «пингвин», который мы будем использовать для генерирования пингвинов.
Теперь мы можем создать пингвина и разместить его на рычаге.
Вы наверняка заметили, что мы установили свойство penguin.latched в значение «true». Это для того, чтобы функция обновления обновляла вращение пингвина по мере вращения рычага, создавая ощущение, что пингвин «привязан» к рычагу. Нам нужно опустить пингвина, когда рычаг вернется в наш изначальный угол после запуска. Мы можем поставить отметку для этого момента в нашу функцию updateRotation и запустить пингвина.
«Запуск» пингвина подразумевает присвоение пингвину значений vx, vy и vr, что запустит его в воздух, как только мы обновим функцию обновления Penguin с помощью банальной физики.
Зацените готовое демо
Иногда, не совсем понятно, как и зачем использовать этот вид математики для создания анимации. Сегодня я хочу показать вам пример, как мы можем использовать тригонометрию в сочетании с нашей банальной физикой для создания забавной анимации.
Мы сделаем рычаг или катапульту, которую пользователь сможет оттянуть мышкой и запустить в воздух маленького пингвинчика.
Как когда-то сказала Тина Тернер, на что способна любовь способен треугольник?
Первый элемент, требуемый для нашей анимации – это рычаг. Базовая часть довольно проста, мы можем нарисовать полукруг, чтобы создать следующее.
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.
Глядя на схему выше, мы видим, что наш рычаг, расстояние x и расстояние y до нашей второй точки составляют стороны треугольника. Мы знаем расстояние рычага и угол, но нам нужно вычислить стороны x и y. Пришло время тригонометрии!
Вот опять наш рычаг, разбитый на три угла: A, B и C и три стороны: a, b и c:
Первый тригонометрический факт, который нам нужно знать для решения нашего треугольника, это то, что три угла в треугольнике всегда в сумме дают 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)
Нам известны значения угла C, угла B и стороны b, поэтому мы можем найти сторону c:
c / sin(C) = b / sin(B) c = b / sin(B) * sin(C)
Наконец, мы можем найти сторону a:
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();
}
Рычаг у нас уже есть, теперь нужно сделать так, чтобы пользователь мог с ним взаимодействовать. Мы можем это сделать, записав движение мышки, и проверив, что мышь привязывается к «области запуска» рычага. При этом «область запуска» представляет собой длинный тонкий треугольник под углом, а потому это не так просто, как отметить положение мышки внутри нормальной рамки x/y.
Мы можем использовать тригонометрию для решения и этой проблемы! Когда пользователь держит мышку в области запуска рычага, он создает еще один треугольник с углом 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°).
Чтобы найти угол 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;
}
Зацените готовое демо
Платежные решения Paysto для читателей Хабра:
> Получите оплату банковской картой прямо сейчас. Без сайта, ИП и ООО.
> Принимайте оплату от компаний через Интернет. Без сайта, ИП и ООО.
> Приём платежей от компаний для Вашего сайта. С документооборотом и обменом оригиналами.
> Автоматизация продаж и обслуживание сделок с юр.лицами. Без посредника в расчетах.
> Принимайте оплату от компаний через Интернет. Без сайта, ИП и ООО.
> Приём платежей от компаний для Вашего сайта. С документооборотом и обменом оригиналами.
> Автоматизация продаж и обслуживание сделок с юр.лицами. Без посредника в расчетах.
Комментарии (7)
kahi4
09.07.2015 18:21+2Я просто оставлю это здесь
Ну и разложить гипотенузу на x, y в 4 синуса — это мощно!
Как оказалось, я забыла еще и тригонометрию.
Простите, но в таком случае вам (точнее, автору оригинала), стоит почитать «Геометрия, 7 класс»
vvovas
09.07.2015 18:25+4Какая завуалированная реклама=)
А по теме: в колледже понравился проект, в котором была реализована система из нескольких грузов разной массы, связанных между собой пружинами. Все это было приделано к подвесам, и была гравитация. После запуска было интересно следить за раскачиванием грузов.
Temirkhan
09.07.2015 20:25После десятого пущенного пингвинчика имею
TypeError: loadedPenguin is null
Также нет обратного вектора силы. То есть, толчок создает инерцию лишь в правую часть оси x.
jauseg
а какже инерция вращения после того, как пингвин отделился от палки?
lanseg
Для игры пингвина вполне можно считать материальной точкой