HTML canvas double buffering frame-rate issues - javascript

I have a full-screen canvas with 3 images drawn on it. When I resize the window, these images change position; however, it appears to be very glitchy, more so in Firefox.
I've been reading that double-buffering should resolve this issue, but I'm wondering how I would double buffer when the next position is unknown. That is to say, I cannot determine what should be buffered in the future, so how would this be possible?
Here is one source that seems doable, but I do not fully understand the concept Fedor is trying to explain.
Does HTML5/Canvas Support Double Buffering?
So far I have,
$canvas = $('#myclouds')[0];
$canvas_buffer = $('canvas')[0].insertAfter($canvas).css('visibility', 'hidden');
context = $canvas.getContext('2d');
context_buffer = $canvas_buffer.getContext('2d');
clouds_arr = [$canvas, $canvas_buffer];
$(window).resize(function () {
drawCanvas();
};
function initCanvas() {
// Sources for cloud images
var cloud1 = '/js/application/home/images/cloud1.png',
cloud2 = '/js/application/home/images/cloud2.png',
cloud3 = '/js/application/home/images/cloud3.png';
// add clouds to be drawn
// parameters are as follows:
// image source, x, y, ratio, adjustment)
addCloud(cloud1, null, 125, .03);
addCloud(cloud2, null, 75, .15);
addCloud(cloud3, null, 50, .55);
addCloud(cloud1, null, 125, .97, 300);
addCloud(cloud2, null, 70, .85, 300);
addCloud(cloud3, null, 45, .5, 300);
// Draw the canvas
drawCanvas();
}
function drawCanvas() {
// Reset
$canvas.attr('height', $window.height()).attr('width', $window.width());
// draw the clouds
var l = clouds.length;
for (var i = 0; i < l; i++) {
clouds[i].x = ($window.width() * clouds[i].ratio) - clouds[i].offset;
drawimage(context, clouds[i]);
}
}
function Cloud() {
this.x = 0;
this.y = 0;
}
function addCloud(path, x, y, ratio, offset) {
var c = new Cloud;
c.x = x;
c.y = y;
c.path = path;
c.ratio = ratio || 0;
c.offset = offset || 0;
clouds.push(c);
}
function drawimage(ctx, image) {
var clouds_obj = new Image();
clouds_obj.src = image.path;
clouds_obj.onload = function() {
ctx.drawImage(clouds_obj, image.x, image.y);
};
}

I think maybe you are misunderstanding what double buffering is. Its a technique for smooth real-time rendering of graphics on a display.
The concept is you have two buffers. Only one is visible at any one time. When you go to draw the elements that make up a frame you draw them to the invisible buffer. In you case the clouds. Then you flip the buffers making the hidden one visible and the visible one hidden. Then on the next frame you draw to the now newly hidden buffer. Then at the end of drawing you flip back.
What this does is stop the user seeing partial rendering of elements before a frame is complete. On gaming systems this would also be synced up with the vertical refresh of the display to be really smooth and stop artefacts such as tearing to occur.
Looking at you code above you seem to have created the two canvas elements, but you're only using the first Context object. I assume this is incomplete as no flipping is taking place.
Its also worth noting that the window resize event can fire continuously when dragging which can cause frantic rendering. I usually create a timer on the resize event to actually re-render. This way the re-render only happens once the user stops resizing for a few milliseconds.
Also, your draw routine is creating new Image objects every time which you don't need to do. You can use one image object and render to the canvas multiple times. This will speed up your render considerably.
Hope this helps.

Related

Simple 3D cube roll animations not working with threejs

I am new to THREEJS and at the moment I am trying to move a cube using arrow keys. Please see this fiddle: https://jsfiddle.net/mauricederegt/y6cw7foj/26/
All works, I can move the cube using the arrow keys and I even managed to rotate the cube around the correct axis when moving it around. The problem is with the animations. I can’t seem to get them to work. At the moment when you press the left arrow key, the cube moves to the left and also rolls around the axis. Well…at the moment it snaps into position, instead of smoothly transitioning.
What I want is that it smoothly moves to the left while it rotates, but how to do that? At the end of the code I do call for the
requestAnimationFrame
but that doesn’t do much. I have a fiddle here of my attempt doing this with CSS. Here the animations work (but never got the rotating direction correct): https://jsfiddle.net/mauricederegt/5ozqg9uL/3/ This does show what animations I want to have.
So what am I missing in the THREEjs? Thanks a lot
What you're looking for is something called "tweening," where you draw intermediary steps, rather than jumping immediately to the end result. There are several JavaScript libraries that will do this for you, but I'll cover some of the basics of implementing it yourself.
Take your example. When you tap the left arrow, you move the mesh 1 unit along the -x axis, and rotate -PI/2 about the +y axis. Rather than snapping to these positions/rotations, consider how long you want the animation to take, and start dividing out steps.
Let's say you want to to take 500ms (half a second). Your browser tries to run at about 60fps, so you have 30 frames (about 500ms) to work with at that rate. So for every frame, you can move the box 1/30 units, and rotate it by -PI/60. After 30 frames, the box should be in about the right place, give or take some rounding.
I use "about" when talking about the framerate of the browser because you aren't always guaranteed to get 60FPS. If your framerates dip, and you're locked to the framerate to draw your animation, then it too will slow down and take longer than you wanted. So what can be done about that?
Rather than relying on requestAnimationFrame as your timer, you can set a real timer to step through your animation. Toss calculating the frames you need to complete the animation, and instead calculate the steps needed.
We already know that 60fps is roughly 1 frame every 16.6ms, so that's the absolute maximum target that can expect the browser to draw. But when we do our updates by steps, nothing stops us from going faster. To make things easier to calculate, let's say we want to do 50 update steps rather than the 30 from before. This means that for the 500ms play time, we will need to perform an update every 10ms (slightly faster than the framerate). Also, because we are performing 50 steps, we will be updating the position by 1/50 units, and rotating by -PI/100.
let animationId = setInterval( ()=>{
// update position by 1/50 units
// update rotation by -PI/100
}, 10 ); // every 10 ms
As the interval runs, it will update the object. Meanwhile, the animation loop churns out new frames whenever it can.
Here's a full, running example, with only left-arrow support:
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({
color: "red"
})
);
cube.position.set(10, 0, 0);
scene.add(cube);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
function animate() {
requestAnimationFrame(animate);
render();
}
requestAnimationFrame(animate);
const yAxis = new THREE.Vector3(0, 1, 0);
function updateCube() {
//cube.position.x -= 1;
//cube.rotateOnWorldAxis(yAxis, THREE.Math.degToRad(-90));
cube.position.x -= 1 / 50;
cube.rotateOnWorldAxis(yAxis, -(Math.PI / 100));
}
let step = 0;
let animationId = null;
function startStepping() {
animationId = setInterval(() => {
updateCube();
if (++step === 50) {
clearInterval(animationId);
animationId = null;
}
}, 10)
}
function handleKeyboard(e) {
//if (e.keyCode == 65 || e.keyCode == 37) {
// updateCube();
//}
if (animationId === null && (e.keyCode == 65 || e.keyCode == 37)) {
step = 0;
startStepping();
}
}
document.addEventListener("keydown", handleKeyboard, false);
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
Now there is a downside to this method. Sometimes you may see updates skipped (if the browser's framerate drops), or the same position drawn twice (if your update rate is significantly lower than the browser's framerate). You could try to get the best of both worlds but computing the framerate live from your render loop, and adjusting your frame steps accordingly, but at that point you must ask whether the extra time spent computing those statistics are actually hurting trying to achieve a rock-steady framerate-locked draw rate.
Switching based on key input
Because your key input is now disjointed from drawing, you now need some kind of flag to determine the action being taken. Your key press handler will set that flag, and then updateCube will act based on that flag. Something like:
let action = null
function startStepping(){
// set up the interval...
// but then also ensure the action stops after the animation plays:
setTimeout( () => action = null, 500 );
}
function handleKeyboard(e){
if (animationId === null) {
step = 0;
switch(e.keyCode){
case 37:
case 65:
action = "left";
break;
// other keys...
}
startStepping();
}
}
function updateCube(){
switch(action){
case "left":
// move as if it's rolling left
break;
case "right":
// move as if it's rolling right
break;
// etc. for the other directions
}
}

