Canvas how to rotate image and redraw without rotating the entire context - javascript

I am trying to recreate asteroids with canvas and JS, and am having trouble rotating the ship based on it's angle. I have tested my methods for calculating the angle and converting to radians, and they checkout, and the angle is defined when called in draw(), but the rotation is not occurring. When I can get some rotation going, it rotates the entire context, not the ship.
I have included the entire ship class, but the relevant function is the draw function at the bottom.
function Ship(posOptions) {
let options = {game: posOptions['game'], color: 'green', pos: posOptions['pos'], radius: 20, vel: [0,0], wrappable: true, type: 0}
MovingObject.call(this, options);
this.facingDir = 0;
this.H = window.innerHeight; //*0.75,
this.W = window.innerWidth; //*0.75;
this.xc = this.W/2; //zeby bylo w centrum :v
this.yc = this.H/2; //jw.
this.x = this.xc;
this.y = this.yc;
this.dv = 0.2;
this.dt = 1;
this.vx = 0;
this.vy = 0;
this.maxVel = 10;
}
Utils.inherits(Ship, MovingObject);
//Relocate ship if hit by asteroid.
Ship.prototype.relocate = function () {
this.pos = this.game.randomPosition();
this.vel = [0,0];
};
Ship.prototype.fireBullet = function () {
let bulletVel = [(this.vel[0] * 4), (this.vel[1] * 5) - 10];
let bullet = new Bullet({pos: this.pos, vel: bulletVel, game: this.game});
this.game.bullets.push(bullet);
};
//Calculate velocity based on keyboard input and angle of rotation
Ship.prototype.power = function (impulse) {
if (impulse[0] === -2 && this.facingDir !== Math.PI) {
this.facingDir <= Math.PI ? this.facingDir += 30 / 180 * Math.PI : this.facingDir -= 30 / 180 * Math.PI;
}
if (impulse[0] === 2 && this.facingDir !== 0) {
this.facingDir <= 0 ? this.facingDir += 30 / 180 * Math.PI : this.facingDir -= 30 / 180 * Math.PI;
}
if (impulse[1] === 2 && this.facingDir !== (Math.PI / 2 * 3)) {
this.facingDir <= (3 * Math.PI / 2) ? this.facingDir += 30 / 180 * Math.PI : this.facingDir -= 30 / 180 * Math.PI;
}
if (impulse[1] === -2 && this.facingDir !== (Math.PI / 2)) {
this.facingDir <= (Math.PI / 2) ? this.facingDir += 30 / 180 * Math.PI : this.facingDir -= 30 / 180 * Math.PI;
}
this.vel[0] += impulse[0];
this.vel[1] += impulse[1];
};
Ship.prototype.move = function () {
if (this.isWrappable) {
this.pos = this.game.wrap(this.pos);
}
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
//Attenuate the velocity over time so it doesn't accelerate forever.
this.vel[0] *= .98;
this.vel[1] *= .98;
};
Ship.prototype.convertToRadians = function(degree) {
return degree*(Math.PI/180);
}
Ship.prototype.draw = function (ctx) {
const img = new Image();
img.onload = function () {
ctx.drawImage(img, this.pos[0]-this.radius, this.pos[1]-this.radius)
};
img.src = 'galaga_ship.png';
ctx.drawImage(img, this.pos[0]-this.radius, this.pos[1]-this.radius);
**//This is where I am stuck.**
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.facingDir);
ctx.translate(-7,-10);
ctx.restore();
};
module.exports = Ship;

You are not drawing anything before restoring the context. The way ctx.save() and ctx.restore() should be used is that you save it, with the original rotation and location of the origin, you transform it, do the drawing, then restore it back to where it was originally. Thus only the drawing uses the transformed coordinate system, and then everything else acts as normal again.
**//This is where I am stuck.**
ctx.save();
ctx.translate(this.x,this.y);
ctx.rotate(this.facingDir);
ctx.translate(-7,-10);
// PUT DRAWING YOUR SHIP HERE
ctx.restore();

Related

Follow image to mouse with rotation on Javascript

