katie
katie

Reputation: 1041

Rotate individual objects in canvas?

the rotate() function seems to rotate the whole drawing area. Is there a way to rotate paths individually? I want the center for the rotation to be the object, not the drawing area.

Using save() and restore() still makes rotate take into account the whole drawing area.

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.save();
context.fillStyle = 'red';
context.rotate(0.35);
context.fillRect(40,40, 100, 100);
context.restore();

context.save();
context.fillStyle = 'blue';
context.rotate(0.35);
context.fillRect(200, 40, 100, 100);
context.restore();
<canvas id="canvas" width="500" height="500"></canvas>

Upvotes: 2

Views: 2047

Answers (3)

Tyler Dalton
Tyler Dalton

Reputation: 69

Use a rotate function to rotate all of the shape's points around its center.

<!DOCTYPE html>
<html>
<head>
    <style>
        body
        {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
        }
        canvas
        {
            position: absolute;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
    var canvas;
    var context;
    canvas = document.getElementById("canvas");
    context = canvas.getContext("2d");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    var degreesToRadians = function(degrees)
    {
        return degrees*Math.PI/180;
    }
    var rotate = function(x, y, cx, cy, degrees)
    {
        var radians = degreesToRadians(degrees);
        var cos = Math.cos(radians);
        var sin = Math.sin(radians);
        var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
        var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
        return new Vector2(nx, ny);
    }
    var Vector2 = function(x, y)
    {
        return {x:x,y:y};
    }
    var Shape = function(points, color)
    {
        this.color = color;
        this.points = points;
    };
    Shape.prototype.rotate = function(degrees)
    {
        var center = this.getCenter();
        for (var i = 0; i < this.points.length; i++)
        {
            this.points[i] = rotate(this.points[i].x,this.points[i].y,center.x,center.y,degrees);
        }
        context.beginPath();
        context.arc(center.x,center.y,35,0,Math.PI*2);
        context.closePath();
        context.stroke();
    }
    Shape.prototype.draw = function()
    {
        context.fillStyle = this.color;
        context.strokeStyle = "#000000";
        context.beginPath();
        context.moveTo(this.points[0].x, this.points[0].y);
        for (var i = 0; i < this.points.length; i++)
        {
            context.lineTo(this.points[i].x, this.points[i].y);
            //context.fillText(i+1, this.points[i].x, this.points[i].y);
        }
        context.closePath();
        context.fill();
        context.stroke();
    }
    Shape.prototype.getCenter = function()
    {
        var center = {x:0,y:0};
        for (var i = 0; i < this.points.length; i++)
        {
            center.x += this.points[i].x;
            center.y += this.points[i].y;
        }
        center.x /= this.points.length;
        center.y /= this.points.length;
        return center;
    }
    Shape.prototype.translate = function(x, y)
    {
        for (var i = 0; i < this.points.length; i++)
        {
            this.points[i].x += x;
            this.points[i].y += y;
        }
    }
    var Rect = function(x,y,w,h,c)
    {
        this.color = c;
        this.points = [Vector2(x,y),Vector2(x+w,y),Vector2(x+w,y+h),Vector2(x,y+h)];
    }
    Rect.prototype = Shape.prototype;
    var r = new Rect(50, 50, 200, 100, "#ff0000");
    r.draw();
    r.translate(300,0);
    r.rotate(30);
    r.draw();

</script>
</body>
</html>

Upvotes: 0

Blindman67
Blindman67

Reputation: 54026

Use local space

Instead of drawing object at the position you want them draw everything around its own origin in its local space. The origin is at (0,0) and is the location that the object rotates around.

So if you have a rectangle that you draw with

function drawRect(){
    context.fillRect(200, 40, 100, 100);
}

change it so that it is drawn at its origin

function drawRect(){
    context.fillRect(-50,-50 , 100, 100);
}

Now you can easily draw it wherevery you want

Start with the setTransform function as that clears any existing tranforms and is a convenient way to set the location of the center of the object will be

ctx.setTransform(1,0,0,1,posX,posY); // clear transform and set center location 

if you want to rotate it then add the rotation

ctx.rotate(ang);

and scale with

ctx.scale(scale,scale);

if you have two different scales you should scale before the rotate.

Now just call the draw function

drawRect();

and it is drawn with its center at posX,posY rotated and scaled.

You can combine it all into a function that has the x,y position, the width and the height, scale and rotation. You can include the scale in the setTransform

function drawRect(x,y,w,h,scale,rotation){
   ctx.setTransform(scale,0,0,scale,x,y);
   ctx.rotate(rotation);
   ctx.strokeRect(-w/2,-h/2,w,h);
}

It also applies to an image as a sprite, and I will include a alpha

function drawImage(img,x,y,w,h,scale,rotation,alpha){
   ctx.globalAlpha = alpha;
   ctx.setTransform(scale,0,0,scale,x,y);
   ctx.rotate(rotation);
   ctx.drawImage(img,-img.width/2,-img.height/2,img.width,img.height);
}

On a 6 year old laptop that can draw 2000 sprites on firefox every 1/60th of a second, each rotated, scaled, positioned, and with a alpha fade.

No need to mess about with translating back and forward. Just keep all the objects you draw around there own origins and move that origin via the transform.

Update Lost the demo so here it is to show how to do it in practice.

Just draws a lot of rotated, scaled translated, alphaed rectangles.

By using setTransform you save a lot of time by avoiding save and restore

// create canvas and add resize 
var canvas,ctx;
function createCanvas(){
    canvas = document.createElement("canvas"); 
    canvas.style.position = "absolute";
    canvas.style.left     = "0px";
    canvas.style.top      = "0px";
    canvas.style.zIndex   = 1000;
    document.body.appendChild(canvas); 
}
function resizeCanvas(){
    if(canvas === undefined){
        createCanvas();
    }
    canvas.width          = window.innerWidth;
    canvas.height         = window.innerHeight; 
    ctx            = canvas.getContext("2d"); 
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);


// simple function to draw a rectangle
var drawRect = function(x,y,w,h,scale,rot,alpha,col){
  ctx.setTransform(scale,0,0,scale,x,y);
  ctx.rotate(rot);
  ctx.globalAlpha = alpha;
  ctx.strokeStyle = col;
  ctx.strokeRect(-w/2,-h/2, w, h);

}

// create some rectangles in unit scale so that they can be scaled to fit
// what ever screen size this is in
var rects = [];
for(var i = 0; i < 200; i ++){
  rects[i] = {
    x : Math.random(),
    y : Math.random(),
    w : Math.random() * 0.1,
    h : Math.random() * 0.1,
    scale : 1, 
    rotate : 0,
    dr : (Math.random() - 0.5)*0.1, // rotation rate
    ds : Math.random()*0.01,   // scale vary rate
    da : Math.random()*0.01,   // alpha vary rate
    col : "hsl("+Math.floor(Math.random()*360)+",100%,50%)",
  };
}

// draw everything once a frame
function update(time){
  var w,h;
  w = canvas.width;  // get canvas size incase there has been a resize
  h = canvas.height;
  ctx.setTransform(1,0,0,1,0,0); // reset transform
  ctx.clearRect(0,0,w,h);  // clear the canvas
  
  // update and draw each rect
  for(var i = 0; i < rects.length; i ++){
    var rec = rects[i];
    rec.rotate += rec.dr;
    drawRect(rec.x * w, rec.y * h, rec.w * w,rec.h * h,rec.scale + Math.sin(time * rec.ds) * 0.4,rec.rotate,Math.sin(time * rec.da) *0.5 + 0.5,rec.col);
  }
  
  requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);

Upvotes: 7

CrazyCasta
CrazyCasta

Reputation: 28302

All transformations in canvas are for the whole drawing area. If you want to rotate around a point you're going to have to translate that point to the origin, do your rotation and translate it back. Something like this is what you want.

Upvotes: 1

Related Questions