Calculating collisions with Radians incrementing - trigonometry

While programming a game with the Tululoo Game Maker API, I am having problems with calculating collisions accurately as the x and y are being calculated from the radians.
The calculations for x and y below are producing a large number number of decimal points which I am finding difficult to check for collisions.
image_angle is the final angle of the pistol in degrees that the bullet uses initially.
In Setup:
x = instance_list(pistol)[0].x;
y = instance_list(pistol)[0].y;
startAng = instance_list(pistol)[0].image_angle;
this.travelX = cos(degtorad(instance_list(pistol)[0].image_angle)) * 5;
this.travelY = sin(degtorad(instance_list(pistol)[0].image_angle)) * 5;
In Update Frame:
x+=this.travelX;
y+=this.travelY;
Calculating the Angle that the ball will bounce off a wall at, messy I know:
for(var i = 0; i < instance_number(bounce_barrier);i++){
if(place_meeting(x,y,instance_list(bounce_barrier)[i])){
if(Math.round(x*10)/10 > instance_list(bounce_barrier)[i].x ){
newAng = 180 - startAng;
this.travelX = +cos(degtorad(newAng)) * 15;
this.travelY = +sin(degtorad(newAng)) * 15;
startAng = newAng;
}
else if(y > instance_list(bounce_barrier)[i].y - 5 && y <= instance_list(bounce_barrier)[i].y + 10){
newAng = 180 - startAng;
this.travelX = -cos(degtorad(newAng)) * 15;
this.travelY = -sin(degtorad(newAng)) * 15;
startAng = newAng;
}
else if(y <= instance_list(bounce_barrier)[i].y - 5 + bounce_spr.height && y >= instance_list(bounce_barrier)[i].y + bounce_spr.height - 10){
newAng = 180 - startAng;
this.travelX = -cos(degtorad(newAng)) * 15;
this.travelY = -sin(degtorad(newAng)) * 15;
startAng = newAng;
}
else if(x < instance_list(bounce_barrier)[i].x){
newAng = 180 - startAng;
this.travelX = +cos(degtorad(newAng)) * 15;
this.travelY = +sin(degtorad(newAng)) * 15;
startAng = newAng;
}
else{}
}
}
place_meeting is Part of the Tululoo Game API:
function __place_meeting__(nx, ny, what, many) {
this.other = null;
var i, l,
// sprite, scale:
ts = this.sprite_index,
tsx, tsy, tfx, tfy, tst,
// circle:
tcx, tcy, tcr,
// bbox:
tbl, tbr, tbt, tbb,
// instances, multiple, output, types:
tz, tm, ct, ch, ra,
// other:
o, ox, oy, os, ost, osx, osy, ofx, ofy, ofr;
if (ts == null) return false;
tfx = ts.xoffset;
tfy = ts.yoffset;
tsx = this.image_xscale;
tsy = this.image_yscale;
tst = ts.collision_shape;
// bbox:
if (tst == 2) {
tbl = nx + tsx * (ts.collision_left - tfx);
tbr = nx + tsx * (ts.collision_right - tfx);
tbt = ny + tsy * (ts.collision_top - tfy);
tbb = ny + tsy * (ts.collision_bottom - tfy);
}
// circle:
if (tst == 3) {
tcr = ts.collision_radius * (tsx > tsy ? tsx : tsy);
tcx = nx + tsx * (ts.width / 2 - tfx);
tcy = ny + tsy * (ts.height / 2 - tfy);
}
//
tz = (what.__instance ? [what] : instance_list(what));
tm = many ? true : false;
if (tm) ra = [];
l = tz.length;
for (i = 0; i < l; i++) {
o = tz[i];
if (!o.collision_checking) continue;
os = o.sprite_index;
if (os == null) continue;
ox = o.x; osx = o.image_xscale;
oy = o.y; osy = o.image_yscale;
ost = os.collision_shape;
ct = (tst << 4) | ost;
ch = false;
switch(ct) {
case 0x22:
if (osx == 1 && osy == 1) {
ofx = os.xoffset; ofy = os.yoffset;
if (!collide_bbox_bbox(tbl, tbt, tbr, tbb,
ox + os.collision_left - ofx, oy + os.collision_top - ofy,
ox + os.collision_right - ofx, oy + os.collision_bottom - ofy)) break;
} else if (!collide_bbox_sbox(tbl, tbt, tbr, tbb, ox, oy, osx, osy, os)) break;
ch = true;
break;
case 0x23:
ofr = os.collision_radius * (osx > osy ? osx : osy);
ofx = ox + osx * (os.width / 2 - os.xoffset);
ofy = oy + osy * (os.height / 2 - os.yoffset);
if (!collide_bbox_circle(tbl, tbt, tbr, tbb, ofx, ofy, ofr)) break;
ch = true;
break;
case 0x32:
if (osx == 1 && osy == 1) {
ofx = os.xoffset; ofy = os.yoffset;
if (!collide_bbox_circle(
ox + os.collision_left - ofx, oy + os.collision_top - ofy,
ox + os.collision_right - ofx, oy + os.collision_bottom - ofy,
tcx, tcy, tcr)) break;
} else if (!collide_sbox_circle(ox, oy, osx, osy, os, tcx, tcy, tcr)) break;
ch = true;
break;
case 0x33:
ofr = os.collision_radius * (osx > osy ? osx : osy);
ofx = ox + osx * (os.width / 2 - os.xoffset);
ofy = oy + osy * (os.height / 2 - os.yoffset);
if (!collide_circle_circle(tcx, tcy, tcr, ofx, ofy, ofr)) break;
ch = true;
break;
} if (!ch) continue;
this.other = o;
o.other = this;
if (!tm) return (o);
ra.push(o);
} return ra;
}
I have managed to make a bullet bounce off a wall at an angle proportional to where it has been shot from, but the collision detection is very bad as it bounces some bullets but not all, some just go straight through the wall.
Just added the Line Collision Detection, ive made the bullets lines so I can see the
this.oldTravelX,this.oldTravelY to this.travelX,this.travelY. It has definitely improved but some of the bullets are still going through.
Some bullets seem to stick inside the bouncing block or slide down the left side of the bouncing block then decide whether to go left or right.
Updated Code:
pntdis = point_distance(this.oldTravelX, this.oldTravelY, this.travelX, this.travelY);
noPoints = pntdis/0.01;
for(var i=0; i < pntdis; i+=noPoints)
{
pointsArrX[i] = this.travelX - (this.oldTravelX * i);
pointsArrY[i] = this.travelY - (this.oldTravelY * i);
}
for(var i=0;i<pointsArrX.length;i++){
if(place_meeting(Math.round(pointsArrX[i]),Math.round(pointsArrY[i]),bounce_barrier) || place_meeting(x,y,bounce_barrier))
{
newAng = 180 - startAng;
this.travelX = +cos(degtorad(newAng)) * 15;
this.travelY = +sin(degtorad(newAng)) * 15;
startAng = newAng;
}
}
Thanks in advance.

