Управление поворотом (преобразованием) куба CSS и извлечением значений из 3d-матрицы

Я сделал куб CSS, который я вращаю, используя клавиши вверх/вниз и влево/вправо, но у меня возникают проблемы с направлением вращения.

Попытка # 1

DEMO

Используя в этой статье Мне удалось привязать ключи и применить поворот к кубу. Моя первая проблема заключалась в том, что функция CSS transform вращает оси элементов так, когда, т.е. Я нажимаю вверх, место смены оси Y и Z. Я скорректировал исходный код для этого случая, но другая проблема заключается в том, что оси - это векторы, когда я нажимаю 2 раза. X и Z вернулись на место, но векторы инвертированы (левая клавиша запускает вращающийся куб вправо и наоборот), поэтому теперь я должен вращать куб в противоположном направлении, чтобы получить желаемый результат, и у меня нет идеи о том, как определить, что ось оси инвертирована.

JavaScript

var xAngle = 0,
    yAngle = 0,
    zAngle = 0,
    cube = $("#cube");

$(document).keydown(function(e) { //keyup maybe better?

  e.preventDefault();

  var key = e.which,
      arrow = {left: 37, up: 38, right: 39, down: 40},
      x = xAngle/90,
      y = yAngle/90;

    switch(key) {
      case arrow.left:
        if (x%2 == 0)
          yAngle -= 90;
        else
          zAngle += 90;
      break;
      case arrow.up:
        if (y%2 == 0)
          xAngle += 90;
        else
          zAngle -= 90;
      break;
      case arrow.right:
        if (x%2 == 0)
          yAngle += 90;
        else
          zAngle -=90;
      break;
      case arrow.down:
        if (y%2 == 0)
          xAngle -= 90;
        else
          zAngle += 90;
      break;
    }

    var rotate = "rotateX(" + xAngle + "deg) rotateY(" + yAngle + "deg) rotateZ(" + zAngle + "deg)";
    cube.css({"transform":rotate});  

});

Попытка # 2

DEMO

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

Было бы здорово, если бы я смог вернуть повернутые значения или векторное направление из 3d-матрицы, но ни один из решений, которые я нашел, похоже, не работает. Я предполагаю, потому что 3D-матрица получается путем умножения значений из всех передаваемых функций (rotateX, rotateY и translateZ), и эта математика намного превосходит мою голову, чтобы понять.

JavaScript

var Vector = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

WebKitCSSMatrix.prototype.transformVector = function(v) {
    var xOut = this.m11*v.x + this.m12*v.y + this.m13*v.z;
    var yOut = this.m21*v.x + this.m22*v.y + this.m23*v.z;
    var zOut = this.m31*v.x + this.m32*v.y + this.m33*v.z;

    return new Vector(xOut, yOut, zOut);
};

function applyRotation(vector, angle) {

    var cube = $('#cube');

    var matrix = new WebKitCSSMatrix(cube.css('webkitTransform'));

    var vector = matrix.transformVector(vector);

    var newMatrix = matrix.rotateAxisAngle(vector.x, vector.y, vector.z, angle);

    cube.get(0).style.webkitTransform = newMatrix;
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
            arrow = {left: 37, up: 38, right: 39, down: 40},
            v,
            a;

        switch(key) {
            case arrow.left:
                v = new Vector(0,1,0),
                a = -90;
            break;

            case arrow.right:
                v = new Vector(0,1,0),
                a = 90;
            break;

            case arrow.up:
                v = new Vector(1,0,0),
                a = 90;
            break;

            case arrow.down:
                v = new Vector(1,0,0),
                a = -90;
            break;
        }

        applyRotation(v, a);

});

Попытка # 3

DEMO

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

JavaScript

$(document).keyup(function(e) {

  e.preventDefault();

  var key = e.which,
      arrow = {left: 37, up: 38, right: 39, down: 40},
      front = "rotateX(0deg) translateZ(100px)",
      back = "rotateX(180deg) translateZ(100px)",
      right = "rotateY(90deg) translateZ(100px)",
      left = "rotateY(-90deg) translateZ(100px)",
      top = "rotateX(90deg) translateZ(100px)",
      bottom = "rotateX(-90deg) translateZ(100px)";

    switch(key) {
      case arrow.left:
        $(".front").css({"transform":left});
        $(".back").css({"transform":right});
        $(".left").css({"transform":back});
        $(".right").css({"transform":front});
        var front = $(".front");
        var back = $(".back");
        var left = $(".left");
        var right = $(".right");
        front.removeClass("front").addClass("left");
        back.removeClass("back").addClass("right");
        right.removeClass("right").addClass("front");
        left.removeClass("left").addClass("back");
      break;
      case arrow.up:
        $(".front").css({"transform":top});
        $(".back").css({"transform":bottom});
        $(".top").css({"transform":back});
        $(".bottom").css({"transform":front});
        var front = $(".front");
        var back = $(".back");
        var top = $(".top");
        var bottom = $(".bottom");
        front.removeClass("front").addClass("top");
        back.removeClass("back").addClass("bottom");
        top.removeClass("top").addClass("back");
        bottom.removeClass("bottom").addClass("front");
      break;
      case arrow.right:
        $(".front").css({"transform":right});
        $(".back").css({"transform":left});
        $(".left").css({"transform":front});
        $(".right").css({"transform":back});
        var front = $(".front");
        var back = $(".back");
        var left = $(".left");
        var right = $(".right");
        front.removeClass("front").addClass("right");
        back.removeClass("back").addClass("left");
        right.removeClass("right").addClass("back");
        left.removeClass("left").addClass("front");
      break;
      case arrow.down:
        $(".front").css({"transform":bottom});
        $(".back").css({"transform":top});
        $(".top").css({"transform":front});
        $(".bottom").css({"transform":back});
        var front = $(".front");
        var back = $(".back");
        var top = $(".top");
        var bottom = $(".bottom");
        front.removeClass("front").addClass("bottom");
        back.removeClass("back").addClass("top");
        top.removeClass("top").addClass("front");
        bottom.removeClass("bottom").addClass("back");
      break;
    }

});

