3

I want to rotate a shape on canvas on mouse move, and I want to detect if mouse is moving in clockwise direction or not, but I do not know how to do that. Here is my code:

var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');

var annotation_rect = canvas.getBoundingClientRect();
rect = {
      startX : 150,
      startY : 50,
      w : 250,
      h : 150,
      endX : 0,
      endY : 0,
      rotate: 0
    };
var drag = false;
var rotating = false;
var update = true; // when true updates canvas
var rotate_angle = 5; // in degrees - for rotating blurred part
var angle = rotate_angle * (Math.PI / 180);
var original_source = img.src;
img.src = original_source;

function rotateRight(){
 rect.rotate += angle;
  update = true;
}

function rotateLeft(){
 rect.rotate -= angle;
  update = true;
}

function init() {
    img.addEventListener('load', function(){
        canvas.width = img.width;
        canvas.height = img.height;
        canvas.addEventListener('mousedown', mouseDown, false);
        canvas.addEventListener('mouseup', mouseUp, false);
        canvas.addEventListener('mousemove', mouseMove, false);
    });
    
    // start the rendering loop
    requestAnimationFrame(updateCanvas);
}

// main render loop only updates if update is true
function updateCanvas(){
  if(update){
      drawCanvas();
      update = false;
  }

  requestAnimationFrame(updateCanvas);
}

// draws a rectangle with rotation 
function drawRect(){
  ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
    ctx.rotate(rect.rotate);
    ctx.beginPath();
    ctx.shadowBlur = 5;
    ctx.filter = 'blur(10px)';
    ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
    ctx.lineWidth = 3;
    ctx.strokeStyle = "#fff";
    ctx.fillStyle = "#fff";
    ctx.fill();
    ctx.stroke();
}

// clears canvas sets filters and draws rectangles
function drawCanvas(){    
  ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawRect()
}

// create new rect add to array 
function mouseDown(e) {
    drag = true;
}

function mouseUp() { drag = false; update = true; }



function startRotation(e){
    rotating = true;
}

function stopRotation(e){
    rotating = false;
}

function onShapeRotating(e){
    if(rotating){
         rotateRight();
    }
} 



function mouseMove(e) {
  var mouseX = e.offsetX - annotation_rect.left,
      mouseY = e.offsetY - annotation_rect.top,
        endX = rect.startX + rect.w,
      endY = rect.startY + rect.h
    
  var cursorOnShape = mouseX >= rect.startX && mouseX <= endX && mouseY >= rect.startY && mouseY <= endY;
    
    
    if(cursorOnShape){
        canvas.style.cursor = "pointer"
        
        canvas.addEventListener('mousedown', startRotation, false);
        canvas.addEventListener('mouseup', stopRotation, false);
        canvas.addEventListener('mousemove', onShapeRotating, false);
    }else{    
        canvas.style.cursor = "default"
        canvas.removeEventListener('mousedown', startRotation, false);
        canvas.removeEventListener('mouseup', stopRotation, false);
        canvas.removeEventListener('mousemove', onShapeRotating, false);
    }
}

init();
canvas{
  position: absolute;
  left: 0; 
  right: 0; 
  top: 0; 
  bottom: 0; 
  display:inline-block;
  background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
    <img id="photo" src="https://carsales.pxcrush.net/carsales/car/cil/cc5166737225893351785.jpg?width=600&height=300&overlay&aspect=FitWithIn&watermark=1439104668"/>
    <canvas id="canvas"></canvas>
</div>

Thus I have an rect drawn on canvas and I detect if the mouse is on that rect and if it is I call the function rotateShape and there I call the function rotateRight. And that is working. If you are on the rect with mouse and you press it down and rotate it would rotate the rect calling the function rotateRight.

But I want to be able to check if the mouse is moving in clockwise direction or not. If it moves in clockwise direction I would call rotateRight and if not then I would call rotateLeft.

Any idea how to do that?

Here is the fiddle.

Boky
  • 11,554
  • 28
  • 93
  • 163
  • var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y); use this center of your object, by that I meant. – nullqube Mar 07 '18 at 08:35
  • @nullqube Can you apply it in the fiddle? – Boky Mar 07 '18 at 08:38
  • your code needs much more change than just that, first you don't send the mouse position to the startRotation and then generally a bit of unnecessary complexity, and your fiddle doesn't work. – nullqube Mar 07 '18 at 08:53
  • @nullqube It's not true. The fiddle works fine. An I have much more code but I tried to make it simpler for fiddle. Obviously you didn't understood it. – Boky Mar 07 '18 at 08:57