Javascript chaining the same animation function with different parameters

I am trying to animate a line two lines along a path, one then the other. Basically it will look like one line being drawn, stopping at a point, then another line being drawn somewhere else. So far I have come across promises and callbacks to achieve this, but being a javascript newbie this is confusing
Current animate function:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
Current call to animate function:
animate(points).then(animate(secondary_points));
The points are similar to:
var points = [{x:100, y:200}];
And the paths the lines need to follow are just the multiple coordinates inside points and secondary_points
Ive tried many solutions on SO that were similar, but small differences cause me to either mess up or not understand the solution. The biggest issue I seem to have is calling the SAME animate function, with that animate function needing to be run on different parameters.
Without this solution, using
animate(points);
animate(secondary_points);
the lines are drawn somewhat at the same time, but the result is actually just randomly placed dots along the path instead of smooth lines, I assume because both are running at the same time.
How would I go about fixing this so that one line is drawn along path1 and then the second line is drawn along path2?
It is probably a simple solution, but Ive worked with JS for 3 days and my head is still spinning from getting used to some of the syntax of the old code Ive had to fix
Thank you
EDIT:
The full flow of the animation is as follows:
I have a php file that contains 2 canvases, each containing an image of a map. The php file has a couple <script/> tags, one of which calls the js script I am writing the animation on via drawPath(source,destination,true) or drawPath(source,destination,false)
The drawPath function uses the boolean to determine which canvas to get the context for, and then draw on the path from point A to point B via finding the path and creating the points mentioned above, then drawing using animate(). There are a couple breaks in the maps that require separate lines, which prompted my original question. I was able to fix that thanks to suggestions, but now I am having a larger issue.
If I need to go from point A on map A to point B on map B, ie
drawPath(source, end_point_of_map_A, true); is called then
drawPath(start_point_of_map_B, destination, false);, the lines are drawn only on one map, and they are similar to before where they are 1. random and 2. incomplete/only dots
I am assuming this is due to the animation again, because it worked when just drawing the lines statically, and each animation works when going from point A to B on a single map
Any help is appreciated!
Edit:
DrawPath()
function drawPath(source, desti, flag) {
/*
* Define context
*/
//lower
if(!flag){
var c = document.getElementById("myCanvas");
context = c.getContext("2d");
//upper
} else {
var cUpr = document.getElementById("myCanvasUpr");
context = cUpr.getContext("2d");
}
/*
* Clear the variables
*/
points = [];
secondary_points = [];
vertices = [];
secondary_vertices = [];
t = 1;
done = false;
//check for invalid locations
if (source != "" && desti != "") {
context.lineCap = 'round';
context.beginPath();
/*
* Get the coordinates from source and destination strings
*/
var src = dict[source];
var dst = dict[desti];
/*
* Get the point number of the point on the path that the source and destination connect to
*/
var begin = point_num[source];
var finish = point_num[desti];
/*
* Draw the green and red starting/ending circles (green is start, red is end)
*/
context.beginPath();
context.arc(src[0], src[1], 8, 0, 2 * Math.PI);
context.fillStyle = 'green';
context.fill();
context.beginPath();
context.arc(dst[0], dst[1], 6, 0, 2 * Math.PI);
context.fillStyle = 'red';
context.fill();
/*
* Call the function that draws the entire path
*/
draw_segments(begin, finish, src, dst, flag);
//window.alert(JSON.stringify(vertices, null, 4))
/*
* Edit what the line looks like
*/
context.lineWidth = 5;
context.strokeStyle = "#ff0000";
context.stroke();
}
}
A nice way to handle this is to put your lines into a an array where each element is a set of points of the line. Then you can call reduce() on that triggering each promise in turn. reduce() takes a little getting used to if you're new to javascript, but it basically takes each element of the array c in this case, does something and that something becomes the next a. You start the whole thing off with a resolve promise which will be the initial a. The promise chain will be returned by reduce to you can tack on a final then to know when the whole thing is finished.
For example:
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d');
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
// make some points:
let points = Array.from({length: 200}, (_,i) => ({x:i+1, y:i+2}))
let points2 = Array.from({length: 200}, (_,i) => ({x:300-i, y:i+2}))
let points3 = Array.from({length: 200}, (_,i) => ({x:i*2, y:100+100*Math.sin(i/10)}))
// create an array holding each set
let sets = [points, points2, points3]
// use reduce to call each in sequence returning the promise each time
sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())
.then(() => console.log("done"))
<canvas id="canvas" height="300" width="500"></canvas>

