const canvas = document.querySelector('#canvas')
const context = canvas.getContext('2d')
let rectX = 0 ;
let rectY = 0;
let secondsPassed = 0;
let timeStamp = 0
let oldTimeStamp = 0;
let movingSpeed = 50;
gameLoop()
function draw() {
context.fillStyle = 'red';
context.fillRect(rectX, rectY, 150, 100);
}
function gameLoop(timeStamp) {
// Calculate how much time has passed
secondsPassed = (timeStamp - oldTimeStamp) / 1000;
oldTimeStamp = timeStamp;
update(secondsPassed);
draw();
window.requestAnimationFrame(gameLoop);
}
function update(secondsPassed) {
rectX += (movingSpeed * secondsPassed);
rectY += (movingSpeed * secondsPassed);
}
rectX and rectY initially have a number value, movingSpeed also has a number value, secondsPassed as well.My question is why the function "update" gives NaN to the variables rectX and rectY ? No errors are shown in console. I tried to log and used typeof to check if every variable had a value with a type number and I noticed that rectX is once considered a string, I tried to parseFloat the rectX but still it was giving me NaN. Normally, we use timeStamp to return a value that can help us calculate the fps. In this case I'm using timeStamp to see how many seconds have passed before running the function gameLoop. I'm doing this because it's no longer the frame rate (and hardware) that decides the speed of the game, but it's time.
Update: is solved thanks to
#epascarello, #James and #Kaiido. There's the updated code for you guys:
const canvas = document.querySelector('#canvas')
const context = canvas.getContext('2d')
let rectX = 0;
let rectY = 0;
let secondsPassed = 0;
let oldTimeStamp = 0;
let timeStamp = 0
let movingSpeed = 50;
let timePassed = 0
function draw() {
context.fillStyle = 'red';
context.fillRect(rectX, rectY, 150, 100);
}
function gameLoop(timeStamp) {
// Calculate how much time has passed
secondsPassed = (timeStamp - oldTimeStamp) / 1000;
oldTimeStamp = timeStamp;
// Pass the time to the update
update(secondsPassed);
draw();
window.requestAnimationFrame(function(timeStamp){gameLoop(timeStamp)});
}
function update(secondsPassed) {
// Use time to calculate new position
rectX += (movingSpeed * secondsPassed);
rectY += (movingSpeed * secondsPassed);
}
window.requestAnimationFrame(function(timeStamp){gameLoop(timeStamp)});
your problem is let rectX; in line 3 creates an "undefined"
if you use rectX += 1 js tries to add undefined + 1 (automaticly string converting from undefined)
sting + int = NaN
let rectX = 0;
let rectY = 0;
let speed = 1;
function update(seconds){
rectX += (speed * seconds);
rectY += (speed * seconds);
}
// test:
update(5);
console.log(rectX, rectY); // output is 5 5
The problem is that you're not passing timeStamp into the gameLoop function when you call it initially. inside gameLoop, timeStamp is undefined, which breaks the other variables.
You could pass in the current time stamp when you first call it. Instead of
gameLoop() try
gameLoop(Date.now().getTime())
intitialing the variable rectX and rectY might help.
try this:
rectX = 0;
rectY = 0;
Related
As stated in the title, rendering slows down significantly at higher resolutions. I'm wondering if this is caused by beginShape() as well as why and how to get around it. Other functions that do not use beginShape() do not affect the frame rate negatively. Link to p5 editor here: https://editor.p5js.org/anton.ermkv/sketches/mSkLrkPJ9
Code below:
function w(v) {if (v == null) return width;return width * v;}
function h(v) {if (v == null) return height;return height * v;}
let zoff = 0;
let irregCircs = []
let numCircs;
function setup() {
createCanvas(windowWidth, windowHeight);
exType = chooseExpandType()
pixelDensity(1)
setIrregCircles(exType)
}
function draw() {
translate(width/2,height/2)
background(255,50);
drawIrregCircles()
console.log(frameRate())
}
function chooseExpandType() {
expandType = 'ellipses'
return expandType
}
function irregExpand(radius,noiseVal) {
beginShape();
for (let a = 0; a <= TWO_PI; a += radians(6)) {
let xoff = map(cos(a), -1, 1, noiseVal/3, noiseVal);
let yoff = map(sin(a), -1, 1, noiseVal/3, noiseVal);
let diff = map(noise(xoff, yoff, zoff), 0, 1, .65, 1.35);
let x = radius * diff * cos(a);
let y = radius * diff * sin(a);
vertex(x,y)
}
endShape(CLOSE);
zoff += 0.0001;
}
function setIrregCircles(expandType) {
numCircs = 4;
for (let i = 0; i < numCircs; i++) {
radius = map(i,0,numCircs,w(.03),w(.65))
noiseVal = random(1,3)
circ = new IrregCircle(radius,noiseVal,expandType,numCircs);
irregCircs.push(circ);
}
}
function drawIrregCircles() {
for (let i = 0; i < irregCircs.length; i++){
irregCircs[i].run();
}
}
class IrregCircle{
constructor(_radius,_noiseVal,_expandType,_numC) {
this.radius = _radius;
this.noiseVal = _noiseVal;
this.expandType = _expandType;
this.numC = _numC;
}
run() {
this.update()
this.checkEdges()
this.show()
}
update() {
this.radius += w(.0015)
}
checkEdges() {
if (this.radius > w(.73)) {
this.radius = w(.01)
}
}
show() {
noFill()
if (this.expandType === 'ellipses'){
push()
rotate(frameCount / 60)
stroke(35,20)
strokeWeight(w(.002))
irregExpand(this.radius,this.noiseVal)
irregExpand(this.radius*1.15,this.noiseVal*1.35)
irregExpand(this.radius*1.3,this.noiseVal*1.7)
irregExpand(this.radius*1.45,this.noiseVal*2)
irregExpand(this.radius*1.6,this.noiseVal*2.3)
irregExpand(this.radius*1.75,this.noiseVal*2.8)
pop()
}
}
}
Thanks in advance to anyone having a look.
the fact that you are printing the frameRate at each frame slows down your program significantly. You can replace:
console.log(frameRate())
by:
if(frameCount % 60 == 0)
console.log(frameRate())
to only print it every 60 frame.
I don't know if it solves your problem but on my side, it seems that it get rid of most of the freezing problem.
As you draw a lot of similar shapes, you should also try to compute an array of the points you need at the beginning and then reuse it at each frame and for each similar shape by scaling it by the right factor (Your code ran a lot faster when I removed the noise to draw circles only so I think what slows your code is the computation inside the BeginShape() block and not the BeginShape() itself).
Im working with this part of code which one render simple loading bar
const smallSpinner = document.getElementById('spinner-small').getContext('2d');
let pointToFill = 4.72;
let cw = smallSpinner.canvas.width; //Return canvas width
let ch = smallSpinner.canvas.height; //Return canvas height
let diff;
let = fillSmallSpinner = (startingPointSmall = 0) => {
diff = ((startingPointSmall / 100) * Math.PI * 2 * 10);
smallSpinner.clearRect(0, 0, cw, ch);
smallSpinner.lineWidth = 5;
smallSpinner.strokeStyle = '#d40511';
/* smallSpinner.textAlign = 'center';
smallSpinner.font = "25px monospace";
smallSpinner.fillText(no + '%', 50, 55); */ //uncomment this if you need percent progress inside spinner
smallSpinner.beginPath();
smallSpinner.arc(50, 50, 40, pointToFill, diff / 10 + pointToFill);
smallSpinner.stroke();
if (startingPointSmall >= 100) {
clearTimeout(fill);
}
startingPointSmall++;
}
let small = setInterval(fillSmallSpinner, 50);
The point is that when "startingPointSmall" is defined like normal variable
let startingPointSmall = 0;
it works totaly fine but i want to make this a little bit more usable and pass the starting point as a function parameter. When i do this like this with predefined starting point on 0% it doesnt work. Can someone explain me how to fix this?
Every time that setInterval queues up a call to fillSmallSpinner it will receive its default parameter - over and over!
A more common pattern is to wrap the function in a way that preserves the desired variable's scope:
const startFiller(callback, interval = 50, start = 0) {
let current = startPoint;
let timer = setInterval(() => {
callback(current++);
if (current >= 100) {
clearTimeout(timer);
}
}, interval);
});
startFiller(fillSmallSpinner);
You would then remove any existing timer-related logic from your fillSmallSpinner function. This approach has the added benefit of Separation of Concerns - if you decide you want to use a different function to render your spinner it no longer needs to concern itself with timers.
Currently I'm drawing vertices to create polygons, I would like to add sliders to allow a user to increase or decrease the amount of sides the polygons have and the amount drawn. Also having the canvas update with refreshing.
I've tried adding a slider to control the noOfSides but I've had no luck.
Thanks for your time and help.
let noOfShapes = 3;
let noOfSides;
let rx, ry, drx, dry, rd1, rd2, drd1, drd2;
let threshold = 1000;
let fillColour;
let strokeThick;
let sidesSlider;
function setup() {
createCanvas(1240, 1754);
noLoop();
// background(0, 230)
colorMode(RGB)
rectMode(CENTER);
strokeWeight(3);
//noOfSides = 3;
fillColour = random(0, 255);
sidesSlider = createSlider(4, 12, 3);
sidesSlider.position(width + 20, 0);
sidesSlider.style('width', '150px');
}
function draw() {
background(0, 230)
noOfSides = sidesSlider.value();
for (let x = 0; x < noOfShapes; x++) {
do {
rx = [];
ry = [];
rd1 = [];
rd2 = [];
for (let y = 0; y < noOfSides; y++) {
rx.push(random(10, width - 20));
ry.push(random(10, height - 20));
rd1.push(rx[y] * 1 + ry[y] * 1);
rd2.push(rx[y] * 1 - ry[y] * 1);
}
drx = max(rx) - min(rx);
dry = max(ry) - min(ry);
drd1 = max(rd1) - min(rd1);
drd2 = max(rd2) - min(rd2);
}
while (drx < threshold || dry < threshold || drd1 < threshold || drd2 < threshold)
beginShape();
stroke(255);
fill(random(1, 255), random(1, 255), random(1, 255), 150);
for (let y = 0; y < rx.length; y++) {
vertex(rx[y], ry[y]);
}
endShape(CLOSE);
}
for (let x = 20; x <= width; x = x + 20) {
blendMode(DODGE);
stroke(255);
beginShape();
vertex(x, 0)
vertex(x, height + 20)
endShape();
}
}
First things first, you need to re-draw your shapes whenever slider changes since you are using noLoop().
To do that you can easily define an on change event like this:
sidesSlider.input(sliderChange);
And i suggest you to assign noOfSides variable's value in that function. After that call the draw funtion again.
function sliderChange() {
noOfSides = sidesSlider.value();
draw();
}
Since you would remove assigning the value to noOfSides in draw function you need to set a default value to that variable either on initialization or in `setup function.
...
noOfSides = 3;
...
Then you are almost good to go, only thing that i don't quite understand was you last part of the code. I removed that part and it works as expected at the moment.
Please be aware that you are setting background color with alpha value. That leads: on each rendering of shapes, latest shapes silhouette are still barely visible.
Here is the latest version of your code:
https://editor.p5js.org/darcane/sketches/5wpp6UgXI
Into this simple code I use an eventListener which doesn't look to work at all. The canvas display an image and the given hitpaint() function is supposed determines whether a click occurs. I cant understand why the eventListener behaves like that. Any insight would be helpful.
mycanv.addEventListener("click", function(e) {
var output = document.getElementByID("output");
ctx.fillStyle = 'blue';
//ctx.clearRect(0,0,100,20);
if (hitpaint) {
//ctx.fillText("hit",100,20);
output.innerHTML = "hit";
} else {
//ctx.fillText("miss",100,20);
output.innerHTML = "miss";
}
}, false);
The hitpaint() function is defined as:
function hitpaint(mouse_event) {
var bounding_box = mycanv.getBoundingClientRect();
var mousex = (mouse_event.clientX - bounding_box.left) *
(mycanv.width / bounding_box.width);
var mousey = (mouse_event.clientY - bounding_box.top) *
(mycanv.height / bounding_box.height);
var pixels = ctx.getImageData(mousex, mousey, 1, 1);
for (var i = 3; i < pixels.data.length; i += 4) {
// If we find a non-zero alpha we can just stop and return
// "true" - the click was on a part of the canvas that's
// got colour on it.
if (pixels.data[i] !== 0) return true;
}
// The function will only get here if none of the pixels matched in
return false;
}
Finally, the main loop which display the picture in random location into the canvas:
function start() {
// main game function, called on page load
setInterval(function() {
ctx.clearRect(cat_x, cat_y, 100, 100);
cat_x = Math.random() * mycanv.width - 20;
cat_y = Math.random() * mycanv.height - 20;
draw_katy(cat_x, cat_y);
}, 1000);
}
There are a some issues here:
As Grundy points out in the comment, the hitpaint is never called; right now it checks for it's existence and will always return true
The mouse coordinates risk ending up as fractional values which is no-go with getImageData
Scaling the mouse coordinates is usually not necessary. Canvas should preferably have a fixed size without an additional CSS size
Add boundary check for x/y to make sure they are inside canvas bitmap
I would suggest this rewrite:
mycanv.addEventListener("click", function(e) {
var output = document.getElementByID("output");
ctx.fillStyle = 'blue';
//ctx.clearRect(0,0,100,20);
if (hitpaint(e)) { // here, call hitpaint()
//ctx.fillText("hit",100,20);
output.innerHTML = "hit";
} else {
//ctx.fillText("miss",100,20);
output.innerHTML = "miss";
}
}, false);
Then in hitpaint:
function hitpaint(mouse_event) {
var bounding_box = mycanv.getBoundingClientRect();
var x = ((mouse_event.clientX - bounding_box.left) *
(mycanv.width / bounding_box.width))|0; // |0 cuts off any fraction
var y = ((mouse_event.clientY - bounding_box.top) *
(mycanv.height / bounding_box.height))|0;
if (x >= 0 && x < mycanv.width && y >= 0 && y < mycanv.height) {
// as we only have one pixel, we can address alpha channel directly
return ctx.getImageData(x, y, 1, 1).data[3] !== 0;
}
else return false; // x/y out of range
}
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;
}
}