rotating a square in javascript

2019-09-02 19:14发布

I am trying to rotate a square about its center in javascript. Although in this case I am using canvas to draw the square, I need to have a functional method of rotating the square for use elsewhere so just rotating the canvas is not acceptable. This is what I have so far

var thecanvas = this.myCanvas;      

  var canvaswidth = thecanvas.width;
  tlx = (0 - ((canvaswidth * 0.6) / 2));
  tly = (0 - ((canvaswidth * 0.6) / 2));
  trx = (0 + ((canvaswidth * 0.6) / 2));
  tryy = (0 - ((canvaswidth * 0.6) / 2));
  blx = (0 - ((canvaswidth * 0.6) / 2));
  bly = (0 + ((canvaswidth * 0.6) / 2));
  brx = (0 + ((canvaswidth * 0.6) / 2));
  bry = (0 + ((canvaswidth * 0.6) / 2));

  tlx = (((tlx) * (this._cosD(orientation))) - ((tly) * (this._sinD(orientation))));
  tly = ((tlx) * (this._sinD(orientation)) + (tly) * (this._cosD(orientation)));
  trx = (((trx) * (this._cosD(orientation))) - ((tryy) * (this._sinD(orientation))));
  tryy = ((trx) * (this._sinD(orientation)) + (tryy) * (this._cosD(orientation)));
  blx = ((blx) * (this._cosD(orientation)) - (bly) * (this._sinD(orientation)));
  bly = ((blx) * (this._sinD(orientation)) + (bly) * (this._cosD(orientation)));
  brx = ((brx) * (this._cosD(orientation)) - (bry) * (this._sinD(orientation)));
  bry = ((brx) * (this._sinD(orientation)) + (bry) * (this._cosD(orientation)));


  tlx = (tlx + (canvaswidth / 2));
  tly = (tly + (canvaswidth / 2));
  trx = (trx + (canvaswidth / 2));
  tryy = (tryy + (canvaswidth / 2));
  blx = (blx + (canvaswidth / 2));
  bly = (bly + (canvaswidth / 2));
  brx = (brx + (canvaswidth / 2));
  bry = (bry + (canvaswidth / 2));


  var c2 = thecanvas.getContext('2d');
  c2.fillStyle = '#f00';
  c2.beginPath();
  c2.moveTo(tlx, tly);
  c2.lineTo(trx, tryy);
  c2.lineTo(brx, bry);
  c2.lineTo(blx, bly);
  c2.closePath();
  c2.fill();`

orientation is a value from -90-90 in degrees. This code starts to rotate the square but the square continues to "squish" until it is completely gone by 90 degrees. Obviously my formula for rotation is thrown off somewhere but I cant figure out how.

1条回答
2楼-- · 2019-09-02 19:44

You could manually implement a transformation matrix. This allows you to set up the matrix for both read and write, then apply it to the points returning absolute points with the new actual values without the need or hassle to make special code for each use-case.

The formula for the (2D affine) using a 3x3 matrix would be:

3x3 affine matrix

or simplified in JavaScript:

var newX = x * a + y * c + e,    // e (or tx) x 1 => e
    newY = x * b + y * d + f;    // f (or ty) x 1 => f

Now you would need to multiply the matrix with another to add rotation, translation, scale etc. The common way of doing this is this way - assume we have an object holding our values, you could do:

function Matrix() {
   this.a = 1;  // 1,0,0,1,0,0 = identity matrix (untransformed)
   this.b = 0;
   this.c = 0;
   this.d = 1;
   this.e = 0;
   this.f = 0;
}

Matrix.prototype = {

  // we need to be able to multiply matrices to accumulate values:
  transform: function(a2, b2, c2, d2, e2, f2) {

    var a1 = this.a,
        b1 = this.b,
        c1 = this.c,
        d1 = this.d,
        e1 = this.e,
        f1 = this.f;

    /* matrix order (canvas compatible):
    * ace
    * bdf
    * 001
    */
    this.a = a1 * a2 + c1 * b2;
    this.b = b1 * a2 + d1 * b2;
    this.c = a1 * c2 + c1 * d2;
    this.d = b1 * c2 + d1 * d2;
    this.e = a1 * e2 + c1 * f2 + e1;
    this.f = b1 * e2 + d1 * f2 + f1;
  }
}

With the core set up you can now add methods to do for example rotation:

rotation

rotate: function(angle) {
    var cos = Math.cos(angle),
        sin = Math.sin(angle);
    this.transform(cos, sin, -sin, cos, 0, 0);
}

scale:

scale: function(sx, sy) {
    this.transform(sx, 0, 0, sy, 0, 0);
}

translate:

translate: function(tx, ty) {
    this.transform(1, 0, 0, 1, tx, ty);
}

and so forth depending on your needs. Note that these will accumulate as with the ordinary canvas/SVG matrix. Reset to identity to remove all transforms.

All you need to do now is to feed your points after settings transformation to get the absolute points - lets say we have a box of 100x100 we want to rotate at center:

Adding this to the prototype:

applyToPoint: function(p) {
    return {
      x: p.x * this.a + p.y * this.c + this.e,
      y: p.x * this.b + p.y * this.d + this.f
    }
}

will allow us to do:

var m = new Matrix();
m.translate(50, 50);
m.rotate(0.3);
m.translate(-50, 50);

var points = [
      {x: 0, y: 0},      // upper-left
      {x: 100, y: 0},    // upper-right
      {x: 100, y: 100},  // bottom-right
      {x: 0, y: 100}     // bottom-left
    ],
    result = [],
    i = 0, p;

// transform points
while(p = points[i++]) result.push(m.applyToPoint(p));

Demo

Red box is original coordinates, blue is the transformed points which we now have access to:

snapshot

function Matrix() {
  this.a = 1; // identity matrix
  this.b = 0;
  this.c = 0;
  this.d = 1;
  this.e = 0;
  this.f = 0;
}

Matrix.prototype = {

    applyToPoint: function(p) {
      return {
        x: p.x * this.a + p.y * this.c + this.e,
        y: p.x * this.b + p.y * this.d + this.f
      }
    },

    transform: function(a2, b2, c2, d2, e2, f2) {

      var a1 = this.a,
          b1 = this.b,
          c1 = this.c,
          d1 = this.d,
          e1 = this.e,
          f1 = this.f;

      /* matrix order (canvas compatible):
       * ace
       * bdf
       * 001
       */
      this.a = a1 * a2 + c1 * b2;
      this.b = b1 * a2 + d1 * b2;
      this.c = a1 * c2 + c1 * d2;
      this.d = b1 * c2 + d1 * d2;
      this.e = a1 * e2 + c1 * f2 + e1;
      this.f = b1 * e2 + d1 * f2 + f1;
    },

    rotate: function(angle) {
      var cos = Math.cos(angle),
          sin = Math.sin(angle);
      this.transform(cos, sin, -sin, cos, 0, 0);
    },

    scale: function(sx, sy) {
      this.transform(sx, 0, 0, sy, 0, 0);
    },

    translate: function(tx, ty) {
      this.transform(1, 0, 0, 1, tx, ty);
    }
};

// apply some transformation:
var m = new Matrix();     // our manual transformation-matrix
m.translate(50, 50);      // center of box
m.rotate(0.3);            // some angle in radians
m.translate(-50, -50);    // translate back

var points = [
      {x: 0, y: 0},       // upper-left
      {x: 100, y: 0},     // upper-right
      {x: 100, y: 100},   // bottom-right
      {x: 0, y: 100}      // bottom-left
    ],
    result = [], i = 0, p;

// transform points
while(p = points[i++]) result.push(m.applyToPoint(p));

// draw boxes to canvas:
var ctx = document.querySelector("canvas").getContext("2d");
ctx.translate(30, 30);    // give some room for rotation for this demo

drawPolygon(points, "red");
drawPolygon(result, "blue");
 
drawCoord(points[0]);     // plot old point
drawCoord(result[0]);     // plot resulting point

// Compare using ordinary canvas: -------------------

ctx.translate(150, 0);     // give some space
ctx.fillText("Regular canvas:", 0, -20);

drawPolygon(points, "red");

ctx.translate(50, 50);      // center of box
ctx.rotate(0.3);            // some angle in radians
ctx.translate(-50, -50);    // translate back

drawPolygon(points, "blue");


// plot result:
function drawPolygon(pts, color) {
  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.moveTo(pts[0].x, pts[0].y);
  for(var i = 1, p; p = pts[i++];) ctx.lineTo(p.x, p.y);
  ctx.closePath();
  ctx.stroke();
}

function drawCoord(p) {
  ctx.fillStyle = "#0c0"; ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
  ctx.fillStyle = "#000";
  ctx.fillText(p.x.toFixed(1) + "," + p.y.toFixed(1), p.x, p.y - 2);
}
<canvas><canvas>

If you don't want to implement this yourselves, or want a more extensive solution, feel free to check out my (free) matrix solution here.

The future will give us currentTransform on the context (currently only available in Chrome, Firefox struggles with a bug) returning a SVGMatrix object which you can use in pretty much the same way as the above implementation.

查看更多
登录 后发表回答