Canvas - Make a circle in canvas move down in particular time - javascript

I'd like to create a circle ctx.arc(10, 10, 15, 0, Math.PI*2, true); and hence make it flow downwards without losing its traces..
You can see this clearly in the below image -
Here as we can see on the dark side...the circle is actually moving as time passes.. I want to control the speed by entering the time...like from top to bottom it should reach in 2 seconds (or some other way to control its speed of flow)
EDIT: Sorry for some buddies the question is: whats the most efficient and "NON-memory-hogging" method to do this, (I know there is this loop method but its very memory hogging method)

Javascript knows setTimeout(fn, ms), which is going to call the given function after the given number of milliseconds. However, the browser will need some time to render your drawings, and when you want the 2 seconds to be exakt you will have to watch about your frames/seconds rate.
Theres also a new specification draft called requestAnimationFrame, see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for that.

You can draw the top semicircle, a rectangle, and the bottom semicircle. You can calculate how much it should be moved down at a particular moment in time with:
(Date.now() - startTime) / t * (y2 - y1)
where t is the time it should take for the complete movement.
http://jsfiddle.net/eGjak/231/.
var ctx = $('#cv').get(0).getContext('2d');
var y1 = 100, // start y
y2 = 300, // end y
x = 200, // x
r = 50, // radius
t = 2000; // time
var dy = y2 - y1, // difference in y
pi = Math.PI,
startTime = Date.now();
var f = function() {
var y = y1 + (Date.now() - startTime) / t * dy;
ctx.beginPath();
ctx.arc(x, y1, r, pi, 0); // top semicircle
ctx.rect(x - r, y1, r * 2, y - y1); // rectangle
ctx.arc(x, y, r, 0, -pi); // bottom semicircle
ctx.fill();
if(y < y2) { // if should be moved further
webkitRequestAnimationFrame(f);
}
};
f();

Related

CanvasRenderingContext2D is faster when a path is rendered, instead of only a text [duplicate]

This question already has an answer here:
HTML5 Canvas performance very poor using rect()
(1 answer)
Closed 2 years ago.
Okay, so I am really scratching my head of what is happening here.
I have the following code running in a loop:
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
let latestTime = performance.now();
function draw(time: number) {
const dt = time - latestTime;
const { width, height } = (document.getElementById(
"main"
) as HTMLElement).getBoundingClientRect();
if (canvas.width != width) canvas.width = width;
if (canvas.height != height) canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#333333";
ctx.rect(0, 0, width, height);
ctx.fill();
/* When this section is commented out, the performance INCREASES...
const size = 20;
const radius = 100;
const period = 10;
const x =
width / 2 + radius * Math.sin((((2 * Math.PI) / period) * time) / 1000);
const y =
height / 2 + radius * Math.cos((((2 * Math.PI) / period) * time) / 1000);
ctx.fillStyle = "#9999ff";
ctx.beginPath();
ctx.ellipse(x, y, size, size, 0, 0, 2 * Math.PI);
ctx.fill();*/
ctx.font = "12px Montserrat";
ctx.fillStyle = "#ffffff";
ctx.fillText(`Frame rate: ${Math.round(1.0 / (dt / 1000))}`, 10, 20);
latestTime = time;
window.requestAnimationFrame(draw);
}
draw(performance.now());
It is a simple circle orbiting around the center when it is uncommented. However, I commented the circle part and only render the framerate.
When I run this and inspect in the Chrome Dev Tools the frame budget, I see this:
7ms is taken up by the system.
The framerate gradually goes down, but in steps. It is nicely 60. Then it goes to 30 all of a sudden after 30 seconds or so. Then it goes to 15. And so on.
When I uncomment the circle code the result in the inspector is this:
The system task only takes up 0.67ms and the framerate is constant.
I tried moving the section but it doesn't matter.
What is going on here? It doesn't make sense to me.
Calling ctx.fillText also does a ctx.beginPath thus resetting any paths you have created.
The function ctx.rect add to the current path. Without the beginPath you are adding a rect to the current path each frame. Thus over time you are rendering more and more rectangles and thus the slow down.
Use ctx.fillRect rather than ctx.rect or start a new path with ctx.beginPath before the call to ctx.rect

Image Manipulation - add image with corners in exact positions

