Pixel Collision Detection Not Working - javascript

Here is my game plnkr.
(Edit: another plnkr with one static monster instead of multiple dynamic ones)
Enter or the button will restart the game.
Can anyone tell why the collision detection algorithm taken from here is not working? It seems to detect a hit not accurately (too widely). The demo on their site works great but I'm not sure what I'm doing wrong.
Most relevant piece of code (inside update function):
// Are they touching?
if (heroImage.width) {
var heroImageData = ctx.getImageData(heroImage.x, heroImage.y, heroImage.width, heroImage.height);
var monsterImageData;
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
monster.x += monster.directionVector.x;
monster.y += monster.directionVector.y;
monsterImageData = ctx.getImageData(monster.monsterImage.x, monster.monsterImage.y, monster.monsterImage.width, monster.monsterImage.height);
if (isPixelCollision(heroImageData, hero.x, hero.y, monsterImageData, monster.x, monster.y)) {
stop();
}
}
}

As #GameAlchemist pointed out you're taking ImageData for monster and hero from the canvas background, which has already been painted with the background image. Thus will always have alpha value 255 (Opaque).
Which is being checked in the collision function
if (
( pixels [((pixelX - x ) + (pixelY - y ) * w ) * 4 + 3 /*RGBA, alpha # 4*/] !== 0/*alpha zero expected*/ ) &&
( pixels2[((pixelX - x2) + (pixelY - y2) * w2) * 4 + 3 /*RGBA, alpha # 4*/] !== 0/*alpha zero expected*/ )
) {
return true;
}
Instead both the ImageData should be generated by drawing these images to a canvas with nothing painted. Even after doing that collision algorithm doesn't seem to work too well.
I have created two variables monsterImageData and heroImageData to hold the imageData these variable are loaded only once.
There's a new canvas in HTML file id=testCanvas. This is used to get image data values for monster and heroes.
Here is the plunker link for modified code.

Your hero image is 71x68px and has a lot of transparent space around the outside. I'm guessing if you crop this to just fit the image it will reduce the space between collisions.

You are taking the imageData on the game's drawing context, so since you have a background, there's no transparent pixel at all, so your pixel collision detection returns always true - > you are just doing a bounding box check, in fact.
The idea of the algorithm is to compare two static imageData that only need to be computed once (getImageData is a costly operation).
A few advices :
• load your images before launching the game.
• redim (crop) your image, it has a lot of void, as #Quantumplate noticed.
• compute only once the imageData of your sprites on the context before the launch of the game. Do not forget to clearRect() the canvas before the drawImage + getImageData. This is the way to solve your bug.
• get rid of the
if (xDiff < 4 && yDiff < 4) {
and the corresponding else. This 'optimisation' is pointless. The point of using pixel detection is to be precise. Redim (crop) your image is more important to win a lot of time (but do you need to ... ?? )
• Rq : How poorly written is the pixel detection algorithm !!! 1) To round a number, it's using !! 5 different methods (round, <<0, ~~, 0 |, ? : ) !!! 2) It loops on X first when CPU cache prefers on Y first, and many other things... But now if that works...