It looks like the place_meeting function is supposed to determine whether there's a collision between the bullet and the barrier. However, this function only takes the new position of the bullet. Given the bullet moves 5 units per update, it's possible that the bullet could move from one side of the barrier to the other in a single frame. e.g.:
|
. |
\| barrier
\
|\_______
\
.
place_meeting should take both the old and new positions of the bullet so that it can determine whether any intermediate position collided with the barrier (e.g. by doing line-line intersection testing with the path of the bullet and the edges of the barrier; it should probably also return the collision point).
EDIT
Your updated code looks like it's intended to check for collision of a number of points along the path of the bullet. There are faster ways to do this, but your method may be fast enough, and is easy to understand. There appears to be a bug in how you calculate the points to check though.
Stepping through the code manually on paper:
pntdis = 5 // assume point_distance returns 5
noPoints = 5 / 0.01 = 500
i = 0
pointsArrX[0] = this.travelX
pointsArrY[0] = this.travelY
i = i + noPoints = 500
500 < 5: false, for loop ends
Other things to consider:
You might want a break statement after you've detected a collision so you don't keep testing further points.
You could combine the two for loops into one because you're not reusing the values in the array. Just calculate your test point and then test it immediately.
Try using console.log to output debugging information as your program runs so you can get a better idea of what's happening.
Use var before your variables (such as pntdis and noPoints) to avoid accidentally creating global variables.

Related

How can I understand the following SVG code?

I have code from a web site, and it looks like it should be simple, but too simple for SVG. How can I determine if this is truly SVG, and what it does? I am especially interested in what looks like nested & and dots[.], then split, map.
Snippet:
// the shape of the dragon, converted from a SVG image
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
Full code:
//7 Dragons
//Rauri
// full source for entry into js1k dragons: http://js1k.com/2014-dragons/demo/1837
// thanks to simon for grunt help and sean for inspiration help
// js1k shim
var a = document.getElementsByTagName('canvas')[0];
var b = document.body;
var d = function(e){ return function(){ e.parentNode.removeChild(e); }; }(a);
// unprefix some popular vendor prefixed things (but stick to their original name)
var AudioContext =
window.AudioContext ||
window.webkitAudioContext;
var requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(f){ setTimeout(f, 1000/30); };
// stretch canvas to screen size (once, wont onresize!)
a.style.width = (a.width = innerWidth - 0) + 'px';
a.style.height = (a.height = innerHeight - 0) + 'px';
var c = a.getContext('2d');
// end shim
var sw = a.width,
sh = a.height,
M = Math,
Mc = M.cos,
Ms = M.sin,
ran = M.random,
pfloat = 0,
pi = M.PI,
dragons = [],
shape = [],
loop = function() {
a.width = sw; // clear screen
for ( j = 0; j < 7; j++) {
if ( !dragons[j] ) dragons[j] = dragon(j); // create dragons initially
dragons[j]();
}
pfloat++;
requestAnimationFrame(loop);
},
dragon = function(index) {
var scale = 0.1 + index * index / 49,
gx = ran() * sw / scale,
gy = sh / scale,
lim = 300, // this gets inlined, no good!
speed = 3 + ran() * 5,
direction = pi, //0, //ran() * pi * 2, //ran(0,TAU),
direction1 = direction,
spine = [];
return function() {
// check if dragon flies off screen
if (gx < -lim || gx > sw / scale + lim || gy < -lim || gy > sh / scale + lim) {
// flip them around
var dx = sw / scale / 2 - gx,
dy = sh / scale / 2 - gy;
direction = direction1 = M.atan(dx/dy) + (dy < 0 ? pi : 0);
} else {
direction1 += ran() * .1 - .05;
direction -= (direction - direction1) * .1;
}
// move the dragon forwards
gx += Ms(direction) * speed;
gy += Mc(direction) * speed;
// calculate a spine - a chain of points
// the first point in the array follows a floating position: gx,gy
// the rest of the chain of points following each other in turn
for (i=0; i < 70; i++) {
if (i) {
if (!pfloat) spine[i] = {x: gx, y: gy}
var p = spine[i - 1],
dx = spine[i].x - p.x,
dy = spine[i].y - p.y,
d = M.sqrt(dx * dx + dy * dy),
perpendicular = M.atan(dy/dx) + pi / 2 + (dx < 0 ? pi : 0);
// make each point chase the previous, but never get too close
if (d > 4) {
var mod = .5;
} else if (d > 2){
mod = (d - 2) / 4;
} else {
mod = 0;
}
spine[i].x -= dx * mod;
spine[i].y -= dy * mod;
// perpendicular is used to map the coordinates on to the spine
spine[i].px = Mc(perpendicular);
spine[i].py = Ms(perpendicular);
if (i == 20) { // average point in the middle of the wings so the wings remain symmetrical
var wingPerpendicular = perpendicular;
}
} else {
// i is 0 - first point in spine
spine[i] = {x: gx, y: gy, px: 0, py: 0};
}
}
// map the dragon to the spine
// the x co-ordinates of each point of the dragon shape are honoured
// the y co-ordinates of each point of the dragon are mapped to the spine
c.moveTo(spine[0].x,spine[0].y)
for (i=0; i < 154; i+=2) { // shape.length * 2 - it's symmetrical, so draw up one side and back down the other
if (i < 77 ) { // shape.length
// draw the one half from nose to tail
var index = i; // even index is x, odd (index + 1) is y of each coordinate
var L = 1;
} else {
// draw the other half from tail back to nose
index = 152 - i;
L = -1;
}
var x = shape[index];
var spineNode = spine[shape[index+1]]; // get the equivalent spine position from the dragon shape
if (index >= 56) { // draw tail
var wobbleIndex = 56 - index; // table wobbles more towards the end
var wobble = Ms(wobbleIndex / 3 + pfloat * 0.1) * wobbleIndex * L;
x = 20 - index / 4 + wobble;
// override the node for the correct tail position
spineNode = spine[ index * 2 - 83 ];
} else if (index > 13) { // draw "flappy wings"
// 4 is hinge point
x = 4 + (x-4) * (Ms(( -x / 2 + pfloat) / 25 * speed / 4) + 2) * 2; // feed x into sin to make wings "bend"
// override the perpindicular lines for the wings
spineNode.px = Mc(wingPerpendicular);
spineNode.py = Ms(wingPerpendicular);
}
c.lineTo(
(spineNode.x + x * L * spineNode.px) * scale,
(spineNode.y + x * L * spineNode.py) * scale
);
}
c.fill();
}
}
// the shape of the dragon, converted from a SVG image
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
loop();
While the context this is used in is <canvas>, the origin may well be a SVG <polyline>.
In a first step, the letters are mapped to numbers. A bit of obscuration, but nothing too serious: get the number representing the letter and write it to an array.
const shape = [];
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
results in an array
[1,0,8,8,6,8,6,10,4,8,4,12,6,14,9,15,13,14,16,12,20,5,19,2,23,4,27,8,32,15,37,33,33,28,31,26,28,25,27,27,24,24,21,23,19,23,18,25,15,23,12,22,8,24,6,27]
Now just write this array to a points attribute of a polyline, joining the numbers with a space character:
const outline = document.querySelector('#outline');
const shape = [];
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
outline.setAttribute('points', shape.join(' '))
#outline {
stroke: black;
stroke-width: 0.5;
fill:none;
}
<svg viewBox="0 0 77 77" width="300" height="300">
<polyline id="outline" />
</svg>
and you get the basic outline of (half) a dragon. The rest is repetition and transformation to make things a bit more complex.

