HTML Canvas Rotating a character's gun to face mouse - html

I'm very new to Javascript and I've started a simple game. I want the character's gun to rotate to follow the mouse. So far, movement and everything else works fine, except that when I added the rotation functionality the character seems to rotate in a huge circle around the screen. Here's the jsfiddle: https://jsfiddle.net/jvwr8bug/#
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
var mouseX = evt.clientX - rect.top;
var mouseY = evt.clientY - rect.left;
return {
x: mouseX,
y: mouseY
};
}
window.addEventListener('load', function() {
canvas.addEventListener('mousemove', function(evt) {
var m = getMousePos(canvas, evt);
mouse.x = m.x;
mouse.y = m.y;
}, false);
}, false);
The error seems to be somewhere there but obviously it could be something else
**Edit: Thanks to Blindman67 for the fix.

You were rotating the current transform by rotation each frame. ctx.rotate(a) rotates the current transform so each time it is called you increase the rotation amount by a. Think of it as a relative rotation rather than setting the absolute rotation.
To fix your code replace the canon rendering with
//cannon
//ctx.rotate(rotation); // << you had
// setTransform overwrites the current transform with a new one
// The arguments represent the vectors for the X and Y axis
// And are simply the direction and length of one pixel for each axis
// And a coordinate for the origin.
// All values are in screen/canvas pixels coordinates
// setTransform(xAxisX, xAxisY, yAxisX, yAxisY, originX, originY)
ctx.setTransform(1,0,0,1,x,y); // set center of rotation (origin) to center of gun
ctx.rotate(rotation); // rotate about that point.
ctx.fillStyle = "#989898";
ctx.fillRect(15, - 12.5, 25, 25); // draw relative to origin
ctx.lineWidth = 2;
ctx.strokeStyle = "#4f4f4f";
ctx.strokeRect( 15,- 12.5, 25, 25); // draw relative to origin
//body
ctx.fillStyle = "#5079c4";
ctx.beginPath();
ctx.arc(0, 0, size, 0, Math.PI * 2); // draw relative to origin
ctx.fill();
ctx.stroke();
// can't leave the transformed state as is because that will effect anything else
// that will be rendered. So reset to the default.
ctx.setTransform(1,0,0,1,0,0); // restore the origin to the default
And a few more problems to get it working
Just above rendering the canon get the direction to the mouse
// you had coordinates mixed up
// rotation = Math.atan2(mouse.x - y, mouse.y - x); // you had (similar)
rotation = Math.atan2(mouse.y - y, mouse.x - x);
And your mouse event listener is mixing up coordinates and not running very efficiently
Replace all your mouse code with. You don't need onload as the canvas already exists.
canvas.addEventListener('mousemove', function(evt) {
var rect = this.getBoundingClientRect();
mouse.x = evt.clientX - rect.left; // you had evt.clientX - rect.top
mouse.y = evt.clientY - rect.top; // you had evt.clientY - rect.left
}, false);

Related

HTML5 canvas rotation not centered at origin [duplicate]

