Как применить несколько вращений осей к одному вызову rotate3d ()? - PullRequest
2 голосов
/ 18 апреля 2019

Я пытаюсь использовать одну функцию rotate3d для вращения нескольких осей куба одновременно, но я получаю странное вращение.

Если я использую transform: rotateX(-30deg) rotateY(45deg), я получаю красивый угол, но если я делаю transform: rotate3d(-2, 3, 0, 15deg), этот угол выглядит странно.

* { box-sizing: border-box; }

.scene {
  width: 50px;
  height: 50px;
}

.cube {
  width: 50px;
  height: 50px;
  margin: 50px;
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(-30deg) rotateY(45deg); /* works as expected */
  /* transform: rotate3d(-2, 3, 0, 15deg); */ /* looks very weird */
  transition: transform 1s;
}

.cube__face {
  position: absolute;
  width: 50px;
  height: 50px;
  border: 1px solid black;
}

.cube__face--front  {
  background: hsla(  0, 100%, 50%, 0.7);
  transform: rotateY(  0deg) translateZ(25px);
}
.cube__face--right  {
  background: hsla( 60, 100%, 50%, 0.7);
  transform: rotateY( 90deg) translateZ(25px);
}
.cube__face--back   {
  background: hsla(120, 100%, 50%, 0.7);
  transform: rotateY(180deg) translateZ(25px);
}
.cube__face--left   {
  background: hsla(180, 100%, 50%, 0.7);
  transform: rotateY(-90deg) translateZ(25px);
}
.cube__face--top    {
  background: hsla(240, 100%, 50%, 0.7);
  transform: rotateX( 90deg) translateZ(25px);
}
.cube__face--bottom {
  background: hsla(300, 100%, 50%, 0.7);
  transform: rotateX(-90deg) translateZ(25px);
}
<div class="cube">
  <div class="cube__face cube__face--front"></div>
  <div class="cube__face cube__face--back"></div>
  <div class="cube__face cube__face--right"></div>
  <div class="cube__face cube__face--left"></div>
  <div class="cube__face cube__face--top"></div>
  <div class="cube__face cube__face--bottom"></div>
</div>

Я знаю, что мне, вероятно, нужно применить некоторые математические приемы, чтобы заставить это работать, но я понятия не имею о линейной алгебре. Есть ли формула для достижения того, чего я хочу?

РЕДАКТ. 1:

Я нашел этот вопрос, который похож на мой, но я до сих пор не могу понять его. Я попытался transform: rotate3d(-30, 45, 0, 54deg) (sqrt (30² + 45²) = 54), что ближе к ожидаемому результату, но все еще не соответствует формуле, которую я ищу.

РЕДАКТИРОВАТЬ 2:

Я нашел веб-сайт , чтобы вычислить matrix3d на основе любых других функций преобразования. Полученная матрица для моего начального преобразования равна matrix3d(0.707107, 0.353553, 0.612372, 0, 0, 0.866025, -0.5, 0, -0.707107, 0.353553, 0.612372, 0, 0, 0, -100, 1), и это работает как шарм! Но мне нужно сделать это динамически.

Я могу получить текущий matrix3d моего элемента с помощью window.getComputedStyle, и я нашел некоторые функции для создания матриц вращения и умножения 2 матриц на mdn docs . Но если я создаю матрицу вращения на 90º для оси X и умножаю ее на текущую матрицу, а затем применяю полученную матрицу к преобразованию CSS, куб сходит с ума от преобразований. Я делаю это так:

const matrix = window.getComputedStyle(cube).transform.slice(9, -1).split(', ').map(Number)
const rotation = rotateAroundXAxis(90)
const final = multiplyMatrices(matrix, rotation)
cube.style.transform = `matrix3d(${final.join(', ')})`

Я был бы очень признателен за любую помощь здесь.

1 Ответ

1 голос
/ 20 апреля 2019