Problems drawing an SVG arc path in a PDF using itextsharp

I'm trying to draw an SVG path in a PDF using itextsharp v5.
The approach I am following is roughly this:
Reading the SVG path from the SVG file (Svg.SvgPath)
Getting the list of segments from the path ({Svg.Pathing.SvgPathSegmentList})
Creating an iTextSharp PdfAnnotation and associate a PdfAppearance to it
Drawing each segment in the SvgPathSegmentList using the corresponding PdfContentByte method ( for SvgLineSegment I use PdfContentByte.LineTo, for SvgCubicCurveSegment I use PdfContentByte.CurveTo )
For most of the SvgPathSegments types, there is a clear mapping between values in the SvgPathSegments and the arguments in the PdfContentByte method. A few examples:
SvgMoveToSegment has the attribute End which is the target point (X, Y) and the PdfContentByte.MoveTo takes two parameters: X, Y
SvgLineSegment, very similar to the Move. It has the Target End and the PdfContentByte.LineTo takes two parameters X and Y and draws a line from the current position to the target point.
app.MoveTo(segment.Start.X, segment.Start.Y);
SvgCubicCurveSegment has all you need to create a Bezier curve (The Start point, the End point, and the first and second control point). With this I use PdfContentByte.CurveTo and get a curve in the PDF that looks exactly as it looks in the SVG editor.
var cubicCurve = (Svg.Pathing.SvgCubicCurveSegment)segment;
app.CurveTo(
cubicCurve.FirstControlPoint.X, cubicCurve.FirstControlPoint.Y,
cubicCurve.SecondControlPoint.X, cubicCurve.SecondControlPoint.Y,
cubicCurve.End.X, cubicCurve.End.Y);
The problem I have is with the ARC ("A" command in the SVG, SvgArcSegment)
The SvgArcSegment has the following values:
Angle
Start (X, Y)
End (X, Y)
RadiusX
RadiusY
Start
Sweep
On the other hand, PdfContentByte.Arc method expect:
X1, X2, Y1, Y2
StartAngle,
Extent
As per the itextsharp documentation, Arc draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2 starting (counter-clockwise) at StartAngle degrees and covering extent degrees. I.e. startAng=0 and extent=180 yield an openside-down semi-circle inscribed in the rectangle.
My question is: How to "map" the values in the SvgArcSegment created from the SVG A command into the arguments that PdfContentByte.Arc method expects.
I know that the Start and End values are indeed the origin and target of the curve I want, but no clue what RadiusX and RadiusY mean.
As #RobertLongson pointed in his comment, what I needed was to convert from Center to Endpoint Parametrization.
I'm posting my own C# implementation of the algorithm documented in the SVG documentation, just in case someone else needs it.
public static SvgCenterParameters EndPointToCenterParametrization(Svg.Pathing.SvgArcSegment arc)
{
//// Conversion from endpoint to center parameterization as in SVG Implementation Notes:
//// https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter
var sinA = Math.Sin(arc.Angle);
var cosA = Math.Cos(arc.Angle);
//// Large arc flag
var fA = arc.Size == Svg.Pathing.SvgArcSize.Large ? 1 : 0;
//// Sweep flag
var fS = arc.Sweep == Svg.Pathing.SvgArcSweep.Positive ? 1 : 0;
var radiusX = arc.RadiusX;
var radiusY = arc.RadiusY;
var x1 = arc.Start.X;
var y1 = arc.Start.Y;
var x2 = arc.End.X;
var y2 = arc.End.Y;
/*
*
* Step 1: Compute (x1′, y1′)
*
*/
//// Median between Start and End
var midPointX = (x1 - x2) / 2;
var midPointY = (y1 - y2) / 2;
var x1p = (cosA * midPointX) + (sinA * midPointY);
var y1p = (cosA * midPointY) - (sinA * midPointX);
/*
*
* Step 2: Compute (cx′, cy′)
*
*/
var rxry_2 = Math.Pow(radiusX, 2) * Math.Pow(radiusY, 2);
var rxy1p_2 = Math.Pow(radiusX, 2) * Math.Pow(y1p, 2);
var ryx1p_2 = Math.Pow(radiusY, 2) * Math.Pow(x1p, 2);
var sqrt = Math.Sqrt(Math.Abs(rxry_2 - rxy1p_2 - ryx1p_2) / (rxy1p_2 + ryx1p_2));
if (fA == fS)
{
sqrt = -sqrt;
}
var cXP = sqrt * (radiusX * y1p / radiusY);
var cYP = sqrt * -(radiusY * x1p / radiusX);
/*
*
* Step 3: Compute (cx, cy) from (cx′, cy′)
*
*/
var cX = (cosA * cXP) - (sinA * cYP) + ((x1 + x2) / 2);
var cY = (sinA * cXP) + (cosA * cYP) + ((y1 + y2) / 2);
/*
*
* Step 4: Compute θ1 and Δθ
*
*/
var x1pcxp_rx = (float)(x1p - cXP) / radiusX;
var y1pcyp_ry = (float)(y1p - cYP) / radiusY;
Vector2 vector1 = new Vector2(1f, 0f);
Vector2 vector2 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var angle = Math.Acos(((vector1.x * vector2.x) + (vector1.y * vector2.y)) / (Math.Sqrt((vector1.x * vector1.x) + (vector1.y * vector1.y)) * Math.Sqrt((vector2.x * vector2.x) + (vector2.y * vector2.y)))) * (180 / Math.PI);
if (((vector1.x * vector2.y) - (vector1.y * vector2.x)) < 0)
{
angle = angle * -1;
}
var vector3 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var vector4 = new Vector2((float)(-x1p - cXP) / radiusX, (float)(-y1p - cYP) / radiusY);
var extent = (Math.Acos(((vector3.x * vector4.x) + (vector3.y * vector4.y)) / Math.Sqrt((vector3.x * vector3.x) + (vector3.y * vector3.y)) * Math.Sqrt((vector4.x * vector4.x) + (vector4.y * vector4.y))) * (180 / Math.PI)) % 360;
if (((vector3.x * vector4.y) - (vector3.y * vector4.x)) < 0)
{
extent = extent * -1;
}
if (fS == 1 && extent < 0)
{
extent = extent + 360;
}
if (fS == 0 && extent > 0)
{
extent = extent - 360;
}
var rectLL_X = cX - radiusX;
var rectLL_Y = cY - radiusY;
var rectUR_X = cX + radiusX;
var rectUR_Y = cY + radiusY;
return new SvgCenterParameters
{
LlX = (float)rectLL_X,
LlY = (float)rectLL_Y,
UrX = (float)rectUR_X,
UrY = (float)rectUR_Y,
Angle = (float)angle,
Extent = (float)extent
};
}