How To wipe the last canvas paint in qml

Canvas {
id: canvas
onPaint: {
if (personalInfo.count === 0) {
return
}
var ctx = canvas.getContext("2d");
ctx.globalCompositeOperation = "source-over";
var points = []
for (var i = 0; i < personalInfoModel.dataCount(); i++) {
var temp = personalInfoModel.get(i)
points.push({
date: temp.date,
heartRate: temp.heartRate,
temprature: temp.temprature,
pressure: temp.bloodPressure
}
)
}
drawAxis(ctx)
drawGridLineAndUnitNum(ctx, chart.activeChart, points, "x", 15);
}
}
I have two button. If button A is clicked, then set chart.activeChart to 7 and call cavas.requestPaint() on A::onClicked, on cavas.drawGridLineAndUnitNum draw seven vertical line. If button B is clicked besides set chart.activeChart to 30, all same to A::onClicked. I hope that when A is clicked, canvas wipe the drawn line which product by B is clicked and vice versa. But in fact, it always reserve the line draw by last time.
A Context2D, associated to a specific Canvas, provides two useful functions:
fillRect
clearRect
In most cases, it could be possible to "clear" a Canvassimply by filling it with the background color, i.e. by using fillRect. That's the approach of the StocQt example, which has a white background.
However, if the background is transparent, filling it does not remove other strokes and thus does not make much sense. In this case, the only possible way to clear the Canvas is by removing all the strokes, i.e. by using clearRect.
I use a transparent background and thus clearRect is the way to go for me.

