Collision Detection with Javascript, Canvas, and Alpha Detection - javascript

I'm currently working on a basic javascript game that has two sprites that are not to be collided together. However, basic bounding box collision won't suffice as there are portions of the sprites that are transparent and wouldn't 'count' as colliding. I found a solution to the problem that I am having, but I can't get it to work. What I would like to do is calculate the transparent portions of the sprites and make sure that if the transparent portions overlap, that there is no collision detected. Here is what I found that solves the problem.
http://blog.weeblog.net/?p=40#comments
/**
* Requires the size of the collision rectangle [width, height]
* and the position within the respective source images [srcx, srcy]
*
* Returns true if two overlapping pixels have non-zero alpha channel
* values (i.e. there are two vissible overlapping pixels)
*/
function pixelCheck(spriteA, spriteB, srcxA, srcyA, srcxB, srcyB, width, height){
var dataA = spriteA.getImageData();
var dataB = spriteB.getImageData();
for(var x=0; x<width; x++){
for(var y=0; y<height; y++){
if( (dataA[srcxA+x][srcyA+y] > 0) && (dataB[srcxB+x][srcyB+y] > 0) ){
return true;
}
}
}
return false;
}
And for calculating the image data:
/**
* creating a temporary canvas to retrieve the alpha channel pixel
* information of the provided image
*/
function createImageData(image){
$('binaryCanvas').appendTo('body');
var canvas = document.getElementById('binaryCanvas');
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var imageData = [image.width];
for(var x=0; x<image.width; x++){
imageData[x] = [image.height];
for(var y=0; y<image.height; y++){
var idx = (x + y * image.width) * 4;
imageData[x][y] = canvasData.data[idx+3];
}
}
$("#binaryCanvas").remove();
return imageData;
}
The problem is that I don't know how to implement this solution, or if this is the best solution to my problem. Is this what I'm looking for? And if so, where do I put these methods? The thing that I'm most confused about is what I should be passing to spriteA and spriteB. I've tried passing Images and I've tried passing the imageData returned from the pixelCheck method, but receiving the same error: that the object or image has no method 'getImageData'. What am I doing wrong?

Two things are wrong with this.
You made a function:
function createImageData(image){...}
But what you are calling is:
spriteA.getImageData();
spriteB.getImageData();
The dot notates a property of an object. You were trying to call a function that was never part to the objects. There are some simple fixes.
add the createImageData() function to your constructor :
function Sprite(){
...
this.createImageData = function(image){...};
...
}
or :
Sprite.createImageData = function(image{...};
or just call it correctly:
createImageData(spriteA.image); // or whatever the image is
Second, your function calls for an image parameter, but you aren't supplying one. Simply remember to supply the image when you call it. You could also remove the parameter and get the image from within the function.
Sort of like this:
function Sprite(){
...
this.createImageData = function(){
var image = this.image;
// or just use this.image, or whatever it is
...
}
...
}
Hope this helped.

Related

How to see if PNG has a transparent background with Javascript [duplicate]

Is there a way to read transparent pixels from a picture using javascript?
I think, that it could be something similar to what PNG fixes does for IE (reading transparent pixels and applying some stuff, lol). But yes, for every browser..
Ah, would be awesome if it could be achieved without HTML5.
Well this question is actually answered by the dude from GoogleTechTalks in this video on javascript-based game engines.
http://www.youtube.com/watch?feature=player_detailpage&v=_RRnyChxijA#t=1610s
It should start at the point where it is explained.
Edit:
So I will summarize what is told in the video and provide a code-example.
It was a lot tougher than I had expected. The trick is to load your image onto a canvas and then check each pixel if it is transparent. The data is put into a two dimension array. Like alphaData[pixelRow][pixelCol]. A 0 is representing transparency while a 1 is not. When the alphaData array is completed it is put in global var a.
var a;
function alphaDataPNG(url, width, height) {
var start = false;
var context = null;
var c = document.createElement("canvas");
if(c.getContext) {
context = c.getContext("2d");
if(context.getImageData) {
start = true;
}
}
if(start) {
var alphaData = [];
var loadImage = new Image();
loadImage.style.position = "absolute";
loadImage.style.left = "-10000px";
document.body.appendChild(loadImage);
loadImage.onload = function() {
c.width = width;
c.height = height;
c.style.width = width + "px";
c.style.height = height + "px";
context.drawImage(this, 0, 0, width, height);
try {
try {
var imgDat = context.getImageData(0, 0, width, height);
} catch (e) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
var imgDat = context.getImageData(0, 0, width, height);
}
} catch (e) {
throw new Error("unable to access image data: " + e);
}
var imgData = imgDat.data;
for(var i = 0, n = imgData.length; i < n; i += 4) {
var row = Math.floor((i / 4) / width);
var col = (i/4) - (row * width);
if(!alphaData[row]) alphaData[row] = [];
alphaData[row][col] = imgData[i+3] == 0 ? 0 : 1;
}
a=alphaData;
};
loadImage.src = url;
} else {
return false;
}
}
I got errors when running local in Firefox and the try catch statement solved it. Oh I gotta eat...
Edit 2:
So I finished my dinner, I'd like to add some sources I used and wich can be helpful.
https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas
Info about the imageData object.
http://blog.nihilogic.dk/2008/05/compression-using-canvas-and-png.html
Even more info about the imageData object and it's use.
http://www.nihilogic.dk/labs/canvascompress/pngdata.js
A really helpful example of the use of imageData, the code I provided resembles this one for a big part.
http://www.youtube.com/watch?v=_RRnyChxijA
Infos on scripting game-engines in javascript, really really interesting.
http://blog.project-sierra.de/archives/1577
Infos about the use of enablePrivilege in firefox.
This is a bit tricky problem, since the only way to access files directly from Javascript is by using FileReader, which is a relatively new feature and not yet supported in most browsers.
However, you could get the desired result by using a canvas. If you have a canvas, you could assign it some distinctive color (such as neon green used in green screens). Then you could insert the image onto canvas and use the method mentioned here to get each individual pixel. Then you could check each pixel's color and see whether that point corresponds to your background color (ergo it's transparent) or does it have some other color (not transparent).
Kind of hackish, but don't think there's anything else you can do with pure JS.
It appears that GameJS can do this and much, much more. I am referencing this SO question for any/all of my knowledge, as I don't claim to actually have any about this topic.
Of course, this is HTML5, and uses the canvas element.

