loremIpsum1771
loremIpsum1771

Reputation: 2527

How to implement rotate method for html5 canvas

I've created and some shapes (pacman looking objects) using javascript and I am animating them using matrix transformations built into the canvas. The objects can move in four directions (left, right, up, down) and the shapes essentially will move back and forth across the canvas until another button is pressed. These functionalities work properly when I use the built in canvas transformation functions; however, when I try to implement my own transformations some weird things are happening.

In the normal cases (when I use the canvas transformations) I first am translating the canvas origin to the point that is being rotated around, performing the rotation around this "new" origin, and then translating the origin back to what it was before, seen in the following code:

function renderContent(pm) {
            var t = new Transform();
            context.save();
            context.beginPath();
            context.fillStyle = "Yellow";
            context.strokeStyle = "Yellow";
            context.save();
            var tCopy1 = t;
            context.translate(pm.posX, pm.posY);
            context.rotate(-pm.direction * Math.PI / 180);
            context.translate(-pm.posX, -pm.posY);
            context.arc(pm.posX, pm.posY, pm.size, (pm.startAngle) * Math.PI, (pm.endAngle) * Math.PI);
            context.lineTo(pm.posX, pm.posY);
            context.stroke();
            context.fill();
            var m = t.m;
            t.transform(m);
            context.restore();
            context.restore();
        }

To implement my own transformations, I have a transform class with functions including:

function Transform() {
            this.identity();
        }
        Transform.prototype.identity = function () {
            this.m = [1, 0, 0, 1, 0, 0];
        };
        Transform.prototype.rotate = function (rad) {
            var c = Math.cos(rad);
            var s = Math.sin(rad);
            var m11 = this.m[0] * c + this.m[2] * s;
            var m12 = this.m[1] * c + this.m[3] * s;
            var m21 = this.m[0] * -s + this.m[2] * c;
            var m22 = this.m[1] * -s + this.m[3] * c;
            this.m[0] = m11;
            this.m[1] = m12;
            this.m[2] = m21;
            this.m[3] = m22;
        };
        Transform.prototype.translate = function (x, y) {
            this.m[4] += this.m[0] * x + this.m[2] * y;
            this.m[5] += this.m[1] * x + this.m[3] * y;
        };

        Transform.prototype.scale = function (sx, sy) {
            this.m[0] *= sx;
            this.m[1] *= sx;
            this.m[2] *= sy;
            this.m[3] *= sy;
        };

In the renderContext() function, I use essentially the same function calls for translate() and rotate() (which enable the pacman objects to turn around when they hit the canvas borders) but for some reason, with my implementation, the objects don't turn around (180 degrees).

For my transformations I essentially instantiate a transform object, call the transformation operations on the object, make a copy of the transform matrix and then set the canvas with the resultant matrix from the transformations: E.G.:

var t = new Transform();
var tCopy1 = t;
tCopy1.translate(pm.posX, pm.posY);
t.rotate(-pm.direction * Math.PI / 180);
t.translate(-pm.posX, -pm.posY);
...
var m = t.m;
t.transform(m);

This is probably a lot of code to look at, but what is the correct way of implementing the rotation matrix transformation (as the canvas.rotate() is working)?

Upvotes: 2

Views: 181

Answers (1)

wolfhammer
wolfhammer

Reputation: 2661

Here's the pacman rotating clockwise about the direction angle.

update

Now showing transform error in purple. Rotation looks correct but translation is off.

update 2

I think my previous test was bad. The transform looks good and there was only some minor adjustment of to_radians. The new test shows the context ranslation/rotation followed by the Transform class.

var can = document.getElementById('can');
var context = can.getContext('2d');
var to_rad = Math.PI / 180; // to radians

function main() {
  context.fillStyle="black";
  context.fillRect(0, 0, can.width, can.height);

  var pm = {
    direction: 0,
    posX: 50,
    posY: 100,
    size: 20,
    startAngle: 45,
    endAngle: 315
  };

  var i = 0;
  
  function loopTest() {
    pm.direction = i * 90;
    pm.posX = 50 * (i+1);
    renderContent(pm);
    setTimeout(function(){
      renderContent2(pm);
    }, 1000);
    i++;
    if (i < 4) {
      setTimeout(loopTest, 2000);
    }
  }

  loopTest();



}

function renderContent(pm) {
  context.save();
  context.beginPath();
  context.fillStyle = "Yellow";
  context.strokeStyle = "Yellow";
  
  context.translate(pm.posX, pm.posY);
  context.rotate(pm.direction * to_rad);
  context.translate(-pm.posX, -pm.posY);
  context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad);
  context.lineTo(pm.posX, pm.posY);
  context.stroke();
  context.fill();

  context.fillStyle="red";
  context.font="16px Arial";
  context.textAlign="center";
  context.fillText(pm.direction, pm.posX,pm.posY+pm.size);
  context.restore();
}



function renderContent2(pm) {
  var t = new Transform();
  context.save();
  context.beginPath();
  context.fillStyle = "#990099";
  context.strokeStyle = "#990099";
  t.translate(pm.posX, pm.posY);
  t.rotate(pm.direction * to_rad);
  t.translate(-pm.posX, -pm.posY);
  context.transform.apply(context, t.m);
  context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad);
  context.lineTo(pm.posX, pm.posY);
  context.stroke();
  context.fill();
  context.fillStyle="White";
  context.font="16px Arial";
  context.textAlign="center";
  context.fillText(pm.direction, pm.posX,pm.posY+pm.size);
  context.restore();
}


function Transform() {
  this.identity();
}
Transform.prototype.identity = function () {
  this.m = [1, 0, 0, 1, 0, 0];
};
Transform.prototype.rotate = function (rad) {
  var c = Math.cos(rad);
  var s = Math.sin(rad);
  var m11 = this.m[0] * c + this.m[2] * s;
  var m12 = this.m[1] * c + this.m[3] * s;
  var m21 = this.m[0] * -s + this.m[2] * c;
  var m22 = this.m[1] * -s + this.m[3] * c;
  this.m[0] = m11;
  this.m[1] = m12;
  this.m[2] = m21;
  this.m[3] = m22;
};
Transform.prototype.translate = function (x, y) {
  this.m[4] += this.m[0] * x + this.m[2] * y;
  this.m[5] += this.m[1] * x + this.m[3] * y;
};

Transform.prototype.scale = function (sx, sy) {
  this.m[0] *= sx;
  this.m[1] *= sx;
  this.m[2] *= sy;
  this.m[3] *= sy;
};


main();
canvas {
  border:3px solid red;
}
<canvas id="can" width="300" height="200"></canvas>

Upvotes: 1

Related Questions