How make Santa follow to mouse with rotation and own speed, not mouse speed?
Where mouse now - it's destination, where go santa with own speed.
Santa rotate with speed.
How can I do this?
Demo code
game.js
var canvas, ctx, player
function init() {
canvas = document.getElementById("canvas")
ctx = canvas.getContext( "2d" )
resizeCanvas()
player = new Player(
ctx,
canvas.width / 2,
canvas.height / 2 + 100
)
window.onresize = resizeCanvas
canvas.onmousemove = mousemove
}
function mousemove( e ) {
player.x = e.clientX * devicePixelRatio
player.y = e.clientY * devicePixelRatio
}
function render() {
ctx.clearRect( 0, 0, canvas.width, canvas.height )
player.draw()
}
function step() {
render()
requestAnimationFrame( step )
}
init()
step()
It's not trivial task with rotation.
Modified version with some magic: demo
For distance used Pythagorean theorem.
Player moving to forward (by current rotation).
class Player {
constructor(ctx, x, y) {
this.x = x
this.y = y
this.dest = {
x: 0,
y: 0
}
this.width = 200
this.height = 200
this.velocity = 12
this.angularVelocity = 7
this.rotation = 0
this.ctx = ctx
this.image = new Image()
this.image.src = "//habrastorage.org/files/447/9b4/6d3/4479b46d397e439a9613ce122a66a506.png"
}
draw() {
this.ctx.translate(this.x, this.y)
this.ctx.rotate(this.rotation + 4.7)
this.ctx.drawImage(
this.image,
-this.width / 2, -this.height / 2,
this.width, this.height
)
this.ctx.rotate(-this.rotation - 4.7)
this.ctx.translate(-this.x, -this.y)
}
distance(target) {
let data = {
x: target.x - this.x,
y: target.y - this.y
}
data.len = Math.sqrt(data.x * data.x + data.y * data.y)
return data
}
rotate(dt) {
let path = this.distance(this.dest)
let target = Math.atan2(path.y, path.x)
let delta = this.rotation - target
if (delta > 0.1 || delta < -0.1) {
var _delta = delta
if (_delta < 0) {
_delta += Math.PI * 2
}
if (delta < -Math.PI || (delta > 0 && delta < Math.PI)) {
this.rotation -= _delta / this.angularVelocity
} else {
this.rotation -= (_delta - Math.PI * 2) / this.angularVelocity
}
// Reduce character rotation into the -PI thru PI range
this.rotation = (this.rotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI
}
}
step() {
let distance = this.distance(this.dest).len
if (distance < this.width / 1.5) {
this.draw()
return
}
let vel = distance / 15
if (vel > this.velocity) {
vel = this.velocity
}
this.rotate()
this.x += vel * Math.cos(this.rotation)
this.y += vel * Math.sin(this.rotation)
this.draw()
}
}

How to move an object diagonally and in zigzag?

I created an algorithm to move a particle diagonally and it works fine using an angle. Basically, this is what I do:
this.x += this.speed * Math.cos(this.angle * Math.PI / 180);
this.y += this.speed * Math.sin(this.angle * Math.PI / 180);
this.draw();
How can I combine this with a zigzag movement?
I recommend calculating the lateral deviation from the normal path or amplitude which is given by
// Triangle wave at position t with period p:
function amplitude(t, p) {
t %= p;
return t > p * 0.25 ? t < p * 0.75 ? p * 0.5 - t : t - p : t;
}
where t will be set to the length of the traveled path, and p is the period of the 'zigzag' triangle wave pattern.
Given the amplitude and the previous position, we can now easily compute the next position by moving ahead as described by your original code and then adding the lateral deviation to our position:
var amplitude = amplitude(distance, p) - this.amplitude(previous_distance, p);
this.x += amplitude * Math.sin(this.angle * Math.PI/180);
this.y -= amplitude * Math.cos(this.angle * Math.PI/180);
A complete example with two movable objects, one moving 'normally' and one following a 'zigzag' pattern:
function Movable(x, y, speed, angle, period) {
this.x = x;
this.y = y;
this.speed = speed;
this.angle = angle;
this.period = period;
this.distance = 0;
}
Movable.prototype.moveDiagonal = function() {
this.distance += this.speed;
this.x += this.speed * Math.cos(this.angle * Math.PI / 180);
this.y += this.speed * Math.sin(this.angle * Math.PI / 180);
}
Movable.prototype.amplitudeZigZag = function() {
var p = this.period, d = this.distance % p;
return d > p * 0.25 ? d < p * 0.75 ? p * 0.5 - d : d - p : d;
}
Movable.prototype.moveZigZag = function() {
var amplitude1 = this.amplitudeZigZag();
this.moveDiagonal();
var amplitude2 = this.amplitudeZigZag();
var amplitude = amplitude2 - amplitude1;
this.x -= amplitude * Math.sin(this.angle * Math.PI/180);
this.y += amplitude * Math.cos(this.angle * Math.PI/180);
}
Movable.prototype.draw = function(context) {
context.beginPath();
context.arc(this.x, this.y, 1, 0, 2 * Math.PI);
context.stroke();
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var m1 = new Movable(0, 0, 2, 0, 50);
var m2 = new Movable(0, 0, 2, 0, 50);
for (var i = 0; i < 1000; ++i) {
m1.angle += Math.cos(i * Math.PI/180);
m2.angle += Math.cos(i * Math.PI/180);
m1.moveDiagonal();
m2.moveZigZag();
m1.draw(context);
m2.draw(context);
}
<canvas id="canvas" width="600" height="200"></canvas>
So let's say that you're moving in the direction this.angle and you want to zig-zag in that direction moving side to side ±45° from that direction. All you need to do is have a variable like var zigzag = 45; and add zigzag to this.angle when calculating the new positions. And to make it zig-zag, you need to negate it every so often like this zigzag *= -1;. If you want to stop zig-zagging, set zigzag = 0;.
The trick is knowing when to alternate between ±45°. Maybe you can have the switch timed and keep a reference to the last time you switched using Date.now();. You can check the difference between the current time and the recorded time, then negate zigzag once you've surpassed a certain number of milliseconds. Just remember to record the new time of the last switch. You could also keep track of distance travelled and use the same approach, whatever works for you.

Why are some of my angles incorrect when changing?

I have programed a program with JavaScript and the DOM that shows a ball bouncing in a box; it is a canvas element. The only things that do not work correctly are the angles. This is not noticeable until the angles are small, when it is clear the ball did not bounce back correctly from the box. The angle may be coming to a box gradually, and then bounce very steeply back. This seems like perhaps instead of the angle-in=angle-out, the angle that was being headed from was what was output angle. This would be equivalent of an angle in and its compliment out. The problem seems to happen with only half the types of bounces: it might not happen on one wall coming in a direction, but would on another wall coming in a direction.
: http://i.stack.imgur.com/WviEd.gif
I have posted all the code for ability for the test of the code, and a gradual angle is used, so the problem can be seen, but the angles that are the problem are in the checkAngle function.
<!doctype html>
<script src="code/chapter/15_game.js"></script>
<script src="code/game_levels.js"></script>
<script src="code/chapter/16_canvas.js"></script>
<canvas width="400" height="400"></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
var lastTime = null;
function frame(time) {
if (lastTime != null)
updateAnimation(Math.min(100, time - lastTime) / 1000);
lastTime = time;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
var x = y = 200//, angle = 2 * Math.PI * Math.random();
var angle = 2 * Math.PI / 40 + Math.PI;
function checkAngle(angle) {
if(x + 10 >= 400) {
if(angle <= Math.PI)
return angle = (Math.PI/2) + ((Math.PI / 2) - reduceAngle(angle));
else if(angle > Math.PI)
return angle = (3 * Math.PI / 2) - ((Math.PI / 2) - reduceAngle(angle));
}else if(x - 10 <= 0) {
if(angle <= Math.PI)
return angle = Math.PI/2 - reduceAngle(angle);
else if(angle > Math.PI)
return angle = 3* Math.PI/2 + (Math.PI/2 - reduceAngle(angle));
}else if(y - 10 <= 0) {
if(angle >= 3 * Math.PI /2)
return angle = Math.PI/2 - reduceAngle(angle);
else if(angle < 3 * Math.PI/2)
return angle = Math.PI - (Math.PI / 2 - reduceAngle(angle));
}else if(y + 10 >= 400) {
if(angle <= Math.PI/2)
return angle = 2*Math.PI - (Math.PI / 2 - reduceAngle(angle));
else if(angle > Math.PI/2)
return angle = Math.PI + (Math.PI / 2 - reduceAngle(angle));
}else
return angle;
}
function reduceAngle(angle) {
if(angle < Math.PI / 2) {
return angle;
}else{
angle = angle - (Math.PI / 2);
return reduceAngle (angle);
}
}
function updateAnimation(step) {
cx.clearRect(0, 0, 400, 400);
cx.lineWidth = 4;
cx.strokeRect(0, 0, 400, 400);
angle = checkAngle(angle);
x += Math.cos(angle) * step * 200;
y += Math.sin(angle) * step * 200;
cx.lineWidth = 2;
cx.beginPath();
cx.arc(x, y, 20, 0, 7);
cx.stroke();
}
</script>
When dealing with reflections in a non-rotated box you don't really need to deal with angles when reflecting. Just define an initial vector based on angle.
var vector = {
x: speed * Math.cos(angle),
y: speed * Math.sin(angle)
};
Then you simply check bounds for x and y separately and inverse the slope-value for each axis:
if (x - radius <= 0 || x + radius>= 400) {
vector.x = -vector.x; // reflect x
}
if (y - radius<= 0 || y + radius> 400) {
vector.y = -vector.y; // reflect y
}
You can always adjust the angle on the fly by adding another vector with the delta-angle.
If your bounce box would not be 0° rotated, then check out this answer for vector-reflection.
For example
Using your code as a basis, this would be implemented like this:
var cx = document.querySelector("canvas").getContext("2d");
var lastTime = null;
var x, y;
// calculate an initial vector
var angle = 20 / 180 * Math.PI; // angle -> radians
var speed = 5;
var vector = {
x: speed * Math.cos(angle),
y: speed * Math.sin(angle)
};
x = y = 200;
function frame(time) {
if (lastTime != null)
updateAnimation(Math.min(100, time - lastTime) / 1000);
lastTime = time;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
function checkAngle() {
var dlt = 10 + 2 + 4; //include radius and line-widths;
if (x - dlt <= 0 || x + dlt >= 400) {
vector.x = -vector.x; // reflect x
}
if (y - dlt <= 0 || y + dlt > 400) {
vector.y = -vector.y; // reflect y
}
}
function updateAnimation(step) {
cx.clearRect(0, 0, 400, 400);
cx.lineWidth = 4;
cx.strokeRect(0, 0, 400, 400);
x += vector.x; // use our vector
y += vector.y;
checkAngle(); // test for hits
cx.lineWidth = 2;
cx.beginPath();
cx.arc(x, y, 20, 0, 7);
cx.stroke();
}
<canvas width="400" height="400"></canvas>
Judging by what I've seen in your drawings and demonstrations, the angles are being taken off the wrong axis. If approaching at 20 degrees, you'd expect the ball to leave at 180-20 degrees. Instead what it's doing is leaving at 90+20 degrees.
While I can't find the precise place in your code that makes this error, I felt I had to point this out in the hopes that someone can improve upon it.

Fill color dynamically in Canvas Cylinder I am getting error as max width undefined

kindly bear with my english
I am working on creating (I am new to Canvas) Cylinder. Dynamically fill color when the user enter the percentage value in the text box for example 10% the cylinder fill for 10% like wise till 100%.
I have created cylinder, I have created the little validation for the text box when i tried to other value instead of %
here is my code for validation and Filling color
<script type="text/javascript">
$(document).ready(function() {
$("#my_input").focusout(function(event) {
if($("#my_input").val().indexOf("%") != -1){
if($.isNumeric($("#my_input").val().replace('%',''))) {
// Allow only backspace and delete
if ( event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 37) {
//$("#myCanvas").animate({ opacity: 0.25 });
}
else {
// Ensure that it is a number and stop the keypress
if (event.keyCode < 48 || event.keyCode > 57) {
event.preventDefault();
}
}
perc = parseInt($("#my_input").val().replace('%','')) / 100;
draw();
}
}
else{
alert('Value in %');
}
});
});
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
var x = 180;
var y = 40;
context.beginPath();
context.rect(x, y, maxWidth, maxHeight);
context.lineWidth = 7;
var per = perc;
if(per > 1)perc = 1;
// fill
context.beginPath();
context.fillStyle = 'yellow';
context.rect(x, y, maxWidth * perc, maxHeight);
context.fill();
}
Here is my HTML Code for canvas
<canvas id="canvas" width="300px" height="300px"></canvas>
Here is the code for Creating Cylinder
<script type='text/javascript'>//<![CDATA[
//the below is code is for to work RequestAnimationFrame (SVG) in all the browsers -- Mahadevan
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
//the below code is to generate Cylinder object -- Mahadevan
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var degreeAngle = 0;
requestAnimationFrame(animate);
function drawRotatedCylinder(x, y, w, h, degreeAngle) {
context.save();
context.translate(x + w / 10, y + h / 10);
context.rotate(degreeAngle * Math.PI / 180);
drawCylinder(-w / 10, -h / 10, w, h);
context.restore();
}
function drawCylinder(x, y, w, h) {
context.beginPath(); //to draw the top circle
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01) {
xPos = (x + w / 2) - (w / 2 * Math.sin(i)) * Math.sin(0 * Math.PI) + (w / 2 * Math.cos(i)) * Math.cos(0 * Math.PI);
yPos = (y + h / 8) + (h / 8 * Math.cos(i)) * Math.sin(0 * Math.PI) + (h / 8 * Math.sin(i)) * Math.cos(0 * Math.PI);
if (i == 0) {
context.moveTo(xPos, yPos);
} else {
context.lineTo(xPos, yPos);
}
}
context.moveTo(x, y + h / 8);
context.lineTo(x, y + h - h / 8);
for (var i = 0 * Math.PI; i < Math.PI; i += 0.01) {
xPos = (x + w / 2) - (w / 2 * Math.sin(i)) * Math.sin(0 * Math.PI) + (w / 2 * Math.cos(i)) * Math.cos(0 * Math.PI);
yPos = (y + h - h / 8) + (h / 8 * Math.cos(i)) * Math.sin(0 * Math.PI) + (h / 8 * Math.sin(i)) * Math.cos(0 * Math.PI);
if (i == 0) {
context.moveTo(xPos, yPos);
} else {
context.lineTo(xPos, yPos);
}
}
context.moveTo(x + w, y + h / 8);
context.lineTo(x + w, y + h - h / 8);
context.strokeStyle = '#ff0000';
context.stroke();
context.fillStyle = 'yellow';
context.fill();
}
function animate() {
//requestAnimationFrame(animate);
context.clearRect(0, 0, canvas.width, canvas.height);
drawRotatedCylinder(100, 100, 180, 180, degreeAngle++);
}
//]]>
</script>
And here is my Final HTml Code
Numberic Value <input type ="text" id="my_input" class="numeric" name="my_input" placeholder="Enter value"/>
When i tried to enter the value in the textbox that cylinder canvas getting hidden and i am getting an error as Uncaught ReferenceError: maxWidth is not defined
When i checked for circle canvas in google it says i need to put (context.rect) instead of rect i need to put arc i tried putting that still it was not working.
Kindly help me for the above solution
Thanks & REgards
Mahadevan
You should adjust the height of the cylinder, not the width... And should redraw the cylinder if you are clearing (clearRect) the canvas.
You can try with the following function:
function draw()
{
maxWidth = 180;
maxHeight = 140;
context.clearRect(0, 0, canvas.width, canvas.height);
var x = 190;
var y = 260;
var per = perc;
if(per > 1)perc = 1;
// fill
context.beginPath();
context.fillStyle = 'yellow';
context.rect(x-maxWidth/2, y-maxHeight * perc, maxWidth, maxHeight * perc);
context.fill();
drawCylinder(100, 100, 180, 180);
}
Check working Fiddle demo ...

HTML 5 homing missile

This is my class
function Missile(drawX, drawY) {
this.srcX = 0;
this.srcY = 0;
this.width = 16;
this.r = this.width * 0.5;
this.height = 10;
this.drawX = drawX;
this.drawY = drawY;
this.img = planeHomingMissile;
this.rotation = -90;
}
Missile.prototype.draw = function () {
ctxHM.save();
ctxHM.translate(this.drawX, this.drawY);
ctxHM.rotate(this.rotation);
ctxHM.drawImage(this.img, -this.r, -this.r);
ctxHM.restore();
}
And this is my JavaScript logic:
function shootHomingMissile() {
//if(!missileOut) {
hMissile = new Missile(player1.drawX + 22, player1.drawY + 32);
missileOut = true;
//}
}
function updateHomingMissile() {
if(missileOut) {
var targetX = 500 - hMissile.drawX;
var targetY = 50 - hMissile.drawY;
//The atan2() method returns the arctangent of the quotient of its arguments, as a numeric value between PI and -PI radians.
//The number returned represents the counterclockwise angle in radians (not degrees) between the positive X axis and the point (x, y)
var rotations = Math.atan2(targetY, targetX) * 180 / Math.PI;
hMissile.rotation = rotations;
var vx = bulletSpd * (90 - Math.abs(rotations)) / 90;
var vy;
if (rotations < 0)
vy = -bulletSpd + Math.abs(vx);
else
vy = bulletSpd - Math.abs(vx);
hMissile.drawX += vx;
hMissile.drawY += vy;
}
}
function drawHomingMissile() {
ctxHM.clearRect(0,0,575,800);
hMissile.draw();
}
I want my missile(facing upwards) to target (500, 50) and face it while targeting.
the movement seems to work but its not facing the target its just keeps rotating but the missile is going to the target.
I'm a starting developer so my code is kind of mess, please help me :)
You are converting your angle from radians to degrees and is using that for your rotation() which require radians.
Try this in your Missile.prototype.draw:
ctxHM.rotate(this.rotation * Math.PI / 180);
(or simply keep the angle in radians at all stages)

Categories

Resources