HTML5 : getImageData with onmousemove make slow my application in Firefox

I create a little html5 game with canvas.
In the canvas, there are many displayed sprites and one of them move automatically from left to right. The others are statics.
When I move the mouse onto the canvas, I draw all sprites in a temporary canvas and I use getImageData to find the sprite onto which the mouse is over.
But getImageData make slow anormally the moving sprite in Firefox.
So what is the solution to avoid this deceleration ?
Here my code :
function getSelectedObject(array_objects)
{
//Clear the temporary canvas :
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
/*Search the right sprite object :*/
for(var i = array_objects.length-1; i >= 0; i--)
{
array_objects[i].draw(tmpcx);
imageData = tmpcx.getImageData(mouse_x, mouse_y, 1, 1);
component = imageData.data;
if(component[3] > 0)
{
//Return the sprite object found :
return array_objects[i];
}
else
{
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
}
}
return false;
}
canvas.onmousemove = function(event)
{
selectedObject = getSelectedObject(array_objects);
}
Not sure how much of a performance gain you'd get with this - no need to clear the temp canvas between sprites .... the pixel is clear until a sprite is painted on it!
I've referenced a function called checkBoundingBoxisOver - not sure if you could write this function, but I can't right now - besides, I don't even know what your array_objects are!!!
I would think it were simple, just need the x, y, width, height of a sprite to do a preliminary check if the sprite could even possibly be under the mouse before doing the expensive draw
function getSelectedObject(array_objects) {
//Clear the temporary canvas :
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
var sprite;
/*Search the right sprite object :*/
for (var i = array_objects.length - 1; i >= 0; i--) {
sprite = array_objects[i];
if (checkBoundingBoxisOver(sprite, mouse_x, mouse_y)) {
sprite.draw(tmpcx);
imageData = tmpcx.getImageData(mouse_x, mouse_y, 1, 1);
component = imageData.data;
if (component[3] > 0) {
return sprite;
}
}
}
return false;
}
I ran into a similar issue reading pixels from a large bitmap every frame of the animation. In my case it is a black and white image showing where the world is water or land.
getImageData is extremely slow on Firefox even when reading just a single pixel.
My solution is to call getImageData only once and store the result in a imageData variable
var imageData = self.context.getImageData(0,0,image.width, image.height);
Then you can make repeated calls to the image data and pull out the part of the image you want. In my case I just need a single pixel or a single color which looks like this
var pixelRed = this.imageData.data[(y* imageWidth * 4) + (x * 4)] == 0;
x and y are self explanatory and since the pixels are 4 byte values (Red, Green, Blue, Alpha) I need to multiply my array index by 4. It proves to be very fast for me.
It be pretty easy to use this code to grab any portion out of the array directly as long as it is not too big.