TL; DR rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)


Хорошо, я не знаю с чего начать, но позвольте мне объяснить, как работают преобразования.Таким образом, любое трехмерное преобразование, например вращение, перемещение, сдвиг, масштаб и т. Д., Может быть выражено в виде однородной матрицы 4x4.(и, следовательно, css matrix3d принимает 16 значений)

Допустим, у нас есть матрица 4x4 T, и мы хотим преобразовать точку (x, y, z), чтобы новая точка была (x', y', z').Мы можем узнать новую точку, выполнив следующее умножение матриц:

| x' |   | T11 T12 T13 T14 |   | x |
| y' | = | T21 T22 T23 T24 | x | y |
| z' |   | T31 T32 T33 T34 |   | z |
| 1  |   | T41 T42 T43 T44 |   | 1 |

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

| x' |   | T11 T12 T13 |   | x |
| y' | = | T21 T22 T23 | x | y |
| z' |   | T31 T32 T33 |   | z |

Хорошо, теперь давайте сначала выразим rotateX(-30deg) rotateY(45deg) в этой матричной форме.Я собираюсь использовать Rx(Θ) и Ry(Θ), как указано здесь , чтобы найти матрицу чистого преобразования T.Также css поворачивает оси / FOR вместо точки, поэтому -30deg будет 30deg, а 45deg будет -45deg для нас, так как здесь также указано здесь .

T = Ry(-45deg) x Rx(30deg) // order of multiplication is important, what happens first is rightmost then things are added on left

  = |  0.707107  -0.353553  -0.612372 |
    |         0   0.866025       -0.5 |
    |  0.707107   0.353553   0.612372 |

  ≈ |  0.707107  -0.353553  -0.612372  0 | // same as above but 4x4 version
    |         0   0.866025       -0.5  0 | // this is what getComputedStyle gives
    |  0.707107   0.353553   0.612372  0 |
    |         0          0          0  1 |

Расчеты здесь на вольфрам альфа

Вы можете также получить вышеуказанную матрицу T из вычисленных стилей.И используйте это здесь и далее.

Теперь давайте посмотрим, что такое rotate3d.rotate3d(ux, uy, uz, a) будет вращать точку, сохраняя вектор оси u (ux, uy, uz) с углом a.У нас есть преобразование, которое нам нужно сделать, это T.Поэтому теперь нам нужно выразить обобщенное значение T в виде rotate3d.

. Для определения оси мы будем использовать эту формулу.

| ux |   |  (0.353553) - (-0.5)      |   |  0.853553 |
| uy | = | (-0.612372) - (0.707107)  | = | -1.319479 |
| uz |   |         (0) - (-0.353553) |   |  0.353553 |

Мы будем использовать эту формулу для определения угла.

0 = arccos((0.707107 + 0.866025 + 0.612372 - 1) / 2)
  = 0.936325 rad // ie -0.936325 rad according to CSS convention

Итак, наконец rotateX(-30deg) rotateY(45deg) такое же, как rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)

Демонстрация:

[...document.querySelectorAll("[name='transform']")]
.forEach(radio => {
    radio.addEventListener("change", () => {
       let selectedTransform = document.querySelector("[name='transform']:checked").value;
       let cubeClasses = document.querySelector(".cube").classList;
       cubeClasses.remove("transform-a", "transform-b", "transform-c");
       cubeClasses.add(selectedTransform)
    })
})
* { box-sizing: border-box; }

.scene {
  width: 50px;
  height: 50px;
}

.cube {
  width: 50px;
  height: 50px;
  margin: 50px;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 1s;
}

.cube.transform-a {
  transform: rotateX(-30deg) rotateY(45deg);
}

.cube.transform-b {
  transform: rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad);
}

.cube.transform-c {
  transform: matrix3d(0.707107, -0.353553, -0.612372, 0, 0, 0.866025, -0.5, 0, 0.707107, 0.353553, 0.612372, 0, 0, 0, 0, 1);
}

