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

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

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

Декомпозиция матрицы.

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

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

Анатомия
По сути своей матрица трансформации содержит в себе три вектора, базисные векторы осей, описывающие наклон и масштабирование осей дочернего объекта относительно родительского и вектор описывающий смещение начала системы координат дочернего объекта относительно родительского.
Если принять во внимание, что базисный вектор оси x обычно обозначается буквой i, а базисный вектор оси y обозначается как j, то матрица трансформации принимает такой вид:
Код:
i.x i.y t.x
j.x j.y t.y
0   0   1
Угол поворота вектора i соответствует повороту оси x и отвечает за значение свойства rotation (с этим согласны все кроме, создателя Starling), а его длина соответствует свойству scaleX. Вектор j отвечает, соответственно за свойство scaleY.

Извлечение
Я буду приводить по четыре варианта, мои изыскания (соответствуют поведению FP от Adobe, с некоторыми оговорками, которые я опишу ниже) и реализации в lightspark, shumway и Starling.

rotation
Начнем со свойства rotation (с ним меньше всего проблем)
Код AS3:
private function getRotation(m:Matrix):Number
{
	return (getSkewY(m) * 180) / Math.PI;
}
 
private function getSkewY(m:Matrix):Number
{
	return Math.atan2(m.b, m.a);
}
lightspark
Код:
number_t getRotation() const
{
	return atan(yx/xx)*180/M_PI;
}
shumway
DisplayObject
Код:
this._rotation = DisplayObject._clampRotation(this._skewY * 180 / Math.PI);
Matrix
Код:
public getSkewY(): number {
	return Math.atan2(this._data[1], this._data[0]);
}
Starling
Код AS3:
mSkewX = Math.atan(-matrix.c / matrix.d);
mSkewY = Math.atan( matrix.b / matrix.a);
 
if (isEquivalent(mSkewX, mSkewY))
{
	mRotation = mSkewX;
	mSkewX = mSkewY = 0;
}
else
{
	mRotation = 0;
}
Ничего страшного в использовании skewX для расчета вращения нет, если правильно рассчитать направление scaleX и scaleY. Но, в данном случае, проверяется равенство skewX == skewY, которое при условии отрицательности одного из scale выполняться не будет. Как следствие, если scaleX или scaleY, но не оба, будет отрицательным Starling посчитает rotation равным нулю.

scaleX / scaleY
Определить величину масштабирования проблемы не составляет (со скрипом вспоминаем школьную программу и теорему Пифагора), проблема определить направление.
Код AS3:
private function getScaleX(m:Matrix):Number
{
	return Math.sqrt(m.a * m.a + m.b * m.b);
}
 
private function getScaleY(m:Matrix):Number
{
	var res:Number = Math.sqrt(m.d * m.d + m.c * m.c);
 
	return (m.d > 0) && (m.a < 0) || (m.d < 0) && (m.a > 0) ? -res : res;
}
Тут необходимо сделать некоторые пояснения. Не важно по какой оси масштабирование было отрицательным изначально, за счет rotation всегда можно получить тот или иной результат. Например в следующих случаях матрица будет одинакова.
Код:
scaleX = -1  scaleY =  1  rotation = 0
scaleX =  1  scaleY = -1  rotation = 180

(a=-1, b=0, c=0, d=1, tx=0, ty=0)
Код:
scaleX = -1  scaleY = -1  rotation = 0
scaleX =  1  scaleY =  1  rotation = 180

(a=-1, b=0, c=0, d=-1, tx=0, ty=0)
Flash runtime всегда считает, что scaleX положительный, кроме случая когда свойства матрицы b и c равны 0, а свойство d равно 1. В таком случае знак scaleX зависит от свойства a.
Таким образом, становится достаточно проверить свойства a и d, чтобы сделать выводы о направлении масштабирования по оси y. Если a и d имеют одинаковый знак, то scaleY положительный, если разный — отрицательный.

lightspark
Код:
number_t getScaleX() const
{
	number_t ret=sqrt(xx*xx + yx*yx);
	if(xx>0)
		return ret;
	else
		return -ret;
}
number_t getScaleY() const
{
	number_t ret=sqrt(yy*yy + xy*xy);
	if(yy>0)
		return ret;
	else
		return -ret;
}
В lightspark знак масштабирования проверяют по знаку свойства a для scaleX и d для scaleY. Такой подход сработает только для матрицы масштабирования, т.к. после умножения на матрицу поворота на свойства a и d будет оказывать влияние косинус угла поворота и придется пересчитывать свойство rotation для компенсации смены направления масштабирования.

shumway
Код:
getDeterminant() {
	var m = this._data;
	return m[0] * m[3] - m[1] * m[2];
}
getScaleX(): number {
	var m = this._data;
	if (m[0] === 1 && m[1] === 0) {
		return 1;
	}
	var result = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
	return this.getDeterminant() < 0 ? -result : result;
}