Argument 1 of CanvasRenderingContext2D.drawImage could not be converted ( attempting draw image on canvas object )

I am trying to make a cache of objects that can be used with canvas.drawImage() to display images but only draw them once. I keep getting this error, I have tried some answers found online like: canvasObject.get(0) and canvasObject[0] and unwrap(canvasObject) before putting them in the draw context, but none of that works. I cant find anything on it. hopefully someone can help. here is my code:
var canvas = full canvas that cached drawings should draw to
var temp = {};
var h = heightOfGridSquare;
var w = widthOfGridSquare;
var canvasElementForCache = document.createElement('canvas');
canvasElementForCache.width = w * 2; // so that i can draw pictures larger then single grid square
canvasElementForCache.height = h * 2;
var cachedCanvas = canvasElementForCache.getContext(ctx);
// cache drawing in app object
var cacheDrawing = function ( name ){
app.cache[name] = objectRepo[name](cachedCanvas);
};
var objectRepo = {
someObjectName: function (canv) {
var m = temp.coordinates;
canv.fillStyle = "rgba(0,0,200,0.9)";
canv.fillRect(m.x + 25, m.y + 25,50, 50); // random filler (x and y are coordinates defined in the draw object funciton )
return canv;
},
};
var drawObejectAtCoordinates = function ( x, y ) {
var px = ( x - ( w / 2 ));
var py = ( y + ( h / 2 ));
if ( app.cache[name] === undefined ){
temp.coordinates = { x:px, y:py };
cacheDrawing(name);
}
// drawing on actual canvas
canvas.drawImage( app.cache[name], px, py );
};
var render = function () {
drawObejectAtCoordinates( coordinateX, coordinateY );
// this is just a place holder, my actual code is very long and has different rendering methods..
// just know that it is being rendered ( not super important though since the error occurs at drawImage not render )
window.requestAnimationFrame(render);
}
this mostly exact, i have changed small parts for brevity.. but there is nothing left out that would be related to the issue im having. if anyone can help I would appreciate it greatly!
the error message is:
TypeError: Argument 1 of CanvasRenderingContext2D.drawImage could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement.
and when I console log the contents of the cached canvas object it looks like this:
CanvasRenderingContext2D { canvas: <canvas>, globalAlpha: 1, globalCompositeOperation: "source-over", strokeStyle: "#41471d", fillStyle: "#ff8800", shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 0, shadowColor: "rgba(0, 0, 0, 0)", mozCurrentTransform: Array[6] }
heres a jsFiddle example: https://jsfiddle.net/1krgqeq7/3/
There are a number of issues here that make this code rather difficult to follow:
1) You are reusing the variable cachedDrawing in a confusing way (is it an element or a function?)
2) You use a variable named temp which is usually a code smell, especially when you don't use it close to its declaration (not present in the code sample). Its also not clear why your stashing coords in there.
3) You use a fair number of descriptive variable names but in addition to temp you also use the rather non-descript 'obj'
But the answer to your question (related to 1 above) is that you are passing a context, not the canvas it was gotten from. Don't reassign that var and pass it into the cache instead.
Problems like this will likely be easier to spot with more descriptive variable names like cachedCanvas being a canvas and cachedCtx being its context.

HTML5 Canvas image rotation with dynamic values and images

