Perspective correct texture mapping; z distance calculation might be wrong - c++

I'm making a software rasterizer, and I've run into a bit of a snag: I can't seem to get perspective-correct texture mapping to work.
My algorithm is to first sort the coordinates to plot by y. This returns a highest, lowest and center point. I then walk across the scanlines using the delta's:
// ordering by y is put here
order[0] = &a_Triangle.p[v_order[0]];
order[1] = &a_Triangle.p[v_order[1]];
order[2] = &a_Triangle.p[v_order[2]];
float height1, height2, height3;
height1 = (float)((int)(order[2]->y + 1) - (int)(order[0]->y));
height2 = (float)((int)(order[1]->y + 1) - (int)(order[0]->y));
height3 = (float)((int)(order[2]->y + 1) - (int)(order[1]->y));
// x
float x_start, x_end;
float x[3];
float x_delta[3];
x_delta[0] = (order[2]->x - order[0]->x) / height1;
x_delta[1] = (order[1]->x - order[0]->x) / height2;
x_delta[2] = (order[2]->x - order[1]->x) / height3;
x[0] = order[0]->x;
x[1] = order[0]->x;
x[2] = order[1]->x;
And then we render from order[0]->y to order[2]->y, increasing the x_start and x_end by a delta. When rendering the top part, the delta's are x_delta[0] and x_delta[1]. When rendering the bottom part, the delta's are x_delta[0] and x_delta[2]. Then we linearly interpolate between x_start and x_end on our scanline. UV coordinates are interpolated in the same way, ordered by y, starting at begin and end, to which delta's are applied each step.
This works fine except when I try to do perspective correct UV mapping. The basic algorithm is to take UV/z and 1/z for each vertex and interpolate between them. For each pixel, the UV coordinate becomes UV_current * z_current. However, this is the result:
The inversed part tells you where the delta's are flipped. As you can see, the two triangles both seem to be going towards different points in the horizon.
Here's what I use to calculate the Z at a point in space:
float GetZToPoint(Vec3 a_Point)
{
Vec3 projected = m_Rotation * (a_Point - m_Position);
// #define FOV_ANGLE 60.f
// static const float FOCAL_LENGTH = 1 / tanf(_RadToDeg(FOV_ANGLE) / 2);
// static const float DEPTH = HALFHEIGHT * FOCAL_LENGTH;
float zcamera = DEPTH / projected.z;
return zcamera;
}
Am I right, is it a z buffer issue?

