Tile map collision detection - tiles

There are many topics like this, but none with concrete answers. I am drawing a tile-map in the traditional way (two for loops) and keeping my player centered except when the edges of the map is reached. How would I create collision detection? I need to know how to translate tile location in the array to screen coordinates I think.

I will give you the code i wrote for point/tilemap collision detection. The code assumes that you have a point at (xfrom, yfrom) and you want to move it to (xto, yto) and want to see if there is a collision with a block in the tilemap map[Y][X]. I assume a method isSolid(tileId) which will return true if the tile is solid.
/**
* This method returns true if there is a collision between a point and a 2D tilemap map[Y][X].
* The isSolid method must be implemented to indicate if a tile is solid or not
* Assumes the tilemap starts at (0,0) and TILEWIDTH and TILEHEIGHT hold the size of a tile (in pixels)
* #param xfrom the original x-coordinate of the point
* #param yfrom the original y-coordinate of the point
* #param xto the destination x-coordinate of the point
* #param yto the destination y-coordinate of the point
* #param outCollisionPoint output the location where the collision occurs
* #return true if a collision is found
*/
public boolean collisionDetection(int xfrom, int yfrom, int xto, int yto, Point outCollisionPoint){
//Ref: A fast voxel traversal algorithm J.Amanatides, A. Woo
float tMaxX, tMaxY, tDeltaX, tDeltaY, collisionLength;
int X, Y, stepX, stepY, endX, endY, blkX, blkY;
//Calculate direction vector
float dirX = (xto - xfrom);
float dirY = (yto - yfrom);
float length = (float) Math.sqrt(dirX * dirX + dirY * dirY);
//Normalize direction vector
dirX /= length;
dirY /= length;
//tDeltaX: distance in terms of vector(dirX,dirY) between two consecutive vertical lines
tDeltaX = TILEWIDTH / Math.abs(dirX);
tDeltaY = TILEHEIGHT / Math.abs(dirY);
//Determine cell where we originally are
X = xfrom / TILEWIDTH;
Y = yfrom / TILEHEIGHT;
endX = xto / TILEWIDTH;
endY = yto / TILEHEIGHT;
//stepX: Determine in what way do we move between cells
//tMaxX: the distance in terms of vector(dirX,dirY) to the next vertical line
if (xto > xfrom){
blkX = 0;
stepX = 1;
tMaxX = ((X+1) * TILEWIDTH - xfrom) / dirX;
}else{
blkX = 1;
stepX = -1;
tMaxX = (X * TILEWIDTH - xfrom) / dirX;
}
if (yto > yfrom){
blkY = 0;
stepY = 1;
tMaxY = ((Y+1) * TILEHEIGHT - yfrom) / dirY;
}else{
blkY = 1;
stepY = -1;
tMaxY = (Y * TILEHEIGHT - yfrom) / dirY;
}
if (isSolid(map[Y][X])) {
//point already collides
outCollisionPoint = new Point(xfrom, yfrom);
return true;
}
//Scan the cells along the line between 'from' and 'to'
while (X != endX || Y !=endY){
if(tMaxX < tMaxY){
tMaxX += tDeltaX;
X += stepX;
if (isSolid(map[Y][X])) {
collisionLength = ((X + blkX) * TILEWIDTH - xfrom) / dirX;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}else{
tMaxY += tDeltaY;
Y += stepY;
if (isSolid(map[Y][X])) {
collisionLength= ((Y + blkY) * TILEHEIGHT - yfrom) / dirY;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}
}
return false;
}

It depends on the model.
If your model (the data) is a grid, then a collision occurs simply when two incompatible objects occupy the same location. The easiest way to handle this type of collision is just to make sure where you are trying to move a game entity to is "available". If it is, no collision, and update the model. If it wasn't free, then there was a collision.
The screen simply renders the model. With the exception of something like of per-pixel collision detection (think the original lemmings or worms), don't use it for collision detection.
The screen/view is just the rendering agent. While you can have the model tied tightly to the screen (e.g. you only need to update parts of the screen in which things have changed such as when a piece is moved), the screen is not, and should not, generally be considered part of the model. However, with modern computing speed, you might as well simple re-render the entire visible model each frame.
(Yes, I know I repeated myself. It was on purpose.)
Now, to answer the secondary question not mentioned in the title:
When you start rendering, simply draw screen_width/cell_width/2 cells to the left and screen_width/cell_width/2 cells to the right of the player (the player is assumed to take 1x1). Do the same for the up-and-down. Make sure to not cause an Index-Out-Of-Bounds exception. You can run the for-loops with out-of-bounds values, as long long as you clamp/filter before using them. If you wish to only make the character "push" the edge when he gets close, keep track of a current model-to-view reference as well.

Related

NetTopologySuite : Rotating a LineSegment

In NetTopologySuite, how do you rotate a LineSegment?
I have a LineRing called outerBoundary. As I could not find a direct method to extract the individual segments of this LineRing, I created an extension method as follows;
`
public static List<LineSegment> GetSegments(this LinearRing lineRing)
{
List<LineSegment> segments = new List<LineSegment>();
Coordinate[] coordinates = lineRing.Coordinates;
for (int i = 0; i < coordinates.Length; i++)
{
segments.Add(new LineSegment(coordinates[i], coordinates[(i + 1 )% coordinates.Length]));
}
return segments;
}
`
Hence I call the GetSegments method on the outerBoundary, which returns the list of individual sides as a list of LineSegments. Now, I want to rotate each of these segments by an angle. If the GetSegments returned a list of LineStrings, I could have cereated a AffineTransformation instance with Rotation and apply that to each of these segments. But I felt having a list of LineSegment is intutional, as each of these sides will have only two end points, compared to LineString which allows more than two vertices. Now, how to do the same with LineSegments? As LineSegment doesnt inherit the Geometry base class, AffineTransformation cannot be applied on LineSegments is what I understand. Could someone help?
I would use a little trigonometry to move P0 and P1 in each segment by the origin point (in this case the midpoint).
Using your code above, loop each of the segments
foreach (LineSegment segment in segments)
Rotate(segments, seg.MidPoint, rotation);
And the code to rotate:
public void Rotate(LineSegment segment, Coordinate origin, double radAngle)
{
double x = origin.X + (Math.Cos(radAngle) * (segment.P0.X - origin.X) - Math.Sin(radAngle) * (segment.P0.Y - origin.Y));
double y = origin.Y + (Math.Sin(radAngle) * (segment.P0.X - origin.X) + Math.Cos(radAngle) * (segment.P0.Y - origin.Y));
segment.P0.X = x;
segment.P0.Y = y;
x = origin.X + (Math.Cos(radAngle) * (segment.P1.X - origin.X) - Math.Sin(radAngle) * (segment.P1.Y - origin.Y));
y = origin.Y + (Math.Sin(radAngle) * (segment.P1.X - origin.X) + Math.Cos(radAngle) * (segment.P1.Y - origin.Y));
segment.P1.X = x;
segment.P1.Y = y;
}

Processing - creating circles from current pixels

I'm using processing, and I'm trying to create a circle from the pixels i have on my display.
I managed to pull the pixels on screen and create a growing circle from them.
However i'm looking for something much more sophisticated, I want to make it seem as if the pixels on the display are moving from their current location and forming a turning circle or something like this.
This is what i have for now:
int c = 0;
int radius = 30;
allPixels = removeBlackP();
void draw {
loadPixels();
for (int alpha = 0; alpha < 360; alpha++)
{
float xf = 350 + radius*cos(alpha);
float yf = 350 + radius*sin(alpha);
int x = (int) xf;
int y = (int) yf;
if (radius > 200) {radius =30;break;}
if (c> allPixels.length) {c= 0;}
pixels[y*700 +x] = allPixels[c];
updatePixels();
}
radius++;
c++;
}
the function removeBlackP return an array with all the pixels except for the black ones.
This code works for me. There is an issue that the circle only has the numbers as int so it seems like some pixels inside the circle won't fill, i can live with that. I'm looking for something a bit more complex like I explained.
Thanks!
Fill all pixels of scanlines belonging to the circle. Using this approach, you will paint all places inside the circle. For every line calculate start coordinate (end one is symmetric). Pseudocode:
for y = center_y - radius; y <= center_y + radius; y++
dx = Sqrt(radius * radius - y * y)
for x = center_x - dx; x <= center_x + dx; x++
fill a[y, x]
When you find places for all pixels, you can make correlation between initial pixels places and calculated ones and move them step-by-step.
For example, if initial coordinates relative to center point for k-th pixel are (x0, y0) and final coordinates are (x1,y1), and you want to make M steps, moving pixel by spiral, calculate intermediate coordinates:
calc values once:
r0 = Sqrt(x0*x0 + y0*y0) //Math.Hypot if available
r1 = Sqrt(x1*x1 + y1*y1)
fi0 = Math.Atan2(y0, x0)
fi1 = Math.Atan2(y1, x1)
if fi1 < fi0 then
fi1 = fi1 + 2 * Pi;
for i = 1; i <=M ; i++
x = (r0 + i / M * (r1 - r0)) * Cos(fi0 + i / M * (fi1 - fi0))
y = (r0 + i / M * (r1 - r0)) * Sin(fi0 + i / M * (fi1 - fi0))
shift by center coordinates
The way you go about drawing circles in Processing looks a little convoluted.
The simplest way is to use the ellipse() function, no pixels involved though:
If you do need to draw an ellipse and use pixels, you can make use of PGraphics which is similar to using a separate buffer/"layer" to draw into using Processing drawing commands but it also has pixels[] you can access.
Let's say you want to draw a low-res pixel circle circle, you can create a small PGraphics, disable smoothing, draw the circle, then render the circle at a higher resolution. The only catch is these drawing commands must be placed within beginDraw()/endDraw() calls:
PGraphics buffer;
void setup(){
//disable sketch's aliasing
noSmooth();
buffer = createGraphics(25,25);
buffer.beginDraw();
//disable buffer's aliasing
buffer.noSmooth();
buffer.noFill();
buffer.stroke(255);
buffer.endDraw();
}
void draw(){
background(255);
//draw small circle
float circleSize = map(sin(frameCount * .01),-1.0,1.0,0.0,20.0);
buffer.beginDraw();
buffer.background(0);
buffer.ellipse(buffer.width / 2,buffer.height / 2, circleSize,circleSize);
buffer.endDraw();
//render small circle at higher resolution (blocky - no aliasing)
image(buffer,0,0,width,height);
}
If you want to manually draw a circle using pixels[] you are on the right using the polar to cartesian conversion formula (x = cos(angle) * radius, y = sin(angle) * radius).Even though it's focusing on drawing a radial gradient, you can find an example of drawing a circle(a lot actually) using pixels in this answer

how to figure out cursive paths for an enemy to follow

The Problem
I am making a game where enemies appear at some point on the screen then follow a smooth curvy path and disappear at some point. I can make them follow a straight path but can't figure out the way to make them follow the paths depicted in the image.
Attempts
I started with parabolic curve and implemented them successfully. I just used the equation of parabola to calculate the coordinates gradually. I have no clue what is the equation for desired paths supposed to be.
What I want
I am not asking for the code.I just want someone to explain me the general technique.If you still want to show some code then I don't have special preference for programming language for this particular question you can use C,Java or even pseudo-code.
First you need to represent each curve with a set of points over time, For example:
-At T(0) the object should be at (X0, Y0).
-At T(1) the object should be at (X1, Y1).
And the more points you have, the more smooth curve you will get.
Then you will use those set of points to generate two formulas-one for X, and another one for Y-, using any Interpolation method, like The La-grange's Interpolation Formula:
Note that you should replace 'y' with the time T, and replace 'x' with your X for X formula, and Y for Y formula.
I know you hoped for a simple equation, but unfortunately this is will take from you a huge effort to simplify each equation, and my advise DON'T do it unless it's worth it.
If you are seeking for a more simple equation to perform well in each frame in your game you should read about SPline method, In this method is about splitting your curve into a smaller segments, and make a simple equation for every segment, for example:
Linear Spline:
Every segment contains 2 points, this will draw a line between every two points.
The result will be some thing like this:
Or you could use quadratic spline, or cubic spline for more smooth curves, but it will slow your game performance. You can read more about those methods here.
I think linear spline will be great for you with reasonable set of points for each curve.
Please change the question title to be more generic.
If you want to generate a spiral path you need.
Total time
How many full rotations
Largest radius
So, total time T_f = 5sec, rotations R_f = 2.5 * 2 * PI, the final distance from the start D_f = 200px
function SpiralEnemy(spawnX, spawnY, time) {
this.startX = spawnX;
this.startY = spawnY;
this.startTime = time;
// these will change and be used for rendering
this.x = this.startX;
this.y = this.startY;
this.done = false;
// constants we figured out above
var TFinal = 5.0;
var RFinal = -2.6 * 2 * Math.PI;
var RStart = -Math.PI / 2;
var DFinal = 100;
// the update function called every animation tick with the current time
this.update = function(t) {
var delta = t - this.startTime;
if(delta > TFinal) {
this.done = true;
return;
}
// find out how far along you are in the animation
var percent = delta / TFinal;
// what is your current angle of rotation (in radians)
var angle = RStart + RFinal * percent;
// how far from your start point should you be
var dist = DFinal * percent;
// update your coordinates
this.x = this.startX + Math.cos(angle) * dist;
this.y = this.startY + Math.sin(angle) * dist;
};
}
EDIT Here's a jsfiddle to mess with http://jsfiddle.net/pxb3824z/
EDIT 2 Here's a loop (instead of spiral) version http://jsfiddle.net/dpbLxuz7/
The loop code splits the animation into 2 parts the beginning half and the end half.
Beginning half : angle = Math.tan(T_percent) * 2 and dist = Speed + Speed * (1 - T_percent)
End half : angle = -Math.tan(1 - T_percent) * 2 and dist = **Speed + Speed * T_percent
T_percent is normalized to (0, 1.0) for both halfs.
function LoopEnemy(spawnX, spawnY, time) {
this.startX = spawnX;
this.startY = spawnY;
this.startTime = time;
// these will change and be used for rendering
this.x = this.startX;
this.y = this.startY;
this.last = time;
this.done = false;
// constants we figured out above
var TFinal = 5.0;
var RFinal = -2 * Math.PI;
var RStart = 0;
var Speed = 50; // px per second
// the update function called every animation tick with the current time
this.update = function(t) {
var delta = t - this.startTime;
if(delta > TFinal) {
this.done = true;
return;
}
// find out how far along you are in the animation
var percent = delta / TFinal;
var localDelta = t - this.last;
// what is your current angle of rotation (in radians)
var angle = RStart;
var dist = Speed * localDelta;
if(percent <= 0.5) {
percent = percent / 0.5;
angle -= Math.tan(percent) * 2;
dist += dist * (1 - percent);
} else {
percent = (percent - 0.5) / 0.5;
angle -= -Math.tan(1 - percent) * 2;
dist += dist * percent;
}
// update your coordinates
this.last = t;
this.x = this.x + Math.cos(angle) * dist;
this.y = this.y + Math.sin(angle) * dist;
};
}
Deriving the exact distance traveled and the height of the loop for this one is a bit more work. I arbitrarily chose a Speed of 50px / sec, which give a final x offset of ~+145 and a loop height of ~+114 the distance and height will scale from those values linearly (ex: Speed=25 will have final x at ~73 and loop height of ~57)
I don't understand how you give a curve. If you need a curve depicted on the picture, you can find a curve is given analytically and use it. If you have not any curves you can send me here: hedgehogues#bk.ru and I will help find you. I leave e-mail here because I don't get any messages about answers of users from stackoverflow. I don't know why.
If you have some curves in parametric view in [A, B], you can write a code like this:
struct
{
double x, y;
}SPoint;
coord = A;
step = 0.001
eps = 1e-6;
while (coord + step - eps < B)
{
SPoint p1, p2;
p1.x = x(coord);
p1.y = y(coord);
coord += step;
p2.x = x(coord);
p2.y = y(coord);
drawline(p1, p2);
}

Rotation Automation

I've run into a few problems with this expression in Maya, basically anytime the radius is less than 1, the calculation is thrown off way too much.
float $radius = `getAttr prefix66_calculations_shape.rad`;
float $prevZval = `getAttr -time (frame -1) prefix66_driver.translateZ`;
float $prevXval = `getAttr -time (frame -1) prefix66_driver.translateX`;
float $Zval = prefix66_driver.translateZ - $prevZval;
float $Xval = prefix66_driver.translateX - $prevXval;
float $distance = ($Zval * $Zval) + ($Xval * $Xval);
float $direction;
$distance = sqrt($distance);
if ($prevZval > prefix66_driver.translateZ) {
$direction = 360;
}
else {
$direction = 360;
}
float $rotation = ($distance / (2 * 3.142 * $radius)) * $direction;
print $rotation;
pCube1.rotateX = pCube1.rotateX + $rotation;
Maybe my order of operations is wrong?
The rotation part of your code looks ok. However, you have an if/else block that returns the same thing in both cases, and as mentioned by #joojaa, you can avoid getAttr -time if you cache the translation values. In fact you should avoid getAttr and setAttr completely in expressions.
Instead, refer to the attributes you want directly and Maya will create connections for you. This is much faster and less prone to errors when you rename nodes and so on.
To cache the translation values, and calculate change in position you can add attributes to the node and use them in the expression.
Let's say you have a cylinder called wheel that rotates around its local X and is parented to a group node called control:
Add a vector attribute: control.lastTranslate
Add a vector attribute: control.deltaTranslate
Add a float attribute: control.distance
Here's an expression that will store the change in translation, then rotate the wheel based on the distance travelled.
// When deltaTranslate is calculated, lastTranslate still has its previous value.
control.deltaTranslateX = control.translateX - control.lastTranslateX;
control.deltaTranslateY = control.translateY - control.lastTranslateY;
control.deltaTranslateZ = control.translateZ - control.lastTranslateZ;
control.lastTranslateX = control.translateX;
control.lastTranslateY = control.translateY;
control.lastTranslateZ = control.translateZ;
control.distance = mag(<<control.deltaTranslateX,control.deltaTranslateY,control.deltaTranslateZ>>);
// Get radius from history node (or somewhere) and move the wheel's hub off the floor.
wheel.translateY = polyCylinder1.radius;
// add rotation to the wheel
float $tau = 6.283185307179586;
wheel.rotateX = wheel.rotateX + ( control.distance* -360.0) / (polyCylinder1.radius * $tau );
It's best to test this kind of thing by animating rather than dragging nodes around in the view.
If you wanted to make the wheel aim to the direction of travel, you could add a locator at translate + deltaTranslate and hook up an aim constraint.
e.g.
aimLocator.translateX = (control.deltaTranslateX / control.distance) + control.translateX;
aimLocator.translateY = (control.deltaTranslateY / control.distance) + control.translateY;
aimLocator.translateZ = (control.deltaTranslateZ / control.distance) + control.translateZ;
Dividing by distance will normalize the offset. You should probably check that distance is not zero.
I believe I have figured it out :)
Queering the old traslation average, with the new translation average will give me a true or false answer, which is what I needed to change direction.
Also added an if statement that if the ball is static and rotating, that the wheel doesn't turn automatically.
float $oldRotateAverage;
float $oldTransAverage;
float $direction;
nurbsCircle1.DeltaTranslateX = nurbsCircle1.translateX - nurbsCircle1.LastTranslateX;
nurbsCircle1.DeltaTranslateY = nurbsCircle1.translateY - nurbsCircle1.LastTranslateY;
nurbsCircle1.DeltaTranslateZ = nurbsCircle1.translateZ - nurbsCircle1.LastTranslateZ;
nurbsCircle1.LastTranslateX = nurbsCircle1.translateX;
nurbsCircle1.LastTranslateY = nurbsCircle1.translateY;
nurbsCircle1.LastTranslateZ = nurbsCircle1.translateZ;
nurbsCircle1.Distance = mag(<<nurbsCircle1.DeltaTranslateX,nurbsCircle1.DeltaTranslateY,nurbsCircle1.DeltaTranslateZ>>);
if ($oldTransAverage >= (nurbsCircle1.LastTranslateX + nurbsCircle1.LastTranslateY + nurbsCircle1.LastTranslateZ)){
$direction = -360.00;
} else {
$direction = 360.00;
};
if (Sh54_anim.auto == 1 )
{
Sh54_point_grp.rotateZ -= nurbsCircle1.Distance * $direction / 2 / 3.14 / 2;
};
if ((nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ) != $oldRotateAverage && nurbsCircle1.Distance == $oldTransAverage){
Sh54_anim.auto = 0;
} else {
Sh54_anim.auto = 1;
};
Sh54_point_grp.back_up = Sh54_point_grp.translateX;
$oldRotateAverage = nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ;
$oldTransAverage = nurbsCircle1.translateX + nurbsCircle1.translateY + nurbsCircle1.translateZ;

How to draw a Perspective-Correct Grid in 2D

I have an application that defines a real world rectangle on top of an image/photograph, of course in 2D it may not be a rectangle because you are looking at it from an angle.
The problem is, say that the rectangle needs to have grid lines drawn on it, for example if it is 3x5 so I need to draw 2 lines from side 1 to side 3, and 4 lines from side 2 to side 4.
As of right now I am breaking up each line into equidistant parts, to get the start and end point of all the grid lines. However the more of an angle the rectangle is on, the more "incorrect" these lines become, as horizontal lines further from you should be closer together.
Does anyone know the name of the algorithm that I should be searching for?
Yes I know you can do this in 3D, however I am limited to 2D for this particular application.
Here's the solution.
The basic idea is you can find the perspective correct "center" of your rectangle by connecting the corners diagonally. The intersection of the two resulting lines is your perspective correct center. From there you subdivide your rectangle into four smaller rectangles, and you repeat the process. The number of times depends on how accurate you want it. You can subdivide to just below the size of a pixel for effectively perfect perspective.
Then in your subrectangles you just apply your standard uncorrected "textured" triangles, or rectangles or whatever.
You can perform this algorithm without going to the complex trouble of building a 'real' 3d world. it's also good for if you do have a real 3d world modeled, but your textriangles are not perspective corrected in hardware, or you need a performant way to get perspective correct planes without per pixel rendering trickery.
Image: Example of Bilinear & Perspective Transform (Note: The height of top & bottom horizontal grid lines is actually half of the rest lines height, on both drawings)
========================================
I know this is an old question, but I have a generic solution so I decided to publish it hopping it will be useful to the future readers.
The code bellow can draw an arbitrary perspective grid without the need of repetitive computations.
I begin actually with a similar problem: to draw a 2D perspective Grid and then transform the underline image to restore the perspective.
I started to read here:
http://www.imagemagick.org/Usage/distorts/#bilinear_forward
and then here (the Leptonica Library):
http://www.leptonica.com/affine.html
were I found this:
When you look at an object in a plane from some arbitrary direction at
a finite distance, you get an additional "keystone" distortion in the
image. This is a projective transform, which keeps straight lines
straight but does not preserve the angles between lines. This warping
cannot be described by a linear affine transformation, and in fact
differs by x- and y-dependent terms in the denominator.
The transformation is not linear, as many people already pointed out in this thread. It involves solving a linear system of 8 equations (once) to compute the 8 required coefficients and then you can use them to transform as many points as you want.
To avoid including all Leptonica library in my project, I took some pieces of code from it, I removed all special Leptonica data-types & macros, I fixed some memory leaks and I converted it to a C++ class (mostly for encapsulation reasons) which does just one thing:
It maps a (Qt) QPointF float (x,y) coordinate to the corresponding Perspective Coordinate.
If you want to adapt the code to another C++ library, the only thing to redefine/substitute is the QPointF coordinate class.
I hope some future readers would find it useful.
The code bellow is divided into 3 parts:
A. An example on how to use the genImageProjective C++ class to draw a 2D perspective Grid
B. genImageProjective.h file
C. genImageProjective.cpp file
//============================================================
// C++ Code Example on how to use the
// genImageProjective class to draw a perspective 2D Grid
//============================================================
#include "genImageProjective.h"
// Input: 4 Perspective-Tranformed points:
// perspPoints[0] = top-left
// perspPoints[1] = top-right
// perspPoints[2] = bottom-right
// perspPoints[3] = bottom-left
void drawGrid(QPointF *perspPoints)
{
(...)
// Setup a non-transformed area rectangle
// I use a simple square rectangle here because in this case we are not interested in the source-rectangle,
// (we want to just draw a grid on the perspPoints[] area)
// but you can use any arbitrary rectangle to perform a real mapping to the perspPoints[] area
QPointF topLeft = QPointF(0,0);
QPointF topRight = QPointF(1000,0);
QPointF bottomRight = QPointF(1000,1000);
QPointF bottomLeft = QPointF(0,1000);
float width = topRight.x() - topLeft.x();
float height = bottomLeft.y() - topLeft.y();
// Setup Projective trasform object
genImageProjective imageProjective;
imageProjective.sourceArea[0] = topLeft;
imageProjective.sourceArea[1] = topRight;
imageProjective.sourceArea[2] = bottomRight;
imageProjective.sourceArea[3] = bottomLeft;
imageProjective.destArea[0] = perspPoints[0];
imageProjective.destArea[1] = perspPoints[1];
imageProjective.destArea[2] = perspPoints[2];
imageProjective.destArea[3] = perspPoints[3];
// Compute projective transform coefficients
if (imageProjective.computeCoeefficients() != 0)
return; // This can actually fail if any 3 points of Source or Dest are colinear
// Initialize Grid parameters (without transform)
float gridFirstLine = 0.1f; // The normalized position of first Grid Line (0.0 to 1.0)
float gridStep = 0.1f; // The normalized Grd size (=distance between grid lines: 0.0 to 1.0)
// Draw Horizonal Grid lines
QPointF lineStart, lineEnd, tempPnt;
for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep)
{
// Compute Grid Line Start
tempPnt = QPointF(topLeft.x(), topLeft.y() + pos*width);
imageProjective.mapSourceToDestPoint(tempPnt, lineStart);
// Compute Grid Line End
tempPnt = QPointF(topRight.x(), topLeft.y() + pos*width);
imageProjective.mapSourceToDestPoint(tempPnt, lineEnd);
// Draw Horizontal Line (use your prefered method to draw the line)
(...)
}
// Draw Vertical Grid lines
for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep)
{
// Compute Grid Line Start
tempPnt = QPointF(topLeft.x() + pos*height, topLeft.y());
imageProjective.mapSourceToDestPoint(tempPnt, lineStart);
// Compute Grid Line End
tempPnt = QPointF(topLeft.x() + pos*height, bottomLeft.y());
imageProjective.mapSourceToDestPoint(tempPnt, lineEnd);
// Draw Vertical Line (use your prefered method to draw the line)
(...)
}
(...)
}
==========================================
//========================================
//C++ Header File: genImageProjective.h
//========================================
#ifndef GENIMAGE_H
#define GENIMAGE_H
#include <QPointF>
// Class to transform an Image Point using Perspective transformation
class genImageProjective
{
public:
genImageProjective();
int computeCoeefficients(void);
int mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint);
public:
QPointF sourceArea[4]; // Source Image area limits (Rectangular)
QPointF destArea[4]; // Destination Image area limits (Perspectivelly Transformed)
private:
static int gaussjordan(float **a, float *b, int n);
bool coefficientsComputed;
float vc[8]; // Vector of Transform Coefficients
};
#endif // GENIMAGE_H
//========================================
//========================================
//C++ CPP File: genImageProjective.cpp
//========================================
#include <math.h>
#include "genImageProjective.h"
// ----------------------------------------------------
// class genImageProjective
// ----------------------------------------------------
genImageProjective::genImageProjective()
{
sourceArea[0] = sourceArea[1] = sourceArea[2] = sourceArea[3] = QPointF(0,0);
destArea[0] = destArea[1] = destArea[2] = destArea[3] = QPointF(0,0);
coefficientsComputed = false;
}
// --------------------------------------------------------------
// Compute projective transform coeeeficients
// RetValue: 0: Success, !=0: Error
/*-------------------------------------------------------------*
* Projective coordinate transformation *
*-------------------------------------------------------------*/
/*!
* computeCoeefficients()
*
* Input: this->sourceArea[4]: (source 4 points; unprimed)
* this->destArea[4]: (transformed 4 points; primed)
* this->vc (computed vector of transform coefficients)
* Return: 0 if OK; <0 on error
*
* We have a set of 8 equations, describing the projective
* transformation that takes 4 points (sourceArea) into 4 other
* points (destArea). These equations are:
*
* x1' = (c[0]*x1 + c[1]*y1 + c[2]) / (c[6]*x1 + c[7]*y1 + 1)
* y1' = (c[3]*x1 + c[4]*y1 + c[5]) / (c[6]*x1 + c[7]*y1 + 1)
* x2' = (c[0]*x2 + c[1]*y2 + c[2]) / (c[6]*x2 + c[7]*y2 + 1)
* y2' = (c[3]*x2 + c[4]*y2 + c[5]) / (c[6]*x2 + c[7]*y2 + 1)
* x3' = (c[0]*x3 + c[1]*y3 + c[2]) / (c[6]*x3 + c[7]*y3 + 1)
* y3' = (c[3]*x3 + c[4]*y3 + c[5]) / (c[6]*x3 + c[7]*y3 + 1)
* x4' = (c[0]*x4 + c[1]*y4 + c[2]) / (c[6]*x4 + c[7]*y4 + 1)
* y4' = (c[3]*x4 + c[4]*y4 + c[5]) / (c[6]*x4 + c[7]*y4 + 1)
*
* Multiplying both sides of each eqn by the denominator, we get
*
* AC = B
*
* where B and C are column vectors
*
* B = [ x1' y1' x2' y2' x3' y3' x4' y4' ]
* C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ]
*
* and A is the 8x8 matrix
*
* x1 y1 1 0 0 0 -x1*x1' -y1*x1'
* 0 0 0 x1 y1 1 -x1*y1' -y1*y1'
* x2 y2 1 0 0 0 -x2*x2' -y2*x2'
* 0 0 0 x2 y2 1 -x2*y2' -y2*y2'
* x3 y3 1 0 0 0 -x3*x3' -y3*x3'
* 0 0 0 x3 y3 1 -x3*y3' -y3*y3'
* x4 y4 1 0 0 0 -x4*x4' -y4*x4'
* 0 0 0 x4 y4 1 -x4*y4' -y4*y4'
*
* These eight equations are solved here for the coefficients C.
*
* These eight coefficients can then be used to find the mapping
* (x,y) --> (x',y'):
*
* x' = (c[0]x + c[1]y + c[2]) / (c[6]x + c[7]y + 1)
* y' = (c[3]x + c[4]y + c[5]) / (c[6]x + c[7]y + 1)
*
*/
int genImageProjective::computeCoeefficients(void)
{
int retValue = 0;
int i;
float *a[8]; /* 8x8 matrix A */
float *b = this->vc; /* rhs vector of primed coords X'; coeffs returned in vc[] */
b[0] = destArea[0].x();
b[1] = destArea[0].y();
b[2] = destArea[1].x();
b[3] = destArea[1].y();
b[4] = destArea[2].x();
b[5] = destArea[2].y();
b[6] = destArea[3].x();
b[7] = destArea[3].y();
for (i = 0; i < 8; i++)
a[i] = NULL;
for (i = 0; i < 8; i++)
{
if ((a[i] = (float *)calloc(8, sizeof(float))) == NULL)
{
retValue = -100; // ERROR_INT("a[i] not made", procName, 1);
goto Terminate;
}
}
a[0][0] = sourceArea[0].x();
a[0][1] = sourceArea[0].y();
a[0][2] = 1.;
a[0][6] = -sourceArea[0].x() * b[0];
a[0][7] = -sourceArea[0].y() * b[0];
a[1][3] = sourceArea[0].x();
a[1][4] = sourceArea[0].y();
a[1][5] = 1;
a[1][6] = -sourceArea[0].x() * b[1];
a[1][7] = -sourceArea[0].y() * b[1];
a[2][0] = sourceArea[1].x();
a[2][1] = sourceArea[1].y();
a[2][2] = 1.;
a[2][6] = -sourceArea[1].x() * b[2];
a[2][7] = -sourceArea[1].y() * b[2];
a[3][3] = sourceArea[1].x();
a[3][4] = sourceArea[1].y();
a[3][5] = 1;
a[3][6] = -sourceArea[1].x() * b[3];
a[3][7] = -sourceArea[1].y() * b[3];
a[4][0] = sourceArea[2].x();
a[4][1] = sourceArea[2].y();
a[4][2] = 1.;
a[4][6] = -sourceArea[2].x() * b[4];
a[4][7] = -sourceArea[2].y() * b[4];
a[5][3] = sourceArea[2].x();
a[5][4] = sourceArea[2].y();
a[5][5] = 1;
a[5][6] = -sourceArea[2].x() * b[5];
a[5][7] = -sourceArea[2].y() * b[5];
a[6][0] = sourceArea[3].x();
a[6][1] = sourceArea[3].y();
a[6][2] = 1.;
a[6][6] = -sourceArea[3].x() * b[6];
a[6][7] = -sourceArea[3].y() * b[6];
a[7][3] = sourceArea[3].x();
a[7][4] = sourceArea[3].y();
a[7][5] = 1;
a[7][6] = -sourceArea[3].x() * b[7];
a[7][7] = -sourceArea[3].y() * b[7];
retValue = gaussjordan(a, b, 8);
Terminate:
// Clean up
for (i = 0; i < 8; i++)
{
if (a[i])
free(a[i]);
}
this->coefficientsComputed = (retValue == 0);
return retValue;
}
/*-------------------------------------------------------------*
* Gauss-jordan linear equation solver *
*-------------------------------------------------------------*/
/*
* gaussjordan()
*
* Input: a (n x n matrix)
* b (rhs column vector)
* n (dimension)
* Return: 0 if ok, 1 on error
*
* Note side effects:
* (1) the matrix a is transformed to its inverse
* (2) the vector b is transformed to the solution X to the
* linear equation AX = B
*
* Adapted from "Numerical Recipes in C, Second Edition", 1992
* pp. 36-41 (gauss-jordan elimination)
*/
#define SWAP(a,b) {temp = (a); (a) = (b); (b) = temp;}
int genImageProjective::gaussjordan(float **a, float *b, int n)
{
int retValue = 0;
int i, icol=0, irow=0, j, k, l, ll;
int *indexc = NULL, *indexr = NULL, *ipiv = NULL;
float big, dum, pivinv, temp;
if (!a)
{
retValue = -1; // ERROR_INT("a not defined", procName, 1);
goto Terminate;
}
if (!b)
{
retValue = -2; // ERROR_INT("b not defined", procName, 1);
goto Terminate;
}
if ((indexc = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -3; // ERROR_INT("indexc not made", procName, 1);
goto Terminate;
}
if ((indexr = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -4; // ERROR_INT("indexr not made", procName, 1);
goto Terminate;
}
if ((ipiv = (int *)calloc(n, sizeof(int))) == NULL)
{
retValue = -5; // ERROR_INT("ipiv not made", procName, 1);
goto Terminate;
}
for (i = 0; i < n; i++)
{
big = 0.0;
for (j = 0; j < n; j++)
{
if (ipiv[j] != 1)
{
for (k = 0; k < n; k++)
{
if (ipiv[k] == 0)
{
if (fabs(a[j][k]) >= big)
{
big = fabs(a[j][k]);
irow = j;
icol = k;
}
}
else if (ipiv[k] > 1)
{
retValue = -6; // ERROR_INT("singular matrix", procName, 1);
goto Terminate;
}
}
}
}
++(ipiv[icol]);
if (irow != icol)
{
for (l = 0; l < n; l++)
SWAP(a[irow][l], a[icol][l]);
SWAP(b[irow], b[icol]);
}
indexr[i] = irow;
indexc[i] = icol;
if (a[icol][icol] == 0.0)
{
retValue = -7; // ERROR_INT("singular matrix", procName, 1);
goto Terminate;
}
pivinv = 1.0 / a[icol][icol];
a[icol][icol] = 1.0;
for (l = 0; l < n; l++)
a[icol][l] *= pivinv;
b[icol] *= pivinv;
for (ll = 0; ll < n; ll++)
{
if (ll != icol)
{
dum = a[ll][icol];
a[ll][icol] = 0.0;
for (l = 0; l < n; l++)
a[ll][l] -= a[icol][l] * dum;
b[ll] -= b[icol] * dum;
}
}
}
for (l = n - 1; l >= 0; l--)
{
if (indexr[l] != indexc[l])
{
for (k = 0; k < n; k++)
SWAP(a[k][indexr[l]], a[k][indexc[l]]);
}
}
Terminate:
if (indexr)
free(indexr);
if (indexc)
free(indexc);
if (ipiv)
free(ipiv);
return retValue;
}
// --------------------------------------------------------------
// Map a source point to destination using projective transform
// --------------------------------------------------------------
// Params:
// sourcePoint: initial point
// destPoint: transformed point
// RetValue: 0: Success, !=0: Error
// --------------------------------------------------------------
// Notes:
// 1. You must call once computeCoeefficients() to compute
// the this->vc[] vector of 8 coefficients, before you call
// mapSourceToDestPoint().
// 2. If there was an error or the 8 coefficients were not computed,
// a -1 is returned and destPoint is just set to sourcePoint value.
// --------------------------------------------------------------
int genImageProjective::mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint)
{
if (coefficientsComputed)
{
float factor = 1.0f / (vc[6] * sourcePoint.x() + vc[7] * sourcePoint.y() + 1.);
destPoint.setX( factor * (vc[0] * sourcePoint.x() + vc[1] * sourcePoint.y() + vc[2]) );
destPoint.setY( factor * (vc[3] * sourcePoint.x() + vc[4] * sourcePoint.y() + vc[5]) );
return 0;
}
else // There was an error while computing coefficients
{
destPoint = sourcePoint; // just copy the source to destination...
return -1; // ...and return an error
}
}
//========================================
Using Breton's subdivision method (which is related to Mongo's extension method), will get you accurate arbitrary power-of-two divisions. To split into non-power-of-two divisions using those methods you will have to subdivide to sub-pixel spacing, which can be computationally expensive.
However, I believe you may be able to apply a variation of Haga's Theorem (which is used in origami to divide a side into Nths given a side divided into (N-1)ths) to the perspective-square subdivisions to produce arbitrary divisions from the closest power of 2 without having to continue subdividing.
The most elegant and fastest solution would be to find the homography matrix, which maps rectangle coordinates to photo coordinates.
With a decent matrix library it should not be a difficult task, as long as you know your math.
Keywords: Collineation, Homography, Direct Linear Transformation
However, the recursive algorithm above should work, but probably if your resources are limited, projective geometry is the only way to go.
I think the selected answer is not the best solution available. A better solution is to apply perspective (projective) transformation of a rectangle to simple grid as following Matlab script and image show. You can implement this algorithm with C++ and OpenCV as well.
function drawpersgrid
sz = [ 24, 16 ]; % [x y]
srcpt = [ 0 0; sz(1) 0; 0 sz(2); sz(1) sz(2)];
destpt = [ 20 50; 100 60; 0 150; 200 200;];
% make rectangular grid
[X,Y] = meshgrid(0:sz(1),0:sz(2));
% find projective transform matching corner points
tform = maketform('projective',srcpt,destpt);
% apply the projective transform to the grid
[X1,Y1] = tformfwd(tform,X,Y);
hold on;
%% find grid
for i=1:sz(2)
for j=1:sz(1)
x = [ X1(i,j);X1(i,j+1);X1(i+1,j+1);X1(i+1,j);X1(i,j)];
y = [ Y1(i,j);Y1(i,j+1);Y1(i+1,j+1);Y1(i+1,j);Y1(i,j)];
plot(x,y,'b');
end
end
hold off;
In the special case when you look perpendicular to sides 1 and 3, you can divide those sides in equal parts. Then draw a diagonal, and draw parallels to side 1 through each intersection of the diagonal and the dividing lines drawn earlier.
This a geometric solution I thought out. I do not know whether the 'algorithm' has a name.
Say you want to start by dividing the 'rectangle' into n pieces with vertical lines first.
The goal is to place points P1..Pn-1 on the top line which we can use to draw lines through them to the points where the left and right line meet or parallel to them when such point does not exist.
If the top and bottom line are parallel to each other just place thoose points to split the top line between the corners equidistantly.
Else place n points Q1..Qn on the left line so that theese and the top-left corner are equidistant and i < j => Qi is closer to the top-left cornern than Qj.
In order to map the Q-points to the top line find the intersection S of the line from Qn through the top-right corner and the parallel to the left line through the intersection of top and bottom line. Now connect S with Q1..Qn-1. The intersection of the new lines with the top line are the wanted P-points.
Do this analog for the horizontal lines.
Given a rotation around the y axis, especially if rotation surfaces are planar, the perspective is generated by vertical gradients. These get progressively closer in perspective. Instead of using diagonals to define four rectangles, which can work given powers of two... define two rectangles, left and right. They'll be higher than wide, eventually, if one continues to divide the surface into narrower vertical segments. This can accommodate surfaces that are not square. If a rotation is around the x axis, then horizontal gradients are needed.
What you need to do is represent it in 3D (world) and then project it down to 2D (screen).
This will require you to use a 4D transformation matrix which does the projection on a 4D homogeneous down to a 3D homogeneous vector, which you can then convert down to a 2D screen space vector.
I couldn't find it in Google either, but a good computer graphics books will have the details.
Keywords are projection matrix, projection transformation, affine transformation, homogeneous vector, world space, screen space, perspective transformation, 3D transformation
And by the way, this usually takes a few lectures to explain all of that. So good luck.

Resources