.cube__face {
  position: absolute;
  width: 50px;
  height: 50px;
  border: 1px solid black;
}

.cube__face--front  {
  background: hsla(  0, 100%, 50%, 0.7);
  transform: rotateY(  0deg) translateZ(25px);
}
.cube__face--right  {
  background: hsla( 60, 100%, 50%, 0.7);
  transform: rotateY( 90deg) translateZ(25px);
}
.cube__face--back   {
  background: hsla(120, 100%, 50%, 0.7);
  transform: rotateY(180deg) translateZ(25px);
}
.cube__face--left   {
  background: hsla(180, 100%, 50%, 0.7);
  transform: rotateY(-90deg) translateZ(25px);
}
.cube__face--top    {
  background: hsla(240, 100%, 50%, 0.7);
  transform: rotateX( 90deg) translateZ(25px);
}
.cube__face--bottom {
  background: hsla(300, 100%, 50%, 0.7);
  transform: rotateX(-90deg) translateZ(25px);
}
<div class="cube transform-b">
  <div class="cube__face cube__face--front"></div>
  <div class="cube__face cube__face--back"></div>
  <div class="cube__face cube__face--right"></div>
  <div class="cube__face cube__face--left"></div>
  <div class="cube__face cube__face--top"></div>
  <div class="cube__face cube__face--bottom"></div>
</div>
<label>
  <input type="radio" name="transform" value="transform-a"/>
  <code>rotateX(-30deg) rotateY(45deg)</code>
</label>
<label><br>
  <input type="radio" name="transform" value="transform-b" checked/>
  <code>rotate3d(0.853553, -1.319479, 0.353553, -0.936325rad)</code>
</label>
<label><br>
  <input type="radio" name="transform" value="transform-c"/>
  <code>matrix3d(0.707107, -0.353553, -0.612372, 0, 0, 0.866025, -0.5, 0, 0.707107, 0.353553, 0.612372, 0, 0, 0, 0, 1)</code>
</label>

Вся эта сложность и математика, а затем они говорят: «ССС НЕ ПРОГРАММИРУЕТСЯ» «ССС ПРОСТО» xD: P

Вот ванильная реализация JS для вычисления rotate3d:

class Matrix {
  
  constructor(raw) {
    this.raw = raw;
  }
  
  static ofRotationX(a) {
    return new Matrix([
      [1, 0, 0],
      [0, Math.cos(a), -Math.sin(a)],
      [0, Math.sin(a), Math.cos(a)]
    ])
  }
  
  static ofRotationY(a) {
    return new Matrix([
      [Math.cos(a), 0, Math.sin(a)],
      [0, 1, 0],
      [-Math.sin(a), 0, Math.cos(a)]
    ])
  }
  
  static ofRotationZ(a) {
    return new Matrix([
      [Math.cos(a), -Math.sin(a), 0],
      [Math.sin(a), Math.cos(a), 0],
      [0, 0, 1],
    ])
  }
  
  get trace() {
    let { raw } = this;
    return raw[0][0] + raw[1][1] + raw[2][2];
  }
  
  multiply(matB) {
    let { raw: a } = this;
    let { raw: b } = matB;
    return new Matrix([
      [
        a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
        a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
        a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2]
      ],
      [
        a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
        a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
        a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2]
      ],
      [
        a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
        a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
        a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2]
      ]
    ]);
  }
}


function getRotate3d(transMat) {
    let { raw: t } = transMat;
    return {
      axis: [t[2][1] - t[1][2], t[0][2] - t[2][0], t[1][0] - t[0][1]],
      angle: -1 * Math.acos((transMat.trace - 1)/2)
    }
}

console.log(getRotate3d(
  Matrix.ofRotationY(-45 * Math.PI/180)
  .multiply(Matrix.ofRotationX(30 * Math.PI/180))
));
...