I'm using Cocos2D for iPhone to build up a game.I have a grid on the screen drawn by horizontal and vertical lines.(I did it with CCDrawNode) As you might guess there're lots of intersection points in there, I mean the points where horizontal and vertical lines intersect. With every touchBegan-Moved-Ended routine I draw a line, a bolder and different color line. In touchesMoved method I need to find the intersection point nearest to the current end point of the line and stick the line end to that point. How can I do that? I have one idea in my mind which is to add all the intersection points to an array when drawing the grid, iterate through that array and find the closest one. But I think this is not the best approach. You have any better ideas?
Assuming it is a normal grid with evenly spaced lines (e.g. every 10 pixels apart), you are much better off using a formula to tell you where an intersection should be.
E.g. given end point X/Y of 17,23, then x(17)/x-spacing(10) = 1.7, rounds to 2. 2*x-spacing = 20. y/y-spacing=2.3 -> 2*20 = 20. Thus your intersection is 20,20.
EDIT: more detailed example, in C# as that's what I use, if I get time I'll write an Objective-C sample
// defined somewhere and used to draw the grid
private int _spacingX = 10;
private int _spacingY = 10;
public Point GetNearestIntersection(int x, int y)
{
// round off to the nearest vertical/horizontal line number
double tempX = Math.Round((double)x / _spacingX);
double tempY = Math.Round((double)y / _spacingY);
// convert back to pixels
int nearestX = (int)tempX * _spacingX;
int nearestY = (int)tempY * _spacingY;
return new Point(nearestX, nearestY);
}
NOTE: the code above is left quite verbose to help you understand, you could easily re-write it to be cleaner
Related
I know using De Casteljau's algorithm is not the best way to draw a Bezier curve, but I need to implement it for an assignment I am defining my algorithm based on the following equations (from Drexel).
Where:
defines the control points.
I am trying to define the function to do the algorithm, but am struggling with where/how to incorporate the control points. The control points are defined by the user; as they interact with the program, a left click adds a new control point. My function currently looks as follows:
2Dpoint deCast(float t)
{
2Dpoint tempDC // Temporary value of point passed back to OpenGL draw function
tempDC.x = 0; tempDC.y = 0 // Initialize temporary value
int r,i;
int n = C->B.size(); // C is pointer to B vector, which is where the control points are stored in a 2D vector
for (r = 1; r<n, r++)
{
for (i = 0; i<n-r; i++)
{
// Calculation of deCast points goes here
}
}
}
Where 2Dpoint is just a structure defined by a header file, C is a pointer to the location of the control points, which are stored in a 2Dpoint struct called B (i.e the i value of the control point vector is accessed by C -> B[i].x and C -> B[i].y). t is provided to the function when it is implemented in my draw function, as shown below.
void draw()
{
glColor3f(0.0f, 1.0f, 0.0f);
glLineWidth(2.0f);
glBegin(GL_LINE_STRIP);
float DCiter = 0;
while (DCiter <= 1.0)
{
2Dpoint DC = decast(DCiter);
glVertex2f(DC.x, DC.y);
DCiter = DCiter + 0.01;
}
}
You're going to have to pass the deCasteljau function your points, too, because that's the whole reason de Casteljau's algorithm works (which you even show in your mathematical formula: it reads "the next point = the weighted sum of two previous points"). Since you need to do linear interpolation between points, you need to work with points =)
In pseudo code:
deCastestljau (t, points):
// the following code is destructive, so remember to be sensible
points = shallowCopy(points);
// de Casteljau's algorithm in four lines:
while points.length > 1:
for i=0 to points.length-2:
points[i] = (1-t) * points[i] + (t) * points[i+1]
points.pop()
// and we're done.
return points[0]
The end: you start with your control points, then you do a linear interpolation pass so you get all the points "between successive coordinates" at distance ratio t (e.g. if t is 0.25, then you're finding each linear interpolation at 1/4th the distance from some point to the next).
That operation yields a new set of points 1 fewer than before. Then you do it again, and again, and again, until you're left with one point. That's your on curve point. This works for any order Bezier curve.
It also has pretty much nothing to do with "drawing" a curve, though, de Casteljau's algorithm is a way to efficiently compute single points on a curve, it makes no claims about, nor has any true relation to, drawing entire curves. That's a completely different task, for which de Casteljau's algorithm is just one of many options to find whatever on-curve points you do need to make your draw algorithm work.
To give some background to this question, I'm creating a game that needs to know whether the 'Orbit' of an object is within tolerance to another Orbit. To show this, I plot a Torus-shape with a given radius (the tolerance) using the Target Orbit, and now I need to check if the ellipse is within that torus.
I'm getting lost in the equations on Math/Stack exchange so asking for a more specific solution. For clarification, here's an image of the game with the Torus and an Orbit (the red line). Quite simply, I want to check if that red orbit is within that Torus shape.
What I believe I need to do, is plot four points in World-Space on one of those orbits (easy enough to do). I then need to calculate the shortest distance between that point, and the other orbits' ellipse. This is the difficult part. There are several examples out there of finding the shortest distance of a point to an ellipse, but all are 2D and quite difficult to follow.
If that distance is then less than the tolerance for all four points, then in think that equates to the orbit being inside the target torus.
For simplicity, the origin of all of these orbits is always at the world Origin (0, 0, 0) - and my coordinate system is Z-Up. Each orbit has a series of parameters that defines it (Orbital Elements).
Here simple approach:
Sample each orbit to set of N points.
Let points from first orbit be A and from second orbit B.
const int N=36;
float A[N][3],B[N][3];
find 2 closest points
so d=|A[i]-B[i]| is minimal. If d is less or equal to your margin/treshold then orbits are too close to each other.
speed vs. accuracy
Unless you are using some advanced method for #2 then its computation will be O(N^2) which is a bit scary. The bigger the N the better accuracy of result but a lot more time to compute. There are ways how to remedy both. For example:
first sample with small N
when found the closest points sample both orbits again
but only near those points in question (with higher N).
you can recursively increase accuracy by looping #2 until you have desired precision
test d if ellipses are too close to each other
I think I may have a new solution.
Plot the four points on the current orbit (the ellipse).
Project those points onto the plane of the target orbit (the torus).
Using the Target Orbit inclination as the normal of a plane, calculate the angle between each (normalized) point and the argument of periapse
on the target orbit.
Use this angle as the mean anomaly, and compute the equivalent eccentric anomaly.
Use those eccentric anomalies to plot the four points on the target orbit - which should be the nearest points to the other orbit.
Check the distance between those points.
The difficulty here comes from computing the angle and converting it to the anomaly on the other orbit. This should be more accurate and faster than a recursive function though. Will update when I've tried this.
EDIT:
Yep, this works!
// The Four Locations we will use for the checks
TArray<FVector> CurrentOrbit_CheckPositions;
TArray<FVector> TargetOrbit_ProjectedPositions;
CurrentOrbit_CheckPositions.SetNum(4);
TargetOrbit_ProjectedPositions.SetNum(4);
// We first work out the plane of the target orbit.
const FVector Target_LANVector = FVector::ForwardVector.RotateAngleAxis(TargetOrbit.LongitudeAscendingNode, FVector::UpVector); // Vector pointing to Longitude of Ascending Node
const FVector Target_INCVector = FVector::UpVector.RotateAngleAxis(TargetOrbit.Inclination, Target_LANVector); // Vector pointing up the inclination axis (orbit normal)
const FVector Target_AOPVector = Target_LANVector.RotateAngleAxis(TargetOrbit.ArgumentOfPeriapsis, Target_INCVector); // Vector pointing towards the periapse (closest approach)
// Geometric plane of the orbit, using the inclination vector as the normal.
const FPlane ProjectionPlane = FPlane(Target_INCVector, 0.f); // Plane of the orbit. We only need the 'normal', and the plane origin is the Earths core (periapse focal point)
// Plot four points on the current orbit, using an equally-divided eccentric anomaly.
const float ECCAngle = PI / 2.f;
for (int32 i = 0; i < 4; i++)
{
// Plot the point, then project it onto the plane
CurrentOrbit_CheckPositions[i] = PosFromEccAnomaly(i * ECCAngle, CurrentOrbit);
CurrentOrbit_CheckPositions[i] = FVector::PointPlaneProject(CurrentOrbit_CheckPositions[i], ProjectionPlane);
// TODO: Distance from the plane is the 'Depth'. If the Depth is > Acceptance Radius, we are outside the torus and can early-out here
// Normalize the point to find it's direction in world-space (origin in our case is always 0,0,0)
const FVector PositionDirectionWS = CurrentOrbit_CheckPositions[i].GetSafeNormal();
// Using the Inclination as the comparison plane - find the angle between the direction of this vector, and the Argument of Periapse vector of the Target orbit
// TODO: we can probably compute this angle once, using the Periapse vectors from each orbit, and just multiply it by the Index 'I'
float Angle = FMath::Acos(FVector::DotProduct(PositionDirectionWS, Target_AOPVector));
// Compute the 'Sign' of the Angle (-180.f - 180.f), using the Cross Product
const FVector Cross = FVector::CrossProduct(PositionDirectionWS, Target_AOPVector);
if (FVector::DotProduct(Cross, Target_INCVector) > 0)
{
Angle = -Angle;
}
// Using the angle directly will give us the position at th eccentric anomaly. We want to take advantage of the Mean Anomaly, and use it as the ecc anomaly
// We can use this to plot a point on the target orbit, as if it was the eccentric anomaly.
Angle = Angle - TargetOrbit.Eccentricity * FMathD::Sin(Angle);
TargetOrbit_ProjectedPositions[i] = PosFromEccAnomaly(Angle, TargetOrbit);}
I hope the comments describe how this works. Finally solved after several months of head-scratching. Thanks all!
I am using IntersectsWith(this->boundingBox)) method to detect collisions between sprites and player. I want to somehow be able to use this method in detecting my enemy sprites that collide with each other, and when they do to make sure they don't move over one another.
All of the enemy sprites follow the player.
MainGame.cpp
Loops over each enemy in the vector and does the update loop:
for (auto &enemyMobsObj : this->enemyMobs)
{
enemyMobsObj->Update(tickTotal, tickDelta, timeTotal, timeDelta, windowBounds, this->ship, this->firstBoss,
this->enemyMobs, this->bullets, this->missiles, NULL, "NULL", "NULL");
}
Here is what I tried before to stop each sprite moving over each other:
EnemyMobOne::Update:
int nextEnemy;
for (int i = 0; i < enemyMobOne.size(); i++)
{
nextEnemy = i + 1;
if (nextEnemy < enemyMobOne.size())
{
//Deal with mobs collision
if (enemyMobOne[i].boundingBox.IntersectsWith(enemyMobOne[nextEnemy].boundingBox))
{
enemyMobOne[i].position.x = enemyMobOne[nextEnemy].position.x - enemyMobOne[i].boundingBox.Width;
}
}
}
However this makes each enemy sprite obviously stick to each other, which doesn't look right, it also makes them teleport.
Anyone know the correct code to stop them moving over each other? Thanks.
When you detect an intersection between two collision objects, you need to make a decision about how you're going to counteract the overlap (as I'm sure you figured out). However, how one does this is a bit trickier than simply "pushing" them to one side (as you've done in your code). What you likely want to do is to have a counter-force to the applied force, so to speak. Basically, you want to calculate the minimum translation, or the direction by which the least amount of movement would be required, to get at least ONE of the boxes to move OUT of the other one.
This is a bit more complicated than simply "put me on the right (or left, depending on how you set up your coordinates, I suppose) side of the other guy," which is more or less what your code does, now.
For a simple solution, just check if one of the colliders is closer to the left, right, top, or bottom of the other. To do this, you can simply take the collision intersection position and check the relative distance between that point and the minimum and maximum x and y coordinates relative to one of the colliders, then move the one or both of the sprites, accordingly.
Ex:
[Edit] After reviewing my previous answer for this, I realized you would need to calculate the overlap of the boxes, which would make it much easier to accomplish this all like so:
float minX = min(sprite0.boundingBox.maxX, sprite1.boundingBox.maxX);// Minimum of the boxes' right-side points (top right and bottom right) x coordinates
float minY = min(sprite0.boundingBox.maxY, sprite1.boundingBox.maxY);// Minimum of the boxes' top-side points (top left and top right) y coordinates
float maxX = max(sprite0.boundingBox.minX, sprite1.boundingBox.minX);// Maximum of the boxes' left-side points (top left and bottom left) x coordinates
float maxY = max(sprite0.boundingBox.minY, sprite1.boundingBox.minY);// Maximum of the boxes' bottom-side points (bottom left and bottom right) y coordinates
float distHoriz = minX - maxX;// The horizontal intersection distance
float distVert = minY - maxY;// The vertical instersection distance
// If the boxes are overlapping less on the horizontal axis than the vertical axis,
// move one of the sprites (in this case, sprite0) in the opposite direction of the
// x-axis overlap
if(abs(distHoriz) < abs(distVert))
{
sprite0.x -= distHoriz;
}
// Else, move one of the sprites (again, I just decided to use sprite0 here,
// arbitrarily) in the opposite direction of the y-axis overlap
else
{
sprite0.y -= distVert;
}
To further clarify (beyond the comments), what we're basically doing here is checking the distance between the overlapping lines. For example:
Box 0 x-axis xmin0|------------------|xmax0
Box 1 x-axis xmin1|----------------------|xmax1
|----|<-- Overlap (xmax0 - xmin1)
Notice that the minimum from the two bounding boxes that is used for the overlap is the maximum among the two minima (xmin0 and xmin1), and the maximum that is used for the overlap is the minimum among the two maxima (xmax0 and xmax1).
The y-axis calculation works exactly the same way. Once we have both axes, we simply check to see which one has a lower absolute value (which distance is shorter) and move along that distance to counteract the intersection.
I have searched stackoverflow and find this question useful and learned about the 2D shape rotation.
I have the coordinates in such format
int x1=-30, x2=-15, x3=20, x4=30;
int my1=-30,y2=-15,y3=0,y4=15,y5=20,y6=30;
and have some center and pivot points like this
int xc=320, yc=240;//Center of the figure
int xp=0, yp=0;//Pivot point for this figure
I used this function to draw the shape
void draw_chair()
{
int loc_xc = xc+xp;
int loc_yc = yc+yp;
line(x2+loc_xc,my1+loc_yc,x2+loc_xc,y5+loc_yc);
line(x3+loc_xc,my1+loc_yc,x3+loc_xc,y5+loc_yc);
line(x2+loc_xc,my1+loc_yc,x3+loc_xc,my1+loc_yc);
line(x2+loc_xc,y2+loc_yc,x3+loc_xc,y2+loc_yc);
line(x2+loc_xc,y3+loc_yc,x3+loc_xc,y3+loc_yc);
line(x1+loc_xc,y4+loc_yc,x4+loc_xc,y4+loc_yc);
line(x2+loc_xc,y3+loc_yc,x1+loc_xc,y4+loc_yc);
line(x3+loc_xc,y3+loc_yc,x4+loc_xc,y4+loc_yc);
line(x1+loc_xc,y4+loc_yc,x1+loc_xc,y6+loc_yc);
line(x4+loc_xc,y4+loc_yc,x4+loc_xc,y6+loc_yc);
}
The problem is that, Now I am confused at how to compute the rotated x and y values
I tried google and found this piece of code to rotate
int tempx=x1;
x1=tempx*cos(angle)-my1*sin(angle);
my1=tempx*sin(angle)+my1*cos(angle);
tempx=x2;
x2=tempx*cos(angle)-y2*sin(angle);
y2=tempx*sin(angle)+y2*cos(angle);
tempx=x3;
x3=tempx*cos(angle)-y3*sin(angle);
y3=tempx*sin(angle)+y3*cos(angle);
tempx=x4;
x4=tempx*cos(angle)-y4*sin(angle);
y4=tempx*sin(angle)+y4*cos(angle);
I tried this but it did not rotated shape properly but instead this code converts shape into some other strange shape. Also I have 4 x points and 6 y points, then how to compute new value for each point?
Any Idea? or hint?
Thanks
You cannot technically rotate a coordinate, as it is just a point with no notion of direction.
The code you found is used to rotate vectors, which is indeed what you'll need, but first you would need to convert your coordinates into vectors. You can think of vectors as being the invisible line that connects the center of the figure to your points, so it consists of two points, which in your case you can assume one to be (0,0) since you later increment them with the center of the figure, and the other corresponds to your pairs such as (x2,my1), (x2,y5)... as used in your line drawing function.
Your code should actually become something like this:
PS: unless you pass in only the perfect angles, you cannot expect the figure to always work with integer coordinates. You would need them to be doubles)
int point1x, point1y;
point1x = (int) round(x2*cos(angle)-m1y*sin(angle));
point1y = (int) round(x2*sin(angle)+m1y*cos(angle));
int point2x, point2y;
point2x = (int) round(x2*cos(angle)-y5*sin(angle));
point2y = (int) round(x2*sin(angle)+y5*cos(angle));
...
line(point1x+loc_xc, point1y+loc_yc, point2x+loc_xc, point2y+loc_yc);
and so on.
Also, make sure your angle value is in radians, as both sin() and cos() functions assume that. If you are passing down degrees, convert them to radians first with the following formula:
double pi = acos(-1);
double rotation_angle = (double) angle / 180.0 * pi;
and use rotation_angle instead of angle in the code above.
I'm in the process of creating a 2P Connect 4 game, but I can't seem to get the circular areas to place tokens spaced evenly.
Here's the code that initializes the positions of each circle:
POINT tilePos;
for (int i = 0; i < Board::Dims::MAXX; ++i)
{
tileXY.push_back (std::vector<POINT> (Board::Dims::MAXY)); //add column
for (int j = 0; j < Board::Dims::MAXY; ++j)
{
tilePos.x = boardPixelDims.left + (i + 1./2) * (boardPixelDims.width / Board::Dims::MAXX);
tilePos.y = boardPixelDims.top + (j + 1./2) * (boardPixelDims.height / Board::Dims::MAXY);
tileXY.at (i).push_back (tilePos); //add circle in column
}
}
I use a 2D vector of POINTs, tileXY, to store the positions. Recall the board is 7 circles wide by 6 circles high.
My logic is such that the first circle starts (for X) at:
left + width / #circles * 0 + width / #circles / 2
and increases by width / #circles each time, which is easy to picture for smaller numbers of circles.
Later, I draw the circles like this:
for (const std::vector<POINT> &col : _tileXY)
{
for (const POINT pos : col)
{
if (g.FillEllipse (&red, (int)(pos.x - CIRCLE_RADIUS), pos.y - CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS) != Gdiplus::Status::Ok)
MessageBox (_windows.gameWindow, "FillEllipse failed.", 0, MB_SYSTEMMODAL);
}
}
Those loops iterate through each element of the vector and draws each circle in red (to stand out at the moment). The int conversion is to disambiguate the function call. The first two arguments after the brush are the top-left corner, and CIRCLE_RADIUS is 50.
The problem is that my board looks like this (sorry if it hurts your eyes a bit):
As you can see, the circles are too far up and left. They're also too small, but that's easily fixed. I tried changing some ints to doubles, but ultimately ended up with this being the closest I ever got to the real pattern. The expanded formula (expanding (i + 1./2)) for the positions looks the same as well.
Have I missed a small detail, or is my whole logic behind it off?
Edit:
As requested, types:
tilePos.x: POINT (the windows API one, type used is LONG)
boardPixelDims.*: double
Board::Dims::MAXX/MAXY: enum values (integral, contain 7 and 6 respectively)
Depending on whether CIRCLE_SIZE is intended as radius or diameter, two of your parameters seem to be wrong in the FillEllipse call. If it's a diameter, then you should be setting location to pos.x - CIRCLE_SIZE/2 and pos.y - CIRCLE_SIZE/2. If it's a radius, then the height and width paramters should each be 2*CIRCLE_SIZE rather than CIRCLE_SIZE.
Update - since you changed the variable name to CIRCLE_RADIUS, the latter solution is now obviously the correct one.
The easiest way I remember what arguments the shape related functions take is to always think in rectangles. FillEllipse will just draw an ellipse to fill the rectangle you give it. x, y, width and height.
A simple experiment to practice with is if you change your calls to FillRect, get everything positioned okay, and then change them to FillEllipse.