3 Answers3

1

Instead of rotating by a fixed value, rotate by the difference between the last known angle and the new one.

To get these angles, you can use

var angle = Math.atan2(
  e.clientY - (rect.startY + rect.h /2),
  e.clientX - (rect.startX + rect.w /2)
);

var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');

var annotation_rect = canvas.getBoundingClientRect();
rect = {
  startX: 150,
  startY: 50,
  w: 250,
  h: 150,
  endX: 0,
  endY: 0,
  rotate: 0
};
var drag = false;
var rotating = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;

// keep track of the last angle
var prevAngle = null;
// a single function
function rotate(angle) {
  rect.rotate += angle;
  update = true;
}
// called on mousemove when dragging
function onShapeRotating(e) {
  if (rotating) {
    var angle = Math.atan2(
      e.clientY - (rect.startY + rect.h / 2),
      e.clientX - (rect.startX + rect.w / 2)
    );
    if (prevAngle !== null)
      rotate(angle - prevAngle)
    prevAngle = angle;
  }
}

function init() {
  img.addEventListener('load', function() {
    canvas.width = img.width;
    canvas.height = img.height;
    canvas.addEventListener('mousedown', mouseDown, false);
    canvas.addEventListener('mouseup', mouseUp, false);
    canvas.addEventListener('mousemove', mouseMove, false);
  });

  // start the rendering loop
  requestAnimationFrame(updateCanvas);
}

// main render loop only updates if update is true
function updateCanvas() {
  if (update) {
    drawCanvas();
    update = false;
  }

  requestAnimationFrame(updateCanvas);
}

// draws a rectangle with rotation 
function drawRect() {
  ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
  ctx.rotate(rect.rotate);
  ctx.beginPath();
  ctx.shadowBlur = 5;
  ctx.filter = 'blur(10px)';
  ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
  ctx.lineWidth = 3;
  ctx.strokeStyle = "#fff";
  ctx.fillStyle = "#fff";
  ctx.fill();
  ctx.stroke();
}

// clears canvas sets filters and draws rectangles
function drawCanvas() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  drawRect()
}

// create new rect add to array 
function mouseDown(e) {
  drag = true;
}

function mouseUp() {
  prevAngle = null;
  drag = false;
  update = true;
}



function startRotation(e) {
  rotating = true;
}

function stopRotation(e) {
  rotating = false;
}


function mouseMove(e) {
  var mouseX = e.offsetX - annotation_rect.left,
    mouseY = e.offsetY - annotation_rect.top,
    endX = rect.startX + rect.w,
    endY = rect.startY + rect.h

  var cursorOnShape = mouseX >= rect.startX && mouseX <= endX && mouseY >= rect.startY && mouseY <= endY;


  if (cursorOnShape) {
    canvas.style.cursor = "pointer"

    canvas.addEventListener('mousedown', startRotation, false);
    canvas.addEventListener('mouseup', stopRotation, false);
    canvas.addEventListener('mousemove', onShapeRotating, false);
  } else {
    canvas.style.cursor = "default"
    canvas.removeEventListener('mousedown', startRotation, false);
    canvas.removeEventListener('mouseup', stopRotation, false);
    canvas.removeEventListener('mousemove', onShapeRotating, false);
  }
}