I have the following code -
for(var i = 0; i < treesLength; i++){
var tmpTree = trees[i];
tmpTreeX = 1+Math.random()*($("#gameBoard").width()-95);
tmpTreeY = 1+Math.random()*($("#gameBoard").height()-90);
var imgTreeFile = new Image();
imgTreeFile.onload = function() {
context.save();
context.translate(tmpTreeX,tmpTreeY);
context.rotate(47 * Math.PI / 180);
context.translate(-tmpTreeX,-tmpTreeY);
context.drawImage(imgTreeFile, tmpTreeX, tmpTreeY);
context.restore();
};
imgTreeFile.src = 'img/tree.png';
}
What I am trying to achieve is to add 'n' amount of images to the canvas (That happen to be trees) I want to rotate the images a random amount of radians. Currently just to get it working I have set this number rather than randomly generate the radians.
If I do no try to rotate the images, I successfully get all the images in the loop randomly placed throughout the canvas. When I try to rotate I just get all of the images in the same place with no rotation.
Can anyone point me in the right direction, my searching has only left me frustrated as I "appear" to be doing it correctly (Clearly I am not!)
In a quick test here there doesn't seem to be an issue: http://jsfiddle.net/ZZ7MQ/
But I do notice two weird things about your code:
.width() and not .width seems weird, but maybe thats a jQuery thing? it should be canvas.width in any case.
You are using a loop variable inside of code called asynchronously, which is 99% likely your problem. I highly recommend that you refactor your code so that all the images are loaded before the loop ever happens. If they are all the same image, that makes this very easy:
var imgTreeFile = new Image();
imgTreeFile.onload = function() {
for(var i = 0; i < treesLength; i++){
var tmpTree = trees[i];
tmpTreeX = 1+Math.random()*($("#gameBoard").width()-95);
tmpTreeY = 1+Math.random()*($("#gameBoard").height()-90);
context.save();
context.translate(tmpTreeX,tmpTreeY);
context.rotate(47 * Math.PI / 180);
context.translate(-tmpTreeX,-tmpTreeY);
context.drawImage(imgTreeFile, tmpTreeX, tmpTreeY);
context.restore();
}
};
imgTreeFile.src = 'img/tree.png';

Image appearing multiple times on canvas?

I'm drawing a simple dynamic canvas and I'm wondering how I can make the .png file in my drawImage method appear like 40 times at different places on my canvas at the same time?
Thanks beforehand! :)
Thank you all very much for your reply! This is as far as I've gotten now:
<script type="text/javascript">
var ctx;
var imgBg;
var imgDrops;
var x = 40;
var y = 0;
function setup() {
var canvas = document.getElementById('canvasRegn');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
setInterval('draw();', 36);
imgBg = new Image();
imgBg.src = 'dimma.jpg';
imgDrops = new Image();
imgDrops.src = 'drop.png';
}
}
function draw() {
drawBackground();
for(var i=0; i <= 40; i++) {
ctx.drawImage (imgDrops, x, y);
y += 3;
if(y > 450)
y = -20;
x=Math.random()*600;
}
}
function drawBackground(){
ctx.drawImage(imgBg, 0, 0);
}
</script>
My problem is now that the images are jumping all over the place... I want them "falling" down slowly from above and coming back around:(
Have a look at this fiddle http://jsfiddle.net/joevallender/D83uC/10/
I made it to explain some basics of canvas to a friend recently. Although I'm using shapes instead of .png files I think the loop you are looking for is the same.
The key bit of code being this loop below
setInterval(function(){
// clear stage
context.clearRect(0, 0, width, height);
for(var i = 0; i < balls.length; i++) {
balls[i].move(balls);
}
}, 1000/FPS)
FPS is a variable, and .move() is a function that calculated new co-ordinates for and then re-draws the ball object.
I think it might simply not clearing the 'stage' context.clearRect(0, 0, width, height);
EDIT Perhaps that example had too much going on in it to be useful.
Please see a much earlier version http://jsfiddle.net/joevallender/D83uC/2 that simple animates the ball. The main point remains though, you need to clear the canvas if you don't want the 'trails'
Think of canvas like windows paint, not flash. The things you have drawn aren't editable objects. You need to redraw the whole thing each time. (Unless you use a JS library that makes things seem more like flash - but I'm guessing you want to learn without helper libraries at first)
As I said, I was explaining canvas to someone recently and you can see the various stages between the two links I've sent you by changing the number on the end of the URL http://jsfiddle.net/joevallender/D83uC/3
http://jsfiddle.net/joevallender/D83uC/4
etc.
EDIT2
Or if I've misunderstood, post a jsfiddle and let us know what is wrong with it
This is what you need: http://edumax.org.ro/extra/new/imagetut/
You can get the code here: http://edumax.org.ro/extra/new/imagetut/script.js
The relevant part is this:
function draw_image(){
//draw one image code
}
window.onload = function () {
for (var i=0;i<10;i++){
for (var j=0;j<10;j++){
draw_image(10+i*40, 10+j*40, 40, 40);
}
}
}
This code only explains the concept, it will not work by itself, for a full version check the link above.

Categories