HTML5 Canvas and Javascript issue drawing multiple animated sprites on same canvas

Sorry if the title is a little confusing, I didn't know what the best way to word it would be.
I'm working on a tile based java-script canvas game that uses sprite sheets and tile maps to create the world and objects in it.
I wrote a section of code to animate a coin to spin around. For a single coin this works fine, but adding more than one coin to the canvas will cause the animation to speed up beyond what's desirable.
Since the game will be adding coins as it progresses, after about 10 coins you probably won't be able to see the animation anymore.
Gif to show the issue:
I've tried multiple methods, even adding a frame delay to slow down the animation, but without the desired results.
Everything is on codepen http://codepen.io/TryHardHusky/pen/EjJdoK
But it's a little messy.
Code I'm using to animate the coin:
var coin = {
height: 32,
width: 32,
cFrame: 0,
mFrame: 8,
image: new Image(),
src: "http://s1.tryhardhusky.com/coin_gold.png",
draw: function(x, y){
coin.cFrame++;
coin.image.src = coin.src;
if(coin.cFrame >= coin.mFrame){
coin.cFrame = 0;
}
ctx.drawImage(coin.image, 32*coin.cFrame,0,32,32,x,y, coin.height,coin.width);
}
}
And to create a coin on the scene:
coin.draw(250,250);
coin.draw(218, 250);
coin.draw(186, 250);
This is a remake of my other pen: http://codepen.io/TryHardHusky/pen/rVbdmw
I had it working there, but was using another inefficient method to animate the coins. It's also very poorly optimized, Hence the reason for the new code.
Can anyone shine some light on what I'm doing wrong?
-- Edit --
Thanks to #canvas was able to fix it with:
var coins = [
[4,5,0],
[2,3,0],
[1,6,0]
];
.
for(var i = 0; i < coins.length; i++){
drawCoin(coins[i], i);
}
.
function drawCoin(cord,i){
coins[i][2] < 8 ? coins[i][2]+=1 : coins[i][2]=0;
var image = new Image();
image.src = "http://s1.tryhardhusky.com/coin_gold.png";
ctx.drawImage(image, 32*cord[2], 0, 32, 32, cord[0]*32, cord[1]*32, 32, 32);
}
Have you tried using this instead of coin?
draw: function(x, y){
this.cFrame++;
this.image.src = this.src;
if(this.cFrame >= this.mFrame){
this.cFrame = 0;
}
ctx.drawImage(this.image, 32*this.cFrame,0,32,32,x,y, this.height,this.width);
Also what you should probably do is have an array of coins, then simply add a new coin to that array then use a loop to draw out each coin and update each coin.
Create an array of coins (example code)
var coins[];
coins.push(new coin(xPosition, yPosition));
// Render coins
for(var i = 0; i < coins.length; i++)
{
coins[i].Draw();
}
I just forked your codepen,
This isn't perfect, but something like this (created an array, updated coin var to be a constructor and then added 3 coins to the new array)
CodePen : http://codepen.io/anon/pen/GJLwJw

2D top-down minimap

I'd like to make a minimap of my rpg game.
Is making a minimap as simple as dividing all object dimensions, velocities, and coordinates by however large you want the minimap?
For example below... You have a size of 1000x1000px, a canvas (viewport) of 500x500px, the player is located in the center of the viewport... If you wanted a minimap half the size of the actual world, you would do:
Player/Viewport x,y velocity/2
Player/Viewport x,y coordinates/2
Canvas, world, and all objects' width and height are divided by 2
etc...
That way the rendering of the minimap on the world and the velocities are scaled accurately? Am I missing anything?
Thanks!
EDIT: Something like this?
function miniMap() {
$(".minimapHolder").show();
$("#mini_map").text("hide minimap");
var minicanvas = document.getElementById("miniMap");
ministage = new createjs.Stage("miniMap");
minicam = new createjs.Shape();
minicam.graphics.beginStroke("white").drawRoundRect(0, 0, 100, 40, 5);
//blip representation of Player
player_blip = new createjs.Shape();
player_blip.graphics.beginFill("yellow").drawRoundRect(0, 0, 11.2, 12, 1);
animal_blip = new createjs.Shape();
animal_blip.graphics.beginFill("red").drawRoundRect(0, 0, 24.4, 21.6, 1);
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
animal_blip.x = animalContainer.x/5;
animal_blip.y = animalContainer.y/5;
minicam.x = players_Array[0].x-110;
minicam.y = players_Array[0].y-110;
ministage.addChild(player_blip, animal_blip, minicam);
ministage.update();
}
function updateMiniMap() {
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
if (ContainerOfAnimals.children[0] != null) {
var pt = ContainerOfAnimals.localToGlobal(ContainerOfAnimals.children[0].x, ContainerOfAnimals.children[0].y);
console.log(pt.x);
animal_blip.x = pt.x/5;
animal_blip.y = pt.y/5;
} else {
ministage.removeChild(animal_blip);
}
minicam.x = player_blip.x-40;
minicam.y = player_blip.y-15;
ministage.update();
}
Gives:
Short anwswer: "It will(most likely) work." ... but:
What you are trying to achieve is just scaling the stage/container, so you could also just use a copy of everything and put it into a container and scale it down to 0.5, but that is not the purpose of a minimap.
Objects of the minimap should only be a representation of the object in the 'real' world and should therefore not have any velocity ect.(that should especially not be updated separately from the 'real' world) - while your approach will probably work, you'd allways have to keep track and update every property, this will get messy quickly or even lead to differences if you miss some tiny things.
A more 'clean'(and simple) approach to this would be, that each minimap-object has a reference to the object in the 'real' world and on each tick, it just reads the x/y-coordinates and updates its' own coordinates based on the minimap-scale.
Another thing is the graphics: Scaling-operations can be costly(performance wise), especially when they are done each frame, so IF you use the same graphics for the minimap you should at least used a cached DisplayObject and not have the graphics scaled each frame.

Categories

Resources