Hi I want to rotate this shape around its center when I move my mouse, but currently it's rotating around (0, 0). How to change my code?
Source code (also see jsfiddle):
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.angle = 0;
this.toAngle = this.angle;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.update(e.clientX, e.clientY);
});
}
update(nx, ny) {
this.toAngle = Math.atan2(ny - this.cy, nx - this.cx);
}
render() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.beginPath();
ctx.lineWidth = 1;
if (this.toAngle !== this.angle) {
ctx.rotate(this.toAngle - this.angle);
}
ctx.strokeStyle = this.color;
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(this.cx - this.radius / 4, this.cy - this.radius / 4, 20, 20);
ctx.closePath();
ctx.restore();
}
}
var rotatingCircle = new Circle({
x: 150,
y: 100,
radius: 40,
color: 'black'
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();
All good answers, well frustratingly no... they fail to mention that the solution only works if the current transform is at it default. They fail to mention how to get back to the default state and save and restore states.
To get the default transformation state
ctx.setTransform(1,0,0,1,0,0);
To save and restore all states
ctx.save();
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.globalAlpha = 0.4;
ctx.restore(); // each save must be followed by a restore at some point
and they can be nested
ctx.save(); // save default state
ctx.globalAlpha = 0.4;
ctx.save(); // save state with alpha = 0.4
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.restore(); // restore to alpha at 0.4
ctx.restore(); // restore to default.
setTransform completely replaces the current transformation. while transform, scale, rotate, translate, multiply the existing transform with the appropriate transform. This is handy if you have an object attached to another, and want the transformation of the first to apply to the second, and additional transforms to the second but not to the first.
ctx.rotate(Math.PI /2); // Rotates everything 90 clockwise
ctx.rotate(Math.PI /2); // Rotates everything another 90 clockwise so that
// everything is 180 from default
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 1,1
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 2,2
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 3,3
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 4,4
ctx.scale(2,2); // scale by 2 everything twice as big
ctx.scale(2,2); // scale by 2 everything four times as big
And an alternative that does not require the default transform state of ctx
// scaleX, scaleY are scales along axis x,y
// posX, posY is position of center point
// rotate is in radians clockwise with 0 representing the x axis across the screen
// image is an image to draw.
ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.drawImage(image,-image.width / 2, -image.height / 2);
Or if not a image but a object
ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.translate(-object.width / 2, -object.height / 2);
You need to:
first translate to the point of rotation (pivot)
then rotate
then either:
A: draw in at (0,0) using (-width/2, -height/2) as relative coordinate (for centered drawings)
B: translate back and use the object's absolute position and subtract relative coordinates for centered drawing
Modified code:
ctx.beginPath();
ctx.lineWidth = 1;
ctx.translate(this.cx, this.cy); // translate to pivot
if (this.toAngle !== this.angle) {
ctx.rotate(this.toAngle - this.angle);
}
ctx.strokeStyle = this.color;
ctx.arc(0, 0, this.radius, 0, Math.PI * 2); // render at pivot
ctx.closePath(); // must come before stroke() btw.
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(-this.radius / 4, -this.radius / 4, 20, 20); // render at pivot
Modified Fiddle
Bonus tip: you're currently using save()/restore() calls to maintain the transformation matrix. Another way could be to set the matrix using absolute values initially replacing the save()/restore() - so instead of the first translate():
ctx.setTranform(1,0,0,1,this.cx, this.cy); // translate to pivot
You can also set things like styles on an individual basis for each. Regardless, it doesn't change the core solution though.
You have to first translate to the circle centre, make the rotation and then translate back
Do this before rendering the circle and the square
ctx.translate(this.cx, this.cy);
ctx.rotate(this.toAngle - this.angle);
ctx.translate(-this.cx, -this.cy);
jsfiddle below:
https://jsfiddle.net/1st8Lbu8/2/

HTML Canvas reset drawing point after transform and rotate

I have this simple canvas webpage that lets user upload photo from camera by using HTML input type file. The idea is to let user make free drawing on their image. However, I have one problem.
On some devices, the image from camera is drawn onto the canvas with wrong orientation, so I have to provide users a button to rotate their image to get the drawing with correct orientation.
The problem is that after the canvas has been transformed and rotated to get the correct orientation, the drawing coordinates seems to be way off. For example, if I draw straight horizontal line, I get instead straight vertical line after the image has been rotated once. I think the problem lies in that fact that canvas orientation is changed.
So how can I correct back the drawing coordinate after image has been transformed and rotate? My code is below..
window.onload = init;
var canvas, ctx, file, fileURL;
var mousePressed = false;
var lastX, lastY;
function init(){
canvas = document.getElementById('myCanvas')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', touchstartHandler, false)
canvas.addEventListener('mousemove', touchmoveHandler, false)
canvas.addEventListener('mouseup', touchendHandler, false)
canvas.addEventListener('mouseleave', touchcancelHandler, false)
}
function touchstartHandler(e){
e.preventDefault()
mousePressed = true;
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, false);
}
function touchmoveHandler(e){
e.preventDefault()
if (mousePressed) {
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
}
}
function touchendHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function touchcancelHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function Draw(x, y, isDown) {
if (isDown) {
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 12;
ctx.lineJoin = "round";
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
lastX = x;
lastY = y;
}
<!DOCTYPE html>
<html>
<head>
<title>Portrait</title>
</head>
<body>
<canvas id="myCanvas"></canvas><br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera"><br/><br/>
<button onclick="rotate()">Rotate</button>
<script>
var file, canvas, ctx, image, fileURL;
function fileUpload(files){
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500*this.height)/this.width
ctx.drawImage(image,0,0,canvas.width,canvas.height)
ctx.save();
}
image.src = fileURL
}
function rotate(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.translate(canvas.width/2, canvas.height/2)
ctx.rotate(90*Math.PI/180)
ctx.translate(-canvas.width/2, -canvas.height/2)
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}
</script>
</body>
</html>
You need to save the canvas state before rotating and translating, and then restore the state when the transformation is done.
var file, canvas, ctx, image, fileURL, rotation = 90;
function fileUpload(files) {
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500 * this.height) / this.width
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
}
image.src = fileURL
}
function rotate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save(); //save canvas state
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(rotation * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
rotation += 90;
ctx.restore(); //restore canvas state
}
canvas {border: 1px solid red}
<canvas id="myCanvas"></canvas>
<br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera">
<br/>
<br/>
<button onclick="rotate()">Rotate</button>
Simple rotation
Quickest way to rotate the image by steps of 90 deg
ctx.setTransform(
0,1, // direction of x axis
-1,0 // direction of y axis
canvas.width,0 // location in pixels of the origin (0,0)
);
Then draw the image
ctx.drawImage(image,0,0);
Rather than use ctx.restore() that can be slow in many situations you can eset only the transform to the default with.
ctx.setTransform(1,0,0,1,0,0);
Rotate 90, 180, -90deg
Thus to rotate 90 deg
ctx.setTransform(0,1,-1,0,canvas.width,0);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate 180 deg
ctx.setTransform(-1,0,0,-1,canvas.width,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate -90 deg
ctx.setTransform(0,-1,1,0,0,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);

How to show part of element from other side of canvas

How to show part of element outside of canvas from opposite side canvas. Illustration:
You need to draw twice when the shape is outside canvas' boundaries. Draw the main part first, then the same part offset by width so it gives the illusion of showing on the other side.
Manually Draw twice
This draws a shape going from right to left, when the shape is outside the left edge it will be redrawn at the right edge representing the part that is non-visible on the left side. For the opposite way (left to right) the principle is just the same, just use x with canvas' width instead of 0.
var ctx = document.querySelector("canvas").getContext("2d"),
x = 100, // start position
w = 200; // shape width
ctx.fillStyle = "#777";
(function loop() {
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
if (x < 0) { // should rotate? draw secondary
ctx.fillRect(ctx.canvas.width + x, 0, w, 150); // use canvas width + x (x<0)
}
x -= 7; // animate
if (x <= -w) x = ctx.canvas.width + x; // at some point reset x
requestAnimationFrame(loop)
})();
<canvas></canvas>
Translated Pattern
To simplify this a CanvasPattern can be used. The later version of canvas allows local transforms on the pattern itself, but since this is not currently widely spread I'll show an example using normal transforms and compensated x position:
var ctx = document.querySelector("canvas").getContext("2d"),
pattern,
x = 100, // start position
w = 200; // shape width
// create pattern
ctx.fillStyle = "#777";
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
pattern = ctx.createPattern(ctx.canvas, "repeat"); // use current canvas as pattern
ctx.fillStyle = pattern; // set pattern as fillStyle
(function loop() {
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.setTransform(1,0,0,1,x,0); // translate absolute x
ctx.fillRect(-x, 0, 300, 150); // fill using pattern, compensate transform
x -= 7; // animate
requestAnimationFrame(loop)
})();
<canvas></canvas>

Rotate the volume knob in html5 with createJS

I want to create volume knob using HTML5 and CreateJS. It is almost done. you can see this on following url
http://www.urbantruanthosting.co.uk/radiosimulator/testpage.html
but it is moving on every event of js. And I want to rotate on mouse's pressmove event on both side and currently it's moving clockwise, how can I rotate it in reverse. Please give me suggestion.
Thanks in advance.
and I have used following code.
var bmp = new createjs.Bitmap(image).set({
scaleX: 1,
scaleY: 1,
regX: w / 2,
regY: h / 2,
cursor: "pointer",
x: 305,
y: -90,
rotation: -55
});
bmp.regX = bmp.image.width / 2;
bmp.regY = bmp.image.height / 2;
var movieClip = new createjs.Container();
movieClip.addChild(bmp);
imageContainer.addChild(movieClip);
/*add events to enavle the knob to moveclockwise START*/
bmp.addEventListener("pressmove", function (evt) {
TweenMax.to(bmp, 5, { rotation: 270, repeat: -1, ease: Linear.easeNone });
if (headerCnt == 0) {
audioElement.play();
screen.src = 'images/header_5.jpg';
headerCnt = 5;
screenImage.set({
image: screen
});
}
stage.update();
});
This is where trigonometry comes in handy. Check out the following fiddle. Using the Math.atan2 function, we can calculate the angle of the mouse pointer relative to the center of the dial. Then, we convert radians to degrees and set the rotation of the dial.
dial.addEventListener("pressmove", function(e){
console.log(e);
//Calc Angle
var adj = e.stageX - dialHolder.x;
var opp = e.stageY - dialHolder.y;
var angle = Math.atan2(opp, adj);
angle = angle / (Math.PI / 180);
dial.rotation = angle;
});
Note: The regX and regY points might be slightly off which is why the dial wobbles a bit.

How do I resize a path already closed on an HTML5 canvas?

I have a quadratic curve rendered on a canvas. I want to animate it by means of window.setInterval and changing it's dimensions (note not simply changing it's scale) thereafter.
How do I retain an editable reference to the path after calling context.closePath()?
I'd recommend that you maintained a reference to the path in a new Path object; that way you could modify x, y, points etc on the fly and then render it each animation step.
var testPath = new Path(100, 100, [[40, 40], [80, 80]]);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function Path(x, y, points)
{
this.x = x;
this.y = y;
this.points = points;
}
function update()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'red';
ctx.moveTo(testPath.points[0][0], testPath.points[0][1]);
for (var i = 1; i < testPath.points.length; i++)
{
ctx.lineTo(testPath.points[i][0], testPath.points[i][1]);
}
ctx.stroke();
testPath.points[1][1]++; // move down
// loop
requestAnimationFrame(update);
}
update();​
For some reason JSFiddle doesn't play nice with Paul Irish's requestAnimationFrame polyfill but it should work locally. I'd definitely recommend this over setInterval.
http://jsfiddle.net/d2sSg/1/