mousemove -> draw, what's the right way? - javascript

My actual use case is to draw something (not html, but render on the canvas) in response to mousemove events - it all actually boils down to nothing important to do unless the mouse changes positions.
I am seeing that requestAnimationFrame can be thought of as a way to throttle user input such as mousemove and I understand why it should be that way and why it is a code smell to use it that way if mousemove -> draw is not the actual purpose of the code.
Is there anything necessarily wrong with calling requestAnimationFrame from within the mousemove handler?
If I do not call requestAnimationFrame from the mousemove handler and the drawing rate is actually slower than the user input loop (the preferred rate or however it works), do I still get the desired throttling in that case?

Firstly, the information provided in this question was very informative.
Second, given that mousemove events can possibly fire much more often than rAF, it would seem that calling rAF from the mousemove event is a bad idea, despite the fact that it can be throttled as suggested in the above question.
For the purpose of rendering in response to mouse movement, it seems that a better approach is to let mousemove fire the way it wants to and DO NOT try to call rAF from there. If the mousemove event is trying to fire at a ridiculous 1000Hz as suggested in the above question, the only thing the handler needs to do is update a couple of shared mouse position variables and set a shared flag if either mouseX or mouseY have changed.
Likewise, set up a standard rAF loop and let it run at it's preferred rate, while from the rAF callback, check the flag to see if the mouse has moved, if it has then there will be something to render, if not, then do nothing.
This way the two processes are not trying to control each other and the code is cleaner and makes more sense.

This will show you the difference of mousemove and an animation:
var doc = document, c = doc.getElementById('canvasId'), d = c.getContext('2d'), b = c.getBoundingClientRect();
function logTime(title){
var dt = new Date;
console.log(title+': '+dt.toLocaleDateString()+' '+dt.toTimeString().replace(/\s.*/, '')+'.'+dt.getMilliseconds());
}
function draw(e){
logTime('mousemove');
requestAnimationFrame(function(){
logTime('requestAnimationFrame');
});
var x = e.clientX - b.left, y = e.clientY - b.top;
d.fillRect(x, y, 1, 1);
}
c.addEventListener('mousedown', function(e){
draw(e); c.addEventListener('mousemove', draw);
});
function stop(){
c.removeEventListener('mousemove', draw);
}
doc.addEventListener('mouseup', stop);
It's also firing with a 'mousemove' title on mousedown, but other than that it logs times you can look at to determine what you need to know. Although this code does not demonstrate it you can use requestAnimationFrame to hold out on the execution of code until the animation is complete to do something else.

Related

How should I organize my update/draw logic when using requestAnimationFrame?

