Multitouch Pinch, Pan, Zoom in HTML 5 Canvas - javascript

I have most of a module written to handle multitouch pinch, pan and zoom on an HTML 5 canvas element. I will share it below. I've been developing in JavaScript for some time now, and this one continues to boggle me. If anybody has any insights, I will post the final version up on stack for everybody to share once it is confirmed working on my iPad.
Here is what I am doing:
touchmove events trigger the changing of variables. I use these variables to change the way in which my image is painted onto the canvas. I have eight variables, each corresponding to the options that can be put into the drawImage() function. These eight variables get updated through functions that increment/decrement their values and keep them within a certain range. The variables are closure variables, so they are global throughout my module. To prevent over-processing, I make a call to this drawImage() function once every 40ms while the user has their finger pressed to the screen using a setInterval().
Here is the problem:
touchmove events seem to be causing a race condition where my variables get updated by many different instances of that same event. I can somewhat confirm this through my console output, that tracks one variable that is bounded to never reach below 20. When I swipe in one direction quickly, that variable dips down far below 20. Then when I release my finger, swipe slowly, it returns to 20. Another thing that points me in this direction, when I look at these variables while stepping through my program, they differ from what my console.log() pumps out.
Note: The code successfully draws the image the first time, but not anytime thereafter. A basic rendition of my code is below... The full version is on GitHub inside the Scripts folder. It is a Sencha Touch v1.1 app at heart
function PinchPanZoomFile(config)
{
/*
* Closure variable declaration here...
* Canvas Declaration here...
*/
function handleTouchStart(e) {
whatDown.oneDown = (e.originalEvent.targetTouches.length == 1) ? true : false;
whatDown.twoDown = (e.originalEvent.targetTouches.length >= 2) ? true : false;
drawInterval = setInterval(draw, 100);
}
function handleTouchEnd(e) {
whatDown.oneDown = (e.originalEvent.targetTouches.length == 1) ? true : false;
whatDown.twoDown = (e.originalEvent.targetTouches.length >= 2) ? true : false;
clearInterval(drawInterval);
}
function handleTouchMove(e) {
if(whatDown.twoDown) {
/*
* Do Panning & Zooming
*/
changeWindowXBy(deltaDistance); //deltaDistance
changeWindowYBy(deltaDistance); //deltaDistance
changeCanvasXBy(deltaX); //Pan
changeCanvasYBy(deltaY); //Pan
changeWindowDimsBy(deltaDistance*-1,deltaDistance*-1); //(deltaDistance)*-1 -- get smaller when zooming in.
changeCanvasWindowDimsBy(deltaDistance,deltaDistance); //deltaDistance -- get bigger when zooming in
} else if(whatDown.oneDown) {
/*
* Do Panning
*/
changeWindowXBy(0);
changeWindowYBy(0);
changeCanvasXBy(deltaX);
changeCanvasYBy(deltaY);
changeWindowDimsBy(0,0);
changeCanvasWindowDimsBy(0,0);
}
}
function draw() {
//Draw Image Off Screen
var offScreenCtx = offScreenCanvas[0].getContext('2d');
offScreenCtx.save();
offScreenCtx.clearRect(0, 0, canvasWidth, canvasHeight);
offScreenCtx.restore();
offScreenCtx.drawImage(base64Image,
parseInt(windowX),
parseInt(windowY),
parseInt(windowWidth),
parseInt(windowHeight),
parseInt(canvasX),
parseInt(canvasY),
parseInt(canvasWindowWidth),
parseInt(canvasWindowHeight)
);
//Draw Image On Screen
var offScreenImageData = offScreenCtx.getImageData(0, 0, canvasWidth, canvasHeight);
var onScreenCtx = canvas[0].getContext('2d');
onScreenCtx.putImageData(offScreenImageData, 0, 0);
}
}

I strongly recommend using Sencha Touch 2.0.1, as it supports a lot of the touch events you need. See http://dev.sencha.com/deploy/touch/examples/production/kitchensink/#demo/touchevents for examples.

Related

Dygraph graph gets distorted/partially shifted

