How to fix rotation of object to follow client's mouse? - javascript

I'm building a basic game using plain javascript and I am trying to rotate my object to follow my mouse.
I've tried getting the client's mouse X and Y then subtracting the canvas width and height divided by two. Then taking those values and inputing it into Math.atan2(). However, I feel the issue may be in my transform and rotate. The code bellow is what I've tried.
WIDTH = c.height;
HEIGHT = c.width;
document.onmousemove = function(ve){
let cX = -c.width / 2;
let cY = -c.height / 2;
let x = ve.offsetX;
let y = ve.offsetY;
var rX = cX + x - 8;
var rY = cY + y - 8;
player.angle = Math.atan2(rX, rY) / Math.PI * 180;
}
function update(){
var now = Date.now();
dt = now - lastUpdate;
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.setTransform(1, 0, 0, 1, WIDTH / 2, HEIGHT / 2);
ctx.rotate(player.angle + 10);
drawCircle(player.x, player.y, 20, 0, 180, "red");
tx.setTransform(1, 0, 0, 1, 0, 0);
}
setInterval(update, dt/10000);
The player spins around my mouse in wide circles with no apparent pattern.
Here's a gif showing what's happening.
https://gyazo.com/006c99879ecf219791d059de14d98b74

In order to rotate the object to follow the mouse you need to get the angle between the previous position of the mouse and the actual position of the mouse and use this angle to rotate the object. Also the object is drawn with the tip in the origin of the canvas {x:0,y:0} so you'll need to translate the player to the position of the mouse.
I hope this is what you need.
const ctx = c.getContext("2d")
const HEIGHT = c.height = window.innerHeight;
const WIDTH = c.width = window.innerWidth;
let m = {x:0,y:0}
let prev = {x:0,y:0}
let angle = 0;
c.addEventListener("mousemove",(evt)=>{
ctx.clearRect(-WIDTH, -HEIGHT, 2*WIDTH, 2*HEIGHT);
// the previous position of the mouse
prev.x = m.x;
prev.y = m.y;
//the actual position of the mouse
m = oMousePos(c, evt);
// if the mpuse is moving get the angle between the previoue position and the actual position of the mouse
if(m.x != prev.x && m.y != prev.y){
angle = Math.atan2(m.y-prev.y, m.x-prev.x)
}
ctx.restore();
ctx.save();
ctx.translate(m.x, m.y);
ctx.rotate(angle);
drawPlayer();
})
function drawPlayer(){
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-20,-5);
ctx.lineTo(-20,5);
ctx.lineTo(0,0);
ctx.closePath();
ctx.fill()
}
// a function to detect the mouse position
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
<canvas id="c"></canvas>
As an observation: in your code you have Math.atan2(rX, rY) The first argument has to be y.

Related

Circle Following Mouse HTML5 Canvas jQuery

