Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > alatar

Рейтинг: 5.00. Голосов: 7.

Трансформации вокруг произвольной точки

Запись от alatar размещена 05.02.2015 в 12:52
Обновил(-а) alatar 05.02.2015 в 20:14

Эпизодически на форуме появляются вопросы типа: "как повернуть объект вокруг точки", "как сдвинуть transform point" и т.д. Как правило, ответы представляют собой частные случаи, вроде, сдвинуть-повернуть-сдвинуть для вращения или с использованием кучи тригонометрии, которые не учитывают, что объект уже может быть трансформирован до того как попадет на вход функции вращения.

В данной статье рассмотрены методы на которые не влияют начальные трансформации объекта.

Примечание: Важно помнить, что порядок умножения матриц играет роль. Т.е. MxN != NxM, в общем случае. Правильной последовательностью будет масштабирование-поворот-смещение. Если этого не соблюдать, результат вас разочарует.

Масштабирование
С масштабированием все относительно просто, т.к. длины векторов {a, b} и {c, d} соответствуют масштабу по осям x и y, то достаточно нормализовать эти вектора для применения нового масштаба. После чего необходимо будет ввести поправку для положения начала локальных координат (вектор {tx, ty}). Для этого нам необходима разница между положением точки вращения (без учета смещения) до масштабирования и после.

Код AS3:
private function scaleAround(initialMatrix:Matrix, pivot:Point, sx:Number, sy:Number):Matrix
{
	var m:Matrix = initialMatrix.clone();
	var delta:Point = m.deltaTransformPoint(pivot);
 
	var i:Point = new Point(m.a, m.b);
	var j:Point = new Point(m.c, m.d);
 
	i.normalize(sx);
	j.normalize(sy);
 
	m.a = i.x;
	m.b = i.y;
	m.c = j.x;
	m.d = j.y;
 
	delta = delta.subtract(m.deltaTransformPoint(pivot));
 
	m.translate(delta.x, delta.y);
 
	return m;
}
После оптимизации код примет следующий вид:
Код AS3:
private function scaleAround(initialMatrix:Matrix, pivot:Point, sx:Number, sy:Number):Matrix
{
	var m:Matrix = initialMatrix.clone();
 
	sx /= Math.sqrt(m.a * m.a + m.b * m.b);
	sy /= Math.sqrt(m.c * m.c + m.d * m.d);
 
	m.tx -= m.a * (sx - 1) * pivot.x + m.c * (sy - 1) * pivot.y;
	m.ty -= m.b * (sx - 1) * pivot.x + m.d * (sy - 1) * pivot.y;
 
	m.a *= sx;
	m.b *= sx;
	m.c *= sy;
	m.d *= sy;
 
	return m;
}
Если необходимо относительное масштабирование, то достаточно создать новую матрицу масштабирования и сцепить (умножить) ее с матрицей объекта.

Вращение
С вращением немного сложнее, так как вращение идет вторым в порядке умножения матриц. Для реализации абсолютного вращения необходимо сначала повернуть объект в нулевое положение (или вычислить разницу между текущим и желаемым углом поворота), а затем уже повернуть на требуемый угол.
Для начала условимся, что угол поворота будем определять по ориентации оси x (см. предыдущий пост).
Код AS3:
var i:Point = new Point(m.a, m.b);
i.normalize(1);
Для того чтобы вернуть объект в нулевое положение, достаточно инвертировать матрицу поворота.
Код AS3:
var r:Matrix = new Matrix(i.x, i.y, -i.y, i.x);
r.invert();
От этой операции можно избавиться если учесть, что для матрицы поворота инвертированная матрица равна транспонированной.
Код AS3:
var r:Matrix = new Matrix(i.x, -i.y, i.y, i.x);
После этих манипуляций мы можем добавить вращение на необходимый угол.
На следующем этапе необходимо избавиться от смещения, так как для корректного результата вращение должно происходить вокруг нулевой точки. После чего умножаем исходную матрицу на полученную, возвращаем смещение и вводим поправку, как и в случае с масштабированием.