I am regularly updating several Dygraphs graphs. After some period of time, normally a few minutes, some or all of them get corrupted as shown in the figure below. I haven't been able to tie this a particular event or browser. This happens even with a simple graph where I am just reloading the data stored in a CSV file. I call updateOptions({ file: URL }) on the graph object, where URL points to the CSV file, followed by calling resetZoom() on the graph object to update the axes. Googling hasn't revealed anyone suffering similar behaviour, so I'm lost as to what is causing this.
Update 1: It is linked to minimizing and maximizing the browser.
Update 2: The problem doesn't occur in Firefox. It does happen in Google Chrome and Internet Explorer, although IE has the additional problem of freezing after a while (a problem for another day).
Update 3: Minimum working examples added at http://jsfiddle.net/williamshipman/tvxekq56/ and http://jsfiddle.net/williamshipman/af66qstt/. Repeatedly minimize and maximize the browser window, after a while the distortion occurs. The first example uses AngularJS (like my own work), while the second demonstrates the same bug in pure JavaScript. You may have to minimize and maximize more than a dozen times to see the bug, it seems pretty random.
For me similar problem appears when I show and hide Y2 axis.
This one line helped me: ctx.clearRect(0, 0, this.width, this.height);
File: dygraph-canvas.js
var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
...
ctx = this.dygraph_.hidden_ctx_;
ctx.clearRect(0, 0, this.width, this.height); // <== clear whole canvas before cliping
ctx.beginPath();
ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
ctx.clip();
};
The root of the problem
Canvas context is not fully restored after all draw is done.
Solution 1. (workaround)
Injecting canvas_ctx_.restore() after draw is done and context.save() before. save() is needed because library is restoring the context before every draw(except the initial one).
let g = new Dygraph('graph', {
underlayCallback: (context) => {
context.save();
},
drawCallback: (dygraph) => {
dygraph.canvas_ctx_.restore();
},
});
Solution 2. (library fix)
Here is my commit you can apply to the lib's src/dygraph.js
https://github.com/pawelzwronek/dygraphs/commit/c66ca37b82f14e096652a338cae8abf568b9c764

Craftyjs Sprite Animation issue

