I found an implementation of the HEALpix algorithm this is the dokumentation
And the output looks very nice.
The following images show the latitude / longitude conversion to HEALpix areas.
The x-axe goes from 0 to 2 * pi. The y-axe goes from 0 to pi. The grey color represents the HEALpix pixel encoded in grey.
Nside = 1
Nside = 2
Nside = 4
Nside = 8
The different grey values are the IDs for the texture I have to use. That means, that each HEALpix pixel represents one texture. The missing part is the UV mapping within each of the HEALpix pixels like shown below:
nSide = 1 with UV mapping
Right now I am using the function:
void ang2pix_ring( const long nside, double theta, double phi, long *ipix)
Which gives me the correct texture ID. But I've no idea how to calculate the UV mapping for each HEALpix pixel.
Is there a way to calculate all four corners in lat/lon coordinates of a HEALpix pixel? Or even better a direct calculation to the UV coordinates?
BTW: I am using the RING scheme. But if the NESTED scheme is simpler to calculate I also would change to that.
After a lot of research I came to a solution for this problem:
First of all, I've changed the scheme to NESTED. With the NESTED scheme and a very high nSide value (8192), the returned value from the
void ang2pix_ring( const long nside, double theta, double phi, long *ipix)
function gives back a long value where the UV coordinates can be read out in the following way:
Bit 26 till 30 represents the level 0 (only the 12 HEALPix pixels).
By using higher levels, the Bits from 30 till 26 - (level * 2) represents the HEALPix pixels.
The leftover 26 - (level * 2) - 1 till bit 1 encode the UV texture-coordinates in the following way:
Each second odd bit shrink together represents the U coordinate and the even once represents the V coordinate.
To normalize these UV-coordinates the responding shrinked values need to be divided by the value of pow(2, (26 - level * 2) / 2).
Code says more than 1000 words:
unsigned long ignoreEverySecondBit(unsigned long value, bool odd, unsigned int countBits)
{
unsigned long result = 0;
unsigned long mask = odd == true ? 0b1 : 0b10;
countBits = countBits / 2;
for (int i = 0; i < countBits; ++i)
{
if ((value & mask) != 0)
{
result += std::pow(2, i);
}
mask = mask << 2;
}
return result;
}
//calculate the HEALPix values:
latLonToHealPixNESTED(nSide, theta, phi, &pix);
result.level = level;
result.texture = pix >> (26 - level * 2);
result.u = static_cast<float>(ignoreEverySecondBit(pix, true, 26 - level * 2));
result.v = static_cast<float>(ignoreEverySecondBit(pix, false, 26 - level * 2));
result.u = result.u / pow(2, (26 - level * 2) / 2);
result.v = result.v / pow(2, (26 - level * 2) / 2);
And of cause a few images to show the results. The blue value represents the textureID, the red value represents the U-coordinate and the green value represents the V-coordinate:
Level 0
Level 1
Level 2
Level 3
Level 4
I hope this solution will help others too.
I am trying to transition between 3+ 3D models with some nice perlin noise based on user input, just like this:
http://experience.mausoleodiaugusto.it/en/
http://na.leagueoflegends.com/en/featured/skins/project-2016
I can easily transition between two models in my vertex shader, passing down a u_morphFactor uniform variable, which I tween between 0 and 1 (0 = first model, 1 = second model). My question is how should I do it with 3 or more models.
Here is how I handle my geometry:
class CustomGeometry extends THREE.BufferGeometry {
// pass down the two models' reference geometries
constructor (geo1, geo2) {
super()
let { count } = geo1.attributes.position
let timeArray = new Float32Array(count)
let targetArray = new Float32Array(count)
for (let i = 0; i < count; i += 3) {
// assign next model's vertex position to the current one.
// if there are is no corresponding vertex, simply move to vec3(0, 0, 0)
targetArray[i + 0] = geo2.attributes.position.array[i + 0] || 0
targetArray[i + 1] = geo2.attributes.position.array[i + 1] || 0
targetArray[i + 2] = geo2.attributes.position.array[i + 2] || 0
}
// assign position AND targetPosition as attributes, so we can transition between them
this.addAttribute('a_targetPosition', new THREE.BufferAttribute(targetArray, 3))
this.addAttribute('position', geo1.attributes.position)
}
}
Now with the two models' vertices uploaded to the GPU, I can pass down the uniform and make my transition:
let uniforms = {
u_time: { value: 0 },
u_morphFactor: { value: 0 } // show first model by default
}
And the GLSL is:
vec3 new_position = mix(position, a_targetPosition, u_morphFactor);
However, I still can't wrap my head around how should I approach this same technique with 3 or more models. I guess I have to mess with the shader math that handles u_morphFactor..
TL;DR: I know how to map vertices from one 3D model to the next, simply going from 0 to 1 in my shaders. How do I do this with 3 or more models?
I am writing a web app using WebGL where I create a grid of vertices which is assembled into quads, a bit like a grid.
This is what it looks like, fully textured.
It works fine, but there is a problem. The texture is the following 8x8 image with 2 arrows, one pointing left and one pointing up.
At this point, you may have realized that the texture is flipped in all directions depending on the vertex.
I use the following Dart code to create the vertices. This shouldn't be too hard to follow for javascript developers though
vertices = new Float32List(verts * ChunkRenderer.FLOATS_PER_VERTEX);
for (int i = 0; i < w + 1; i++) {
for (int j = 0; j < h + 1; j++) {
int index = (i + j * (w + 1)) * ChunkRenderer.FLOATS_PER_VERTEX;
double x = i * 16.0;
double z = j * 16.0;
double y = 0.0;
double r = rand.nextDouble();
double g = rand.nextDouble();
double b = rand.nextDouble();
double u = i % 2 == 0 ? 0.0 : 1.0;
double v = j % 2 == 0 ? 0.0 : 1.0;
vertices.setAll(index, [
x, y, z,
u, v,
r, g, b,
]);
}
}
These lines in particular are responsible for setting the UV mapping
double u = i % 2 == 0 ? 0.0 : 1.0;
double v = j % 2 == 0 ? 0.0 : 1.0;
Is there a way to reuse the vertices for each triangle without messing up the textures or do I really need to duplicate vertices for each cells?
I like this current setup because I can move one vertex up for example and it will create a "spike" if you will. If each cell had their own vertices instead, I'd have to update 4 different vertices to get the same effect (excluding vertices on the edges of the grid of course)
Thanks
You can provide 4 pairs of (U,V) for each vertex and then decide which one use.
I don't know your render code so it's hard to provide any example.
I think the most basic form of reuse would be to prepare the mesh data for one quad, and then redraw that quad with a different translation and rotation information for each draw call. This would be significantly less performant than what you're doing, because of the extra draw calls, but would also require less memory.
I am looking to compute the axis-aligned bounding box (AABB) of a 2D ellipse on which a tranformation matrix was applied (rotation, scale, translation, etc.)
Something similar to this solution : Calculating an AABB for a transformed sphere
So far, it doesn't seem to work for 2D ellipses.
This is what I got (in pseudo-code) :
Matrix M; // Transformation matrix (already existing)
Matrix C = new Matrix( // Conic matrix
radiusX, 0, 0,
0, radiusY, 0,
0, 0, -1
);
Matrix MT = M.transpose();
Matrix CI = C.inverse();
Matrix R = M*CI*MT;
int minX = (R13 + sqrt(R13^2 - (R11 * R33))) / R33;
int minY = (R23 + sqrt(R23^2 - (R22 * R33))) / R33;
// maxX etc...
// Build AABB Rectangle out of min & max...
Simple demo of the current behavior
radiusX = 2
radiusY = 2 // To keep it simple, M is identity
// (no transformation on the ellipse)
M = /1 0 0\ // /M11 M21 M31\
|0 1 0| // |M12 M22 M32| Transform matrix format
\0 0 1/ // \0 0 1 /
C = /2 0 0\ // C as conic
|0 2 0|
\0 0 -1/
CI =/0.5 0 0\ // CI as dual conic
|0 0.5 0|
\0 0 -1/
R = /1 0 0\ * /0.5 0 0\ * /1 0 0\ // R = M*CI*MT
|0 1 0| |0 0.5 0| |0 1 0|
\0 0 1/ \0 0 -1/ \0 0 1/
= /0.5 0 0\ // /R11 R12 R13\
|0 0.5 0| // |R12 R22 R23| (R is symmetric)
\0 0 -1/ // \R13 R23 R33/
minX = (0 + sqrt(0^2 - (0.5 * -1))) / -1
= -0.7071 // Should be -2
// Also, using R = MIT*C*MI
// leads to -1.4142
Solution (using dual conic matrix)
Matrix M;
Matrix C = new Matrix(
1/radiusX^2, 0, 0,
0, 1/radiusY^2, 0,
0, 0, -1
);
Matrix MT = M.transpose();
Matrix CI = C.inverse();
Matrix R = M*CI*MT;
int minX = (R13 + sqrt(R13^2 - (R11 * R33))) / R33;
int minY = (R23 + sqrt(R23^2 - (R22 * R33))) / R33;
Final solution (no direct use of conic matrix)
Here's a simplified version.
Matrix M;
int xOffset = sqrt((M11^2 * radiusX^2) + (M21^2 * radiusY^2));
int yOffset = sqrt((M12^2 * radiusX^2) + (M22^2 * radiusY^2));
int centerX = (M11 * ellipse.x + M21 * ellipse.y) + M31; // Transform center of
int centerY = (M12 * ellipse.x + M22 * ellipse.y) + M32; // ellipse using M
// Most probably, ellipse.x = 0 for you, but my implementation has an actual (x,y) AND a translation
int xMin = centerX - xOffset;
int xMax = centerX + xOffset;
int yMin = centerY - yOffset;
int yMax = centerY + yOffset;
From dual conic
So you state that M is a transformation matrix. But what does it transform, is it points or lines? I assume points. How do you represent points, as a row vector so that the point is on the left and the matrix on the right, or as a column vector so that the matrix is on the left and the point on the right of a multiplication? I'll assume column vectors. So a transformation would be p' = M*p for some point p.
Next is C. The way you write it, that's an ellipse but not with the radii you are using. A point lies on the ellipse if it satisfies (x/radiusX)^2 + (y/radiusY)^2 = 1 so the values on the main diagonal have to be (1/radiusX^2, 1/radiusY^2, -1). I repeatedly missed this mistake in pervious revisions of my answer.
Next you combine these things. Suppose CP were the primal conic, i.e. the conic as a set of points. Then you'd obtain the transformed version by doing MT.inverse()*CP*M.inverse(). The reason is because you apply M.inverse() to every point and then check whether it lies on the original conic. But you are not using M.inverse(), you are using M. This indicates that you try to transform a dual conic. If M transforms points, then MT.inverse() transforms lines, so M*CD*MT is the correct transformation if CD is a dual conic.
And if R is a dual conic, then your formulas are correct. So perhaps the main problem with your code is the fact that you forgot to use inverse radii in the matrix C.
From primal conic
When I read your post for the first time, I assumed R would describe a set of points, i.e. that a point (x,y) lies on that ellipse if (x,y,1)*R*(x,y,1).transpose()=0. Based on this, I did come up with formulas for the AABB without using the dual conic. I'm not saying that this is simpler, particularly not if you have matrix inversion available as a building block. But I'll still leave it here for reference. Keep in mind that the R in this paragraph is a different one from the one used in your code example.
For my approach, consider that R*(1,0,0) (which is simply the first column of R) is some vector (a,b,c) which you can interpret as a definition of a line ax+by+c=0. Intersect that line with the conic and you get the points where the tangents are horizontal, which are the extrema in y direction. Do the same for R*(0,1,0) (i.e. the seond column) to find extrema in the x direction.
The key idea here is that R*p computes the polar line for some point p, so we are constructing the polar line for the point at infinity in x resp. y direction. That polar line will intersect the conic in those points where the tangents through p touch the conic, which in this case would be horizontal resp. vertical tangents since parallel lines intersect at infinity.
If I do the above computation symbolically, I get the following formulas:
xmin, xmax = (R13*R22^2 - R12*R22*R23 ± sqrt(R13^2*R22^4 - 2*R12*R13*R22^3*R23 + R11*R22^3*R23^2 + (R12^2*R22^3 - R11*R22^4)*R33))/(R12^2*R22 - R11*R22^2)
ymin, ymax = (R11*R12*R13 - R11^2*R23 ± sqrt(R11^3*R13^2*R22 - 2*R11^3*R12*R13*R23 + R11^4*R23^2 + (R11^3*R12^2 - R11^4*R22)*R33))/(R11^2*R22 - R11*R12^2)
These expressions can certainly be simplified, but it should get you started. Feel free to edit this post if you reformulate this to something simpler, or easier to read, or whatever.
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.