find whether given (x,y) coordinate exists in SVG Image?

I wanted to find whether a given point exists in image area. Image width and height is 40. The four corners are (10,10),(50,10),(10,50),(50,50). Is there a way to find a given (x,y) exists or not in the area?
to check whether point lies inside the rect or not, i uses line equation and compare the point with all four lines.
The key to this approach is the sequence of points.
check the below code:-
function isPointInside(fourPointsArray, pointToCheck) {
var counter = 0;
var ele = pointToCheck;
var A, B, C, D, p1, p2, indx;
for (var k = 0; k < fourPointsArray.length; k++) {
p1 = fourPointsArray[k];
indx = k + 1;
if (indx >= fourPointsArray.length)
indx = 0;
p2 = fourPointsArray[indx];
A = -(p2.y - p1.y);
B = p2.x - p1.x;
C = -(A * p1.x + B * p1.y);
D = A * ele.x + B * ele.y + C;
if (D >= 0) {
counter++;
}
}
if (counter >= fourPointsArray.length) {
return true;
}
return false;
}
var arraypoints=[{x:10,y:10},{x:50,y:10},{x:50,y:50},{x:10,y:50}];
var pointtocheck={
x:5,
y:10
}
var flag=isPointInside(arraypoints,pointtocheck);
console.log(flag);

How do you identify natural and forced neighbors in Jump Point Search?