ZBuffer has nothing to do with it.
THe ZBuffer is only useful when triangles are overlapping and you want to make sure that they are drawn correctly (e.g. correctly ordered in the Z). The ZBuffer will, for every pixel of the triangle, determine if a previously placed pixel is nearer to the camera, and if so, not draw the pixel of your triangle.
Since you are drawing 2 triangles which don't overlap, this can not be the issue.
I've made a software rasterizer in fixed point once (for a mobile phone), but I don't have the sources on my laptop. So let me check tonight, how I did it. In essence what you've got is not bad! A thing like this could be caused by a very small error
General tips in debugging this is to have a few test triangles (slope left-side, slope right-side, 90 degree angles, etc etc) and step through it with the debugger and see how your logic deals with the cases.
EDIT:
peudocode of my rasterizer (only U, V and Z are taken into account... if you also want to do gouraud you also have to do everything for R G and B similar as to what you are doing for U and V and Z:
The idea is that a triangle can be broken down in 2 parts. The top part and the bottom part. The top is from y[0] to y[1] and the bottom part is from y[1] to y[2]. For both sets you need to calculate the step variables with which you are interpolating. The below example shows you how to do the top part. If needed I can supply the bottom part too.
Please note that I do already calculate the needed interpolation offsets for the bottom part in the below 'pseudocode' fragment
first order the coords(x,y,z,u,v) in the order so that coord[0].y < coord[1].y < coord[2].y
next check if any 2 sets of coordinates are identical (only check x and y). If so don't draw
exception: does the triangle have a flat top? if so, the first slope will be infinite
exception2: does the triangle have a flat bottom (yes triangles can have these too ;^) ) then the last slope too will be infinite
calculate 2 slopes (left side and right side)
leftDeltaX = (x[1] - x[0]) / (y[1]-y[0]) and rightDeltaX = (x[2] - x[0]) / (y[2]-y[0])
the second part of the triangle is calculated dependent on: if the left side of the triangle is now really on the leftside (or needs swapping)
code fragment:
if (leftDeltaX < rightDeltaX)
{
leftDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
rightDeltaX2 = rightDeltaX
leftDeltaU = (u[1]-u[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaU2 = (u[2]-u[1]) / (y[2]-y[1])
leftDeltaV = (v[1]-v[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaV2 = (v[2]-v[1]) / (y[2]-y[1])
leftDeltaZ = (z[1]-z[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaZ2 = (z[2]-z[1]) / (y[2]-y[1])
}
else
{
swap(leftDeltaX, rightDeltaX);
leftDeltaX2 = leftDeltaX;
rightDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
leftDeltaU = (u[2]-u[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaU2 = leftDeltaU
leftDeltaV = (v[2]-v[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaV2 = leftDeltaV
leftDeltaZ = (z[2]-z[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaZ2 = leftDeltaZ
}
set the currentLeftX and currentRightX both on x[0]
set currentLeftU on leftDeltaU, currentLeftV on leftDeltaV and currentLeftZ on leftDeltaZ
calc start and endpoint for first Y range: startY = ceil(y[0]); endY = ceil(y[1])
prestep x,u,v and z for the fractional part of y for subpixel accuracy (I guess this is also needed for floats)
For my fixedpoint algorithms this was needed to make the lines and textures give the illusion of moving in much finer steps then the resolution of the display)
calculate where x should be at y[1]: halfwayX = (x[2]-x[0]) * (y[1]-y[0]) / (y[2]-y[0]) + x[0]
and same for U and V and z: halfwayU = (u[2]-u[0]) * (y[1]-y[0]) / (y[2]-y[0]) + u[0]
and using the halfwayX calculate the stepper for the U and V and z:
if(halfwayX - x[1] == 0){ slopeU=0, slopeV=0, slopeZ=0 } else { slopeU = (halfwayU - U[1]) / (halfwayX - x[1])} //(and same for v and z)
do clipping for the Y top (so calculate where we are going to start to draw in case the top of the triangle is off screen (or off the clipping rectangle))
for y=startY; y < endY; y++)
{
is Y past bottom of screen? stop rendering!
calc startX and endX for the first horizontal line
leftCurX = ceil(startx); leftCurY = ceil(endy);
clip the line to be drawn to the left horizontal border of the screen (or clipping region)
prepare a pointer to the destination buffer (doing it through array indexes everytime is too slow)
unsigned int buf = destbuf + (ypitch) + startX; (unsigned int in case you are doing 24bit or 32 bits rendering)
also prepare your ZBuffer pointer here (if you are using this)
for(x=startX; x < endX; x++)
{
now for perspective texture mapping (using no bilineair interpolation you do the following):
code fragment:
float tv = startV / startZ
float tu = startU / startZ;
tv %= texturePitch; //make sure the texture coordinates stay on the texture if they are too wide/high
tu %= texturePitch; //I'm assuming square textures here. With fixed point you could have used &=
unsigned int *textPtr = textureBuf+tu + (tv*texturePitch); //in case of fixedpoints one could have shifted the tv. Now we have to multiply everytime.
int destColTm = *(textPtr); //this is the color (if we only use texture mapping) we'll be needing for the pixel
dummy line
dummy line
dummy line
optional: check the zbuffer if the previously plotted pixel at this coordinate is higher or lower then ours.
plot the pixel
startZ += slopeZ; startU+=slopeU; startV += slopeV; //update all interpolators
} end of x loop
leftCurX+= leftDeltaX; rightCurX += rightDeltaX; leftCurU+= rightDeltaU; leftCurV += rightDeltaV; leftCurZ += rightDeltaZ; //update Y interpolators
} end of y loop
//this is the end of the first part. We now have drawn half the triangle. from the top, to the middle Y coordinate.
// we now basically do the exact same thing but now for the bottom half of the triangle (using the other set of interpolators)
sorry about the 'dummy lines'.. they were needed to get the markdown codes in sync. (took me a while to get everything sort off looking as intended)
let me know if this helps you solve the problem you are facing!

I don't know that I can help with your question, but one of the best books on software rendering that I had read at the time is available online Graphics Programming Black Book by Michael Abrash.

If you are interpolating 1/z, you need to multiply UV/z by z, not 1/z. Assuming you have this:
UV = UV_current * z_current
and z_current is interpolating 1/z, you should change it to:
UV = UV_current / z_current
And then you might want to rename z_current to something like one_over_z_current.

Related

Triangles that are sometimes equilateral and sometimes not?

I am trying to produce random equilateral triangles on the console screen.
The method I am using is creating a center point for the triangle (randomly positioned), moving the center point to the origin (0,0) and then creating 3 points from the center (adding the radius(random number) of the triangle to the Y axis of each point). Then I rotate 2 of the points, one at 120 degrees and the other at 240 making an equilateral triangle then draw lines between the points. Then bring the points back to the original plot relating to the centroid.
This for the most past of the time works and I get an equilateral triangle, however other times I don't quite get an equilateral triangle and I am at a complete loss as to why.
I am using Brensenham's line algorithm to draw the line between points.
Image of working triangle: http://imgur.com/GpF406O
Image of broken triangle: http://imgur.com/Oa2BYun
Here is the code that plots the coords for the triangle:
void Triangle::createVertex(Vertex cent)
{
// angle of 120 in radians
double s120 = sin(2.0943951024);
double c120 = cos(2.0943951024);
// angle of 240 in radians
double s240 = sin(4.1887902048);
double c240 = cos(4.1887902048);
// bringing centroid to the origin and saving old pos to move later on
int x = cent.getX();
int y = cent.getY();
cent.setX(0);
cent.setY(0);
// creating the points all equal distance from the centroid
Vertex v1(cent.getX(), cent.getY() + radius);
Vertex v2(cent.getX(), cent.getY() + radius);
Vertex v3(cent.getX(), cent.getY() + radius);
// rotate points
double newx = v1.getX() * c120 - v1.getY() * s120;
double newy = v1.getY() * c120 + v1.getX() * s120;
double xnew = v2.getX() * c240 - v2.getY() * s240;
double ynew = v2.getY() * c240 + v2.getX() * s240;
// giving the points the actual location in relation the the old pos of the centroid
v1.setX(newx + x);
v1.setY(newy + y);
v2.setX(xnew + x);
v2.setY(ynew + y);
v3.setX(x);
v3.setY(y + radius);
// adding the to a list (list is used in a function to draw the lines)
vertices.push_back(v1);
vertices.push_back(v2);
vertices.push_back(v3);
}
Looking at the images of your two triangles (and looking at the line drawing algorithm) you are drawing lines as a series of discrete pixels. That means a vertex must fall in a pixel (it can't be on a boundary) like in this image.
So what happens if your vertex falls on* a border between pixels? Your line drawing algorithm has to make a decision on which pixel to put the vertex in.
Looking at the algorithm description on wikipedia and the c++ implementation on a page a www.cs.helsinki.fi
I see that both list implementations using integer arithmetic** which in this case is not unreasonable given you have discreet rows of pixels. This means that if your floating point calculations put one vertex above the threshold of the integer label for the next row of pixels when the floor (conversion from float to int) is done, but the other vertex is below that threshold then the two vertices will be placed on different rows.
think v1.y = 5.00000000000000000001 and v2.y = 4.99999999999999999999 which leads to v1 being placed on row 5 and v2 being placed on row 4.
This explains why you only see the issue occurring occasionally, you only occasionally have your vertices land on a boundary like this.
In order to fix a couple of things come to mind:
Fix it when you assign values to your vertices, the y values are the same anyways.
given:
v1.getX() = v2.getX() = 0 (defined by your code)
v1.getY() = v2.getY() = radius (defined by your code)
cos(120 degrees) = cos(240 degrees) ('tis true)
This reduces your two y values to
double newy = v1.getY() * c120
double ynew = v1.getY() * c120
ergo:
v1.setY(newy + y);
v2.setY(newy + y);
If you wrote your own Brensenham's algorithm implementation you could add a check in that code to make sure your vertices are at the same height, but that seems like a really bad place to put that kind of check since the height of the endpoints is specific to your problem and not drawing lines in general.
*Or not exactly on, but close enough you can't tell the difference after accounting for floating point error
**The algorithm is not restricted to integer arithmetic, but I suspect given the irregularity of your problem and the way the algorithm has been presented, along with the fact that you are using discreet characters for the lines in your images the integer arithmetic is the issue.

What is wrong with my Z-buffer calculations?

I am implementing a Z-buffer to determine which pixels should be drawn in a simple scene filled with triangles. I have structural representations of a triangle, a vertex, a vector (the mathematical (x, y, z) kind, of course), as well as a function that draws an individual pixel to the screen. Here are the structures I have:
struct vertex{
float x, y, z;
... //other members for lighting, etc. that will be used later and are not relevant here
};
struct myVector{
float x, y, z;
};
struct triangle{
... //other stuff
vertex v[3];
};
Unfortunately, as I scan convert my triangles to the screen, which relies on calculating depths to determine what is visible and gets to be drawn, I am getting incorrect/unrealistic Z values (e.g., the depth at a point in the triangle is out of bounds of the depths of all 3 of its vertices)! I have been looking through my code over and over and cannot figure out whether my math is off or I have a careless mistake somewhere, so I will try to present exactly what I am trying to do in the hopes that someone else can see something that I don't. (And I have looked carefully at making sure that floating point values remain floating point values, that I am passing in arguments correctly, etc., so this is really baffling!)
Overall, my scan conversion algorithm fills pixels across a scan line like this (pseudocode):
for all triangles{
... //Do edge-related sorting stuff, etc...get ready to fill pixels
float zInit; //the very first z-value, with a longer calculation
float zPrev; //the "zk" needed when interpolating "zk+1" across a scan line
for(xPos = currentX at left side edge; xPos != currentX at right side edge; currentX++){
*if this is first pixel acorss scan line, calculate zInit and draw pixel/store z if depth is less
than current zBuffer value at this point. Then set zPrev = zInit.
*otherwise, interpolate zNext using zPrev. Draw pixel/store z if depth < current zBuffer value at
this point. Then set zPrev = zNext.
}
... //other scan conversion stuff...update x values, etc.
}
To get the value of zInit for each scan line, I consider the plane equation Ax + By + Cz + D = 0 and rearrange it to get z = -1*(Ax + By + D)/C, where x and y are plugged in as the current x value across a scan line and the current scan line value itself, respectively.
For subsequent z values across a scan line, I interpolate as zk+1 = zk - A/C, where A and C come from the plane equation.
To get the A, B and C for these z calculations, I need the normal vector of the plane defined by the 3 vertices (the array vertex v[3]) of the current triangle. To get this normal (which I named planeNormal in the code), I defined a cross product function:
myVector cross(float x1, float y1, float z1, float x2, float y2, float z2)
{
float crX = (y1*z2) - (z1*y2);
float crY = (z1*x2) - (x1*z2);
float crZ = (x1*y2) - (y1*x2);
myVector res;
res.x = crX;
res.y = crY;
res.z = crZ;
return res;
}
To get the D value for the plane equation/my z calculations, I use the plane equation A(x-x1) + B(y-y1) + C(z-z1) = 0, where (x1, y1, z1) is just a reference point in the plane. I just chose the triangle vertex v[0] for the reference point and rearranged:
Ax + By + Cz = Ax1 + By1 + Cz1
Thus, D = Ax1 + By1 + Cz1
So, finally, to get the A, B, C, and D for the z calculations, I did this for each triangle, where trianglelist[nt] is the triangle at current index nt in the overall triangle array for the scene:
float pA = planeNormal.x;
float pB = planeNormal.y;
float pC = planeNormal.z;
float pD = (pA*trianglelist[nt].v[0].x)+(pB*trianglelist[nt].v[0].y)+(pC*trianglelist[nt].v[0].z);
From here, within the scan conversion algorithm I described, I calculated the zs:
zInit = -1*((pA*cx)+(pB*scanLine)+(pD))/(pC); //cx is current x value; scanLine is current y value
...
...
float zNext = zPrev - (pA/pC);
Alas, after all that careful work, something is off! In some triangles, the depth values come out realistic (except for the sign). With triangle given by the vertices (200, 10, 75), (75, 200, 75) and (15, 60, 75), all depths come out as -75. The same happened for other triangles with all vertices at the same depth. But with the vertices (390, 300, 105), (170, 360, 80), (190, 240, 25), all of the z values are over 300! The very first one comes out as 310.5, and the rest just get bigger, with a max around 365. This should not happen when the deepest vertex is at z = 105!!! So, after all of the rambling, can anyone see what might have caused this? I wouldn't be surprised if it's a sign-related thing, but where (after all, the absolute values are right in the constant depth cases)?
The correct equations are:
n = cross (v[2] - v[0], v[1] - v[0]);
D = - dot (n, v[0]);
Note the minus sign.
you should have a look at www.scratchapixel.com, particularly this lesson:
http://scratchapixel.com/lessons/3d-advanced-lessons/perspective-and-orthographic-projection-matrix/
It contains a self-contained program that shows you how to project vertices.

Simple algorithm for drawing filled ellipse in C/C++

On SO, found the following simple algorithm for drawing filled circles:
for(int y=-radius; y<=radius; y++)
for(int x=-radius; x<=radius; x++)
if(x*x+y*y <= radius*radius)
setpixel(origin.x+x, origin.y+y);
Is there an equally simple algorithm for drawing filled ellipses?
Simpler, with no double and no division (but be careful of integer overflow):
for(int y=-height; y<=height; y++) {
for(int x=-width; x<=width; x++) {
if(x*x*height*height+y*y*width*width <= height*height*width*width)
setpixel(origin.x+x, origin.y+y);
}
}
We can take advantage of two facts to optimize this significantly:
Ellipses have vertical and horizontal symmetry;
As you progress away from an axis, the contour of the ellipse slopes more and more.
The first fact saves three-quarters of the work (almost); the second fact tremendously reduces the number of tests (we test only along the edge of the ellipse, and even there we don't have to test every point).
int hh = height * height;
int ww = width * width;
int hhww = hh * ww;
int x0 = width;
int dx = 0;
// do the horizontal diameter
for (int x = -width; x <= width; x++)
setpixel(origin.x + x, origin.y);
// now do both halves at the same time, away from the diameter
for (int y = 1; y <= height; y++)
{
int x1 = x0 - (dx - 1); // try slopes of dx - 1 or more
for ( ; x1 > 0; x1--)
if (x1*x1*hh + y*y*ww <= hhww)
break;
dx = x0 - x1; // current approximation of the slope
x0 = x1;
for (int x = -x0; x <= x0; x++)
{
setpixel(origin.x + x, origin.y - y);
setpixel(origin.x + x, origin.y + y);
}
}
This works because each scan line is shorter than the previous one, by at least as much
as that one was shorter than the one before it. Because of rounding to integer pixel coordinates, that's not perfectly accurate -- the line can be shorter by one pixel less that that.
In other words, starting from the longest scan line (the horizontal diameter), the amount by which each line is shorter than the previous one, denoted dx in the code, decreases by at most one, stays the same, or increases. The first inner for finds the exact amount by which the current scan line is shorter than the previous one, starting at dx - 1 and up, until we land just inside the ellipse.
| x1 dx x0
|###### |<-->|
current scan line --> |########### |<>|previous dx
previous scan line --> |################ |
two scan lines ago --> |###################
|#####################
|######################
|######################
+------------------------
To compare the number of inside-ellipse tests, each asterisk is one pair of coordinates tested in the naive version:
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
*********************************************
... and in the improved version:
*
**
****
***
***
***
**
**
An ellipse (about the origin) is a circle that has been linearly stretched along the x or y axes. So you can modify your loop like this:
for(int y=-height; y<=height; y++) {
for(int x=-width; x<=width; x++) {
double dx = (double)x / (double)width;
double dy = (double)y / (double)height;
if(dx*dx+dy*dy <= 1)
setpixel(origin.x+x, origin.y+y);
}
}
You can see that if width == height == radius, then this is equivalent to your code for drawing a circle.
Replace
x*x+y*y <= radius*radius
with
Axx*x*x + 2*Axy*x*y + Ayy*y*y < radius*radius
where you have three constants, Axx, Axy, Ayy. When Axy=0, the ellipse will have its axes straight horizontal and vertical. Axx=Ayy=1 makes a circle. The bigger Axx, the smaller the width. Similar for Ayy and height. For an arbitrary ellipse tilted at any given angle, it takes a bit of algebra to figure out the constants.
Mathematically Axx, Axy, Ayy are a "tensor" but perhaps you don't want to get into that stuff.
UPDATE - detailed math. I don't think S.O. can make nice math like Math S.E. so this will look crude.
You want to draw (or do whatever) with an ellipse in x,y coordinates. The ellipse is tilted. We create an alternative coordinate system x',y' aligned with the ellipse. Clearly, points on the ellipse satisfy
(x'/a)^2 + (y'/b)^2 = 1
By contemplating some well-chosen random points we see that
x' = C*x + S*y
y' = -S*x + C*y
where S, C are sin(θ) and cos(θ), θ being the angle of the x' axis w.r.t. the x axis. We can shorten this with notation x = (x,y) and similar for primed, and R a 2x2 matrix involving C and S:
x' = R x
The ellipse equation can be written
T(x') A'' x' = 1
where 'T' to indicates transpose and, dropping '^' to avoid poking everyone in the eyes, so that "a2" really means a^2,
A'' =
1/a2 0
0 1/b2
Using x' = Rx we find
T(Rx) A'' Rx = 1
T(x) T(R) A'' R x =1
T(x) A x = 1
where A, the thing you need to know to make your tilted drawing scan line algorithm work, is
A = T(R) A'' R =
C2/a2+S2/b2 SC(1/a2-1/b2)
SC/(1/a2-1/b2) S2/a2 + C2/b2
Multiply these by x and y according to T(x)Ax and you've got it.
A fast Bresenham type algorithm, as proposed by this paper, works really well. Here's an OpenGL implementation that I wrote for the same.
The basic premise is that you plot the curve on one quadrant, which we can mirror on to the other three quadrants. These vertices are computed using an error function, similar to what you use in the midpoint circle algorithm for circles. The paper I have linked above has a pretty nifty proof for the equation, and the algorithm distills down to just checking if a given vertex is within an ellipse or not, just by substituting its values in the error function. The algorithm also tracks the tangent line slope of the curve we are drawing in the first quadrant, and increments x or y depending on the slope value - which contributes further to the performance of the algorithm. Here's an image that shows what's going on:
As for filling the ellipse, once we know the vertices in each quadrant (which is essentially mirror reflections across x and y axes), we get 4 vertices for every vertex that we compute - which is sufficient to draw a quad (in OpenGL anyway). Once we draw quads for all such vertices, we get a filled ellipse. The implementation I have given employs VBO for performance reasons, but you don't strictly need it.
The implementation also shows you how to achieve a filled ellipse using triangles and lines instead of drawing quads - the quads are clearly better though, as it is a primitive and we only draw one quad for 4 vertices, as opposed to one triangle per vertex in the triangle implementation.

opengl select sphere with mouse

I have a number of spheres in 3d space which the user should be able to select with a mouse click. Now I've seen some examples around using gluUnProject so I gave it a shot. So I have (please correct me every step of the way if I'm wrong because I'm not 100% sure of any part of it):
def compute_pos(x, y, z):
'''
Compute the 3d opengl coordinates for 3 coordinates.
#param x,y: coordinates from canvas taken with mouse position
#param z: coordinate for z-axis
#return; (gl_x, gl_y, gl_z) tuple corresponding to coordinates in OpenGL context
'''
modelview = numpy.matrix(glGetDoublev(GL_MODELVIEW_MATRIX))
projection = numpy.matrix(glGetDoublev(GL_PROJECTION_MATRIX))
viewport = glGetIntegerv(GL_VIEWPORT)
winX = float(x)
winY = float(viewport[3] - float(y))
winZ = z
return gluUnProject(winX, winY, winZ, modelview, projection, viewport)
Then, having the x and y of a mouse click and the position of the center of the sphere:
def is_picking(x, y, point):
ray_start = compute_pos(x, y, -1)
ray_end = compute_pos(x, y, 1)
d = _compute_2d_distance( (ray_start[0], ray_start[1]),
(ray_end[0], ray_end[1]),
(point[0], point[1]))
if d > CUBE_SIZE:
return False
d = _compute_2d_distance( (ray_start[0], ray_start[2]),
(ray_end[0], ray_end[2]),
(point[0], point[2]))
if d > CUBE_SIZE:
return False
d = _compute_2d_distance( (ray_start[1], ray_start[2]),
(ray_end[1], ray_end[2]),
(point[1], point[2]))
if d > CUBE_SIZE:
return False
return True
So because my 3d geometry is not good at all, I compute two points as a ray start and end point, the go into 2d 3 times eliminating one dimension at a time and compute the distance there between my line and the center of the sphere. If any of those distances are bigger than my sphere ray the it's not clicked. I think the formula for the distance is correct but just in case:
def _compute_2d_distance(p1, p2, target):
'''
Compute the distance between the line defined by two points and a target point.
#param p1: first point that defines the line
#param p2: second point that defines the line
#param target: the point to which distance needs to be computed
#return: distance from point to line
'''
if p2[0] != p1[0]:
if p2[1] == p1[1]:
return abs(p2[0] - p1[0])
a = (p2[1] - p1[1])/(p2[0] - p1[0])
b = -1
c = p1[1] + p1[0] * (p2[1] - p1[1]) / (p2[0] - p1[0])
d = abs(a * target[0] + b * target[1] + c) / math.sqrt(a * a + b * b)
return d
if p2[0] == p1[0]:
d = abs(p2[1] - p1[1])
return d
return None
Now the code seems to work fine in the start position. But after you use to mouse and rotate the screen even for a little bit, nothing works as expected anymore.
Hi there are a lot of solutions for this kind of problem.
Ray casting is one of the best but it involves a lot of geometry knowledge and it is not easy at all.
Moreover the gluUnProject is not available in other OpenGL implementations such as ES for mobile devices (though you can write it in your matrices manipulation functions).
I personally prefer the color picking solution which is quite flexible and very fast computing wise.
The idea is to render the select-able (only the select-able for performance boost) with a given unique color on an offscreen buffer.
Then you take the color of the pixel at the coordinates clicked by the user and you select the corresponding 3D object.
Cheers
Maurizio Benedetti

gluProject on NDS?

I've been struggling with this for a good while now. I'm trying to determine the screen coordinates of the vertexes in a model on the screen of my NDS using devKitPro. The library seems to implement some functionality of OpenGL, but in particular, the gluProject function is missing, which would (I assume) allow me to do just exactly that, easily.
I've been trying for a good while now to calculate the screen coordinates manually using the projection matricies that are stored in the DS's registers, but I haven't been having much luck, even when trying to build the projection matrix from scratch based on OpenGL's documentation. Here is the code I'm trying to use:
void get2DPoint(v16 x, v16 y, v16 z, float &result_x, float &result_y)
{
//Wait for the graphics engine to be ready
/*while (*(int*)(0x04000600) & BIT(27))
continue;*/
//Read in the matrix that we're currently transforming with
double currentMatrix[4][4]; int i;
for (i = 0; i < 16; i++)
currentMatrix[0][i] =
(double(((int*)0x04000640)[i]))/(double(1<<12));
//Now this hurts-- take that matrix, and multiply it by the projection matrix, so we obtain
//proper screen coordinates.
double f = 1.0 / tan(70.0/2.0);
double aspect = 256.0/192.0;
double zNear = 0.1;
double zFar = 40.0;
double projectionMatrix[4][4] =
{
{ (f/aspect), 0.0, 0.0, 0.0 },
{ 0.0, f, 0.0, 0.0 },
{ 0.0, 0.0, ((zFar + zNear) / (zNear - zFar)), ((2*zFar*zNear)/(zNear - zFar)) },
{ 0.0, 0.0, -1.0, 0.0 },
};
double finalMatrix[4][4];
//Ugh...
int mx = 0; int my = 0;
for (my = 0; my < 4; my++)
for (mx = 0; mx < 4; mx++)
finalMatrix[mx][my] =
currentMatrix[my][0] * projectionMatrix[0][mx] +
currentMatrix[my][1] * projectionMatrix[1][mx] +
currentMatrix[my][2] * projectionMatrix[2][mx] +
currentMatrix[my][3] * projectionMatrix[3][mx] ;
double dx = ((double)x) / (double(1<<12));
double dy = ((double)y) / (double(1<<12));
double dz = ((double)z) / (double(1<<12));
result_x = dx*finalMatrix[0][0] + dy*finalMatrix[0][1] + dz*finalMatrix[0][2] + finalMatrix[0][3];
result_y = dx*finalMatrix[1][0] + dy*finalMatrix[1][1] + dz*finalMatrix[1][2] + finalMatrix[1][3];
result_x = ((result_x*1.0) + 4.0)*32.0;
result_y = ((result_y*1.0) + 4.0)*32.0;
printf("Result: %f, %f\n", result_x, result_y);
}
There are lots of shifts involved, the DS works internally using fixed point notation and I need to convert that to doubles to work with. What I'm getting seems to be somewhat correct-- the pixels are translated perfectly if I'm using a flat quad that's facing the screen, but the rotation is wonky. Also, since I'm going by the projection matrix (which accounts for the screen width/height?) the last steps I'm needing to use don't seem right at all. Shouldn't the projection matrix be accomplishing the step up to screen resolution for me?
I'm rather new to all of this, I've got a fair grasp on matrix math, but I'm not as skilled as I would like to be in 3D graphics. Does anyone here know a way, given the 3D, non-transformed coordinates of a model's vertexes, and also given the matricies which will be applied to it, to actually come up with the screen coordinates, without using OpenGL's gluProject function? Can you see something blatantly obvious that I'm missing in my code? (I'll clarify when possible, I know it's rough, this is a prototype I'm working on, cleanliness isn't a high priority)
Thanks a bunch!
PS: As I understand it, currentMatrix, which I pull from the DS's registers, should be giving me the combined projection, translation, and rotation matrix, as it should be the exact matrix that's going to be used for the translation by the DS's own hardware, at least according to the specs at GBATEK. In practise, it doesn't seem to actually have the projection coordinates applied to it, which I suppose has something to do with my issues. But I'm not sure, as calculating the projection myself isn't generating different results.
That is almost correct.
The correct steps are:
Multiply Modelview with Projection matrix (as you've already did).
Extend your 3D vertex to a homogeneous coordinate by adding a W-component with value 1. E.g your (x,y,z)-vector becomes (x,y,z,w) with w = 1.
Multiply this vector with the matrix product. Your matrix should be 4x4 and your vector of size 4. The result will be a vector of size4 as well (don't drop w yet!). The result of this multiplication is your vector in clip-space. FYI: You can already do a couple of very useful things here with this vector: Test if the point is on the screen. The six conditions are:
x &lt -w : Point is outside the screen (left of the viewport)
x &gt W : Point is outside the screen (right of the viewport)
y &lt -w : Point is outside the screen (above the viewport)
y &gt w : Point is outside the screen (below the viewport)
z &lt -w : Point is outside the screen (beyond znear)
z &gt w : Point is outside the screen (beyond zfar)
Project your point into 2D space. To do this divide x and y by w:
x' = x / w;
y' = y / w;
If you're interested in the depth-value (e.g. what gets written to the zbuffer) you can project z as well:
z' = z / w
Note that the previous step won't work if w is zero. This case happends if your point is equal to the camera position. The best you could do in this case is to set x' and y' to zero. (will move the point into the center of the screen in the next step..).
Final Step: Get the OpenGL viewport coordinates and apply it:
x_screen = viewport_left + (x' + 1) * viewport_width * 0.5;
y_screen = viewport_top + (y' + 1) * viewport_height * 0.5;
Important: The y coordinate of your screen may be upside down. Contrary to most other graphic APIs in OpenGL y=0 denotes the bottom of the screen.
That's all.
I'll add some more thoughts to Nils' thorough answer.
don't use doubles. I'm not familiar with NDS, but I doubt it's got any hardware for double math.
I also doubt model view and projection are not already multiplied if you are reading the hardware registers. I have yet to see a hardware platform that does not use the full MVP in the registers directly.
the matrix storage into registers may or may not be in the same order as OpenGL. if they are not, the multiplication matrix-vector needs to be done in the other order.