СПРАВОЧНЫЙ МАТЕРИАЛ:

Ответ 1

Проблема с попыткой 2 состоит в том, что rotateAxisAngle выполняет матричное умножение в oposite порядке того, что вы хотите. И, что еще хуже, в классе нет функции для выполнения умножения в том порядке, в котором вы хотите.

Как альтернативный вариант, я решил использовать сам браузер для выполнения математики. Я создаю div, который будет скрыт, и где я буду применять преобразования для получения новой матрицы.

При таком подходе javascript становится еще короче:

function applyTransform (transform) {

    var cubeCalculator = $('.cubecalculator');
    var cube = $('#cube');

    var matrix = cubeCalculator.css('webkitTransform');
    var composite = transform + ' ' + matrix;
    cubeCalculator.get(0).style.webkitTransform = composite;

    matrix = cubeCalculator.css('webkitTransform');
    cube.get(0).style.webkitTransform = matrix;
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
        arrow = {left: 37, up: 38, right: 39, down: 40},
        t;

    switch(key) {
        case arrow.left:
            t = 'rotateY(-90deg)';
        break;

        case arrow.right:
            t = 'rotateY(90deg)';
        break;

        case arrow.up:
            t = 'rotateX(90deg)';
        break;

        case arrow.down:
            t = 'rotateX(-90deg)';
        break;
    }

    applyTransform (t);

});

Я думаю, что код вполне объяснителен: я применяю преобразование к элементу в виде составного элемента нового преобразования и текущего преобразования (вам не нужно извлекать значения из матрицы, можно применять как )

демонстрация

(Я не знаю, почему, он не работал в кодефене, переместил его на скрипку...)

Наконец, я получил * Firefox, чтобы вести себя!

function applyTransform (transform1, transform2) {
    var matrix, composite1, composite2;
    var cubeCalculator = $('.cubecalculator');
    var cube = $('#cube');

    matrix = cubeCalculator.css('transform');
    composite1 = transform1 + ' ' + matrix;
    composite2 = transform2 + ' ' + matrix;
    cubeCalculator.get(0).style.transform = composite2;
    cube.get(0).style.transition = 'none';
    cube.get(0).style.transform = composite1;

    window.setTimeout (function() {
        cube.get(0).style.transform = composite2;
        cube.get(0).style.transition = 'transform 1s';
    }, 10   );
}

// rotate using arrow keys
$(document).keyup(function(e) {

    e.preventDefault();

    var key = e.which,
        arrow = {left: 37, up: 38, right: 39, down: 40},
        t1, t2;

    switch(key) {
        case arrow.left:
            t1 = 'rotateY(0deg)';
            t2 = 'rotateY(-90deg)';
        break;

        case arrow.right:
            t1 = 'rotateY(0deg)';
            t2 = 'rotateY(90deg)';
        break;

        case arrow.up:
            t1 = 'rotateX(0deg)';
            t2 = 'rotateX(90deg)';
        break;

        case arrow.down:
            t1 = 'rotateX(0deg)';
            t2 = 'rotateX(-90deg)';
        break;
    }

    applyTransform (t1, t2);

});

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

Ответ 2

Демо 2 почти есть. Ваша проблема в том, что вы используете промежуточное состояние анимации куба для вычисления новой позиции:

var matrix = new WebKitCSSMatrix(cube.css('webkitTransform'));

Вместо этого вы должны сохранить и обновить внутреннюю целевую матрицу:

Кроме того, кажется, что этот фрагмент кода:

var vector = matrix.transformVector(vector);
var newMatrix = matrix.rotateAxisAngle(vector.x, vector.y, vector.z, angle);

Не работает так, как предполагалось. Это делает то, что вы ищете:

var newMatrix = new WebKitCSSMatrix().rotateAxisAngle(...).multiply(matrix)

http://codepen.io/eric-wieser/pen/BoeyD