Вращение матрицы преобразования не сохраняет масштабирование локальной оси?

У меня есть простой класс преобразования для применения переводов, масштабов и поворотов в div в любом произвольном порядке:

class TransformDiv{

  constructor(div)
  {
    this.div = div;
    this.translateX = 0;
    this.translateY = 0;
    this.scaleX = 1;
    this.scaleY = 1;
    this.shearX = 0;
    this.shearY = 0;
  }
  
  translate(x, y)
  {
    this.translateX += x;
    this.translateY += y;
    this.setTransform();
  }
  
  scale(x, y, anchorX = 0, anchorY = 0)
  {
    this.scaleX *= x;
    this.shearX *= x;
    this.scaleY *= y;
    this.shearY *= y;
    this.translateX -= (this.translateX - anchorX) * (1 - x);
    this.translateY -= (this.translateY - anchorY) * (1 - y);
    this.setTransform();
  }
  
  rotate(rad, anchorX = 0, anchorY = 0)
  {
    let cos = Math.cos(rad);
    let sin = Math.sin(rad);
    
    // the composition of two successive rotations are additive
    let newScaleX = this.scaleX * cos + this.shearX * sin;
    let newShearX = this.scaleX * (-sin) + this.shearX * cos;
    let newShearY = this.shearY * cos + this.scaleY * sin;
    let newScaleY = this.shearY * (-sin) + this.scaleY * cos;
    this.scaleX = newScaleX;
    this.shearX = newShearX;
    this.shearY = newShearY;
    this.scaleY = newScaleY;
    //rotation about an arbitrary point
    let originX = (this.translateX - anchorX);
    let originY = (this.translateY - anchorY);
    this.translateX -= (originY * sin - originX * (cos - 1));
    this.translateY -= (-originY * (cos - 1) - originX * sin);
    
    this.setTransform();
  }
  
  setTransform()
  {
    this.div.style.transform = `matrix(${this.scaleX}, ${this.shearY}, ${this.shearX}, ${this.scaleY}, ${this.translateX}, ${this.translateY})`;
  }
}

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

Изменить - новый интерактивный пример: https://codepen.io/manstie/pen/RwGGOmB

Вот созданный мной пример: https://jsfiddle.net/ft61q230/1/

В этом примере:

div2.translate(100, 100);
div2.scale(2, 1, 100, 100);
div2.rotate(Math.PI / 2, 100, 100);

Ожидаемый результат - Test 1 Text и Test 2 Text будут иметь одинаковую длину, как если бы вы поворачивались от верхнего левого угла div по часовой стрелке на 90 градусов; но, как вы можете видеть, результат таков, что логика вращения, которую я выполняю, сохраняет масштаб на оси мирового пространства, так что теперь Test 2 Text в два раза выше, чем в два раза длиннее.

Текущий результат:

Текущий результат

Желаемый результат:

Желаемый результат

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

Спасибо за помощь.

Редактировать:

Был рекомендован DOMMatrix, который выполняет всю эту математику для меня, но имеет ту же проблему, хотя есть некоторый перекос, который я не считаю точным:

https://jsfiddle.net/heqo7vrt/1/

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


person Manstie    schedule 02.10.2020    source источник


Ответы (1)


Тут удалось исправить:

Обычный: https://jsfiddle.net/sbca61k5/.

let newScaleX = cos * this.scaleX + sin * this.shearY;
let newShearX = cos * this.shearX + sin * this.scaleY;
let newShearY = -sin * this.scaleX + cos * this.shearY;
let newScaleY = -sin * this.shearX + cos * this.scaleY;

Версия DOMMatrix: https://jsfiddle.net/b36kqrsg/

this.matrix = new DOMMatrix([cos, sin, -sin, cos, 0, 0]).multiply(this.matrix);
// or
this.matrix = new DOMMatrix().rotate(deg).multiply(this.matrix);

Разница в том, что матрица вращения умножается на остальную часть матрицы, чтобы добавить ее, а не наоборот:

[a c e]   [cos -sin 0]   [scx shy tx]
[b d f] = [sin  cos 0] . [shx scy ty]
[0 0 1]   [0    0   1]   [0   0   1 ]

Я не уверен в деталях математики привязки, но привязка версии DOMMatrix находится относительно своего собственного левого верхнего угла, а другая - относительно левого верхнего угла документа.

В моем интерактивном примере математика привязки не работает, так как после множества поворотов объекты удаляются дальше от точки привязки. https://codepen.io/manstie/pen/PoGXMed

person Manstie    schedule 04.02.2021