Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling CSS cube rotation(transform) and extracting values from 3d matrix

I made CSS cube that I'm rotating using up/down and left/right keys but I'm having problems regarding rotation direction.

Attempt #1

DEMO

Using this article I managed to bind keys and apply rotation to the cube. My first problem was that CSS transform function rotates elements axes so when, ie. I press up, Y and Z axis change place. I adjusted original code for that case, but another problem is, since axes are vectors, when I press up 2 times X and Z are back in place but vectors are inverted (left key starts rotating cube to the right and vice-versa), so now I must rotate cube in opposite direction to get desired result and I don't have idea how to detect wheather axis are inverted.

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});  

});

Attempt #2

DEMO

I made another version using methods from this article which tries to solve same problem by decomposing and then updating css 3d matrix but it has other problems. After repeatedly pressing arrows in random directions cube changes viewing angle (more than one side is visible at once).

It would be great when I could get back rotated values or vector direction from 3d matrix but none of solutions I found seems to work. I'm guessing because 3d matrix is derived by multipling values from all functions that are passed (rotateX, rotateY and translateZ) and that kind of math is way over my head to figure out.

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);

});

Attempt #3

DEMO

Third version I made rotates each side seperately and changes classes after rotation so I always just rotate X and Y in right direction, but while rotation is happening cube gets decomposed and I think up and down rotation is wrong (plus code is kinda bloated and ugly). Only plus side of this approach is bigger cross-browser compatibility for browsers that don't support preserve-3d property.

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;
    }

});

REFERENCE MATERIAL:

  • Creating basic CSS cube
  • MDN docs on transform property
  • W3.org documentation on 3D transforms
  • 3dmatrix documentation
  • webkitCSSMatrix documentation
like image 913
Teo Dragovic Avatar asked Dec 16 '13 15:12

Teo Dragovic


1 Answers

The problem with the attempt 2 is that rotateAxisAngle does the matrix multiplication in the oposite order of what you want. And, worse still, there is no function in the class to do the multiplication in the order that you want.

As an alternate way, I have choose to use the browser itself to do the math. I create a div that will be hidden, and where I will apply the transforms to get the new matrix.

With this approach, the javascript gets even shorter:

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);

});

I think that the code is quite self explanatory: I apply the transform to the element as a composite of the new transform and the current transform (you don't need to extract the values from the matrix, can be applied as is)

demo

(I don't know why, it didn't work in codepen. have moved it to fiddle ...)

Finally I got the * Firefox to behave !

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);

});

A little bit more complex code, but makes to the browser perfectly clear what you want it to do ... Works fine as long as you wait till the transition is over.

like image 154
vals Avatar answered Oct 20 '22 12:10

vals