I am programming a game in HTML5 canvas. If a line touches any point of its own or opponents line, that line stops moving. The Function I wrote follows below.
function hasPoints(x, y) {
for (var i = 0; i < points.length; i++) {
if (Math.abs(points[i][0] - x) < 2 && Math.abs(points[i][1] - y) < 2) {
return true;
break;
}
}
return false;
}
//check if this point crosses other players' points
if (hasPoints(p1.x,p1.y) || canPlay==false) {
clearLoop();
}else{
context.fillStyle = "green";
context.fillRect(p1.x,p1.y, 5,5);
addPoints(p1.x,p1.y);
sendMyPoints();
loopTimer = setTimeout('drawLine()', 50);
}
It works most of the time. But in some special case(As you can see in the picture below) it simply fails to return false.
Could anyone help me to improve this function, so that it works flawless?
Do not use dots, use vectors. An array of vectors.
To find out if a point is in contact with a line (vector)
Just mount a triangle with two points and the point vector analysis, if the area of the triangle is near zero (sets a limit), then this contact.
y1 x1 1
y2 x2 1 = (y1*x2*1)+(x1*1*y3)+(1*y2*x3)-(x1*y2*1)-(y1*1*x3)-(1*x2*y3)
y3 x3 1
the result should be close to zero (0.012121 ...)
This a way to discovery if a line is align in direction of a target
http://en.wikipedia.org/wiki/Determinant
Use math's expression to vectors then you can check if the vectors cross thenselves
Related
I am trying to recreate the game duet but I can't figure out how to check collision on an that has been translated and rotated.
To rotate my players blue and red ball I translated the object to the center of the screen and then rotated it based on that point.
here is my code (I know its not the best code, this is just a prototype)
how can I check for collision with the white obstacles and the players circles?
What exactly happens to the X and Y coordinates when you translate/rotate the shape?
If you don't mind your polygon hit testing being based on a single point then it is fairly trivial. You can implement the point in polygon algorithm which basically consists of counting intersections between a horizontal line and the edges of the polygon.
const pts = []
function setup() {
createCanvas(windowWidth, windowHeight);
pts.push(
createVector(56, 89), createVector(108, 37), createVector(117, 118)
)
}
function draw() {
background('#fff29c');
if (pts.length > 1) {
stroke('#301551');
fill(pointInPoly(pts, createVector(mouseX, mouseY)) ? 'white' : 'grey');
beginShape();
pts.forEach(pt => vertex(pt.x, pt.y));
endShape(CLOSE);
} else if (pts.length == 1) {
stroke('#301551');
beginShape();
pts.forEach(pt => vertex(pt.x, pt.y));
vertex(mouseX, mouseY);
endShape(CLOSE);
}
stroke('white');
line(0, mouseY, mouseX, mouseY);
noStroke();
intersections.sort((v1, v2) => v1.x < v2.x ? -1 : (v1.x > v2.x ? 1 : 0))
for (let i = 0; i < intersections.length; i++) {
fill(i % 2 == 0 ? 'green': 'red');
circle(intersections[i].x, intersections[i].y, 5);
}
fill('#ed8a0a');
noStroke();
circle(mouseX, mouseY, 10);
}
let intersections = [];
function pointInPoly(verts, pt) {
intersections = [];
let c = false;
// for each edge of the polygon
for (let i = 0, j = verts.length - 1; i < verts.length; j = i++) {
// Compute the slope of the edge
let slope = (verts[j].y - verts[i].y) / (verts[j].x - verts[i].x);
// If the mouse is positioned within the vertical bounds of the edge
if (((verts[i].y > pt.y) != (verts[j].y > pt.y)) &&
// And it is far enough to the right that a horizontal line from the
// left edge of the screen to the mouse would cross the edge
(pt.x > (pt.y - verts[i].y) / slope + verts[i].x)) {
// To help visualize the algorithm.
intersections.push({ x: (pt.y - verts[i].y) / slope + verts[i].x, y: mouseY });
// Flip the flag
c = !c;
}
}
return c;
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.min.js"></script>
If you want your hit testing to be based on a circle you could also perform collision detection between the circle and each polygon edge segment in addition to the point-in-polygon algorithm.
Here is a description of how to do this from a tutorial I wrote on detecting calculating circle collisions:
Given a line defined by two points on that line (p1 and p2), let the boundaryVector be a vector from the first point to the second. We will assume that the vector produced by rotating boundaryVector clockwise 90 degrees orients it towards the inside of the bounded area. In other words the points must be specified in a clockwise order around the boundary.
Given boundaryVector we can find the distance of the center of a circular body, give by pos, to the nearest point on the line, by taking the distance from p1 to pos, and multiplying it by the sine of the angle between boundaryVector and the vector from p1 to pos.
If the distance from the center of the circular body to the nearest point on the line is less than or equal to the radius of the circle, then a collision has occurred.
This assumes that the boundary is infinite, but you should be able to adopt this technique to properly handle finite line segments.
function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?
The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.
I am trying to draw 5 random circles to a canvas that I hope to at some point make in to a centipede game. I have my logic set up so that a for loop loops through 5 circles. Then there is a do loop that makes certain that the circles X and Y points are in the canvas because at some point they will all have to be in an array so that I can determine if they have been hit by the player.
If the coordinates are no good then I simple loop again in the do-loop to get another set of X and Y coordinates. If a pair of cords is OK then I enter the if-statement and set isDrawable equal to TRUE.
Question: What simple logic do I need to change to make this always draw 5 visible circles on the canvas?
Code:
function buildCircle() {
for(var i=0; i<5; i++) {
do {
isDrawable = false;
radX = Math.random()*theCanvas.width;
radY = Math.random()*theCanvas.height;
radR = Math.random()*10+3;
console.log(i);
console.log(radX);
console.log(radY);
if ((radX>5 && radX<435) && (radY>0 && radY<520)) {
isDrawable = true;
canvasContext.fillStyle='#123321;' //getRandomColor();
drawCircle(radX,radY,radR,canvasContext);
}
console.log(isDrawable);
}
while (isDrawable = false);
}
}
I'm not sure I see the problem with the original code, but if your problem is the loop and do/whilte, this should work and is probably more easily read:
var circlesLeft = 5;
while (circlesLeft) {
//stuff
if (draw_circle) {
//draw circle
--circlesLeft;
}
}
The only other potential problem I see with your code is that it doesn't handle if two circles chose the same (or very close to the same) coordinates, they would overlap and while you drew two, only one (the largest) would be visible.
If I got it right, you want to make sure each circle is completely inside the canvas. A circle in (x, y) with radius r is inside the canvas if, and only if:
x-r>=0
x+r<=width
y-r>=0
y+r<=height
That is:
r <= x <= width-r
r <= y <= height-r
So, a simpler logic would be to apply the limits right to your random generator. First you pick a random radius r=3+Math.random()*10, then pick:
x in [r, width-r]
y in [r, height-r]
That gives you: x = r+(width-2*r)*Math.random() and y = r+(height-2*r)*Math.random()
If you want to avoid a circle on top of another, then you'll need a similar logic of trial and error: for every new circle, pick random (x, y) and r as above, then check if it doesn't collide with any previous circle. If it does, try again.
The logic for checking collision is simply checking if the distance is greater than the radius combined:
function checkCollision(x1, y1, r1, x2, y2, r2) {
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) > (r1+r2)*(r1+r2) // true if don't collide
}
The condition of the do while loop is wrong. You should set "isDrawable == false" instead of "isDrawable = false".
I'm trying to detect polygons on a canvas. I'm using code from this stack overflow question https://stackoverflow.com/a/15308571/3885989
function Vec2(x, y) {
return [x, y]
}
Vec2.nsub = function (v1, v2) {
return Vec2(v1[0] - v2[0], v1[1] - v2[1])
}
// aka the "scalar cross product"
Vec2.perpdot = function (v1, v2) {
return v1[0] * v2[1] - v1[1] * v2[0]
}
// Determine if a point is inside a polygon.
//
// point - A Vec2 (2-element Array).
// polyVerts - Array of Vec2's (2-element Arrays). The vertices that make
// up the polygon, in clockwise order around the polygon.
//
function coordsAreInside(point, polyVerts) {
var i, len, v1, v2, edge, x
// First translate the polygon so that `point` is the origin. Then, for each
// edge, get the angle between two vectors: 1) the edge vector and 2) the
// vector of the first vertex of the edge. If all of the angles are the same
// sign (which is negative since they will be counter-clockwise) then the
// point is inside the polygon; otherwise, the point is outside.
for (i = 0, len = polyVerts.length; i < len; i++) {
v1 = Vec2.nsub(polyVerts[i], point)
v2 = Vec2.nsub(polyVerts[i + 1 > len - 1 ? 0 : i + 1], point)
edge = Vec2.nsub(v1, v2)
// Note that we could also do this by using the normal + dot product
x = Vec2.perpdot(edge, v1)
// If the point lies directly on an edge then count it as in the polygon
if (x < 0) {
return false
}
}
return true
}
It's working OK, but with more complex shapes, it doesn't work that well... Here's a link to the isolated code with an example of one shape that works and one that doesn't:
http://jsfiddle.net/snqF7/
ah! like GameAlchemist said, the issue is that this algorithm is meant for convex polygons,
here's a new && improved algorithm which works with non-convex ( or complex ) polygons ( not perfect ) which I translated from these C code examples ( http://alienryderflex.com/polygon/ )
function pointInPolygon(point, polyVerts) {
var j = polyVerts.length - 1,
oddNodes = false,
polyY = [], polyX = [],
x = point[0],
y = point[1];
for (var s = 0; s < polyVerts.length; s++) {
polyX.push(polyVerts[s][0]);
polyY.push(polyVerts[s][1]);
};
for (var i = 0; i < polyVerts.length; i++) {
if ((polyY[i]< y && polyY[j]>=y
|| polyY[j]< y && polyY[i]>=y)
&& (polyX[i]<=x || polyX[j]<=x)) {
oddNodes^=(polyX[i]+(y-polyY[i])/(polyY[j]-polyY[i])*(polyX[j]-polyX[i])<x);
}
j=i;
}
return oddNodes;
}
and here's a working fiddle: http://jsfiddle.net/snqF7/3/
The algorithm you are using require the polygon to be convex.
Convex mean, in a nutshell, that you can draw a line in between any two points in the polygon, all the line will be inside the polygon.
- More or less a potato or a square :) -.
Solution is either to split your polygons in convex parts or to go for a more complex algorithm that handles non-convex polygons.
To handle non convex polygons, idea is to take a reference point you know is (or isn't) in the polygon, then draw a line between your tested point and this reference point, then count how many times and in which direction it intersect with the polygon segments. Count +1 or -1 on each cross, and the point is inside if final sum is null.
EDIT: I updated the program with the answer and it works great!
I am making a program (feel free to try it out) that lets users draw polygons which it then triangulates. They can click to add vertices and hit enter to triangulate. Anyways, the algorithm works fine as long as I tell it if the points were drawn in a clockwise or counterclockwise fashion (right now I have it set only to work with clockwise polygons). I have been trying to figure this out for days, but have no idea how to determine whether the points are clockwise or counterclockwise. Try drawing shapes with the program mentioned earlier to get a better idea, you can experience what I am talking about better than I can try to explain it.
Here is how the points are defined:
function Point(x, y) {
this.x = x;
this.y = y;
}
var vertices = [];
// Called on click
function addPoint(mouseX, mouseY) {
vertices.push(new Point(mouseX, mouseY));
}
Here is an image of a clockwise polygon:
Here is an image of a counterclockwise polygon:
If you could help me figure out how to determine the "clockwise-ness" of the points, I would be very grateful!
Compute the polygon area using the shoelace formula, but without the absolute value sign. If the result is positive, the points are ordered counterclockwise, and if negative - clockwise.
function polygonArea() {
var area = 0;
for (var i = 0; i < vertices.length; i++) {
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
}
return area / 2;
}
var clockwise = polygonArea() > 0;
In case someone is using three.js the ShapeUtils comes with an inbuilt isClockWise method which internally uses the area method to determine the sign of the calculated area.
isClockWise: function ( pts ) {
return ShapeUtils.area( pts ) < 0;
}
The ShapeUtils.isClockWise Method can be found here.
area: function ( contour ) {
var n = contour.length;
var a = 0.0;
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
}
return a * 0.5;
},
The ShapeUtils.area Method can be found here.
A general idea would be to take a look at the convex hull of your polygone and guess the orientation from there. However, I think that you do not need to build the whole hull to find the orientation, but just one segment belonging to it.
So:
Find two points of your polygones so that all the other points are on one side of this line.
If all the points are on the left (just check one of the points), it's counterclockwise. If they are on the right, it's clockwise.
Example:
On the top figure: 4-5 let the figure on the right, 5-11 let the figure on the right, ...
On the bottom figure: 6-7 let the figure on the left, 7-14 let the figure on the left, ...
Warning: While "walking" on your polygon, do not restart the numeration, otherwise it will be wrong. On the top figure, 4-(n-1) let the figure on the left!
Your intuitive definition of clockwisedness is not well defined. For example, If I draw a horseshoe:
/---a-b--\
/ _d_c_ \
/ / \ \
| | | |
| | | |
\ \ / /
\ \ / /
-- --
If 0 = a < b < b < d and I look at a and b I would conclude from your description that the shape has been drawn clockwise, but if 0 = c < d < a < b I would conclude that the shape has been drawn anticlockwise. Since both of these scenarios involve the same direction in which the points were drawn, just from different starting points, I can only conclude that your definition is lacking.
The horseshoe I drew isn't the best; the idea is that it is almost a circle with just a small hole at the bottom, to allow the other side to be drawn in the opposite direction.
If you are interested in defining things more strictly, then I suggest something along the following lines:
Considering any finite simple polygon as separating the plane into two distinct areas (one finite and one infinite), we can always consider the finite area to be the interior of the polygon. In such a scenario we define a vertex ordering to be clockwise iff the order of the points runs with the exterior along its right-hand side. This is called curve orientation.
Once you have this more solid definition, implementation can be as simple as counting the winding number. Take the midpoint of any ordered pair, say 0 and 1, take a line segment to the right of the ordered pair (at any angle, say perpendicular), and count how many intersections it has with other line segments: The curve is clockwise iff the number is odd.
This is simple to implement, linear in time O(n), and adds constant space O(1).
This a function function that specialized for OpenLayers. As You Can See The Condition Of Clockwise Polygon Is area<0 This Reference Confirm It.
function IsClockwise(feature)
{
if(feature.geometry==null)return -1;
var vertices=feature.geometry.getVertices();
var area=0;
for (var i = 0; i < (vertices.length); i++)
{
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
// console.log(area);
}
return (area < 0);
}