Код AS3:
private function rotateAround(initialMatrix:Matrix, pivot:Point, angle:Number):Matrix
{
	var m:Matrix = initialMatrix.clone();
 
	var delta:Point = m.deltaTransformPoint(pivot);
 
	var i:Point = new Point(m.a, m.b);
	i.normalize(1);
 
	var r:Matrix = new Matrix(i.x, -i.y, i.y, i.x);
	r.rotate(angle);
 
	var tx:Number = m.tx;
	var ty:Number = m.ty;
 
	m.tx = 0;
	m.ty = 0;
 
	m.concat(r);
 
	delta = delta.subtract(m.deltaTransformPoint(pivot));
	m.translate(tx, ty);
	m.translate(delta.x, delta.y);
 
	return m;
}
После оптимизации код примет следующий вид:
Код AS3:
private function rotateAround(initialMatrix:Matrix, pivot:Point, angle:Number):Matrix
{
	var m:Matrix = initialMatrix.clone();
 
	var scale:Number = Math.sqrt(m.a * m.a + m.b * m.b);
	var xx:Number = m.a / scale;
	var xy:Number = m.b / scale;
 
	var sinA:Number = Math.sin(angle);
	var cosA:Number = Math.cos(angle);
 
	var dx:Number = xx * cosA - xy * sinA;
	var dy:Number = xy * cosA + xx * sinA;
 
	var a:Number = m.a * dx + m.b * dy;
	var b:Number = m.b * dx - m.a * dy;
	var c:Number = m.c * dx + m.d * dy;
	var d:Number = m.d * dx - m.c * dy;
 
	m.tx += (m.a - a) * pivot.x + (m.c - c) * pivot.y;
	m.ty += (m.b - b) * pivot.x + (m.d - d) * pivot.y;
	m.a = a;
	m.b = b;
	m.c = c;
	m.d = d;
 
	return m;
}
Еще одной из распространенных задач, является поворот в сторону другого объекта, или другими словами точки. Принцип остается тот же, что и для обычного поворота. Единственная разница состоит в том, что вместо использования метода rotate добавляется матрица поворота на объект.

Код AS3:
private function rotateAroundTo(initialMatrix:Matrix, pivot:Point, target:Point):Matrix
{
	var m:Matrix = initialMatrix.clone();
 
	var d:Point = m.deltaTransformPoint(pivot);
 
	var i:Point = new Point(m.a, m.b);
	i.normalize(1);
 
	target = target.clone();
	target.normalize(1);
 
	var tx:Number = m.tx;
	var ty:Number = m.ty;
 
	m.tx = 0;
	m.ty = 0;
 
	var r:Matrix = new Matrix(i.x, -i.y, i.y, i.x);
 
	var tr:Matrix = new Matrix(target.x, target.y, -target.y, target.x);
	r.concat(tr);
 
	m.concat(r);
 
	d = d.subtract(m.deltaTransformPoint(pivot));
	m.translate(tx, ty);
	m.translate(d.x, d.y);
 
	return m;
}
После оптимизации код примет следующий вид:
Код AS3:
private function rotateAroundTo(initialMatrix:Matrix, pivot:Point, target:Point):Matrix
{
	var m:Matrix = initialMatrix.clone();
 
	var scale:Number = Math.sqrt(m.a * m.a + m.b * m.b);
	var xx:Number = m.a / scale;
	var xy:Number = m.b / scale;
 
	target = target.clone();
	target.normalize(1);
 
	var dx:Number = xx * target.x - xy * target.y;
	var dy:Number = xy * target.x + xx * target.y;
 
	var a:Number = m.a * dx + m.b * dy;
	var b:Number = m.b * dx - m.a * dy;
	var c:Number = m.c * dx + m.d * dy;
	var d:Number = m.d * dx - m.c * dy;
 
	m.tx += (m.a - a) * pivot.x + (m.c - c) * pivot.y;
	m.ty += (m.b - b) * pivot.x + (m.d - d) * pivot.y;
	m.a = a;
	m.b = b;
	m.c = c;
	m.d = d;
 
	return m;
}
Так как вращение на угол и ориентирование на точку по-сути равнозначны, мы можем выразить одно через другое.
Код AS3:
private function rotateAround(initialMatrix:Matrix, pivot:Point, angle:Number):Matrix
{
	return rotateAroundTo(initialMatrix, pivot, Point.polar(1, angle));
}
transformations.swf   (12.6 Кб)

Начальная матрица объекта (a=1, b=0.17632697522640228, c=0.17632697522640228, d=1, tx=100, ty=100). Щелчок по объекту создаст точку вращения.
Вложения
Тип файла: swf transformations.swf (12.6 Кб, 1096 просмотров)
Всего комментариев 5

Комментарии

Старый 05.02.2015 20:09 Zebestov вне форума
Zebestov
 
Аватар для Zebestov
Отлично!
Старый 06.02.2015 10:11 Tails вне форума
Tails
 
Аватар для Tails
Очень полезная статейка, спасибо за труд!
Старый 06.02.2015 14:06 MikroAcse вне форума
MikroAcse
 
Аватар для MikroAcse
*Добавить в закладки*

Спасибоо
Старый 06.02.2015 14:12 УильямБрэдберри вне форума
УильямБрэдберри
 
Аватар для УильямБрэдберри
Вот ещё интересное чтиво на тему вращения =)
Старый 06.02.2015 14:55 alatar вне форума
alatar
 
Аватар для alatar
Тогда уже так и так. Какой смысл давать ссылку на одну из реализаций без теории. Ну и Vector3D, собственно, является кватернионом.
 

 


Часовой пояс GMT +4, время: 17:13.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.