bad three js performance with raycasters - javascript

so i have been experimenting with ray casting in three js but i ran into terrible performance issues in Firefox and chrome (but something else is causing chromes camera to rubber band even though its a small local game) anyways when i add this code to the animate loop
var intersects = raycaster.intersectObjects(sceneObjects);
if (intersects.length > 0) {
var firstIntersectedObject = intersects[0];
console.log(intersects[0].object.userData)
console.log(intersects[0].object.userData.glow )
if (intersects[0].object.userData.glow === 'true'){
console.log("GLOW")
}else{
console.log("NO!")
}
//intersects[0].object.material.wireframe = true
// this will give you the first intersected Object if there are multiple.
}
my game starts to get all laggy and i have no idea why any pointers

Don't raycast on every frame, instead you should have it raycast on an interval. You can use setTimeout or setInterval or check the timing in the update loop.
onUpdate() {
// Code that runs once per frame
// Check that we've waited long enough to raycast
if (Date.now() - this.lastRaycast > this.raycastInterval && this.qRaycast) {
this.handleRaycast();
this.lastRaycast = Date.now();
this.qRaycast = false;
}
requestAnimationFrame( () => this.onUpdate() );
}
I also only queue up raycasts when the mouse moves (no reason to keep raycasting if the mouse isn't moving) and because I have panning in my project, I disable raycast during panning movements to prevent any jitter during movement.
// Event Handlers
// Record mouse position for raycast
onMouseMove(e) {
this.mouse.x = (e.clientX / window.innerWidth ) * 2 - 1;
this.mouse.y = -((e.clientY - 50) / window.innerHeight ) * 2 + 1;
// If we are panning, don't queue a raycast
this.qRaycast = !this.mouseState.held;
}
// My app has panning, and we don't wanna keep raycasting during pan
onMouseDown(e) {
this.mouseState.lastClick = Date.now();
this.mouseState.clicked = false;
this.mouseState.held = true;
}
onMouseUp(e) {
this.mouseState.held = false;
}
then we handle the raycast:
// Like lasers, but virtual.
handleRaycast() {
let hits = null;
let hitcount = 0;
if (UI.raycast && meshObj) {
this.raygun.setFromCamera(this.mouse, this.camera);
hits = this.raygun.intersectObject(meshObj, false);
hitcount = hits.length;
}
if (hitcount > 0) {
// Do stuff with the raycast here
}
}
If you are still having performance issues, then you might wanna look into breaking down that loop function so that after XXms it breaks to let the UI update, and then continues updating on the next frame:
For example, I sort through all hits and find the point that is closest to the mouse:
// Optimization
let startTime = 0;
let maxTime = 75; // max time in ms
let dist = 1;
let hitIndex;
let i = 0;
function findClosest() {
return new Promise((resolve) => {
function loop() {
startTime = performance.now();
while (i < hitcount) {
// Break loop after maxTime
let currentTime = performance.now();
if ((currentTime - startTime) > maxTime) {
console.log('Loop exceeded max time: ' +
(currentTime - startTime).toFixed(3) );
startTime = currentTime;
break;
}
// I am finding the raycast point that is closest to the cursor
dist = hits[i].distanceToRay;
if (dist < smallestDist) {
smallestDist = dist;
smallestPointIndex = hits[i].index;
}
i++;
}
if (i < hitcount) {
// Allow the UI to update, then loop
setTimeout(loop, 1);
} else {
resolve(smallestPointIndex);
}
}
loop();
});
}
findClosest().then(result => {
// do something with the result here
}
Also the comments were good suggestions also to reduce the number of objects you are raycasting to.

Related

How do I make a loop animation stop and resume?

I have a JavaScript code that I am going to use for an animated train map.
I have a loop that goes up and down, but I would like it to stop for 10 seconds every 20px, and then resume it's movement.
How do I accomplish this?
`window.onload = () => {
startSetTrain0Animation();
startSetTrain1Animation();
};
function startSetTrain0Animation() {
const refreshRate = 1000 / 60;
const maxXPosition = 470;
let rect = document.getElementById('rect0');
let speedX = 0.02;
let positionX = 25;
window.setInterval(() => {
positionX = positionX + speedX;
if (positionX > maxXPosition || positionX < 25) {
speedX = speedX * (-1);
}
rect.style.top = positionX + 'px';
}, refreshRate);
}`
There are typically two options to handle this:
Use a flag to indicate the state of the loop
Store a reference to the setInterval() function so that you can clear/stop via the clearInterval() function and later restart.
Using a Flag
Using a flag would be as simple as it sounds. Simply declare a variable and it would dictate if the body of the loop is executed or not:
// Flag to pause animation (change its value to toggle the animation)
var paused = false;
// Omitted for brevity
window.setInterval(() => {
// If you are paused, don't do anything
if (paused) {
return;
}
positionX = positionX + speedX;
if (positionX > maxXPosition || positionX < 25) {
speedX = speedX * (-1);
}
rect.style.top = positionX + 'px';
}, refreshRate);
This is the simplest of use cases, however you could easily adjust it to define some logic regarding when you would start/stop within the interval itself.
Explicitly Stopping / Restarting the Loop
Another option would be to store a reference to your loop in a variable when you declare it:
// Store a reference to your loop
var loop = setInterval(() => { ... }, refreshRate);
// Clear it when you need to stop it
window.clearInterval(loop);
If you prefer this approach, you'd likely elect to have a function that would wrap your "start loop" functionality such that you could do something like:
// Start the loop (wraps your setInterval() function)
var loop = startLoop();
// Stop the loop (could easily be renamed stopLoop())
window.clearInterval(loop);

How to tell on which part of the screen divided to N rectangles am I (with mousemove)

I need to divide screen to 20 parts (horizontally) and set the value of mouse position from 1 to 20 to update sprite background-image position (for a smooth rotation animation). The code below is working, but there is a problem, when I move mouse very fast - than it can skip a few points, and I need to always change the number by one step. How can I achieve that?
https://codepen.io/kgalka/pen/vbpoqe
var frames = 20;
var frameWidth = Math.round(window.innerWidth / frames);
var xIndex;
function updatePosition(x) {
if (xIndex != x) {
xIndex = x;
document.getElementById('val').innerText = xIndex;
}
}
document.onmousemove = function(e) {
updatePosition(Math.round(e.clientX / frameWidth));
}
Ok i saw th example and i think that i understand the problem and here is how i would fix this.
Have a look and let me know if it work.
var frames = 20;
var frameWidth = Math.round(window.innerWidth / frames);
var xIndex;
var timeout;
function updatePosition(x) {
if (xIndex != x) {
xIndex = x;
document.getElementById('val').innerText = xIndex;
}
}
document.onmousemove = function(e) {
// clear the prev call if the mouse was to fast.
clearTimeout(timeout);
// Now ini new call to updatePosition
timeout= setTimeout(()=> updatePosition(Math.round(e.clientX / frameWidth)), 1 )
// you could play with the timeout and increase it from 1 to 100ms. and see what you prefere
}
<p id="val"></p>

Phaser P2 Sprite Scale Change not working properly for all group children

I hope this code explains what I'm trying to do. I have a pool table, and I want the balls to accelerate into the pockets if they are close enough. At this point I'm not yet checking the distance, just working to figure out how to do it.
I'm sure there is a better way!
balls.forEachAlive(
pockets.forEachAlive( moveBallTowardPocket, this), this);
Update: The following code is working except one thing, the scale change for balls on the first five pockets. Acceleration is working for all balls to all pockets. Scale change is only working on the last pocket, not the first five.
function update() {
pockets.forEachAlive(function(pocket) {
accelerateBallToPocket(flipper, pocket, 60);
balls.forEachAlive(function(ball) {
accelerateBallToPocket(ball, pocket, 60);
});
});
//...
}
function accelerateBallToPocket(ball, pocket, speed) {
if (typeof speed === 'undefined') {
var speed = 120;
}
var pocket_body_x = pocket.body.x;
var pocket_body_y = pocket.body.y;
var ball_body_x = ball.body.x;
var ball_body_y = ball.body.y;
// move ball toward pocket if close enough
var dx = ball_body_x - pocket_body_x; //distance ship X to enemy X
var dy = ball_body_y - pocket_body_y; //distance ship Y to enemy Y
var dist = Math.sqrt(dx*dx + dy*dy); //pythagoras
if (dist < pocket_radius * pocket_leniency_factor) {
// accelerate ball to pocket on right angle
var angle = Math.atan2(pocket.y - ball.y,
pocket.x - ball.x);
ball.body.rotation = angle + game.math.degToRad(90);
ball.body.force.x = Math.cos(angle) * speed;
ball.body.force.y = Math.sin(angle) * speed;
// change scale
// FIXME only works on the last pocket lower right
if (ball === flipper) {
ball.scale.setTo(Math.tan(pocket.x - ball.x),
Math.tan(pocket.y - ball.y));
} else {
ball.scale.setTo(Math.sin(pocket.x - ball.x),
Math.cos(pocket.y - ball.y));
}
} else {
// reset the scale when the ball is out of range of the pocket
ball.scale.setTo(1.0, 1.0);
}
}
2nd Update:
The following, based on the solution, has me going in the right direction again, I think...
for (var i = 0; i < pockets.children.length; i++) {
accelerateBallToPocket(cue, pockets.children[i], 60);
if (cue.pocketing) break;
}
for (var i = 0; i < balls.children.length; i++) {
if (balls.children[i].pocketing) continue;
for (var j = 0; j < pockets.children.length; j++) {
accelerateBallToPocket(balls.children[i], pockets.children[j], 60);
if (balls.children[i].pocketing) return;
}
}
Ok, the problem is that you set the scale to 1 if the ball isn't close to a pocket. And, as you check each ball against each pocket, there will always be one pocket (that is checked later in the loop) that the ball is not close too, except the last pocket in the pocket list. So, even if the ball scale is set to the correct value, it will be reset when the next pocket is being checked.
What you can do is check whether a ball is close to at least one pocket, if it is then it can't be close the the other pockets so you don't check again agaist the other pockets.
// Consider that every ball is not inside a pocket
balls.forEachAlive(function(ball) {
ball.inPocket = false;
});
flipper.inPocket = false; // You should really add the flipper to the balls group to remove duplicate code
pockets.forEachAlive(function(pocket) {
if(!flipper.inPocket) accelerateBallToPocket(flipper, pocket, 60);
balls.forEachAlive(function(ball) {
if(!ball.inPocket) accelerateBallToPocket(ball, pocket, 60);
});
});
Then, in your move function you have to set the inPocket member to true if a ball is close to the pocket.
function accelerateBallToPocket(ball, pocket, speed) {
...
if (ball === flipper) {
ball.scale.setTo(Math.tan(pocket.x - ball.x),
Math.tan(pocket.y - ball.y));
ball.inPocket = true;
} else {
ball.scale.setTo(Math.sin(pocket.x - ball.x),
Math.cos(pocket.y - ball.y));
ball.inPocket = true;
}
} else {
// reset the scale when the ball is out of range of the pocket
ball.scale.setTo(1.0, 1.0);
}
}
And alternative would be to revers the loop order, first iterate through all balls and for each ball check each pocket, once you find that is in a pocket continue the outer loop (skipping the check for the other pockets). In order to do this your accelerateBall function should return true or false, being true when the ball is close enough to the pocket and false otherwise.
I would re-write your iterations like this:
for (var i = 0; i < pockets.children.length; i++) {
accelerateBallToPocket(cue, pockets.children[i], 60);
if (cue.pocketing) break;
}
// Stumped...
for (var i = 0; i < balls.children.length; i++) {
// No need for the check here, each ball should have pocketing=false, set at the top of the update loop
// This means, that balls.children[i].pocketing will always be false here
for (var j = 0; j < pockets.children.length; j++) {
accelerateBallToPocket(balls.children[i], pockets.children[j], 60);
if (balls.children[i].pocketing) break; // stop checking the rest of the pockets for this ball
}
}

Recording FPS in webGL

I am trying to compare performance for 3d applications on mobile devices. I have a 3d solar system set up in webGL and im trying to record or at least display the FPS. So far this is what i Have:
in the body
<script language="javascript">
var x, message;
x = Time;
message = "fps is equal to ";
document.write (message); // prints the value of the message variable
document.write (x); //prints the value of x
</script>
and to get The Time Var in the draw function of canvas i have this
var Time = 0;
function drawScene() {
var startTime = new Date();
//draw scene here
var endTime = new Date();
Time = (endTime - startTime)
}
the output i get at the bottom of the canvas is "fps is equal to null"
any help would be great!
Displaying FPSs is pretty simple and has really nothing to do with WebGL other than it's common to want to know. Here's a small FPS display
const fpsElem = document.querySelector("#fps");
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<div>fps: <span id="fps"></span></div>
Use requestAnimationFrame for animation because that's what it's for. Browsers can sync to the screen refresh to give you buttery smooth animation. They can also stop processing if your page is not visible. setTimeout on the other hand is not designed for animation, will not be synchronised to the browser's page drawing.
You should probably not use Date.now() for computing FPS as Date.now() only returns milliseconds. Also using (new Date()).getTime() is especially bad since it's generating a new Date object every frame.
requestAnimationFrame already gets passed the time in microseconds since the page loaded so just use that.
It's also common to average the FPS across frames.
const fpsElem = document.querySelector("#fps");
const avgElem = document.querySelector("#avg");
const frameTimes = [];
let frameCursor = 0;
let numFrames = 0;
const maxFrames = 20;
let totalFPS = 0;
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
// add the current fps and remove the oldest fps
totalFPS += fps - (frameTimes[frameCursor] || 0);
// record the newest fps
frameTimes[frameCursor++] = fps;
// needed so the first N frames, before we have maxFrames, is correct.
numFrames = Math.max(numFrames, frameCursor);
// wrap the cursor
frameCursor %= maxFrames;
const averageFPS = totalFPS / numFrames;
avgElem.textContent = averageFPS.toFixed(1); // update avg display
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { font-family: monospace; }
<div> fps: <span id="fps"></span></div>
<div>average fps: <span id="avg"></span></div>
I assume you are calling drawScene repeatedly but if you are setting x only once then it will not update every time drawScene is called. Also what you are storing in Time is elapsed time and not frames per second.
How about something like the below? The idea is to count the number of frames rendered and once one second has passed store that in the fps variable.
<script>
var elapsedTime = 0;
var frameCount = 0;
var lastTime = 0;
function drawScene() {
// draw scene here
var now = new Date().getTime();
frameCount++;
elapsedTime += (now - lastTime);
lastTime = now;
if(elapsedTime >= 1000) {
fps = frameCount;
frameCount = 0;
elapsedTime -= 1000;
document.getElementById('test').innerHTML = fps;
}
}
lastTime = new Date().getTime();
setInterval(drawScene,33);
</script>
<div id="test">
</div>
I created an object oriented version of Barış Uşaklı's answer.
It also tracks the average fps over the last minute.
Usage:
global Variable:
var fpsCounter;
Create the object somewhere when starting your program:
fpsCounter = new FpsCounter();
Call the update method in your draw() funktion & update the fps-displays:
function drawScene() {
fpsCounter.update();
document.getElementById('fpsDisplay').innerHTML = fpsCounter.getCountPerSecond();
document.getElementById('fpsMinuteDisplay').innerHTML = fpsCounter.getCountPerMinute();
// Code
}
Note: I only put the fps-display updates in the draw function for simplicity. With 60fps it gets set 60 times per second, even though once a second is enough.
FpsCounter Code:
function FpsCounter(){
this.count = 0;
this.fps = 0;
this.prevSecond;
this.minuteBuffer = new OverrideRingBuffer(60);
}
FpsCounter.prototype.update = function(){
if (!this.prevSecond) {
this.prevSecond = new Date().getTime();
this.count = 1;
}
else {
var currentTime = new Date().getTime();
var difference = currentTime - this.prevSecond;
if (difference > 1000) {
this.prevSecond = currentTime;
this.fps = this.count;
this.minuteBuffer.push(this.count);
this.count = 0;
}
else{
this.count++;
}
}
};
FpsCounter.prototype.getCountPerMinute = function(){
return this.minuteBuffer.getAverage();
};
FpsCounter.prototype.getCountPerSecond = function(){
return this.fps;
};
OverrideBuffer Code:
function OverrideRingBuffer(size){
this.size = size;
this.head = 0;
this.buffer = new Array();
};
OverrideRingBuffer.prototype.push = function(value){
if(this.head >= this.size) this.head -= this.size;
this.buffer[this.head] = value;
this.head++;
};
OverrideRingBuffer.prototype.getAverage = function(){
if(this.buffer.length === 0) return 0;
var sum = 0;
for(var i = 0; i < this.buffer.length; i++){
sum += this.buffer[i];
}
return (sum / this.buffer.length).toFixed(1);
};
Since none of the other answers addressed the "in WebGL" part of the question, I'll add the following important details when measuring FPS in WebGL correctly.
window.console.time('custom-timer-id'); // start timer
/* webgl draw call here */ // e.g., gl.drawElements();
gl.finish(); // ensure the GPU is ready
window.console.timeEnd('custom-timer-id'); // end timer
For simplicity I used the console timer. I'm trying to make the point to always use WebGLRenderingContext.finish() to ensure the correct time is measured as all WebGL calls to the GPU are asynchronous!
Using a rotating array can do better.
with dom element:
<div id="fps">
the following script do the trick:
var fpsLastTick = new Date().getTime();
var fpsTri = [15, 15, 15]; // aims for 60fps
function animate() {
// your rendering logic blahh blahh.....
// update fps at last
var now = new Date().getTime();
var frameTime = (now - fpsLastTick);
fpsTri.shift(); // drop one
fpsTri.push(frameTime); // append one
fpsLastTick = now;
fps = Math.floor(3000 / (fpsTri[0] + fpsTri[1] + fpsTri[2])); // mean of 3
var fpsElement = document.getElementById('fps')
if (fpsElement) {
fpsElement.innerHTML = fps;
}
}

Calculate FPS in Canvas using requestAnimationFrame

How could I calculate the FPS of a canvas game application? I've seen some examples, but none of them use requestAnimationFrame, and im not sure how to apply their solutions there. This is my code:
(function(window, document, undefined){
var canvas = document.getElementById("mycanvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
fps = 0,
game_running = true,
show_fps = true;
function showFPS(){
context.fillStyle = "Black";
context.font = "normal 16pt Arial";
context.fillText(fps + " fps", 10, 26);
}
function gameLoop(){
//Clear screen
context.clearRect(0, 0, width, height);
if (show_fps) showFPS();
if (game_running) requestAnimationFrame(gameLoop);
}
gameLoop();
}(this, this.document))
canvas{
border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="150"></canvas>
By the way, is there any library I could add to surpervise performance?
Do not use new Date()
This API has several flaws and is only useful for getting the current date + time. Not for measuring timespans.
The Date-API uses the operating system's internal clock, which is constantly updated and synchronized with NTP time servers. This means, that the speed / frequency of this clock is sometimes faster and sometimes slower than the actual time - and therefore not useable for measuring durations and framerates.
If someone changes the system time (either manually or due to DST), you could at least see the problem if a single frame suddenly needed an hour. Or a negative time. But if the system clock ticks 20% faster to synchronize with world-time, it is practically impossible to detect.
Also, the Date-API is very imprecise - often much less than 1ms. This makes it especially useless for framerate measurements, where one 60Hz frame needs ~17ms.
Instead, use performance.now()
The Performance API has been specificly made for such use cases and can be used equivalently to new Date(). Just take one of the other answers and replace new Date() with performance.now(), and you are ready to go.
Sources:
Also unlike Date.now(), the values returned by Performance.now()
always increase at a constant rate, independent of the system clock
(which might be adjusted manually or skewed by software like NTP).
Otherwise, performance.timing.navigationStart + performance.now() will
be approximately equal to Date.now().
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
And for windows:
[The time service] adjusts the local clock rate to allow it to
converge toward the correct time.
If the time difference between the local clock and the [accurate time sample] is too large to correct by adjusting the local
clock rate,
the time service sets the local clock to the correct time.
https://technet.microsoft.com/en-us/library/cc773013(v=ws.10).aspx
Chrome has a built-in fps counter: https://developer.chrome.com/devtools/docs/rendering-settings
Just open the dev-console (F12), open the drawer (Esc), and add the "Rendering" tab.
Here, you can activate the FPS-Meter overlay to see the current framerate (incl. a nice graph), as well as GPU memory consumption.
Cross-browser solution:
You can get a similar overlay with the JavaScript library stat.js: https://github.com/mrdoob/stats.js/
It also provides a nice overlay for the framerate (incl. graph) and is very easy to use.
When comparing the results from stats.js and the chrome dev tools, both show the exact same measurements. So you can trust that library to actually do the correct thing.
You could keep track of the last time requestAnimFrame was called.
var lastCalledTime;
var fps;
function requestAnimFrame() {
if(!lastCalledTime) {
lastCalledTime = Date.now();
fps = 0;
return;
}
delta = (Date.now() - lastCalledTime)/1000;
lastCalledTime = Date.now();
fps = 1/delta;
}
http://jsfiddle.net/vZP3u/
Here's another solution:
var times = [];
var fps;
function refreshLoop() {
window.requestAnimationFrame(function() {
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
refreshLoop();
});
}
refreshLoop();
This improves on some of the others in the following ways:
performance.now() is used over Date.now() for increased precision (as covered in this answer)
FPS is measured over the last second so the number won't jump around so erratically, particularly for applications that have single long frames.
I wrote about this solution in more detail on my website.
I have a different approach, because if you calculate the the FPS you'll get this flickering when returning the number. I decided to count every Frame and return it once a second
window.countFPS = (function () {
var lastLoop = (new Date()).getMilliseconds();
var count = 1;
var fps = 0;
return function () {
var currentLoop = (new Date()).getMilliseconds();
if (lastLoop > currentLoop) {
fps = count;
count = 1;
} else {
count += 1;
}
lastLoop = currentLoop;
return fps;
};
}());
requestAnimationFrame(function () {
console.log(countFPS());
});
jsfiddle
I was missing an implementation that allows to customize the size of the sample for the averaged FPS value. Here is mine , it has the following features :
Accurate : performance.now() based
Stabilized : Returned FPS value is an averaged value ( fps.value | fps.tick() )
Configurable : FPS samples array size can be customized ( fps.samplesSize )
Efficient : Rotatory array for collecting samples (avoids array resizing)
const fps = {
sampleSize : 60,
value : 0,
_sample_ : [],
_index_ : 0,
_lastTick_: false,
tick : function(){
// if is first tick, just set tick timestamp and return
if( !this._lastTick_ ){
this._lastTick_ = performance.now();
return 0;
}
// calculate necessary values to obtain current tick FPS
let now = performance.now();
let delta = (now - this._lastTick_)/1000;
let fps = 1/delta;
// add to fps samples, current tick fps value
this._sample_[ this._index_ ] = Math.round(fps);
// iterate samples to obtain the average
let average = 0;
for(i=0; i<this._sample_.length; i++) average += this._sample_[ i ];
average = Math.round( average / this._sample_.length);
// set new FPS
this.value = average;
// store current timestamp
this._lastTick_ = now;
// increase sample index counter, and reset it
// to 0 if exceded maximum sampleSize limit
this._index_++;
if( this._index_ === this.sampleSize) this._index_ = 0;
return this.value;
}
}
// *******************
// test time...
// *******************
function loop(){
let fpsValue = fps.tick();
window.fps.innerHTML = fpsValue;
requestAnimationFrame( loop );
}
// set FPS calulation based in the last 120 loop cicles
fps.sampleSize = 120;
// start loop
loop()
<div id="fps">--</div>
Actually none of the answers were sufficient for me. Here is a better solution which:
Use's performance.now()
Calculates the actual average fps per second
Average per second and decimal places are configurable
Code:
// Options
const outputEl = document.getElementById('fps-output');
const decimalPlaces = 2;
const updateEachSecond = 1;
// Cache values
const decimalPlacesRatio = Math.pow(10, decimalPlaces);
let timeMeasurements = [];
// Final output
let fps = 0;
const tick = function() {
timeMeasurements.push(performance.now());
const msPassed = timeMeasurements[timeMeasurements.length - 1] - timeMeasurements[0];
if (msPassed >= updateEachSecond * 1000) {
fps = Math.round(timeMeasurements.length / msPassed * 1000 * decimalPlacesRatio) / decimalPlacesRatio;
timeMeasurements = [];
}
outputEl.innerText = fps;
requestAnimationFrame(() => {
tick();
});
}
tick();
JSFiddle
Just check the difference in time between the AFR-callbacks. AFR already passes the time as an argument to the callback. I updated your fiddle to show it: http://jsfiddle.net/WCKhH/1/
Just a proof of concept. Very simple code. All we do is set our frames per second and intervals between each frame. In the drawing function we deduct our last frame’s execution time from the current time to check whether the time elapsed since the last frame is more than our interval (which is based on the fps) or not. If the condition evaluates to true, we set the time for our current frame which is going to be the “last frame execution time” in the next drawing call.
var GameLoop = function(fn, fps){
var now;
var delta;
var interval;
var then = new Date().getTime();
var frames;
var oldtime = 0;
return (function loop(time){
requestAnimationFrame(loop);
interval = 1000 / (this.fps || fps || 60);
now = new Date().getTime();
delta = now - then;
if (delta > interval) {
// update time stuffs
then = now - (delta % interval);
// calculate the frames per second
frames = 1000 / (time - oldtime)
oldtime = time;
// call the fn
// and pass current fps to it
fn(frames);
}
}(0));
};
Usage:
var set;
document.onclick = function(){
set = true;
};
GameLoop(function(fps){
if(set) this.fps = 30;
console.log(fps);
}, 5);
http://jsfiddle.net/ARTsinn/rPAeN/
My fps calculation uses requestAnimationFrame() and the matching timestamp argument for its callback function.
See https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame and https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp.
No need for new Date() or performance.now()!
The rest is inspired heavily by other answers in this thread, especially https://stackoverflow.com/a/48036361/4706651.
var fps = 1;
var times = [];
var fpsLoop = function (timestamp) {
while (times.length > 0 && times[0] <= timestamp - 1000) {
times.shift();
}
times.push(timestamp);
fps = times.length;
console.log(fps);
requestAnimationFrame(fpsLoop);
}
requestAnimationFrame(fpsLoop);
The best way that I use with performance.now()
Simple I passed TIME on gameLoop function and
calculate fps
fps = 1 / ( (performance.now() - LAST_FRAME_TIME) / 1000 );
(function(window, document, undefined){
var canvas = document.getElementById("mycanvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
fps = 0,
game_running = true,
show_fps = true,
LAST_FRAME_TIME = 0;
function showFPS(){
context.fillStyle = "Black";
context.font = "normal 16pt Arial";
context.fillText(fps + " fps", 10, 26);
}
function gameLoop(TIME){
//Clear screen
context.clearRect(0, 0, width, height);
if (show_fps) showFPS();
fps = 1 / ((performance.now() - LAST_FRAME_TIME) / 1000);
LAST_FRAME_TIME = TIME /* remember the time of the rendered frame */
if (game_running) requestAnimationFrame(gameLoop);
}
gameLoop();
}(this, this.document))
canvas{
border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="150"></canvas>
i had to create a function which sets on which fps should animation run, because i have a 240hz monitor and animations on my screen are much faster then on other screens, so that my end projects was always slower on other monitors
function setFPSandRunAnimation(fps, cb) {
let frameCount = 0;
let fpsInterval, startTime, now, then, elapsed;
runAnimating(fps);
function runAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}
function animate(timestamp) {
requestAnimationFrame(animate);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
const sinceStart = now - startTime;
const currentFps = Math.round(1000 / (sinceStart / ++frameCount) * 100) / 100;
const elapsedTime = Math.round(sinceStart / 1000 * 100) / 100;
cb(timestamp, currentFps, elapsedTime)
}
}
}
this is how to you use it
setFPSandRunAnimation(fpsSpeedYouWant, cbFunctionWhereYouGet timestamp, currentfps and elapsedTime).
inside of the cb function you can run any code you would run in animation function

Categories