I'm currently trying to make a game with crafty js and I'm stuck with the sprite Animation.
I don't really know what I'm doing wrong ..
Here is the working code :
http://aaahdontpaintmeinred.me.pn/
Here is how I load the sprite in my loading scene :
Crafty.scene('Loading', function(){
// Draw some text for the player to see in case the file
// takes a noticeable amount of time to load
Crafty.e('2D, DOM, Text')
.text('Loading...')
.attr({ x: 0, y: Game.height()/2 - 24, w: Game.width() });
// Load our sprite map image
Crafty.load(['assets/mansprite.gif'], function(){
// Once the image is loaded...
// Define the individual sprites in the image
// Each one (spr_tree, etc.) becomes a component
// These components' names are prefixed with "spr_"
// to remind us that they simply cause the entity
// to be drawn with a certain sprite
Crafty.sprite(133, 'assets/mansprite.gif', {
mansprite:[0, 0]
});
// Now that our sprites are ready to draw, start the game
Crafty.scene('LevelEditor');
})
})
And here is how I try to bind and Animate in my player component :
Crafty.c('PlayerCharacter', {
init: function() {
this.requires('Actor, Collision,FPS,mansprite,SpriteAnimation,WiredHitBox')
.attr({maxValues:1,boost:false,trailSpacing:100,currentFrame:0})
.collision();
this.animate('run',0, 0, 3);
this.animate('idle',3, 0, 1);
this.requires('FluidControls')
//this.rotation+=90;
.onHit("FinishLine",this.endLevel)
.onHit("DeathWall",this.omagaDie)
.onHit("Booster",this.booster)
.bind("EnterFrame",function(fps){
if(this.move.up)
{
this.animate('run', 4,-1);
var spacing = this.trailSpacing;
if( this.currentFrame%((60*spacing)/1000) == 0)
Crafty.e("montexte").spawn(this.x,this.y,this.boost,this.xspeed,this.yspeed,this.rotation%360,this.h,this.w);
}else
{
if(!this.move.down)
{
this.animate('idle', 4,1);
}
}
this.currentFrame++;
if(this.currentFrame >=60)
this.currentFrame=0
})
;
},
Hope someone could point out what is going wrong !
If you need more details or you have questions, don't hesistate !
Thanks
Ok, so I solved the problem by using the 0.5.3 version.
Still don't know what's going on. But will try to give an answer here.
Use the non minified version.
I was using Crafty v0.5.4 minified and sprite animation was not working, no errors in console log.
Then after several hours of struggling I tried with the non minified version and the sprite animation started to work properly.

Disable collision on the fly in Box2D: does not work when entity is already colliding

I'm writing a game with impactjs and i'm using Box2D as physics-engine (Box2DFlash to be specific). So there is a move off the player where he should not collide with the enemys. To achieve this Collision Filtering seems to be the best method.
So I set for the enemy fixture:
var shapeDef = new b2.PolygonDef();
shapeDef.filter.categoryBits = 0x0002;
And for the player:
var shapeDef = new b2.PolygonDef();
shapeDef.filter.categoryBits = 0x0004;
When I want to turn on/off the collision of the player with the enemys i call these functions:
ghost: function(){
this.setMaskBits( 0x0001 ); // only collide with boundary
},
deghost: function () {
this.setMaskBits( 0xFFFF ); // collide with everything
},
setMaskBits: function (bits) {
this.body.GetShapeList().m_filter.maskBits = bits;
}
And it is working... but only when they are not colliding with each other. The same problem occurs when i'm facing a wall and I set the categoryBits to 0x0000.
UPDATE
Ok, I fixed it with a dirty hack:
The Problem was, that contacts were still existing, so I'd have to delete them somehow. I tried to delete them at first manually but failed.
In the Manual of Box2D is written:
Contacts are destroyed with the AABBs cease to overlap.
So I wrote a function that moves the player about 6px (the smallest amount that seem to work) in the directory he is facing (if he's not colliding with a wall at that moment), this would overlap the entities and solve the problem.
Here is the function in question:
changePosition: function (x,y) {
this.facingWall = false;
this.collision(); // will set facingWall to true if he's facing a wall.
if(!this.facingWall) {
this.body.m_xf.position.x = x;
this.body.m_xf.position.y = y;
}
}
You can call Refilter() on the fixtures for which the collision settings were changed.
Ok, I fixed it with a dirty hack:
The Problem was, that contacts were still existing, so I'd have to delete them somehow. I tried to delete them at first manually but failed.
In the Manual of Box2D is written:
Contacts are destroyed with the AABBs cease to overlap.
So I wrote a function that moves the player about 6px (the smallest amount that seem to work) in the directory he is facing (if he's not colliding with a wall at that moment), this would overlap the entities and solve the problem.
Here is the function in question:
changePosition: function (x,y) {
this.facingWall = false;
this.collision(); // will set facingWall to true if he's facing a wall.
if(!this.facingWall) {
this.body.m_xf.position.x = x;
this.body.m_xf.position.y = y;
}
}

KeyDown/KeyUp 70-120ms delay. How to reduce?

We're developing arcade (a lot of action and speed) browser 2d-game using canvas.
Sometimes our testing players report us that there is a delay: player still moving 5-10 pixels away after keyup.
I've digged this issue, you can see yourself delay http://jsfiddle.net/C4ev3/7/ (try keydown/up any key as fast as you can). My results is from 70 to 120ms. And i think that's a lot. (FYI, our network latency is 10-20ms).
Any ideas how to reduce this delay?
upd i've noticed that on good hardware this delay is under 30-40ms. But i'm testing on core2duo, winxp, chrome 19 - it's not a P4 with IE6 :)
Hi one thing you could do is instead of using an anonymous function try using defined functions,
http://jsfiddle.net/C4ev3/10/ - for me this reported at 50-100 MS
However i would not recommend jQuery for Canvas Applications it's very big for the very little you using, you should try using native Javascript
http://jsfiddle.net/C4ev3/11/ - for me this reported 30-70 MS
Javascript Threading
One thing i noticed in the comments Javascript is not Multi-Threaded Well Urm-Arr,
it sort of is setInterval is Async not Sync, however affecting the window is a single thread E.G if you have a Class that has some number is it using a setInteval will use another thread and not have a problem altering the math however in the Task then requires a Draw on the page it will enter the bottom of the JS handle Que,
Certain parts of Javascript are on a different thread how ever any thing changing the page has to run on the Main Thread same as any Windows application if your thread want to change the Form your have to invoke the main thread to do it for you
however it is not multi-threaded like any thing else you cant just handle or abort at a given Wim like windows,
Other ASync Tasks include AJAX has the option to be both Async and Sync
Updated to show my comment about FPS limiting:
Please bear with me. This is linking to a project that is allready built to show the example:
so my Game is Completely OOP
var elem = document.getElementById('myCanvas');
var context = elem.getContext("2d");
context.fillStyle = '#888';
context.lineWidth = 4;
// Draw some rectangles.
context.fillRect(0, 0, 800, 600);
context.fillStyle = '#f00';
var ball = new Ball();
var leftPadel = new Padel(10, 60, 40, 120);
var rightPadel = new Padel(750, 520, 40, 120);
pong = new Pong();
pong.draw();
setTimeout("ball.move()", pong.redrawTime());
Inside my pong class is where all the main workings of the game goes but here are the FPS bit you need to see
this.fps = 30;
this.maxFPS = 60;
this.redrawTime = function(){
return (1000 / this.fps)
}
this.lastDraw = (new Date)*1 - 1;
Then as you can see my Interval is on ball.move this calls the main pong class again on redraw at the End of the redraw i have the FPS checking and limiting code
this.fps = ((now=new Date) - this.lastDraw);
if(this.fps > this.maxFPS){
this.fps = this.maxFPS;
}
this.lastDraw = (new Date)*1 - 1;
if(this.reporting = true){
console.clear();
console.log("FPS: "+this.fps.toFixed(1))
}
setTimeout("ball.move()", pong.redrawTime());
This then forces you to get the Best Possible FPS without queuing the Main Thread
Try this:
e.stopPropagation()
Stops the bubbling of an event to parent elements, preventing any
parent handlers from being notified of the event.
e.preventDefault()
Prevents the browser from executing the default action. Use the method
isDefaultPrevented to know whether this method was ever called (on
that event object).
My min. results in Google chrome: 7ms

Javascript events responsiveness over a <canvas> element

I am writing an html5 app which manipulates video on a canvas.
I am also showing a custom (self-drawn) mouse cursor over it.
To update my app I am calling the setInterval function to draw stuff to the canvas.
As I am manipulating video I need: Very high refresh rates + Lot's of processor time to draw. (~50ms per frame).
My draw function causes my on-mouse function to starve. (this is acceptable by me).
But... After it is finished starving it responds to old events. It can take up to 3 frames for the mouse to catch so that I can render it in the right position. Meaning you can see the cursor "crawling" after you've stopped moving the mouse.
Is there a way to give the onmousemove events a higher priority then my setInterval(drawFunction)?
When in the draw function, Can I "ask" if there are pending mouse events, and revoke my current call to draw?
Is there some-other hack I can use? (I can draw to back-buffer in a webWorker, but as I understand from reading up on html5 this is only thread abstraction [threads are not concurrent] )
You can't prioritize event handling, at least not directly.
What you might consider would be having your own timer-driven code check for pending mouse events. The mouse event handler would just put a work request into a queue. Your video manipulating could could just check that queue and handle operations as it sees fit. Thus, the real mouse work would also be done in the timer code.
edit Here's what I'm thinking. You'd still have handlers for your mouse events:
var eventQueue = [];
canvasElement.onmousemove = function(evt) {
evt = evt || event;
eventQueue.push({ type: evt.type, x: evt.clientX, y: evt.clientY, when: new Date() });
};
Thus the mouse handlers would not do any actual work. They just record the details in a list of events and then return.
The main, timer-driven loop can then just check the queue of events and decide what to do. For example if it sees a whole string of "mousemove" events, it could compute the net change in position over all of them and update the cursor position just once.
You certainly can use a Web Worker to manipulate your pixels in the background. Web Workers do run concurrently. It doesn't seem to be part of the spec, but every implementation runs workers in a separate process. So your main script will be responsible for updating your custom cursor, but you'd pass the canvas ImageData off to the background worker for the video processing. Note that you can't send an actual Canvas or Context to the worker, as workers have no DOM access.
There is no way to give events higher priority, they seem to be serviced on a first come, first serve basic. Assuming you are using setTimeout, one thing that might help though is to break up your tasks into smaller restartable chunks, e.g. if you are processing an image like this:
for(y=0; y<height; y++) {
// Deal with rows of pixels
}
// Show image
You could do this instead:
var INTERVAL = height/4;
for(y = old_y; y<height && y<old_y+INTERVAL ; y++) {
// Deal with row of pixels
}
if (y == height) {
// show image
}
old_y = (y == height) ? 0 : y;
Then the other events will have 4x (or whatever, depending on the INTERVAL) greater chance of being dealt with.

Categories