I have an image which is a background containing a boxed area like this:
I know the exact positions of the corners of that shape, and I'd like to place another image within it. (So it appears to be inside the box).
I'm aware of the drawImage method for HTML5 canvas, but it seems to only support x, y, width, height parameters rather than exact coordinates. How might I draw an image onto a canvas at a specific set of coordinates, and ideally have the browser itself handle stretching the image.
Quadrilateral transform
One way to go about this is to use Quadrilateral transforms. They are different than 3D transforms and would allow you to draw to a canvas in case you want to export the result.
The example shown here is simplified and uses basic sub-divison and "cheats" on the rendering itself - that is, it draws in a small square instead of the shape of the sub-divided cell but because of the small size and the overlap we can get away with it in many non-extreme cases.
The proper way would be to split the shape into two triangles, then scan pixel wise in the destination bitmap, map the point from destination triangle to source triangle. If the position value was fractional you would use that to determine pixel interpolation (f.ex. bi-linear 2x2 or bi-cubic 4x4).
I do not intend to cover all this in this answer as it would quickly become out of scope for the SO format, but the method would probably be suitable in this case unless you need to animate it (it is not performant enough for that if you want high resolution).
Method
Lets start with an initial quadrilateral shape:
The first step is to interpolate the Y-positions on each bar C1-C4 and C2-C3. We're gonna need current position as well as next position. We'll use linear interpolation ("lerp") for this using a normalized value for t:
y1current = lerp( C1, C4, y / height)
y2current = lerp( C2, C3, y / height)
y1next = lerp(C1, C4, (y + step) / height)
y2next = lerp(C2, C3, (y + step) / height)
This gives us a new line between and along the outer vertical bars.
Next we need the X positions on that line, both current and next. This will give us four positions we will fill with current pixel, either as-is or interpolate it (not shown here):
p1 = lerp(y1current, y2current, x / width)
p2 = lerp(y1current, y2current, (x + step) / width)
p3 = lerp(y1next, y2next, (x + step) / width)
p4 = lerp(y1next, y2next, x / width)
x and y will be the position in the source image using integer values.
We can use this setup inside a loop that will iterate over each pixel in the source bitmap.
Demo
The demo can be found at the bottom of the answer. Move the circular handles around to transform and play with the step value to see its impact on performance and result.
The demo will have moire and other artifacts, but as mentioned earlier that would be a topic for another day.
Snapshot from demo:
Alternative methods
You can also use WebGL or Three.js to setup a 3D environment and render to canvas. Here is a link to the latter solution:
Three.js
and an example of how to use texture mapped surface:
Three.js texturing (instead of defining a cube, just define one place/face).
Using this approach will enable you to export the result to a canvas or an image as well, but for performance a GPU is required on the client.
If you don't need to export or manipulate the result I would suggest to use simple CSS 3D transform as shown in the other answers.
/* Quadrilateral Transform - (c) Ken Nilsen, CC3.0-Attr */
var img = new Image(); img.onload = go;
img.src = "https://i.imgur.com/EWoZkZm.jpg";
function go() {
var me = this,
stepEl = document.querySelector("input"),
stepTxt = document.querySelector("span"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
corners = [
{x: 100, y: 20}, // ul
{x: 520, y: 20}, // ur
{x: 520, y: 380}, // br
{x: 100, y: 380} // bl
],
radius = 10, cPoint, timer, // for mouse handling
step = 4; // resolution
update();
// render image to quad using current settings
function render() {
var p1, p2, p3, p4, y1c, y2c, y1n, y2n,
w = img.width - 1, // -1 to give room for the "next" points
h = img.height - 1;
ctx.clearRect(0, 0, c.width, c.height);
for(y = 0; y < h; y += step) {
for(x = 0; x < w; x += step) {
y1c = lerp(corners[0], corners[3], y / h);
y2c = lerp(corners[1], corners[2], y / h);
y1n = lerp(corners[0], corners[3], (y + step) / h);
y2n = lerp(corners[1], corners[2], (y + step) / h);
// corners of the new sub-divided cell p1 (ul) -> p2 (ur) -> p3 (br) -> p4 (bl)
p1 = lerp(y1c, y2c, x / w);
p2 = lerp(y1c, y2c, (x + step) / w);
p3 = lerp(y1n, y2n, (x + step) / w);
p4 = lerp(y1n, y2n, x / w);
ctx.drawImage(img, x, y, step, step, p1.x, p1.y, // get most coverage for w/h:
Math.ceil(Math.max(step, Math.abs(p2.x - p1.x), Math.abs(p4.x - p3.x))) + 1,
Math.ceil(Math.max(step, Math.abs(p1.y - p4.y), Math.abs(p2.y - p3.y))) + 1)
}
}
}
function lerp(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t}
}
/* Stuff for demo: -----------------*/
function drawCorners() {
ctx.strokeStyle = "#09f";
ctx.lineWidth = 2;
ctx.beginPath();
// border
for(var i = 0, p; p = corners[i++];) ctx[i ? "lineTo" : "moveTo"](p.x, p.y);
ctx.closePath();
// circular handles
for(i = 0; p = corners[i++];) {
ctx.moveTo(p.x + radius, p.y);
ctx.arc(p.x, p.y, radius, 0, 6.28);
}
ctx.stroke()
}
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
function inCircle(p, pos) {
var dx = pos.x - p.x,
dy = pos.y - p.y;
return dx*dx + dy*dy <= radius * radius
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e);
for(var i = 0, p; p = corners[i++];) {if (inCircle(p, pos)) {cPoint = p; break}}
}
window.onmousemove = function(e) {
if (cPoint) {
var pos = getXY(e);
cPoint.x = pos.x; cPoint.y = pos.y;
cancelAnimationFrame(timer);
timer = requestAnimationFrame(update.bind(me))
}
}
window.onmouseup = function() {cPoint = null}
stepEl.oninput = function() {
stepTxt.innerHTML = (step = Math.pow(2, +this.value));
update();
}
function update() {render(); drawCorners()}
}
body {margin:20px;font:16px sans-serif}
canvas {border:1px solid #000;margin-top:10px}
<label>Step: <input type=range min=0 max=5 value=2></label><span>4</span><br>
<canvas width=620 height=400></canvas>
You can use CSS Transforms to make your image look like that box. For example:
img {
margin: 50px;
transform: perspective(500px) rotateY(20deg) rotateX(20deg);
}
<img src="https://via.placeholder.com/400x200">
Read more about CSS Transforms on MDN.
This solution relies on the browser performing the compositing. You put the image that you want warped in a separate element, overlaying the background using position: absolute.
Then use CSS transform property to apply any perspective transform to the overlay element.
To find the transform matrix you can use the answer from: How to match 3D perspective of real photo and object in CSS3 3D transforms

Understanding HTML5 Canvas

I am trying to get to grips and understand how to use and create colliding balls with HTML5 canvas,examples I have looked at have a lot of JavaScript, but I need to break it down into much smaller chunks to get a better understanding of what's going on.
In my example what I understand so far is that I am redrawing the circles every 40 milliseconds onto the canvas, and calling the animate function each time. Every time this is called the position of the circle changes as I am changing it with
circles[0].x+=1;
circles[0].y+=-1.5;
So my circle objects are in an array, and there are 2 things I would like to achieve:
1) not to let the balls escape the canvas area
2) if the balls collide then bounce off each other and reverse in direction.
What I want to tackle first though is not letting the balls escape the canvas and how I would go about working that out.
I have access to the window.width and window.height, so it's a case of understanding how to get the position of each ball in the array, and ensure that it does not cross those boundaries.
I don't want to just have it work, would much prefer to understand what is happening.
This will check collisions on the bounds of the canvas. I updated your objects to store vx and vy (velocity) and the draw() function to move based on these properties. I added checkBounds() which reverses the velocity when the circle goes outside the bounds.
EDIT: modified so that it takes into account the radius of the circles too.
Doing a collision detect between the circles could follow a similar pattern
http://jsfiddle.net/3tfUN/5/
var canvas = document.getElementById('ball-canvas');
var context = canvas.getContext('2d')
var radius = 50;
var strokewidth = 2;
var strokestyle = '#666';
var frameCount = 0;
var w = canvas.width;
var h = canvas.height;
// Circle Objects
var yellowCircle = {
x: 50,
y: h / 2,
radius: radius,
color: 'yellow',
vx: 1,
vy: 1.5
}
var redCircle = {
x: 450,
y: h / 2,
radius: radius,
color: 'red',
vx: 1,
vy: -1
}
var blueCircle = {
x: 850,
y: h / 2,
radius: radius,
color: 'blue',
vx: -1,
vy: -1.5
}
// Create empty array and then push cirlce objects into array
var circles = [];
circles.push(yellowCircle, blueCircle, redCircle);
function checkBounds() {
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
if (c.x > w - c.radius || c.x < c.radius) {
c.vx = -c.vx;
}
if (c.y > h - c.radius || c.y < c.radius) {
c.vy = -c.vy;
}
}
}
// Clear last circle and draw again
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the circle from the from page
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
context.beginPath();
context.fillStyle = c.color // Set the color of the circle using key:valuecontext.fill();
context.lineWidth = strokewidth;
context.strokeStyle = strokestyle;
context.stroke();
context.arc(c.x, c.y, c.radius, 0, Math.PI * 2); // X-axis Position, y-axis Position, radius, % of fill, ?
context.closePath();
context.fill();
}
}
function animate() {
for (i = 0; i <= 2; i++) {
circles[i].x += circles[i].vx;
circles[i].y += circles[i].vy;
}
checkBounds();
draw();
}
var canvas = document.getElementById('ball-canvas');
var context = canvas.getContext('2d')
var radius = 50;
setInterval(animate, 40);
circles[0].x+=1;
circles[0].y+=-1.5;
That's pretty tough to maintain. Instead, I'd suggest you have properties for X and Y speeds (I used moveX and moveY in the example).
Next, you need to check whether the position of the ball + the radius compensation is touching the canvas edges, and if so, reverse the speed value. So, for example, the X speed of the ball is 4 and now it hits the left or the right canvas egde, the X speed now becomes -4.
This is it, in a nutshell:
var c = circles[i];
// check rebounds
if (c.x - c.radius <= 0 || c.x + c.radius >= canvas.width)
c.moveX = -c.moveX; // flip the horizontal speed component
if (c.y - c.radius <= 0 || c.y + c.radius >= canvas.height)
c.moveY = -c.moveY; // flip the vertical speed component
// Yellow Circle
c.x += c.moveX; // here we don't have to worry
c.y += c.moveY; // about directions anymore
See my example here: http://jsfiddle.net/3tfUN/8/
The same principle applies for collisions between balls. I'm assuming you want to do simple collisions without angle changes.
But if you wish to simulate real ball collisions, that would require some more serious trigonometry to calculate when exactly the pixel-perfect collision happens, and to calculate the new X and Y speed components.
UPDATE
An example featuring slightly improved collision detection and speed transfer between balls: http://jsfiddle.net/3tfUN/12/
The canvas is just a "canvas" where you draw the circles. What you need to accomplish what you want is to model a "world" where the circles are object with width and height dimensions and their current position, and where the bounds are well defined. Once you have the width and height of each circle and their position, you can calculate where they are in respect to the bounds you set and see if you need to change direction or keep going.
Collisions stem from the same principle but are somewhat harder to model if you want them to be "realistic" (in the bounds problem you are only interested in the width and height of the circles because the bounding area is box shaped and the circle will always collide in the furthest point from its center, while when two circles collide you should take into account the radius of each circle instead of the "bounding box" around them.
I don't have time right now to show you this concepts with examples, but hopefully I sent you in the right track :).

Finding coordinates after canvas Rotation

0 to 0,-70 by this :
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.rotate(Math.PI/-10;);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,-70);
ctx.stroke();
And I can rotate that by 'PI/-10', and that works.
How i can get the x,y points of this after using rotate?
Your x and y points will still be 0 and -70 as they are relative to the translation (rotation). It basically means you would need to "reverse engineer" the matrix to get the resulting position you see on the canvas.
If you want to calculate a line which goes 70 pixels at -10 degrees you can use simple trigonometry to calculate it yourself instead (which is easier than going sort of backwards in the matrix).
You can use a function like this that takes your context, the start position of the line (x, y) the length (in pixels) and angle (in degrees) of the line you want to draw. It draw the line and returns an object with x and y for the end point of that line:
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
return {x: x2, y: y2};
}
Then just call it as:
var pos = lineToAngle(ctx, 0, 0, 70, -10);
//show result of end point
console.log('x:', pos.x.toFixed(2), 'y:', pos.y.toFixed(2));
Result:
x: 68.94 y: -12.16
Or you can instead extend the canvas' context by doing this:
if (typeof CanvasRenderingContext2D !== 'undefined') {
CanvasRenderingContext2D.prototype.lineToAngle =
function(x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
this.moveTo(x1, y1);
this.lineTo(x2, y2);
return {x: x2, y: y2};
}
}
And then use it directly on your context like this:
var pos = ctx.lineToAngle(100, 100, 70, -10);
ctx.stroke(); //we stroke separately to allow this being used in a path
console.log('x:', pos.x.toFixed(2), 'y:', pos.y.toFixed(2));
(0 degrees will point to the right).
So you're asking "after I set a transform, how can I run points through that transform"?
In that case, see HTML5 Canvas get transform matrix? . The question and answers are somewhat old, but seem up-to-date. I can't find anything in the current HTML5 spec that lets you access and use the transform matrix. (I see that it's theoretically accessable through context.currentTransform, but I don't see any functionality to let you use the matrix - you'd have to multiply your point through the matrix yourself, or fake it by creating a full SVGMatrix for your point vector.)
The top answer shows a transform class you can use. Track your changes through that, and use their transformPoint function to get the point you want transformed to its endpoint.