I am trying to make a circle follow the mouse in HTML Canvas which I am using in a game. I am trying to make the circle move 5px per iteration, but it goes slower when traveling horizontal and faster when it goes vertical. Here's the math that I used:
x=distance between mouse and circle on the x-axis
y=distance between mouse and circle on the y-axis
z=shortest distance between mouse and circle
a=number of units circle should move along the x-axis
b=number of units circle should move along the y axis
x^2 + y^2=z^2
Want the total distance traveled every iteration to be five pixels
a^2 + b^2 = 25
b/a=y/x
b=ay/x
a=sqrt(25-ay/x^2)
a^2+ay/x-25=0
Use Quadratic formula to find both answers
a=(-y/x+-sqrt(y/x)^2+100)/2
I replicated the problem in the code below
$(function(){
let canvas = $("canvas")[0];
let ctx = canvas.getContext("2d");
//Gets position of mouse and stores the value in variables mouseX and mouseY
let mouseX = mouseY = 0;
$("canvas").mousemove(function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}).trigger("mousemove");
let circleX = 0;
let circleY = 0;
function loop(t){
//Background
ctx.fillStyle="blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
let xFromMouse = mouseX-circleX;
let yFromMouse = mouseY-circleY;
let yxRatio = yFromMouse/xFromMouse;
let xyRatio = xFromMouse/yFromMouse;
let speed = 25;
let possibleXValues = [(-yxRatio+Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2,(-yxRatio-Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2];
//I use this code as a temporary fix to stop the circle from completely disappearing
if(xFromMouse === 0 || isNaN(yxRatio) || isNaN(possibleXValues[0]) || isNaN(possibleXValues[1])){
possibleXValues = [0,0];
yxRatio = 0;
}
//Uses b=ay/x to calculate for y values
let possibleYValues = [possibleXValues[0]*yxRatio,possibleXValues[1]*yxRatio];
if(xFromMouse >= 0){
circleX += possibleXValues[0];
circleY += possibleYValues[0];
} else {
circleX += possibleXValues[1];
circleY += possibleYValues[1];
}
ctx.beginPath();
ctx.arc(circleX, circleY, 25, 0, 2 * Math.PI,false);
ctx.fillStyle = "red";
ctx.lineWidth = 0;
ctx.fill();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="450" height="250"></canvas>
I think you may be better using a cartesian to polar conversion. Here's an example from something I made previously. This will allow you to have a consistent step per iteration "speed".
//Canvas, context, mouse.
let c, a, m = { x:0, y:0};
//onload.
window.onload = function(){
let circle = {},
w, h,
speed = 5; //step speed = 5 "pixels" (this will be fractional in any one direction depending on direction of travel).
//setup
c = document.getElementById('canvas');
a = c.getContext('2d');
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
function move(){
//get distance and angle from mouse to circle.
let v1m = circle.x - m.x,
v2m = circle.y - m.y,
vDm = Math.sqrt(v1m*v1m + v2m*v2m),
vAm = Math.atan2(v2m, v1m);
//if distance is above some threshold, to stop jittering, move the circle by 'speed' towards mouse.
if(vDm > speed) {
circle.x -= Math.cos(vAm) * speed;
circle.y -= Math.sin(vAm) * speed;
}
}
function draw(){
//draw it all.
a.fillStyle = "blue";
a.fillRect(0,0,w,h);
a.fillStyle = "red";
a.beginPath();
a.arc(circle.x, circle.y, circle.r, Math.PI * 2, false);
a.closePath();
a.fill();
}
circle = {x:w/2, y:h/2, r:25};
function animate(){
requestAnimationFrame(animate);
move();
draw();
}
c.onmousemove = function(e){
m.x = e.pageX;
m.y = e.pageY;
};
animate();
}
<canvas id="canvas" width="450" height="250"></canvas>

Rotating Rectangles Around Circle Perimeter on Canvas

I'm trying to create a little circular "equalizer" effect using JavaScript and HTML canvas for a little project I'm working on, and it works great, except one little thing. It's just a series of rectangular bars moving in time to an mp3 - nothing overly fancy, but at the moment all the bars point in one direction (i.e. 0 radians, or 90 degrees).
I want each respective rectangle around the edge of the circle to point directly away from the center point, rather than to the right. I have 360 bars, so naturally, each one should be 1 degree more rotated than the previous.
I thought that doing angle = i*Math.PI/180 would fix that, but it doesn't seem to matter what I do with the rotate function - they always end up pointing in weird and wonderful directions, and being translated a million miles from where they were. And I can't see why. Can anyone see where I'm going wrong?
My frame code, for reference, is as follows:
function frames() {
// Clear the canvas and get the mp3 array
window.webkitRequestAnimationFrame(frames);
musicArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(musicArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
bars = 360;
for (var i = 0; i < bars; i++) {
// Find the rectangle's position on circle edge
distance = 100;
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * distance + (canvas.width / 2);
var y = Math.sin(angle) * distance + (canvas.height / 2);
barWidth = 5;
barHeight = (musicArray[i] / 4);
// Fill with a blue-green gradient
var grd = ctx.createLinearGradient(x, 0, x + 40, 0);
grd.addColorStop(0, "#00CCFF");
grd.addColorStop(1, "#00FF7F");
ctx.fillStyle = grd;
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.fillRect(x, y, barHeight, barWidth);
}
For clarity I've removed part of your code. I'm using rotate as you intended. Also I'm using barHeight = (Math.random()* 50); instead your (musicArray[i]/4); because I wanted to have something to show.
Also I've changed your bars to 180. It's very probable that you won't have 360 bars but 32 or 64 or 128 or 256 . . . Now you can change the numbers of bare to one of these numbers to see the result.
I'm drawing everything around the origin of the canvas and translating the context in the center.
I hope it helps.
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400;
let ch = canvas.height = 400;
let bars = 180;
let r = 100;
ctx.translate(cw / 2, ch / 2)
for (var i = 0; i < 360; i += (360 / bars)) {
// Find the rectangle's position on circle edge
var angle = i * ((Math.PI * 2) / bars);
//var x = Math.cos(angle)*r+(canvas.width/2);
//var y = Math.sin(angle)*r+(canvas.height/2);
barWidth = 2 * Math.PI * r / bars;
barHeight = (Math.random() * 50);
ctx.fillStyle = "green";
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.save();
ctx.rotate(i * Math.PI / 180);
ctx.fillRect(r, -barWidth / 2, barHeight, barWidth);
//ctx.fillRect(r ,0, barHeight, barWidth);
ctx.restore();
}
canvas {
border: 1px solid
}
<canvas id="c"></canvas>
Here is another solution, I'm preserving your initial trigonometry approach.
But instead of rectangles I used lines, I don't think it makes a difference for you, if what you need is bars moving in time to an mp3 all you need to do is change the var v = Math.random() + 1; to a reading from the Amplitude, and those bars will be dancing.
const canvas = document.getElementById("c");
canvas.width = canvas.height = 170;
const ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.lineWidth = 2;
let r = 40;
let bars = 180;
function draw() {
ctx.clearRect(-100, -100, 200, 200)
for (var i = 0; i < 360; i += (360 / bars)) {
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * r;
var y = Math.sin(angle) * r;
ctx.beginPath();
var v = Math.random() + 1;
ctx.moveTo(x, y);
ctx.lineTo(x * v, y * v)
grd = ctx.createLinearGradient(x, y, x*2, y*2);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.strokeStyle = grd;
ctx.stroke();
}
}
setInterval(draw, 100)
<canvas id="c"></canvas>

angled direction sine wave

I have been able to draw the sin wave in horizontal direction as shows the image(image link: https://i.stack.imgur.com/RTpDY.png) and in the vertical direction.
now I need to draw it in an angled 45° direction
could any one help me pleese!
the script code:
var c =document.getElementById("c");
var ctx=c.getContext('2d');
var x=0,y=250,vx=0.05,vy=0.05,a=1;
for(var i=0; i<501;i++){
x += a;
y = Math.floor(500 * (0.5 - 0.15 * Math.sin(vy)));
vy += vx;
// this.ctx.clearRect(0, 0, 500,500);
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI * 2, true);
this.ctx.closePath();
this.ctx.fillStyle = 'red';
this.ctx.fill();
console.log("x:"+x+"y:"+y+"vy:"+vy);
}
Draw a sin wave along a line
The following will draw a sin wave aligned to a line. The line can be in any direction.
The standard settings
The wave length will be in pixels. For a sin wave to make a complete cycle you need to rotate its input angle by Math.PI * 2 so you will need to convert it to value matching pixel wavelength.
const waveLen = 400; // pixels
The phase of the sin wave is at what part of the wave it starts at, as the wave length is in pixels, the phase is also in pixels, and represents the distance along the wave that denotes the starting angle.
const phase = 200; // mid way
The amplitude of the wave is how far above and below the center line the wave max and min points are. This is again in pixels
const amplitude = 100;
A wave also has an offset, though in this case not really important I will added it as well. In pixels as well
const offset = 0;
The line that marks the center of the wave has a start and end coordinate
const x1 = 20;
const y1 = 20;
const x2 = 400;
const y2 = 400;
And some context settings
const lineWidth = 3;
const lineCap = "round";
const lineJoin = "round";
const strokeStyle = "blue";
Example code
And so to the code that does the rendering, I have expanded the code with comments so you can read what is going on. Below that is a more useable version.
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
y2 = x2 = innerWidth; // at 45 deg
drawSinWave();
})
const waveLen = 120; // pixels
const phase = 50; // mid way
const amplitude = 25;
const offset = 0;
const x1 = 20;
const y1 = 20;
var x2 = 400; // as vars to let it change to fit resize
var y2 = 400;
function drawSinWave() {
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.strokeStyle = "blue";
// get the vector form of the line
const vx = x2 - x1;
const vy = y2 - y1;
// Get the length of the line in pixels
const dist = Math.sqrt(vx * vx + vy * vy);
// Make the vector one pixel long to move along the line
const px = vx / dist;
const py = vy / dist;
// We also need a vector to move out from the line (at 90 deg to the ine)
// So rotate the pixel vector 90deg CW
const ax = -py; // a for amplitude vector
const ay = px;
// Begin the path
ctx.beginPath();
// Now loop along every pixel in the line
// We go past the end a bit as floating point errors can cause it to end
// a pixels too early
for (var i = 0; i <= dist + 0.5; i++) {
// fix i if past end
if (i > dist) {
i = dist
} // Carefull dont mess with this ot it will block the page
// Use the distance to get the current angle of the wave
// based on the wave length and phase
const ang = ((i + phase) / waveLen) * Math.PI * 2;
// and at this position get sin
const val = Math.sin(ang);
// Scale to match the amplitude and move to offset
// as the distance from the center of the line
const amp = val * amplitude + offset;
// Get line ceneter at distance i using the pixel vector
var x = x1 + px * i;
var y = y1 + py * i;
// Use the amp vector to move away from the line at 90 degree
x += ax * amp;
y += ay * amp;
// Now add the point
ctx.lineTo(x, y);
}
ctx.stroke();
}
drawSinWave();
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
As a more useable function with a few shortcuts
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
waveExample.y2 = waveExample.x2 = innerWidth; // at 45 deg
drawSinWave(waveExample);
})
const waveExample = {
waveLen: 100, // pixels
phase: 50, // mid way
amplitude: 35,
offset: 0,
x1: 20,
y1: 20,
x2: 400, // as vars to let it change to fit resize
y2: 400,
lineWidth : 5,
lineCap : "round",
lineJoin : "round",
strokeStyle : "Red",
}
function drawSinWave(wave) {
ctx.lineWidth = wave.lineWidth;
ctx.lineCap = wave.lineCap;
ctx.lineJoin = wave.lineJoin;
ctx.strokeStyle = wave.strokeStyle;
var vx = wave.x2 - wave.x1;
var vy = wave.y2 - wave.y1;
const dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
ctx.beginPath();
for (var i = 0; i <= dist + 0.5; i++) {
if (i > dist) { i = dist }
const pos = Math.sin(((i + wave.phase) / wave.waveLen) * Math.PI * 2) * wave.amplitude + wave.offset;
ctx.lineTo(
wave.x1 + vx * i - vy * pos,
wave.y1 + vy * i + vx * pos
);
}
ctx.stroke();
}
drawSinWave(waveExample);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
The easiest solution is rotating the canvas:
ctx.rotate(45*Math.PI/180);
Although I'm presuming you need the canvas orientation fixed and you need to mathematically alter the way you draw? In which case here's a whole bunch of math about how to plot sine waves at a rotated counterclockwise:
http://mathman.biz/html/rotatingsine.html

HTML5 capturing absolute position following rotation and translation

I'm trying to manipulate a simple rectangle on a HTML5 canvas. The Javascript that does this is here:
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, windowWidth, windowHeight);
var halfWidth = (iconWidth / 2);
var halfHeight = (iconHeight / 2);
var centreX = x + halfWidth;
var centreY = y + halfHeight;
ctx.fillStyle = "#FF0000";
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
ctx.translate(-centreX, -centreY);
As I increase y, I can see the rectangle travelling along the screen and, if I rotate the rectangle, it rotates and moves along the new trajectory; however, in order to stop the rectangle leaving the screen, I had a basic boundary check, which was just not working (the rectangle was travelling off the screen, and being "bounced" where it had not reached the edge.
As an experiment, I then tried the following:
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, windowWidth, windowHeight);
var halfWidth = (iconWidth / 2);
var halfHeight = (iconHeight / 2);
var centreX = x + halfWidth;
var centreY = y + halfHeight;
ctx.save();
ctx.fillStyle = "#FF0000";
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
// ctx.translate(-centreX, -centreY);
ctx.restore();
This works, but the rotation no longer guides the rectangle. My conclusion is that the rotate function rotates the canvas, but then leaves it in the new, rotated form (like rotating a piece of paper underneath a pen). So, the bug I had was that the rotation was not being reset; however, apart from the boundary check, this "bugged" behaviour was what I was actually aiming for.
Is there a way to get from a canvas 2d context the absolute position, taking into account the rotation so that, even if I leave the canvas in its "rotated" state, I can perform a boundary check?
Here is a fiddle of the site.
To transform a point from local space (the transformed space) to screen space create a matrix that is a shadow (copy) of the context transform then multiply the point with that matrix
function getTransformToScreen(x,y,rotation,posX,posY){
var xAx = Math.cos(rotation); // x axis x
var xAy = Math.sin(rotation); // x axis y
// the equivalent to
// ctx setTransform(xAx, xAy ,-xAy, xAx, posX, posY);
// second two values (y Axis) is at 90 deg of x Axis if it is
// not at 90 (skewed) then you need to calculate the skewed axis (y axis) direction
return {
x : x * xAx - y * xAy + posX,
y : x * xAy + y * xAx + posY
}
}
To use
// your code
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
// get the top left
var topLeft = getTransformToScreen(
-halfWidth, -halfHeight,
rotationDegree * Math.PI / 180,
centreX, centreY
);

Rotated shape is moving on y-axis when resizing shape width

I'm trying to resize a rotated shape on canvas. My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. How can I prevent this?
I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards.
Here's the fiddle: https://jsfiddle.net/x5gxo1p7/
<style>
canvas {
position: absolute;
box-sizing: border-box;
border: 1px solid red;
}
</style>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = w2;
var y = h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.translate(x, y);
ctx.rotate(Math.PI / 180 * 15);
ctx.translate(-x, -y);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently.
EDIT: updated fiddle to clarify the issue:
https://jsfiddle.net/x5gxo1p7/9/
Always use local coordinates to define shapes.
When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. Think of a image. the top left pixel is always at 0,0 on the image no matter where you render it. The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation.
So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
Now you don't have to mess about with translating forward and back... blah blah total pain.
Just
ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();
or event quicker and eliminating the need to use save and restore
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
The local shape origin (0,0) is where the transformation places the translation.
This greatly simplifies a lot of the work that has to be done
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
function draw() {
ctx.setTransform(1,0,0,1,0,0); // to clear use default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
// you were scaling the shape, that can be done via a transform
// once you have moved the shape to the world coordinates.
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
// after the transformations have moved the local to the world
// you can ignore the canvas coordinates and work within the objects
// local. In this case showing the unscaled box
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
// and a line above the box
ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1);
ctx.scale(0.5,0.5); // the scaling you were doing
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
}
canvas.addEventListener('click', function() {
shape.width += 15;
shape.left -= 15 / 2;
shape.world.rot += Math.PI / 45; // rotate to illustrate location
// of local origin
var distToMove = 15/2;
shape.world.x += Math.cos(shape.world.rot) * distToMove;
shape.world.y += Math.sin(shape.world.rot) * distToMove;
draw();
});
// no need to use requestAnimationFrame (RAF) if you are not animation
// but its not wrong. Nor do you need to bind this (in this case
// this = window) to the callback RAF does not bind a context
// to the callback
/*window.requestAnimationFrame(draw.bind(this));*/
requestAnimationFrame(draw); // functionaly identical
// or just
/*draw()*/ //will work
body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>
<p>Click to grow "and rotate" (I add that to illustrate the local origin)</p>
<p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p>
Try this. You had ctx.translate() used where it was not entirely necessary. That caused the problems.
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.rotate(Math.PI / 180 * 15);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
This is happening because the x and y are set as the half value of the shape size, which completely changes its position.
You should set a point for the center of the shape, anyway. I set this point as ctx.canvas.[width or height] / 2, the half of the canvas.
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));
ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);
ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
Fiddle.
Found a solution, problem was that I wasn't calculating the new center point coordinates.
The new fiddle with solution: https://jsfiddle.net/HTxGb/151/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width =500;
canvas.height = 500;
var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;
var draw = function() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillRect(rectx, recty, rectw, recth);
ctx.restore();
}
document.getElementById('growXRight').addEventListener('click', function() {
rectx -= addedWidth/2;
x += addedWidth/2 * Math.cos(rotation);
y -= addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growXLeft').addEventListener('click', function() {
rectx -= addedWidth/2;
x -= addedWidth/2 * Math.cos(rotation);
y += addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growYTop').addEventListener('click', function() {
recty -= addedHeight/2;
x += addedHeight/2 * Math.sin(rotation);
y -= addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('growYBottom').addEventListener('click', function() {
recty -= addedHeight/2;
x -= addedHeight/2 * Math.sin(rotation);
y += addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('rotatePlus').addEventListener('click', function() {
rotation += addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
document.getElementById('rotateMinus').addEventListener('click', function() {
rotation -= addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
draw();

Categories