init();
canvas {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  display: inline-block;
  background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
  <img id="photo" src="https://carsales.pxcrush.net/carsales/car/cil/cc5166737225893351785.jpg?width=600&height=300&overlay&aspect=FitWithIn&watermark=1439104668" />
  <canvas id="canvas"></canvas>
</div>

Now, your code has a lot of things to be fixed, but I'll let it to you.

You'd be better have single events on the whole canvas/document rather than adding ones selectively.
Your cursorOnShape algo doesn't take into account the rotation of your rectangle.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks. So many times that you helped me. :) Many thanks. And any idea how to solve the problem with `cursorOnShape `? – Boky Mar 07 '18 at 09:44
  • @Boky randomly picked: https://stackoverflow.com/questions/38268779/how-to-test-if-a-point-is-in-a-rectangle-area-which-rotates-an-angle – Kaiido Mar 07 '18 at 09:57
1

Computing change in angle

The problem with using Math.atan2() is that the difference between Math.atan2(1,-10) and Math.atan2(-1,-10) is 6.084 not 0.199 which means as you accumulate the sum of the differences you never get a value over Math.PI or under -Math.PI

In other words you can not directly track the number of turns you have made nor is it simple to know if you have turned clockwise or counter clockwise.

Cross product

The best way to compute the direction and amount the mouse has rotated around a point is to use the cross product of the two vectors from the center to the mouse position and old position

// cx,cy center of rotation
// ox,oy old position of mouse
// mx,my new position of mouse.
function getAngle(cx, cy, ox, oy, mx, my){
    var x1 = ox - cx;
    var y1 = oy - cy;
    var x2 = mx - cx;
    var y2 = my - cy;
    var d1 = Math.sqrt(x1 * x1 + y1 * y1);
    var d2 = Math.sqrt(x2 * x2 + y2 * y2);

    return Math.asin((x1 / d1) * (y2 / d2) - (y1 / d1) * (x2 / d2));
}

The function will return a negative change in angle if counter clockwise and positive if clockwise.

Example

Example show the above function being used to track the total rotation, not the absolute orientation as the other answers do.

const mouse  = {x : 0, y : 0, ox : 0, oy : 0, down : false};
["down","up","move"].forEach(name => document.addEventListener("mouse" + name,mouseEvents));
const ctx = canvas.getContext("2d");

function mouseEvents(e) {
  mouse.x = e.pageX; mouse.y = e.pageY;
  mouse.down = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.down;
}

function getAngleBetween(cx, cy, ox, oy, mx, my) {
  var x1 = ox - cx;
  var y1 = oy - cy;
  var x2 = mx - cx;
  var y2 = my - cy;
  // max to prevent div by zero
  var d1 = Math.max(0.001, Math.sqrt(x1 * x1 + y1 * y1));
  var d2 = Math.max(0.001, Math.sqrt(x2 * x2 + y2 * y2));
  return Math.asin((x1 / d1) * (y2 / d2) - (y1 / d1) * (x2 / d2));
}
var w, h, cw, ch;
var angle = 0;
function update() {
  ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
  if (w !== innerWidth || h !== innerHeight) {
    cw = (w = canvas.width = innerWidth) / 2;
    ch = (h = canvas.height = innerHeight) / 2;
  } else {
    ctx.clearRect(0, 0, w, h);
  }
  var change = 0;
  if (mouse.down) {
    change = getAngleBetween(cw, ch, mouse.ox, mouse.oy, mouse.x, mouse.y);
    angle += change;
  }

  ctx.setTransform(1, 0, 0, 1, cw, ch);
  ctx.rotate(angle);
  ctx.fillRect(-100, -25, 200, 50);


  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.font = "28px arial";
  ctx.textAlign = "center";
  ctx.fillText("Total rotation : " + ((angle * 180 / Math.PI) | 0), cw, 30);
  ctx.font = "14px arial";
  ctx.fillText("Change : " + (change * 180 / Math.PI).toFixed(2), cw, 48);

  ctx.fillText("Click drag to rotate box.", cw, h - 20);

  mouse.ox = mouse.x;
  mouse.oy = mouse.y
  requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}
<canvas id="canvas"></canvas>
Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136
0

ok here it is but not the appropriate way , sorry :

var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');

var annotation_rect = canvas.getBoundingClientRect();
rect = {
      startX : 150,
      startY : 50,
      w : 250,
      h : 150,
      endX : 0,
      endY : 0,
      rotate: 0
    };
var drag = false;
var rotating = false;
var update = true; // when true updates canvas
var rotate_angle = 5; // in degrees - for rotating blurred part
var angle = rotate_angle * (Math.PI / 180);
var original_source = img.src;
var bLeft = true;
img.src = original_source;

function rotateRight(){
 rect.rotate += angle;
  update = true;
}

function rotateLeft(){
 rect.rotate -= angle;
  update = true;
}

function init() {
    img.addEventListener('load', function(){
        canvas.width = img.width;
        canvas.height = img.height;
        canvas.addEventListener('mousedown', mouseDown, false);
        canvas.addEventListener('mouseup', mouseUp, false);
        canvas.addEventListener('mousemove', mouseMove, false);
    });
    
    // start the rendering loop
    requestAnimationFrame(updateCanvas);
}

// main render loop only updates if update is true
function updateCanvas(){
  if(update){
      drawCanvas();
      update = false;
  }

  requestAnimationFrame(updateCanvas);
}

// draws a rectangle with rotation 
function drawRect(){
  ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
    ctx.rotate(rect.rotate);
    ctx.beginPath();
    ctx.shadowBlur = 5;
    ctx.filter = 'blur(10px)';
    ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
    ctx.lineWidth = 3;
    ctx.strokeStyle = "#fff";
    ctx.fillStyle = "#fff";
    ctx.fill();
    ctx.stroke();
}

// clears canvas sets filters and draws rectangles
function drawCanvas(){    
  ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawRect()
}

// create new rect add to array 
function mouseDown(e) {
    drag = true;
}

function mouseUp() { drag = false; update = true; }



function startRotation(e){
    rotating = true;
}

function stopRotation(e){
    rotating = false;
}

function onShapeRotating(e){
    if(rotating && !bLeft){
         rotateRight();
    } else if(rotating && bLeft){
         rotateLeft();
    }
} 



function mouseMove(e) {
  var mouseX = e.offsetX - annotation_rect.left,
      mouseY = e.offsetY - annotation_rect.top,
        endX = rect.startX + rect.w,
      endY = rect.startY + rect.h
    
  var cursorOnShape = mouseX >= rect.startX && mouseX <= endX && mouseY >= rect.startY && mouseY <= endY;
    
    bLeft = ( rect.startX + rect.w/2 - mouseX ) > 0 ? true:false;
    if(cursorOnShape){
        canvas.style.cursor = "pointer"
        
        canvas.addEventListener('mousedown', startRotation, false);
        canvas.addEventListener('mouseup', stopRotation, false);
        canvas.addEventListener('mousemove', onShapeRotating, false);
    }else{    
        canvas.style.cursor = "default"
        canvas.removeEventListener('mousedown', startRotation, false);
        canvas.removeEventListener('mouseup', stopRotation, false);
        canvas.removeEventListener('mousemove', onShapeRotating, false);
    }
}

init();
canvas{
  position: absolute;
  left: 0; 
  right: 0; 
  top: 0; 
  bottom: 0; 
  display:inline-block;
  background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
    <img id="photo" src="https://carsales.pxcrush.net/carsales/car/cil/cc5166737225893351785.jpg?width=600&height=300&overlay&aspect=FitWithIn&watermark=1439104668"/>
    <canvas id="canvas"></canvas>
</div>
nullqube
  • 2,959
  • 19
  • 18
  • since you did the state machine code, so I just added a bLeft variable that keeps track of direction. but the whole point is calc the diff between mouse pos and center of the shape you try to rotate. – nullqube Mar 07 '18 at 09:03