I am trying to implement Jump Point Search, an optimization on A* that reduces the number of nodes in the open list by na average of 20x, as well as memory consumption. The algorithm was implemented according to the original research paper and some online tutorials, but the program enters an infinite recursion loop when searching for natural and forced neighbors, which causes a stack overflow. Pathfinding A* is already working and I am just adding the JPS functions to it.
Here is the code for those functions:
void Movement::GetNaturalNeighbors(std::vector<Node>& output, Node& currentNode)
{
int nextX = currentNode.pos.x + currentNode.direction.x;
int nextY = currentNode.pos.y + currentNode.direction.y;
// Straight line pruning excludes all neighbors except the one directly in front of currentNode
if(!g_terrain.IsWall(nextX, nextY))
{
Map[nextX][nextY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[nextX][nextY].direction.x = Map[nextX][nextY].parent->direction.x;
Map[nextX][nextY].direction.y = Map[nextX][nextY].parent->direction.y;
Map[nextX][nextY].heurCost = GetHeuristicCost(nextX, nextY);
// Movement cost for diagonals is square root of 2
Map[nextX][nextY].movCost = currentNode.movCost + 1.0f;
Map[nextX][nextY].pos.x = nextX;
Map[nextX][nextY].pos.y = nextY;
if ((Map[nextX][nextY].movCost + Map[nextX][nextY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[nextX][nextY]);
}
}
int dirX = currentNode.direction.x;
int dirY = currentNode.direction.y;
// If we are moving diagonally, add the adjacent nodes in the same direction
if(dirX!= 0 && dirY!=0)
{
if((dirY == +1) &&(!g_terrain.IsWall(currentNode.pos.x+ neighborX[TOP], currentNode.pos.y+ neighborY[TOP])))
{
int naturalNeighborX = currentNode.pos.x+ neighborX[TOP];
int naturalNeighborY = currentNode.pos.y+ neighborY[TOP];
Map[naturalNeighborX][naturalNeighborY].direction.x = neighborX[TOP];
Map[naturalNeighborX][naturalNeighborY].direction.y = neighborY[TOP];
Map[naturalNeighborX][naturalNeighborY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[naturalNeighborX][naturalNeighborY].heurCost = GetHeuristicCost(nextX, nextY);
// Movement cost for diagonals is square root of 2
Map[naturalNeighborX][naturalNeighborY].movCost = currentNode.movCost + 1.41421f;
Map[naturalNeighborX][naturalNeighborY].pos.x = naturalNeighborX;
Map[naturalNeighborX][naturalNeighborY].pos.y = naturalNeighborY;
if ((Map[naturalNeighborX][naturalNeighborY].movCost + Map[naturalNeighborX][naturalNeighborY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[naturalNeighborX][naturalNeighborY]);
}
}
else if(!g_terrain.IsWall(currentNode.pos.x+ neighborX[BOTTOM], currentNode.pos.y+ neighborY[BOTTOM]))
{
int naturalNeighborX = currentNode.pos.x+ neighborX[BOTTOM];
int naturalNeighborY = currentNode.pos.y+ neighborY[BOTTOM];
Map[naturalNeighborX][naturalNeighborY].direction.x = neighborX[BOTTOM];
Map[naturalNeighborX][naturalNeighborY].direction.y = neighborY[BOTTOM];
Map[naturalNeighborX][naturalNeighborY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[naturalNeighborX][naturalNeighborY].heurCost = GetHeuristicCost(nextX, nextY);
// Movement cost for diagonals is square root of 2m
Map[naturalNeighborX][naturalNeighborY].movCost = currentNode.movCost + 1.41421f;
Map[naturalNeighborX][naturalNeighborY].pos.x = nextX;
Map[naturalNeighborX][naturalNeighborY].pos.y = nextY;
if ((Map[naturalNeighborX][naturalNeighborY].movCost + Map[naturalNeighborX][naturalNeighborY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[naturalNeighborX][naturalNeighborY]);
}
}
if((dirX == +1) && (!g_terrain.IsWall(currentNode.pos.x+ neighborX[RIGHT], currentNode.pos.y+ neighborY[RIGHT])))
{
int naturalNeighborX = currentNode.pos.x+ neighborX[RIGHT];
int naturalNeighborY = currentNode.pos.y+ neighborY[RIGHT];
Map[naturalNeighborX][naturalNeighborY].direction.x = neighborX[RIGHT];
Map[naturalNeighborX][naturalNeighborY].direction.y = neighborY[RIGHT];
Map[naturalNeighborX][naturalNeighborY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[naturalNeighborX][naturalNeighborY].heurCost = GetHeuristicCost(nextX, nextY);
// Movement cost for diagonals is square root of 2
Map[naturalNeighborX][naturalNeighborY].movCost = currentNode.movCost + 1.41421f;
Map[naturalNeighborX][naturalNeighborY].pos.x = naturalNeighborX;
Map[naturalNeighborX][naturalNeighborY].pos.y = naturalNeighborY;
if ((Map[naturalNeighborX][naturalNeighborY].movCost + Map[naturalNeighborX][naturalNeighborY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[naturalNeighborX][naturalNeighborY]);
}
}
else if(!g_terrain.IsWall(currentNode.pos.x+ neighborX[LEFT], currentNode.pos.y+ neighborY[LEFT]))
{
int naturalNeighborX = currentNode.pos.x+ neighborX[LEFT];
int naturalNeighborY = currentNode.pos.y+ neighborY[LEFT];
Map[naturalNeighborX][naturalNeighborX].direction.x = neighborX[LEFT];
Map[naturalNeighborX][naturalNeighborX].direction.y = neighborX[LEFT];
Map[naturalNeighborX][naturalNeighborX].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[naturalNeighborX][naturalNeighborX].heurCost = GetHeuristicCost(nextX, nextY);
// Movement cost for diagonals is square root of 2
Map[naturalNeighborX][naturalNeighborY].movCost = currentNode.movCost + 1.41421f;
Map[naturalNeighborX][naturalNeighborY].pos.x = nextX;
Map[naturalNeighborX][naturalNeighborY].pos.y = nextY;
if ((Map[naturalNeighborX][naturalNeighborY].movCost + Map[naturalNeighborX][naturalNeighborY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[naturalNeighborX][naturalNeighborY]);
}
}
}
}
void Movement::GetForcedNeighbors(std::vector<Node>& output, Node& currentNode)
{
int dir_x = currentNode.direction.x;
int dir_y = currentNode.direction.y;
int nextNodeX = currentNode.pos.x + dir_x;
int nextNodeY = currentNode.pos.y + dir_y;
// Forced neighbors only exist for diagonal moves
if((dir_x != 0) && (dir_y != 0))
{
// Check for diagonal forced neighbors
if(dir_x == +1)
{
// Does the top right diagonal have any forced neighbors ?
if(g_terrain.IsWall(nextNodeX + neighborX[TOP_RIGHT], nextNodeY + neighborY[TOP_RIGHT]) && !g_terrain.IsWall(nextNodeX + neighborX[TOP], nextNodeY + neighborY[TOP]))
{
Map[nextNodeX][nextNodeY].direction.x = neighborX[TOP];
Map[nextNodeX][nextNodeY].direction.y = neighborY[TOP];
Map[nextNodeX][nextNodeY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[nextNodeX][nextNodeY].heurCost = GetHeuristicCost(nextNodeX, nextNodeY);
// Movement cost for diagonals is square root of 2
Map[nextNodeX][nextNodeY].movCost = currentNode.movCost + 1.41421f;
Map[nextNodeX][nextNodeY].pos.x = nextNodeX;
Map[nextNodeX][nextNodeY].pos.y = nextNodeY;
if ((Map[nextNodeX][nextNodeY].movCost + Map[nextNodeX][nextNodeY].heurCost) < (currentNode.movCost + currentNode.heurCost))
{
output.push_back(Map[nextNodeX][nextNodeY]);
}
}
// Does the bottom right diagonal have any forced neighbors ?
if(g_terrain.IsWall(nextNodeX + neighborX[BOTTOM_RIGHT], nextNodeY + neighborY[BOTTOM_RIGHT]) && !g_terrain.IsWall(nextNodeX + neighborX[BOTTOM], nextNodeY + neighborY[BOTTOM]))
{
Map[nextNodeX][nextNodeY].direction.x = neighborX[BOTTOM];
Map[nextNodeX][nextNodeY].direction.y = neighborY[BOTTOM];
Map[nextNodeX][nextNodeY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[nextNodeX][nextNodeY].heurCost = GetHeuristicCost(nextNodeX, nextNodeY);
// Movement cost for diagonals is square root of 2
Map[nextNodeX][nextNodeY].movCost = currentNode.movCost + 1.41421f;
Map[nextNodeX][nextNodeY].pos.x = nextNodeX;
Map[nextNodeX][nextNodeY].pos.y = nextNodeY;
output.push_back(Map[nextNodeX][nextNodeY]);
}
}
if(dir_x == -1)
{
// Does the top left diagonal have any forced neighbors ?
if(g_terrain.IsWall(nextNodeX + neighborX[TOP_LEFT], nextNodeY + neighborY[TOP_LEFT]) && !g_terrain.IsWall(nextNodeX + neighborX[TOP], nextNodeY + neighborY[TOP]))
{
Map[nextNodeX][nextNodeY].direction.x = neighborX[TOP];
Map[nextNodeX][nextNodeY].direction.y = neighborY[TOP];
Map[nextNodeX][nextNodeY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[nextNodeX][nextNodeY].heurCost = GetHeuristicCost(nextNodeX, nextNodeY);
// Movement cost for diagonals is square root of 2
Map[nextNodeX][nextNodeY].movCost = ((dir_x == 0) || (dir_y == 0))? currentNode.movCost + 1.0f: currentNode.movCost + 1.41421f;
Map[nextNodeX][nextNodeY].pos.x = nextNodeX;
Map[nextNodeX][nextNodeY].pos.y = nextNodeY;
output.push_back(Map[nextNodeX][nextNodeY]);
}
// Does the bottom left diagonal have any forced neighbors ?
if(g_terrain.IsWall(nextNodeX + neighborX[BOTTOM_LEFT], nextNodeY + neighborY[BOTTOM_LEFT]) && !g_terrain.IsWall(nextNodeX + neighborX[BOTTOM], nextNodeY + neighborY[BOTTOM]))
{
Map[nextNodeX][nextNodeY].direction.x = neighborX[BOTTOM];
Map[nextNodeX][nextNodeY].direction.y = neighborY[BOTTOM];
Map[nextNodeX][nextNodeY].parent = &Map[currentNode.pos.x][currentNode.pos.y];
Map[nextNodeX][nextNodeY].heurCost = GetHeuristicCost(nextNodeX, nextNodeY);
// Movement cost for diagonals is square root of 2
Map[nextNodeX][nextNodeY].movCost = ((dir_x == 0) || (dir_y == 0))? currentNode.movCost + 1.0f: currentNode.movCost + 1.41421f;
Map[nextNodeX][nextNodeY].pos.x = nextNodeX;
Map[nextNodeX][nextNodeY].pos.y = nextNodeY;
output.push_back(Map[nextNodeX][nextNodeY]);
}
}
}
}
Everything else follows the logic of the original document, but these two functions are the only two I am unsure about. Both the creators and many tutorials do not explain in depth how to identify them.
My implementation of A* stores the world information in a 2D matrix called Map instead of employing a closed list. The open list is merely a vector of what nodes to open. Nodes contain their x and y positions, heuristic cost and movement cost (more commonly known as given cost) and the direction the player was facing in when he stepped on the tile. The player only moves in x and y.
I apologize for the wall of text, but this is my first post and I wanted to post as much relevant information as possible.

Graphic algorithm Unions, intersect, subtract

I need a good source for reading up on how to create a algorithm to take two polylines (a path comprised of many lines) and performing a union, subtraction, or intersection between them. This is tied to a custom API so I need to understand the underlying algorithm.
Plus any sources in a VB dialect would be doubly helpful.
This catalogue of implementations of intersection algorithms from the Stony Brook Algorithm Repository might be useful. The repository is managed by Steven Skiena,
author of a very well respected book on algorithms: The Algorithm Design Manual.
That's his own Amazon exec link by the way :)
Several routines for you here. Hope you find them useful :-)
// routine to calculate the square of either the shortest distance or largest distance
// from the CPoint to the intersection point of a ray fired at an angle flAngle
// radians at an array of line segments
// this routine returns TRUE if an intersection has been found in which case flD
// is valid and holds the square of the distance.
// and returns FALSE if no valid intersection was found
// If an intersection was found, then intersectionPoint is set to the point found
bool CalcIntersection(const CPoint &cPoint,
const float flAngle,
const int nVertexTotal,
const CPoint *pVertexList,
const BOOL bMin,
float &flD,
CPoint &intersectionPoint)
{
float d, dsx, dsy, dx, dy, lambda, mu, px, py;
int p0x, p0y, p1x, p1y;
// get source position
const float flSx = (float)cPoint.x;
const float flSy = -(float)cPoint.y;
// calc trig functions
const float flTan = tanf(flAngle);
const float flSin = sinf(flAngle);
const float flCos = cosf(flAngle);
const bool bUseSin = fabsf(flSin) > fabsf(flCos);
// initialise distance
flD = (bMin ? FLT_MAX : 0.0f);
// for each line segment in protective feature
for(int i = 0; i < nVertexTotal; i++)
{
// get coordinates of line (negate the y value so the y-axis is upwards)
p0x = pVertexList[i].x;
p0y = -pVertexList[i].y;
p1x = pVertexList[i + 1].x;
p1y = -pVertexList[i + 1].y;
// calc. deltas
dsx = (float)(cPoint.x - p0x);
dsy = (float)(-cPoint.y - p0y);
dx = (float)(p1x - p0x);
dy = (float)(p1y - p0y);
// calc. denominator
d = dy * flTan - dx;
// if line & ray are parallel
if(fabsf(d) < 1.0e-7f)
continue;
// calc. intersection point parameter
lambda = (dsy * flTan - dsx) / d;
// if intersection is not valid
if((lambda <= 0.0f) || (lambda > 1.0f))
continue;
// if sine is bigger than cosine
if(bUseSin){
mu = ((float)p0x + lambda * dx - flSx) / flSin;
} else {
mu = ((float)p0y + lambda * dy - flSy) / flCos;
}
// if intersection is valid
if(mu >= 0.0f){
// calc. intersection point
px = (float)p0x + lambda * dx;
py = (float)p0y + lambda * dy;
// calc. distance between intersection point & source point
dx = px - flSx;
dy = py - flSy;
d = dx * dx + dy * dy;
// compare with relevant value
if(bMin){
if(d < flD)
{
flD = d;
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
}
} else {
if(d > flD)
{
flD = d;
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
}
}
}
}
// return
return(bMin ? (flD != FLT_MAX) : (flD != 0.0f));
}
// Routine to calculate the square of the distance from the CPoint to the
// intersection point of a ray fired at an angle flAngle radians at a line.
// This routine returns TRUE if an intersection has been found in which case flD
// is valid and holds the square of the distance.
// Returns FALSE if no valid intersection was found.
// If an intersection was found, then intersectionPoint is set to the point found.
bool CalcIntersection(const CPoint &cPoint,
const float flAngle,
const CPoint &PointA,
const CPoint &PointB,
const bool bExtendLine,
float &flD,
CPoint &intersectionPoint)
{
// get source position
const float flSx = (float)cPoint.x;
const float flSy = -(float)cPoint.y;
// calc trig functions
float flTan = tanf(flAngle);
float flSin = sinf(flAngle);
float flCos = cosf(flAngle);
const bool bUseSin = fabsf(flSin) > fabsf(flCos);
// get coordinates of line (negate the y value so the y-axis is upwards)
const int p0x = PointA.x;
const int p0y = -PointA.y;
const int p1x = PointB.x;
const int p1y = -PointB.y;
// calc. deltas
const float dsx = (float)(cPoint.x - p0x);
const float dsy = (float)(-cPoint.y - p0y);
float dx = (float)(p1x - p0x);
float dy = (float)(p1y - p0y);
// Calc. denominator
const float d = dy * flTan - dx;
// If line & ray are parallel
if(fabsf(d) < 1.0e-7f)
return false;
// calc. intersection point parameter
const float lambda = (dsy * flTan - dsx) / d;
// If extending line to meet point, don't check for ray missing line
if(!bExtendLine)
{
// If intersection is not valid
if((lambda <= 0.0f) || (lambda > 1.0f))
return false; // Ray missed line
}
// If sine is bigger than cosine
float mu;
if(bUseSin){
mu = ((float)p0x + lambda * dx - flSx) / flSin;
} else {
mu = ((float)p0y + lambda * dy - flSy) / flCos;
}
// if intersection is valid
if(mu >= 0.0f)
{
// calc. intersection point
const float px = (float)p0x + lambda * dx;
const float py = (float)p0y + lambda * dy;
// calc. distance between intersection point & source point
dx = px - flSx;
dy = py - flSy;
flD = (dx * dx) + (dy * dy);
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
return true;
}
return false;
}
// Fillet (with a radius of 0) two lines. From point source fired at angle (radians) to line Line1A, Line1B.
// Modifies line end point Line1B. If the ray does not intersect line, then it is rotates every 90 degrees
// and tried again until fillet is complete.
void Fillet(const CPoint &source, const float fThetaRadians, const CPoint &Line1A, CPoint &Line1B)
{
if(Line1A == Line1B)
return; // No line
float dist;
if(CalcIntersection(source, fThetaRadians, Line1A, Line1B, true, dist, Line1B))
return;
if(CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI * 0.5f), Line1A, Line1B, true, dist, Line1B))
return;
if(CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI), Line1A, Line1B, true, dist, Line1B))
return;
if(!CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI * 1.5f), Line1A, Line1B, true, dist, Line1B))
ASSERT(FALSE); // Could not find intersection?
}
// routine to determine if an array of line segments cross gridSquare
// x and y give the float coordinates of the corners
BOOL CrossGridSquare(int nV, const CPoint *pV,
const CRect &extent, const CRect &gridSquare)
{
// test extents
if( (extent.right < gridSquare.left) ||
(extent.left > gridSquare.right) ||
(extent.top > gridSquare.bottom) ||
(extent.bottom < gridSquare.top))
{
return FALSE;
}
float a, b, c, dx, dy, s, x[4], y[4];
int max_x, max_y, min_x, min_y, p0x, p0y, p1x, p1y, sign, sign_old;
// construct array of vertices for grid square
x[0] = (float)gridSquare.left;
y[0] = (float)gridSquare.top;
x[1] = (float)(gridSquare.right);
y[1] = y[0];
x[2] = x[1];
y[2] = (float)(gridSquare.bottom);
x[3] = x[0];
y[3] = y[2];
// for each line segment
for(int i = 0; i < nV; i++)
{
// get end-points
p0x = pV[i].x;
p0y = pV[i].y;
p1x = pV[i + 1].x;
p1y = pV[i + 1].y;
// determine line extent
if(p0x > p1x){
min_x = p1x;
max_x = p0x;
} else {
min_x = p0x;
max_x = p1x;
}
if(p0y > p1y){
min_y = p1y;
max_y = p0y;
} else {
min_y = p0y;
max_y = p1y;
}
// test to see if grid square is outside of line segment extent
if( (max_x < gridSquare.left) ||
(min_x > gridSquare.right) ||
(max_y < gridSquare.top) ||
(min_y > gridSquare.bottom))
{
continue;
}
// calc. line equation
dx = (float)(p1x - p0x);
dy = (float)(p1y - p0y);
a = dy;
b = -dx;
c = -dy * (float)p0x + dx * (float)p0y;
// evaluate line eqn. at first grid square vertex
s = a * x[0] + b * y[0] + c;
if(s < 0.0f){
sign_old = -1;
} else if(s > 1.0f){
sign_old = 1;
} else {
sign_old = 0;
}
// evaluate line eqn. at other grid square vertices
for (int j = 1; j < 4; j++)
{
s = a * x[j] + b * y[j] + c;
if(s < 0.0f){
sign = -1;
} else if(s > 1.0f){
sign = 1;
} else {
sign = 0;
}
// if there has been a chnage in sign
if(sign != sign_old)
return TRUE;
}
}
return FALSE;
}
// calculate the square of the shortest distance from point s
// and the line segment between p0 and p1
// t is the point on the line from which the minimum distance
// is measured
float CalcShortestDistanceSqr(const CPoint &s,
const CPoint &p0,
const CPoint &p1,
CPoint &t)
{
// if point is at a vertex
if((s == p0) || (s == p1))
return(0.0F);
// calc. deltas
int dx = p1.x - p0.x;
int dy = p1.y - p0.y;
int dsx = s.x - p0.x;
int dsy = s.y - p0.y;
// if both deltas are zero
if((dx == 0) && (dy == 0))
{
// shortest distance is distance is to either vertex
float l = (float)(dsx * dsx + dsy * dsy);
t = p0;
return(l);
}
// calc. point, p, on line that is closest to sourcePosition
// p = p0 + l * (p1 - p0)
float l = (float)(dsx * dx + dsy * dy) / (float)(dx * dx + dy * dy);
// if intersection is beyond p0
if(l <= 0.0F){
// shortest distance is to p0
l = (float)(dsx * dsx + dsy * dsy);
t = p0;
// else if intersection is beyond p1
} else if(l >= 1.0F){
// shortest distance is to p1
dsx = s.x - p1.x;
dsy = s.y - p1.y;
l = (float)(dsx * dsx + dsy * dsy);
t = p1;
// if intersection is between line end points
} else {
// calc. perpendicular distance
float ldx = (float)dsx - l * (float)dx;
float ldy = (float)dsy - l * (float)dy;
t.x = p0.x + RoundValue(l * (float)dx);
t.y = p0.y + RoundValue(l * (float)dy);
l = ldx * ldx + ldy * ldy;
}
return(l);
}
// Calculates the bounding rectangle around a set of points
// Returns TRUE if the rectangle is not empty (has area), FALSE otherwise
// Opposite of CreateRectPoints()
BOOL CalcBoundingRectangle(const CPoint *pVertexList, const int nVertexTotal, CRect &rect)
{
rect.SetRectEmpty();
if(nVertexTotal < 2)
{
ASSERT(FALSE); // Must have at least 2 points
return FALSE;
}
// First point, set rectangle (no area at this point)
rect.left = rect.right = pVertexList[0].x;
rect.top = rect.bottom = pVertexList[0].y;
// Increst rectangle by looking at other points
for(int n = 1; n < nVertexTotal; n++)
{
if(rect.left > pVertexList[n].x) // Take minimum
rect.left = pVertexList[n].x;
if(rect.right < pVertexList[n].x) // Take maximum
rect.right = pVertexList[n].x;
if(rect.top > pVertexList[n].y) // Take minimum
rect.top = pVertexList[n].y;
if(rect.bottom < pVertexList[n].y) // Take maximum
rect.bottom = pVertexList[n].y;
}
rect.NormalizeRect(); // Normalise rectangle
return !(rect.IsRectEmpty());
}

Resources