I'm trying to make a pinball game and wondering the logic of the pinball flipper motion.
On the even handler of a right arrow, I want to move my rectangular piece several degrees up.
ctx.rotate(-(20*Math.PI/180));
ctx.beginPath();
ctx.rect(this.rposX , this.rposY, this.width, this.height);
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath();
ctx.rotate(20*Math.PI/180);
if (rPressed) {
this.flipRight(ctx);
}
Is what I have. How should I try to flip the flipper. Sometimes, I rotate it but that rotates all the objects.
Rotating a rendering
To do the flippers you create a function that draws a flipper at the origin (0,0) and along the xAxis left to right.
So from your rectangle example rather than draw the rectangle where the flipper is, draw it so that the canvas coords 0,0 is at the point of rotation. You will move it via the transform to the correct position.
ctx.strokeRect(-10,-10,100,20); // 0,0 is point of rotation
You position the flipper by moving its center point setTransform I use setTransform as it save having to use save and restore.
// x,y is the position you want the flipper to be.
ctx.setTransform(1,0,0,1,x,y); // sets position of flipper by moving point of rotation
And then rotate with
ctx.rotate(angle); // angle is in radians
The just draw the flipper
ctx.strokeRect(-10,-10,100,20); // 0,0 is point of rotation
Keyboard events
To animate I draw the flippers 60 times a second. I have two event listeners listen to keydown and keyup events. When a key is down I set the flipper flag on to true and when key up to false. I don't do any other processing in the key events.
See demo for more details on key event listeners
Animating the flippers
In the animation loop I call the flipper update function. It checks if the flipper is on or off and then moves the flipper depending on its state. This function is called once for every animation frame.
Example
I have not done a flipper or pinball game in a long time so I had a bit of fun and created some working flippers.
The function you want is called drawFlipper it has comments. The whole lot is animated using requestAnimationFrame
// get the canvas context
const ctx = canvas.getContext("2d");
// defines the flipper
const flipper = {
on : false, // on when key down
size : 20, // radius of flipper base
pos : 0.1,
shapeAng : Math.PI * 0.4, // approx angle to pointy end that line starts at the big arc
point : 5, // radius of pointy end
length : 100, // length of flipper
action : 0, // action is the rotational position
actionDest : 0, // is current destination where flipper should be
actionV : 0.0, // is movement towards destination
// next two numbers v must be 0 < v < 1
actionAccel : 0.7, // relative acceleration of flipper for action. bigger number faster flipper
actionDrag : 0.61, // amount of drag on flipper action. Smaller number more drag
actionExtent : 1, // max travel of flipper
actionBumperDamp : 0.8, // Amount of dampening flipper stop has
update(){
if(this.on){
this.actionDest = this.actionExtent;
}else{
this.actionDest = 0; // home position
}
this.actionV += (this.actionDest - this.action) * this.actionAccel;
this.actionV *= this.actionDrag
this.action += this.actionV
if(this.action > this.actionExtent){
this.action = this.actionExtent;
this.actionV *= this.actionBumperDamp;
this.actionV -= this.actionV;
}
}
}
// keyboard listeners
document.addEventListener("keydown",(e)=>{
flipper.actionDrag = Number(dragIn.value);
flipper.actionAccel = Number(accelIn.value);
flipper.on = true
});
document.addEventListener("keyup",(e)=>{ flipper.on = false});
window.focus(); // get the keyboards attention
// draws a flipper
// x,y is the location of the flipper base
// start angle is the home angle of the flipper
// flipperTravelDirection is flipper travel direction when active
function drawFlipper(x,y,startAng,flipperTravelDirection){
// set the position
ctx.setTransform(1,0,0,1,x,y);
// set the angle of the flipper plus action position
ctx.rotate(startAng + flipper.action * flipperTravelDirection);
// draw the flipper. the transform sets position and rotation
// so just have to render the flipper around its own center 0,0
ctx.beginPath();
ctx.arc(0, 0, flipper.size,flipper.shapeAng, Math.PI * 2 - flipper.shapeAng);
ctx.lineTo(flipper.length, - flipper.point);
ctx.arc(flipper.length,0,flipper.point,-Math.PI / 2,Math.PI /2)
ctx.closePath();
ctx.stroke();
}
var W,H; // canvas width and height
// draws the flippers. This function would do all rendering of the
// game animations
function mainLoop(time){
// resize if needed
if(canvas.width !== innerWidth || canvas.height !== innerHeight){ // resize canvas if window size has changed
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
}
// clear canvas
ctx.setTransform(1,0,0,1,0,0); // set default transform
ctx.clearRect(0,0,W,H); // clear the canvas
// update flipper actions
flipper.update();
// render the flippers left and right
drawFlipper(flipper.size * 2, H / 2, Math.PI * 0.25,-1);
drawFlipper(W - flipper.size * 2, H / 2, Math.PI * 0.75,1);
// get next animation loop
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index : -10;
}
body {font-family : arial;}
Press any key to move flippers. Move sliders to change flipper acction.<br>
Left slider is acceleration, right is drag. Some settings make them flip out, change setting and hit key to fix<br>
Accel<input id="accelIn" type="range" min = 0.01 max = 0.99 step = 0.01 value = 0.5></input>
Drag<input id="dragIn" type="range" min = 0.01 max = 0.99 step = 0.01 value = 0.5></input>
<canvas id=canvas></canvas>
Related
I need help, I have been trying to rotate my image which is a pointer. with my code below it rotates but it rotates not as I want.
Code:
var c = document.getElementById("ctx");
var ctx = c.getContext("2d");
for (var d = 0; d < 360; d++) {
setTimeout(function (d) {
c.globalAlpha = 0.5;
ctx.clearRect(0, 0, c.width, c.height);
ctx.save();
ctx.translate(c.width / 2, c.height / 2);
ctx.rotate(d * Math.PI / 180);
ctx.drawImage(imageLoader.pointer, -imageLoader.pointer.width / 2, -imageLoader.pointer.height / 2);
ctx.restore();
}, 100 * d, d);
}
This code makes my image rotate weirdly I think it rotates on its own axis but I am not sure.
However I need a rotation something like this image.
I think this rotation is around a circle, i need something like this , can someone give me a hint or help me out?
I was trying to do it with a shape but its more difficult because i need to find the tangent and more geometric formulas to make it rotate like this.
I appreciate your time in advance, thanks.
The function to draw a rotated image rotating around a point on the canvas and offset to its own center of rotation.
// x,y is the location on the canvas that the image will rotate around
// cx,cy is the coordinates on the image that is rotated around
// angle is the amount of rotation in radians
function drawImage(image,x,y,cx,cy,angle){
ctx.setTransform(1,0,0,1,x,y); // set the rotation origin
ctx.rotate(angle); // rotate
ctx.drawImage(image,-cx,-cy); // draw image offset to put cx,cy at the point of rotation
ctx.setTransform(1,0,0,1,0,0); // restore the transform
}
So if your image is 50 by 100 pixels and you want the image to rotate about the point on it at 25, 80 (center near bottom) and that rotation point to be on the canvas at 200,200 then
drawImage(image,200,200,25,80,3); //rotate 3 radians
To do so in an animation.
// assumes image has loaded and ctx is the context of the canvas.
requestAnimationFrame(mainLoop); // starts the animation
function mainLoop(time){ // time is passed by requestAnimationFrame
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); // clear
drawImage(image,200,200,25,80,(time / 5000) * Math.PI * 2); // rotate one every 5 seconds
requestAnimationFrame(mainLoop);
}
const image = new Image
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const ctx = myCanvas.getContext("2d");
function drawImage(image,x,y,cx,cy,angle){
ctx.setTransform(1,0,0,1,x,y); // set the rotation origin
ctx.rotate(angle); // rotate
ctx.drawImage(image,-cx,-cy); // draw image offset to put cx,cy at the point of rotation
ctx.setTransform(1,0,0,1,0,0); // restore the transform
}
// assumes image has loaded and ctx is the context of the canvas.
requestAnimationFrame(mainLoop); // starts the animation
function mainLoop(time){ // time is passed by requestAnimationFrame
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); // clear
if(image.complete){
drawImage(image,250,250,image.width / 2,image.height * 0.8,(time / 5000) * Math.PI * 2); // rotate one every 5 seconds
}
requestAnimationFrame(mainLoop);
}
canvas {
border : 2px black solid;
}
<canvas id="myCanvas" width = 500 height = 500></canvas>
I've been recently adding shadows to a project. I've ended up with something that I like, but the shadows are a solid transparent color throughout. I would prefer them to be a fading gradient as they go further.
What I currently have:
What I'd like to achieve:
Right now I'm using paths to draw my shadows on a 2D Canvas. The code that is currently in place is the following:
// Check if edge is invisible from the perspective of origin
var a = points[points.length - 1];
for (var i = 0; i < points.length; ++i, a = b)
{
var b = points[i];
var originToA = _vec2(origin, a);
var normalAtoB = _normal(a, b);
var normalDotOriginToA = _dot(normalAtoB, originToA);
// If the edge is invisible from the perspective of origin it casts
// a shadow.
if (normalDotOriginToA < 0)
{
// dot(a, b) == cos(phi) * |a| * |b|
// thus, dot(a, b) < 0 => cos(phi) < 0 => 90° < phi < 270°
var originToB = _vec2(origin, b);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(a.x + scale * originToA.x,
a.y + scale * originToA.y);
ctx.lineTo(b.x + scale * originToB.x,
b.y + scale * originToB.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.globalAlpha = _shadowIntensity / 2;
ctx.fillStyle = 'black';
ctx.fillRect(_innerX, _innerY, _innerWidth, _innerHeight);
ctx.globalAlpha = _shadowIntensity;
ctx.fill();
ctx.globalAlpha = 1;
}
}
Suggestions on how I could go about achieving this? Any and all help is highly appreciated.
You can use composition + the new filter property on the context which takes CSS filters, in this case blur.
You will have to do it in several steps - normally this falls under the 3D domain, but we can "fake" it in 2D as well by rendering a shadow-map.
Here we render a circle shape along a line represented by length and angle, number of iterations, where each iteration increasing the blur radius. The strength of the shadow is defined by its color and opacity.
If the filter property is not available in the browser it can be replaced by a manual blur (there are many out there such as StackBoxBlur and my own rtblur), or simply use a radial gradient.
For multiple use and speed increase, "cache" or render to an off-screen canvas and when done composite back to the main canvas. This will require you to calculate the size based on max blur radius as well as initial radius, then render it centered at angle 0°. To draw use drawImage() with a local transform transformed based on start of shadow, then rotate and scale (not shown below as being a bit too broad).
In the example below it is assumed that the main object is drawn on top after the shadow has been rendered.
The main function takes the following arguments:
renderShadow(ctx, x, y, radius, angle, length, blur, iterations)
// ctx - context to use
// x/y - start of shadow
// radius - shadow radius (assuming circle shaped)
// angle - angle in radians. 0° = right
// length - core-length in pixels (radius/blur adds to real length)
// blur - blur radius in pixels. End blur is radius * iterations
// iterations - line "resolution"/quality, also affects total end blur
Play around with shape, shadow color, blur radius etc. to find the optimal result for your scene.
Demo
Result if browser supports filter:
var ctx = c.getContext("2d");
// render shadow
renderShadow(ctx, 30, 30, 30, Math.PI*0.25, 300, 2.5, 20);
// show main shape
ctx.beginPath();
ctx.moveTo(60, 30);
ctx.arc(30, 30, 30, 0, 6.28);
ctx.fillStyle = "rgb(0,140,200)";
ctx.fill();
function renderShadow(ctx, x, y, radius, angle, length, blur, iterations) {
var step = length / iterations, // calc number of steps
stepX = step * Math.cos(angle), // calc angle step for x based on steps
stepY = step * Math.sin(angle); // calc angle step for y based on steps
for(var i = iterations; i > 0; i--) { // run number of iterations
ctx.beginPath(); // create some shape, here circle
ctx.moveTo(x + radius + i * stepX, y + i * stepY); // move to x/y based on step*ite.
ctx.arc(x + i * stepX, y + i * stepY, radius, 0, 6.28);
ctx.filter = "blur(" + (blur * i) + "px)"; // set filter property
ctx.fillStyle = "rgba(0,0,0,0.5)"; // shadow color
ctx.fill();
}
ctx.filter = "none"; // reset filter
}
<canvas id=c width=450 height=350></canvas>
I'm trying to rotate an image and move it around the canvas using the arrow keys. The plan is to have the left and right keys control the rotation of the image, and the up-down key control the movement - a bit like a tank!
I can successfully rotate an image around a centre point and place it back where it should be in the canvas, but once I rotate it by, say 45 deg, I would like the up key to move it right, rotate 180 and the up-key moves it down the canvas etc. At the moment, I can rotate the image using left/right keys, but up/down keys are always up/down the canvas.
Do I somehow need to rotate the canvas coordinates by the same amount as the image?
This is what I have so far and is in my draw function…
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(T1.x + base_image.width/2, T1.y + base_image.height/2);
ctx.rotate(rotation * Math.PI/180);
ctx.translate(-(T1.x + base_image.width/2), -(T1.y + base_image.height/2));
ctx.drawImage(base_image, T1.x, T1.y);
ctx.stroke();
ctx.restore()
T1.x and T1.y are the x and y coordinates of the image.
Thanks!
Finally got it! The solution was to separate the rotation and the movement rather than trying to do it all using ctx.translate.
In my draw function called every 100 Hz:
ctx.save();
ctx.translate(T1.x + base_image.width/2, T1.y + base_image.height/2);
ctx.rotate(rotation*Math.PI/180);
ctx.drawImage(base_image, -base_image.width/2, -base_image.height/2);
ctx.restore();
ctx.stroke();
The left key is, for example, like:
rotation = rotation - 5;
Draw();
The up key is, for example, like:
var radians = (rotation*Math.PI/180)
T1.x += 4*Math.cos(radians);
T1.y += 4*Math.sin(radians);
Draw();
Note: For this to work, I had to change the default orientation of the image in paint by 45 deg.
The quickest way to draw a rotated, uniformly-scaled image on Edge, Chrome, and Firefox is
// When the image has loaded add the center offsets
image.onload = function(){
// … your code
this.centerX = -this.width/2;
this.centerY = -this.height/2;
}
// When rendering
// x,y position of image center
// scale the uniform scale
// rotate angle in radians starting at 0 and + clockwise
var xdx = Math.cos(rotate) * scale;
var xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y);
ctx.drawImage(image,image.centerX,image.centerY)
The above method with the extra sin and cos and the two multiplications is significantly quicker on Chrome, slightly quicker on Firefox, and I forget the margin on Edge, but it was quicker than the next quickest method:
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(rotate);
ctx.drawImage(image, image.centerX, image.centerY);
Though I am leaving the canvas transform state as it is, you can continue using both methods without having to reset the canvas state. When you are done with all the rendering, to restore the current transform, use
ctx.setTransform(1, 0, 0, 1, 0, 0);
To save a tiny bit of time, each time you convert from degrees to radians, create a const DEG2RAD = Math.PI/180;. Then, you can convert with var radians = degrees*DEG2RAD; or save by typing const D2R = Math.PI/180; or call const oneDeg = Math.PI/180;.
I've a little problem with canvas
I'm trying to make a paddle game , I want to rotate the upper paddle according to the mouse X-position My problem is that the drawing position is the top left of the paddle as normal and I want to change it after Drawing to be in the center of the paddle for rotation.
so the origin position of the paddle will be the center and every time the mouse moved the paddle will be rotated from the center not the top left.
here is updated function which invoked to updated the canvas.
function update() {
// Update scores
updateScore();
// Move the paddles on mouse move
// Here we will add another condition to move the upper paddle in Y-Axis
if(mouse.x && mouse.y) {
for(var i = 1; i < paddles.length; i++) {
p = paddles[i];
// the botoom paddle
if (i ==1){
p.x = mouse.x - p.w/2;
}else{
// the top paddle
ctx.save(); // saves the coordinate system
ctx.translate(W/4,H/2); // now the position (0,0) is found at (250,50)
ctx.rotate(0.30 * mouse.x); // rotate around the start point of your line
ctx.moveTo(0,0) // this will actually be (250,50) in relation to the upper left corner
ctx.lineTo(W/4,H/2) // (250,250)
ctx.stroke();
ctx.restore(); // restores the coordinate system back to (0,0)
}// end else
}//end for
}
Your translations are a little off, but it's easy to fix. Consider this alternative -- translate the context to the center of the paddle. After all, this is where you will be doing the rotation. Rotate the canvas, and then draw a horizontal line centered around the origin. I've codified my suggestion, and I've stored a few things in local variables to make it clearer.
var W = 200;
var H = 200;
var x = W / 2;
var y = H / 2;
var lineLength = 80;
ctx.save();
ctx.translate(x, y);
ctx.rotate(0.3 * mouse.X);
ctx.moveTo(-lineLength / 2,0, 0);
ctx.lineTo(lineLength / 2.0, 0);
ctx.stroke();
ctx.restore();
I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.
Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.
I've seen a few examples but I couldn't get them working nor they seam to address my question.
Thanks in advance!
To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.
To get this result you can do the following (see full code here):
Setup variables:
var ctx = canvas.getContext('2d'),
ix = 0, iy = 0, /// image position
offsetX = 0, offsetY = 0, /// current offsets
deltaX, deltaY, /// deltas from mouse down
mouseDown = false, /// in mouse drag
img = null, /// background
rect, /// rect position
rectW = 200, rectH = 150; /// size of highlight area
Set up the main functions that you use to set size according to window size (including on resize):
/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
rect = [canvas.width * 0.5 - rectW * 0.5,
canvas.height * 0.5 - rectH * 0.5,
rectW, rectH]
update();
}
/// window resize so recalc canvas and rect
window.onresize = setSize;
The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).
First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
Then draw the complete image.
Reset alpha
Draw the peep-hole using clipping with corrected offsets for the source.
-
/// main draw
function update() {
if (img === null) return;
/// limit x/y as drawImage cannot draw with negative
/// offsets for clipping
if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;
/// clear background to clear off garbage
ctx.clearRect(0, 0, canvas.width, canvas.height);
/// make everything transparent
ctx.globalAlpha = 0.2;
/// draw complete background
ctx.drawImage(img, ix + offsetX, iy + offsetY);
/// reset alpha as we need opacity for next draw
ctx.globalAlpha = 1;
/// draw a clipped version of the background and
/// adjust for offset and image position
ctx.drawImage(img, -ix - offsetX + rect[0], /// sx
-iy - offsetY + rect[1], /// sy
rect[2], rect[3], /// sw/h
/// destination
rect[0], rect[1], rect[2], rect[3]);
/// make a nice sharp border by offsetting it half pixel
ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}
Now it's a matter of handling mouse down, move and up and calculate the offsets -
In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:
canvas.onmousedown = function(e) {
/// don't do anything until we have an image
if (img === null) return;
/// correct mouse pos
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// store current position to calc deltas
deltaX = x;
deltaY = y;
/// here we go..
mouseDown = true;
}
Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:
canvas.onmousemove = function(e) {
/// in a drag?
if (mouseDown === true) {
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// offset = current - original position
offsetX = x - deltaX;
offsetY = y - deltaY;
/// redraw what we have so far
update();
}
}
And finally on mouse up we make the offsets a permanent part of the image position:
document.onmouseup = function(e) {
/// was in a drag?
if (mouseDown === true) {
/// not any more!!!
mouseDown = false;
/// make image pos. permanent
ix += offsetX;
iy += offsetY;
/// so we need to reset offsets as well
offsetX = offsetY = 0;
}
}
For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor
To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.
Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.
However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.