Трансформации вокруг произвольной точки
Эпизодически на форуме появляются вопросы типа: "как повернуть объект вокруг точки", "как сдвинуть transform point" и т.д. Как правило, ответы представляют собой частные случаи, вроде, сдвинуть-повернуть-сдвинуть для вращения или с использованием кучи тригонометрии, которые не учитывают, что объект уже может быть трансформирован до того как попадет на вход функции вращения.
В данной статье рассмотрены методы на которые не влияют начальные трансформации объекта.
Примечание: Важно помнить, что порядок умножения матриц играет роль. Т.е. MxN != NxM, в общем случае. Правильной последовательностью будет масштабирование-поворот-смещение. Если этого не соблюдать, результат вас разочарует.
Масштабирование
С масштабированием все относительно просто, т.к. длины векторов {a, b} и {c, d} соответствуют масштабу по осям x и y, то достаточно нормализовать эти вектора для применения нового масштаба. После чего необходимо будет ввести поправку для положения начала локальных координат (вектор {tx, ty}). Для этого нам необходима разница между положением точки вращения (без учета смещения) до масштабирования и после.
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; }
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 (см. предыдущий пост).
Для того чтобы вернуть объект в нулевое положение, достаточно инвертировать матрицу поворота.
От этой операции можно избавиться если учесть, что для матрицы поворота инвертированная матрица равна транспонированной.
После этих манипуляций мы можем добавить вращение на необходимый угол.
На следующем этапе необходимо избавиться от смещения, так как для корректного результата вращение должно происходить вокруг нулевой точки. После чего умножаем исходную матрицу на полученную, возвращаем смещение и вводим поправку, как и в случае с масштабированием.
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; }
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; }
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; }
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; }
private function rotateAround(initialMatrix:Matrix, pivot:Point, angle:Number):Matrix { return rotateAroundTo(initialMatrix, pivot, Point.polar(1, angle)); }
Начальная матрица объекта (a=1, b=0.17632697522640228, c=0.17632697522640228, d=1, tx=100, ty=100). Щелчок по объекту создаст точку вращения.
Всего комментариев 5
Комментарии
05.02.2015 20:09 | |
Отлично!
|
06.02.2015 10:11 | |
Очень полезная статейка, спасибо за труд!
|
06.02.2015 14:06 | |
*Добавить в закладки*
Спасибоо |
06.02.2015 14:12 | |
Вот ещё интересное чтиво на тему вращения =)
|
06.02.2015 14:55 | |
Последние записи от alatar
- Трансформации вокруг произвольной точки (05.02.2015)
- Декомпозиция матрицы. (25.12.2014)
- Баги TextField в iOS (29.07.2013)
- [Spark] Стрелочный индикатор. (06.12.2012)
- RSLs Monkey Patching (23.01.2012)