getScaleY(): number {
	var m = this._data;
	if (m[2] === 0 && m[3] === 1) {
		return 1;
	}
	var result = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
	return this.getDeterminant() < 0 ? -result : result;
}
Тут было бы все хорошо, если бы детерминант проверялся только для scaleY, а так знак для scaleX м scaleY будет меняться синхронно.

Starling
Код AS3:
const PI_Q:Number = Math.PI / 4.0;
 
mScaleY = (mSkewX > -PI_Q && mSkewX < PI_Q) ?  matrix.d / Math.cos(mSkewX)
                                            : -matrix.c / Math.sin(mSkewX);
mScaleX = (mSkewY > -PI_Q && mSkewY < PI_Q) ?  matrix.a / Math.cos(mSkewY)
                                            :  matrix.b / Math.sin(mSkewY);
Тут все печально. Мало того, что направление масштабирования всегда положительно, так еще и при rotation=0 будет неверно посчитана величина масштабирования по одной из осей. Также странно выглядит сравнение с Math.PI / 4.0, возможно имелось ввиду Math.PI / 2.
Хотя прогресс по сравнению с состоянием до коммита от 16 января налицо.

Дополнительные свойства
Если в дополнение к skewY (наклон оси X) посчитать skewX (наколон оси Y), то можно дополнительно узнать были ли оси наклонены независимо друг от друга.
Код AS3:
private function getSkewY(m:Matrix):Number
{
	return Math.atan2(m.b, m.a);
}
 
private function getSkewX(m:Matrix):Number
{
	return Math.atan2(-m.c, m.d);
}
 
private function isEquivalent(a:Number, b:Number, epsilon:Number=0.0001):Boolean
{
	return (a - epsilon < b) && (a + epsilon > b);
}
...
var skewY:Number = getSkewY(m);
var skewX:Number = getSkewX(m);
 
var diff:Number = skewY - skewX;
 
if (isEquivalent(diff, 0))
{
	//simple rotate
}
else if (isEquivalent(diff, Math.PI) || isEquivalent(diff, -Math.PI))
{
	//rotate and flip;
}
else
{
	//skew;
}
Всего комментариев 24

Комментарии

Старый 25.12.2014 16:32 dimarik вне форума
dimarik
 
Аватар для dimarik
Кратенько, не мог бы ты резюмировать в чем Даниэлька был неправ?
Старый 25.12.2014 16:53 alatar вне форума
alatar
 
Аватар для alatar
Если резюмировать, то если задать его DO матрицу трансформации, то он неверно рассчитает rotation, scaleX и scaleY. По этим значениям не получится восстановить матрицу из которой они получены.
Он почему-то определяет вращение по повороту оси Y. Очень похоже, что он просто их перепутал из-за названия сдвигов (skewY соответствует оси X, а skewX — оси Y). До сих пор осталась лишняя тригонометрия. Есть подозрение, что он путает PI/4 с PI/2. Вообще scaleX/scaleY рассчитываются странно, я так до конца и не понял, что он этим хотел сказать. Очень похоже, что он и сам не понял, что он там насчитал.
Старый 25.12.2014 17:14 dimarik вне форума
dimarik
 
Аватар для dimarik
Еще страннее, что никто с момента существования двигла не обратил на это внимание. До сегодняшнего дня.

Цитата:
skewY соответствует оси X, а skewX — оси Y
Это что-то в виде http://apike.ca/media/svg/exampleMatrixSkewx.svg?
Старый 25.12.2014 17:50 alatar вне форума
alatar
 
Аватар для alatar
Ну как никто не заметил, он же частично исправил, я ссылку дал на коммит, до этого вообще ад был. На отображение это не влияет, эффект будет если кто-то делает проверку свойства или итерацию. Например, сначала задаст матрицу, а потом сделает scaleY -= 2.
Цитата:
Это что-то в виде
Да, при наклоне оси Y вершины/пиксели смещаются вдоль оси X. Отсюда и название.
Старый 25.12.2014 18:22 dimarik вне форума
dimarik
 
Аватар для dimarik
Цитата:
Да, при наклоне оси Y вершины/пиксели смещаются вдоль оси X. Отсюда и название.
Когда размышлял о геометрическом смысле skew(X), то думал не о "наклоне" оси ординат как таковой, а о коэффициенте в линейном уравнении, который указывает на смещение относительно оси абсцисс.
Старый 26.12.2014 10:14 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Я вот думаю что когда нам нужны векторы, то есть отдельно взятые позиция, поворот и масштаб, то и использовать нужно векторы. А а когда нужны матричные операции - тогда использовать матрицы.
Вероятно, это звучит по-капитански, но все же. Я когда-то давно использовал сущность, которая инкапсулировала и векторы, и матрицу. Соответственно, у меня никогда не было проблем в работе с трансформациями. И восстановлением векторов из матрицы тоже заниматься не приходилось.
Старый 28.12.2014 12:15 alatar вне форума
alatar
 