Here's an alternate (more efficient) pixel perfect collision test...
Preparation: For each image you want to test for collisions
As mentioned, trim any excess transparent pixels off the edges of your image,
Resize a canvas to the image size, (you can reuse 1 canvas for multiple images)
Draw the image on the canvas,
Get all the pixel info for the canvas: context.getImageData,
Make an array containing only alpha information: false if transparent, otherwise true.
To do a pixel-perfect collision test
Do a quick test to see if the image rects are colliding. If not, you're done.
// r1 & r2 are rect objects {x:,y:,w:.h:}
function rectsColliding(r1,r2){
return(!(
r1.x > r2.x+r2.w ||
r1.x+r1.w < r2.x ||
r1.y > r2.y+r2.h ||
r1.y+r1.h < r2.y
));
}
Calculate the intersecting rect of the 2 images
// r1 & r2 are rect objects {x:,y:,w:.h:}
function intersectingRect(r1,r2){
var x=Math.max(r1.x,r2.x);
var y=Math.max(r1.y,r2.y);
var xx=Math.min(r1.x+r1.w,r2.x+r2.w);
var yy=Math.min(r1.y+r1.h,r2.y+r2.h);
return({x:x,y:y,w:xx-x,h:yy-y});
}
Compare the intersecting pixels in both alpha arrays. If both arrays have a non-transparent pixel at the same location then there is a collision. Be sure to normalize against the origin (x=0,y=0) by offsetting your comparisons.
// warning untested code -- might need tweaking
var i=intersectingRect(r1,r2);
var offX=Math.min(r1.x,r2.x);
var offY=Math.min(r1.y,r2.y);
for(var x=i.x-offX; x<=(i.x-offX)+i.w; x++{
for(var y=i.y-offY; y<=(i.y-offY)+i.h; y++{
if(
// x must be valid for both arrays
x<alphaArray1[y].length && x<alphaArray2[y].length &&
// y must be valid for both arrays
y<alphaArray1.length && y<alphaArray2.length &&
// collision is true if both arrays have common non-transparent alpha
alphaArray1[x,y] && alphaArray2[x,y]
){
return(true);
}
}}
return(false);

Related

Three JS check if position is behind object

I have a 3D character along with several predefined positions on the floor. Is there a way to check if a position is behind the 3D character and not in front or sideways?
I’ve included a rough sketch of what I’m trying to achieve (sorry for the poor drawing). Essentially I would like to return all of the positions of the red circles within the red lines and exclude all other circles outside of these two lines.
Is this possible? If so, is there any suggestion on how I can achieve this? I’m sorry but I don’t actually know which functions to use from Three JS for something like this or if it is possible.
Thank you!
Yes, it's possible.
You start out by first checking if a point/circle is behind the player. You do this by getting the dot product between the direction player is facing (a unit vector) and the direction vector to the circle (normalize it so that it's also a unit vector). If the values dotProduct <= 0 then the circle is behind your player.
(unit vector means that your vector has a magnitude of one. Which is a fancy way of saying your x/y/z will never go beyond 1)
Code example
// Let's assume that the following code is in some sort of loop, yes
// Get the direction vector to the circle
const directionVect = circle.position.clone().sub(player.position).normalize();
// If your player is a camera you can get the direction like so
const playerFacing = player.getWorldDirection(new THREE.Vector3());
// Orientation
if (playerFacing.dot(directionVect) <= 0) {
// Circle is behind the player
// ...to be continued...
} else {
return;
}
Now that you know which circles are behind your player, you can get the circles within the cone. This is done by getting the angle between the player's position and the circle's position. Then check that the angle fits some criteria (e.g. the angle can't be more than 45deg from back of player).
// ...
// Orientation
if (playerFacing.dot(directionVect) <= 0) {
// Circle is behind the player
const angle = player.position.angleTo(circle.position);
if (angle < Math.PI * 0.25) {
// Do something with circle
}
} else {
return;
}

threejs - normalize UV coordinates to 0 and 1 issue

i have a model with UV coordinates less than 0 and greater than 1. im trying to normalize these coordinates into the range 0 and 1 but with bad results.
at moment im using this function to convert any UV coordinate into my target range
private convertNumberToUV(numb:number) {
let converted = 0;
if(numb < 0 || numb > 1) {
if(numb < 0){ numb = -numb; }
converted = numb - Math.floor(numb);
}
else if(numb > 1){
converted = numb - Math.floor(numb)
}
else {
converted = numb;
}
return converted;
}
the result is this
but the expected result is this
where im wrong?
#pailhead, my goal is to port the first texture onto the atlas with normalized UV points. My question is if could work in this way. I normalize the first UV coordinates to 0 and 1 and to avoid the "stretch" effect i also repeat the texture onto the second atlas for a number of times equal to what was done with the "repeat" function
Imagine you have a wall with a window and it's planar mapped. It's scaled such that one corner is at UV(0,0) and another is at UV(3,1). Let's just focus on the U dimension, and observe where the window is. One edge is at U(1.5) and the other can be at U(2).
|0|---|1.5|-|2|--|3| <- a slice through the wall, U axis
If we map a brick texture onto this, and set myTexture.wrapS = THREE.RepeatWrapping the brick will be uniformly distributed across the wall and repeat 3 times in that process.
If you modulate these uvs the way you do, you would end up with something like this
|0|-[0.5]-|0||0|
Which would produce senseless but predictable results. The left half of the texture would be stretched all the way to the window. Then under and above the window it would display the same half but in reverse, the remaining bit of the wall would show just horizontal lines and no brick texture since it's stretched to infinity.

How to consider polygon's corners

I have a canvas code which render polygon shapes. I can drag a figure and move it to another place. I have move event functions which change the color of the nearest polygons and the draggable polygon. The problem is that I don't know how to make them change their color only when I touching a corner of a figure with my draggable figure. I did what I did considering only width and height of a figure so when I drag a figure near the others some of them change their colors when I even don't touch them. So I should somehow consider their corners ... It's just too complicated to me.
There are relatively much code so I did codepen where you can see what I'm talking about.
PS: only javascript's API allowed.
There are many different ways to do hit detection, depending on what you want to detect, how efficiently you want to detect it etc. The link below gives some nice solutions to finding points inside arbitrary polygons by looking at the relationship between the point in question and any y axis intercepts.
http://alienryderflex.com/polygon/
By comparing the given point to the y intercepts, the counts on either side can be used to determine if there is a hit.
This is solved using a function like the one below, providing an array of x and y coordinates of the polygons vertices, as well as the x and y of the test point.
function pointInPolygon(xs, ys, x, y) {
var j = xs.length - 1
var oddNodes = false
for (var i=0; i<xs.length; i++) {
if ((ys[i] < y && ys[j] >= y ||
ys[j] < y && ys[i] >= y)
&& (xs[i] <= x || xs[j] <= x)) {
oddNodes ^= (xs[i] + (y - ys[i]) / (ys[j] - ys[i]) * (xs[j] - xs[i]) < x)
}
j=i
}
return oddNodes
}
I have created a fiddle that shows this in action (clicking on the poly will change its colour).
https://jsfiddle.net/95k3t26q/19/

Finding x/y coordinates that fall within filled canvas arcs

Here is the fiddle
The arcs I've drawn around the outside of the circle - I'd like to know how to find all of the x/y coordinates that they cover so that I don't have to re-draw them every time using isPointInPath() to determine whether the mouse cursor is over them or not. I was thinking about writing all of the x/y coordinates to an array that I could check against the mouse position x/y coordinates and if I find a match then I change the cursor. The problem is, I don't know the code to derive all of the x/y values.
You don't actually have to redraw your arcs to use .isPointInPath()-- just omit any calls to .fill() or .stroke() and you'll have a path which you can use to test whether it contains a point.
I would suggest having one function which outlines the arc path (.beginPath(), path commands, .closePath()) and then two functions which call it-- one which calls the arc path function, then sets the fill style and fills the path to draw it, and another which calls the arc path function and then just tests whether a point is in the path.
This is the method you should use:
http://en.wikipedia.org/wiki/Point_in_polygon
The way it works is actually extremely simple: if the amount of times a ray that ends at any point passes through the polygon perimeter is even, the respective point HAS to be outside of the polygon. If it's odd, it's within the polygon.
Here's a function found by Pimvdb:
function isPointInPoly(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
}
I wouldn't call what you have 'arcs' (they're more like bowed rectangles), but here's a broad sketch of how to write a function to determine if a point is within such a thing:
Calculate the center of the circle from the end points and radius.
If the point is closer to the center than the close arc (distance-to-center-squared is greater than close-radius-squared) then return false.
If the point is farther from the center than the far arc then return false.
Calculate the start and end angles for the endpoints of your rectangles with respect to the center of the circle. (Hint: Math.atan2)
Calculate the angle for the point with respect to the center of the circle. If it is not between the angles for the end points, return false.
Beware endpoints that cross the wrapping values for Math.atan2.
Return true if other tests passed.
You can't calculate "all" points in this region, as there an an infinite number of them. Creating a lookup table of all integer pixel coordinates in your image is possible, but wasteful.
What I would do, instead of what you are proposing, is use a retained-mode graphics system (hint: SVG) and let it track mouse events for me using its far-more-efficient code.
Here's an easier method:
I've altered the drawtabs function to also check if mouse is within a tab:
http://jsfiddle.net/kkDqz/4/
WARNING
This method is easy, but requires you to redraw EVERYTHING on mousemove.

Repeat shapes with boundary detection - html5, canvas, javascript

I am wanting to create a simple abstract pattern using the html5 canvas tag and javascript. I have worked out the guts of what I want it to do using some variables, functions and objects, but with the boundary detection that I have employed I am wanting each particular shape to go back to its starting position when it goes out of the screen (and thus loop the animation).
So with that being my question, here is my code. Also any other structure tips are appreciated as I am new to OO in Javascript.
See my progress here: http://helloauan.com/apps/test/
Cheers!
I'm not really sure if what you mean exactly is, once the big white diagnal lines are all the way off the top right corner of page, that's when you want them to start back at the bottom left ? right?
What you need to do is check if the line is beyond the width and height of the canvas, and in your case, the window itself since the canvas fills the browser window. So you need to do a series of conditionals. You check if the line x + line width is > canvas width and line.y + line height is > canvas height. If both are true then set the x and y of the line to - what it is at that time. So something like:
if( line.x + line.width > canvas.width && line.y + line.height < 0) {
line.x = -0;
line.y = canvasHeight + line.height;
}
This is how I recycle circles that come in from the right side of the screen and once they exit the left side they start over on the right.
if( d.x + d.radius < 0 ) {
d.radius = 5+(Math.random()*10);
d.x = cwidth + d.radius;
d.y = Math.floor(Math.random()*cheight);
d.vX = -5-(Math.random()*5);
}
The first thing is just psuedo, you should take a look at a thing I made to use as a starting point for things like this. The structure of your code could use some more organization, canvas gets real complex real quick.
Using the arrow keys, move the square off any one of the 4 sides and see it come in on opposite side.
http://anti-code.com/games/envy/envy.html
Fork if you want: https://github.com/jaredwilli/envy

Categories

Resources