Slow performance drawing dashed lines in HTML5 Canvas

I am attempting to make a Pong clone using HTML5 canvas. I want to draw a dashed line down the middle of the playing field as is found in the original Pong. I am doing this by extending the CanvasRenderingContext2D object as shown in David Geary's excellent book:
CanvasRenderingContext2D.prototype.dashedLine = function (x1, y1, x2, y2, dashLength) {
dashLength = dashLength === undefined ? 5 : dashLength;
var deltaX = x2 - x1;
var deltaY = y2 - y1;
var numDashes = Math.floor(
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength);
for (var i=0; i < numDashes; ++i) {
context[ i % 2 === 0 ? 'moveTo' : 'lineTo' ]
(x1 + (deltaX / numDashes) * i, y1 + (deltaY / numDashes) * i);
}
I then have a render() function that actually makes all the calls to render elements on the canvas. Included in this is my renderBackground() function which colors the background and draws the dashed line:
function render() {
ctx.clearRect(0, 0, cWidth, cHeight);
renderBackground();
// Rest removed for brevity
}
function renderBackground() {
ctx.lineWidth = 5;
ctx.strokeStyle = '#FFFFFF';
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, cWidth, cHeight);
ctx.dashedLine(0, 0, 0, cHeight, 10);
ctx.stroke()
}
Then at the end I have a function called animLoop() that actually calls the render() function and makes use of requestAnimationFrame() for smoother animations:
function animLoop() {
render();
requestAnimationFrame(animLoop);
}
window.requestAnimationFrame = (function() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
}
);
})();
If I let my game run for more than 30 seconds it starts slowing down dramatically to the point that it is unplayable and CPU usage by the browser hovers around 134% for both Firefox and Chrome. The slowness is only present when I am rendering the dashed line. I am not sure what is going, but below I also ran my code through Chrome Inspectors profiler and get the following:
My renderBackground() function is only taking .46% of the CPU time. Also I am not sure what the (program) is supposed to signify. Any thoughts on what could be causing the slowness?
Also you can see the complete code I have so far on my Github repo.
You are accumulating all calls of lineTo on the default path each time ctx.dashedLine is called and call stroke will stroke all lines in path since the application start. Because you are running an animation, quickly the path will have a LOT of lines to draw when stroke is called each frame.
Add ctx.beginPath() before ctx.dashedLine to solve the problem.
function renderBackground() {
ctx.lineWidth = 5;
ctx.strokeStyle = '#FFFFFF';
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, cWidth, cHeight);
ctx.beginPath(); // <-- add it here
ctx.dashedLine(0, 0, 0, cHeight, 10);
ctx.stroke();
}
When drawing using a path, you are using a virtual "pen" or "pointer". So you'll create a virtual path with begin path, draw the lines and finally stroke that lines. In next frame you'll begin a new virtual path, draw the new lines in the path and stroke again. This way the performance stays stable.
Demo

Categories