Аватар для alatar
Цитата:
а о коэффициенте в линейном уравнении, который указывает на смещение относительно оси абсцисс.
Можно и так представить, но этот коэффициент будет тангенсом угла наклона оси.
Старый 28.12.2014 12:24 alatar вне форума
alatar
 
Аватар для alatar
@gloomyBrain как-то не уловил сути о чем ты. Матрицы не особо отделимы от векторов, они из одного раздела математики. Как вектор можно представить в виде матрицы, так и матрицу в виде векторов.
Цитата:
И восстановлением векторов из матрицы тоже заниматься не приходилось.
API у DisplayObject такое.
Старый 29.12.2014 11:54 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
@alatar, я, собтсвенно, о том, что нет необходимости вычислять что-то (например, вектор поворота из матрицы), можно просто хранить в одной структуре два представления.
Старый 29.12.2014 13:18 alatar вне форума
alatar
 
Аватар для alatar
Я тебе даже больше скажу, нет необходимости хранить дополнительные структуры, у тебя уже есть матрица трансформации. Но повторюсь еще раз у DisplayObject такое API и если он будет выдавать неверные данные, рано или поздно у кого-то что-то сломается.
Старый 12.01.2015 11:41 dark256 вне форума
dark256
 
Аватар для dark256
Прошу прощение за невежество, но где это все применяется-то?
Примерчик бы или картиночку.
Старый 12.01.2015 16:00 alatar вне форума
alatar
 
Аватар для alatar
Примерчики Sprite, Shape, MovieClip. Мы можем "переместить" DisplayObject задав ему свойства x, y, rotation, scaleX, scaleY (width, height), после чего он сформирует матрицу трансформации, которая будет использоваться в дальнейшем для его отображения. Или задать матрицу трансформации напрямую, после чего он заново вычислит на ее основе свойства x, y, rotation, scaleX, scaleY (width, height).
В общем применяется в реализациях DisplayObject.
Старый 12.01.2015 16:19 nubideus вне форума
nubideus
dark256, тс хочет обокласть старлинг, это же очевидно.

Цитата:
но где это все применяется-то
матрицы используются в 2d и 3d движках потому что они клевые и умеют клево умножаться, если кратко. если развернуто, то это математическая фигня которую нужно осаждать неделю что бы вьехать, и которая понадобится по-любому
Старый 12.01.2015 16:47 alatar вне форума
alatar
 
Аватар для alatar
nubideus, еще shumway и lightspark забыли. Но, вообще-то, статья не об "обокласть".
Старый 06.02.2015 14:08 УильямБрэдберри вне форума
УильямБрэдберри
 
Аватар для УильямБрэдберри
Ну, ещё про кватернионы "забыли", помимо лайтспарка =)
Тема интересная, спасибо.
Старый 06.02.2015 15:02 alatar вне форума
alatar
 
Аватар для alatar
Тут просто конкретно о 2D матрице, потому и про кватернионы ни слова.
Старый 06.02.2015 17:00 УильямБрэдберри вне форума
УильямБрэдберри
 
Аватар для УильямБрэдберри
Я было подумал, что тема разовьётся
Старый 08.02.2015 13:40 alatar вне форума
alatar
 
Аватар для alatar
Вопрос куда её развивать? По 3D написана не одна тонна статей, если есть интересная проблема, то подскажите.
Старый 19.02.2015 23:18 dimarik вне форума
dimarik
 
Аватар для dimarik
Статья реально крутая. Что-то из разряда "вот! вот как оно работает!" Но вот вопрос. Полез в гугл и... А на каком этапе мы смогли сделать декомпозицию матрицы?
Старый 20.02.2015 00:18 alatar вне форума
alatar
 
Аватар для alatar
Согласен. заголовок получился не очень корректным с математической точки зрения. Я был бы благодарен за предложенное, более корректное названия.
Старый 20.02.2015 00:21 alatar вне форума
alatar
 
Аватар для alatar
Ну а по поводу "крутости" для последнего параграфа есть более простой, с точки зрения вычислительной сложности, способ.
Старый 26.03.2015 14:47 dimarik вне форума
dimarik
 
Аватар для dimarik
Очень полезная статья, я использовал способ "декомпозиции", которым пользуется flash runtime. Спасибо, alatar.
Старый 27.03.2015 18:12 Astraport вне форума
Astraport
 
Аватар для Astraport
А Даниэль извещен об этом?
Старый 29.03.2015 15:24 alatar вне форума
alatar
 
Аватар для alatar
Я не извещал, я пока не использую Starling и мне пока безразлично, что там есть, а чего нет. Просто его код подруку попался. Да и времени, все это оформить в pull request нет.
 

 


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


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