The JavaScript code for my HTML5 game has the following structure:
// <body onload="load()">
function load() {} // Load all images then call init()
function init() {} // Get all images ready for the game logic then call animate()
function animate() {} // Use requestAnimationFrame(), update() and drawing()
function update() {} // Update the game logic
function drawing() {} // Render the images on canvas
The issue lies inside animate(). I'm not finding any consistent sources around the web on how to organize requestAnimationFrame(), update() and drawing() in it.
I tried to elaborate it by myself, but the game did run in pratically any approach, like passing either animate(), update() or drawing() as an argument to requestAnimationFrame(), or having requestAnimationFrame() at either the beginning or the end of the function, or having any of these functions in any order, or one function inside another, etc.
That, however, doesn't mean anything is fine. Some of those arrangements result in issues that I'd find out only later, like when testing in a different computer or at a different frame rate. And then I have to go back to the code to try another approach.
So, how should I organize that? I'd appreciate if you can present me a proper algorithm, and even more if you have any good sources on teaching about it.
Use requestAnimationFrame to call animate repeatedly. animate calls update then draw. That's basically it. To have more control of time since you don't control the intervals exactly, it makes sense to pass the last time that animate was invoked. Maybe event the delta time that has passed since, makes more sense. Then you can use delta time to calculate distance given a speed and so on.
Here's an example of a game loop which is explained here:
var now,
dt = 0,
last = timestamp(),
step = 1/60;
function frame() {
now = timestamp();
dt = dt + Math.min(1, (now - last) / 1000);
while(dt > step) {
dt = dt - step;
update(step);
}
render(dt);
last = now;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
There are many resources online. Here's a decent one for beginners https://www.sitepoint.com/quick-tip-game-loop-in-javascript/

How do I keep Transform Control from moving your object if there is a collision, using raycasting?

So I'm using Three.js and I have some cubes inside of a box. I'm using the Transform Control to move the cubes around inside of the box with my mouse. I'd like to use raycasting in order to check for collisions. The question is how to I prevent the transform controller from moving the object if there is a collision? I'd like to stop it if it hits the wall. By the way, I'm on version r81 for Three.js.
UPDATE: I've used the size of the room to constrain the cubes from
moving outside of the room. This seems to work well. Is there a way
to use the cannon.js just for collisions? I don't want the momentum
or gravity or any other feature. JUST the collision check and to stop
it dead in its tracks when there is a collision.
I know this post is from a long time ago, but hopefully a googler finds this helpful. I wasn't able to stop the user from moving my object, but I was able to move it back to its proper position immediately afterward by adding some logic to the render method.
For the original poster's problem with collisions, you could attach an event listener to the transform controls and request the object to be repositioned if it is in an illegal state.
transformControls.addEventListener('objectChange', (e) => {
if (illegalPosition(this.obj.position)) {
needsReset = true;
}
lastPosition = attachedObject.position.clone();
});
and then in your render function
if (needsReset) {
attachedObject.position.set(lastPosition.x, lastPosition.y, lastPosition.z);
}
If this feels a little hacky, that's because it is. But for those of us who don't have the time or skill to read and modify TransformControls.js, I think it may prove helpful.
You could create helper raycaster and place all colliders in separate container. After movement is applied to object move raycaster to its position and test if ray intersects any of other objects in container. If yes: reset previous position for that object. In case of cube colliders you could want to raycast from cube center in multiple directions with half of side length as ray length.
Ben S does have the best and most painless way to implement collision detection with transform controls. Within a event listener.
But I don't know if the time of writing his answer he knew about or if there even was a function called "requestAnimationFrame". All you would have to do for collision detection instead of simply resetting the models position is to set up your render call within a loop (60 fps) by adding "requestAnimationFrame" to your render (I call it animate since that is more descriptive) function.
Since it is in a loop and is called when the every frame the scene is drawn it will just not allow the object to move past the point of collision.
function animate() {
// Called to draw onto screen every frame (60fps).
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
And your event listener would just look like this.
control.addEventListener('objectChange', (e) => {
// Collision detection code here. Set colliding model position here.
// No need to set it in render
});
Old post, I know. But here is a method that is still fairly simple but does not flicker or use ray casting. The biggest catch here is that you have a little bit of a bounce if you move the Transform control really quickly. But otherwise it seems to work fairly well. You can control the precision of the collision by adjusting the step value.
let transStart = null;
//capture objects position on start
control.addEventListener('mouseDown', function(){
transStart = control.object.position.clone();
})
//you'll have to provide your own collision function
control.addEventListener('objectChange', function(e){
if(collision(sphere, cube)){ stopControls() };
});
function stopControls(){
if(control.dragging && stopAt){
//calculate direction object was moving at time of collision
const s = transStart;
const e = control.object.position.clone();
const n = e.clone().sub(s).negate().normalize();
//janky hack nonsense that stops the transform control from
//continuing without making the camera controller go nuts.
control.pointerUp({button:0});
control.dragging = true;
//translate back the direction it came by the step amount and do not
//stop until the objects are no longer colliding.
//Increase the step size if you do not need super precise collision
//detection. It will save calculations.
let step = 0.00005;
while(colliding(sphere, cube)){
sphere.translateOnAxis( n, step ) ;
sphere.updateMatrix();
}
}
}

Fluent animation to follow the cursor

I trying to figure out, how to create a smooth cursor follow animation with cocos2d js
Here some code:
var listener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
onMouseMove: function (event) {
var str = "MousePosition X: " + event.getLocationX() + " Y:" + event.getLocationY();
var actionBy = cc.MoveTo.create(2, cc.p(event.getLocationX(), $scope.bar._position.y));
$scope.bar.stopAllActions();
$scope.bar.runAction(
cc.sequence(
//cc.rotateTo(2, 0),
actionBy
)
);
// do something...
}});
The problem here, its stuck because the event is fired to often and the "$scope.bar.stopAllActions();" stops the animation.
If I remove the "$scope.bar.stopAllActions();" the animation is driving crazy, the "bar" is flying over the screen.
I just wont the bar follow the mouse cursor, just like the dog follow human
The problem here is that if you remove stopAllActions(); you'd be trying to run an action on an object that's already got an action of the same time. And in either case you are firing an animation too often on an object, I've rarely seen this work as expected.
I'd try running the action directly instead of putting it within a cc.Sequence first, but if that doesn't work, you've got two choices, both rely on manual labor:
a. Simply put the bar on the position of the mouse whenever it moves:
$scope.bar.x = event.getLocationX();
b. Assuming you want to "juice up" the game, just setting the bar on the mouse will be boring, so you could make the bar progressively catch up with the mouse:
In your constant section:
var EASING_CONSTANT = 0.9;
In the event handler:
$scope.bar.x += ($scope.bar.x - event.getLocationX()) * EASING_CONSTANT;
The lower your EASING_CONSTANT the slower the bar'll catch up to the mouse (only use values between 0 and 1).
Now, if you try to use this, you'll realize the bar never quite catches up with your mouse, so you'll have to place this code within an update function (in your main game layer's update method, for example) so it runs each frame.
BUT! Then you won't have access to an event object, so you'll have to end up with something like this:
In the event handler:
$scope.bar.targetX = event.getLocationX();
In the update method:
$scope.bar.x += ($scope.bar.x - $scope.bar.targetX) * EASING_CONSTANT;
There are lots of easing functions you can use, I just gave you the simplest example.
For instance, note that you also have the event.getDeltaX() method that'll return you the difference in position between the last call of the event and the current (how much the mouse moved since the last call of the event). With this you could do something along the lines of:
In your constant section:
var EASING_CONSTANT = 0.9;
var WOBBLING_CONSTANT = 10;
In the event handler:
$scope.bar.targetX = event.getLocationX();
$scope.bar.mouseDeltaX = event.getDeltaX();
In the update method:
$scope.bar.x += ($scope.bar.x - $scope.bar.targetX) * EASING_CONSTANT + Math.cos($scope.bar.mouseDeltaX) * WOBBLING_CONSTANT;
Just get creative and start messing around with the formula until you find a behaviour that "feels" right. But be sure to start simple first!
PS: I'm guessing you want your "bar" to only move on the x-axis, so.. are you making an arkanoid clone? :D

requestAnimationFrame calling at more than 60FPS

I'm making a simple game and I'm having a problem where after minimizing the window for a few seconds, upon return the game runs at twice the framerate, and even more after that, adding 60 each time. My game loop looks like this:
function disp(){
update();
draw();
requestAnimationFrame(disp);
}
requestAnimationFrame(disp);
With both update and draw not including requestAnimationFrame. I have tried this on both firefox and chrome with the same results. Any ideas why this is happening? I've used this same method tons of times and this is the first time this has ever happened.
EDIT: You can find a fiddle of it at http://jsfiddle.net/5ttGs/
It's really simple so far as this kinda paused my progress. Click it a couple times and enjoy 10000+FPS gameplay
Fixed the problem with the help of cocco. Basically the onclick event called the disp function every time the canvas was clicked. Because the disp function had a requestAnimationFrame frame in it, it called it twice as much every second, resulting in the influx of frames. In order to fix I simply got rid of the disp in
canvas.addEventListener
function mouse_up() {
var matches = 0;
var overlap = 69;
mouse.up = false;
console.log("clicked", mouse.x,mouse.y);
disp();
}
You can find the result here: http://jsfiddle.net/5ttGs/

How can I time the length of a drag/drop event on a HTML5 canvas? (KineticJS)

I am trying to create an application in which a user is required to drag a Kinetic.Image object for a requisite length of time (in this case 5 seconds); this is going to be used to ensure that they do not move on to the next stage without having performed the task in hand. I am using KineticJS for my application. Is this possible with some combination of the dragstart, dragmove and dragend event handlers?
I've now come up with a solution myself, so putting it here for anyone who may be interested. The simplest way to do this, I believe, is to just take a timestamp in the dragstart and do the same in the dragend and perform a diff. Increment a global counter variable with the difference, and then do a check in the dragend to see if that counter is over the required amount.
object1.on("dragstart", function() { lastTimestamp = Date.now(); });
object1.on("dragend", function() { timeTaken = Date.now() - lastTimestamp; });
var count = 0;
function Tick() {
count++;
window.setTimeout("Tick()", 1000);
}
myObject.on('dragstart', function(){
Tick();
});
myObject.on('dragend', function(){
return count;
}
I'm not sure if this is a good way to do it, but you can use either setTimeout or setInterval to increment a variable 'count' and then return that variable when dragging is finished.
If you want a native KineticJS implementation, you'll have to use Kinetic.Animation as it has a built in 'frame.time' which will count milliseconds for you, but that's a bit messy if you want just drag events.

Categories

Resources