I've set up a demo with simple 3d first-person demo using C++ and OpenGL, and it seems to work reasonably well. My goal is this: when the user points the camera at a plane and clicks the left mouse button, I want to draw the intersection of a ray pointing in the direction the camera is facing from the player's position with that plane.
So, I start off with two Vectors, Vector position and Vector rotation, where Vector is a pretty standard three-dimensional vector class:
class Vector
{
public:
GLfloat x, y, z;
Vector() {};
Vector(GLfloat x, GLfloat y, GLfloat z)
{
this->x = x;
this->y = y;
this->z = z;
}
GLfloat dot(const Vector &vector) const
{
return x * vector.x + y * vector.y + z * vector.z;
}
... etc ...
And Plane p, with Plane being a simple struct storing the normal of the plane and d. I copied this struct directly from the book "Real-Time Collision Detection," by Christer Ericson:
struct Plane
{
Vector n; // Plane normal. Points x on the plane satisfy Dot(n,x) = d
float d; // d = dot(n,p) for a given point p on the plane
};
To start, I take position as the start of the ray, which I call a. I use that point and rotation to find the end of the ray, b. Then I use an algorithm for finding the intersection of a ray and a plane from that same book. I've actually implemented the same method myself, but I'm using the code from the book directly here just to make sure I didn't mess anything up:
void pickPoint()
{
const float length = 100.0f;
// Points a and b
Vector a = State::position;
Vector b = a;
// Find point b of directed line ab
Vector radians(Math::rad(State::rotation.x), Math::rad(State::rotation.y), 0);
const float lengthYZ = Math::cos(radians.x) * length;
b.y -= Math::sin(radians.x) * length;
b.x += Math::sin(radians.y) * lengthYZ;
b.z -= Math::cos(radians.y) * lengthYZ;
// Compute the t value for the directed line ab intersecting the plane
Vector ab = b - a;
GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);
printf("Plane normal: %f, %f, %f\n", p.n.x, p.n.y, p.n.z);
printf("Plane value d: %f\n", p.d);
printf("Rotation (degrees): %f, %f, %f\n", State::rotation.x, State::rotation.y, State::rotation.z);
printf("Rotation (radians): %f, %f, %f\n", radians.x, radians.y, radians.z);
printf("Point a: %f, %f, %f\n", a.x, a.y, a.z);
printf("Point b: %f, %f, %f\n", b.x, b.y, b.z);
printf("Expected length of ray: %f\n", length);
printf("Actual length of ray: %f\n", ab.length());
printf("Value t: %f\n", t);
// If t in [0..1] compute and return intersection point
if(t >= 0.0f && t <= 1.0f)
{
point = a + t * ab;
printf("Intersection: %f, %f, %f\n", point.x, point.y, point.z);
}
// Else no intersection
else
{
printf("No intersection found\n");
}
printf("\n\n");
}
When I render this point with OpenGL, it looks to be pretty close to the where the intersection of the ray and the plane would be. But from printing out the actual values, I discovered that for specific positions and rotations, the intersection point can be off by up to 0.000004. Here's an example of where the intersection is inaccurate - I know the intersection point is NOT on the plane because its Y value should be 0, not 0.000002. I could also sub it back into the plane equation and get an inequality:
Plane normal: 0.000000, 1.000000, 0.000000
Plane value d: 0.000000
Rotation (degrees): 70.100044, 1.899823, 0.000000
Rotation (radians): 1.223477, 0.033158, 0.000000
Point a: 20.818802, 27.240383, 15.124892
Point b: 21.947229, -66.788452, -18.894285
Expected length of ray: 100.000000
Actual length of ray: 100.000000
Value t: 0.289702
Intersection: 21.145710, 0.000002, 5.269455
Now, I know floating-point numbers are just approximations of real numbers, so I'm guessing this inaccuracy is just the effect of floating-point rounding, though it's possible I made a mistake somewhere else in the code. I know the intersection is off only by an extremely small amount, but I still care about it because I'm planning to use these points to define vertices of a model or level by snapping them to an arbitrarily-oriented grid, so I actually want those points to be ON that grid, even if they're slightly inaccurate. This might be a misguided approach - I don't really know.
So my question is: is this inaccuracy just floating-point rounding at work, or did I make a mistake somewhere else?
If it is just floating-point rounding, is there any way to deal with it? I've tried rounding the values of the rotation and position vectors in various ways, which obviously results in a less accurate intersection point, but I still sometimes get intersections that aren't on the plane. I did read an answer to a similar question (Is this plane-ray intersection code correct?) that mentions keeping the dimensions large, but I'm not sure exactly what that means.
Sorry if this question has been asked before - I searched, but I didn't see anything that was quite what I'm having trouble with. Thanks!
Your math seems correct and this definitely looks like a rounding error. I have a strong feeling that it is this line:
GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);
That said I don't see any other method to compute t. You could maybe verify if you are losing precision by using "%.12f" (or more) in your printf statements. Another way to pinpoint the culprit is to try doing your t computation step by step and printing the results along the way to see if you are losing precision somewhere.
Did you try using double precision floating point, if precision really matters to you that much?
Related
Given the BSDF function and the Normal vector of the intersection point in world space, how can I generate a new direction vector wi that is valid? Does the method for generating valid wis change based on the BSDF?
Here's an example of what I'm thinking to do for ideal diffuse material the BSDF: I generate a new direction vector wi as points on a unit hemisphere as follow and then compute the dot product of the produced vector with the Normal vector. If the dot product result is positive the direction vector wi is valid. Otherwise I negate wi as suggested here.
Here's how I get a random wi:
float theta = 2 * M_PI * uniform01(generator);
float phi = acos(uniform01(generator));
float x = sin(phi) * cos(theta);
float y = sin(phi) * sin(theta);
float z = cos(phi);
Vector3f wi(x, y, z);
if (dot(wi, Normal) > 0){
return wi;
}
else{
return -wi;
}
However, this doesn't seem to be the right approach based on a conversation I had with someone recently. Apparently the new direction vector produced this way is somehow not in the right space (not sure whether it was world or object space) and could only work if my material is ideal diffuse. So I will have to apply some transformations in order to be able to get the right wi. Is this correct? If so, can someone provide a solution that includes doing such transformation? Also, is there a general way to ensure all of my produced wis are valid with respect to the BSDF (not just ideal diffuse)?
You are generating your wi in tangent space, with z pointing along the normal. It is neither world nor object space, and you will have to transform into world space or do all your calculations in tangent space (or shading space, they're both the same).
What you should be doing, as it will make your life much easier when doing other calculations, is to transform your wo to tangent space, and do all calculations in it. Over here, you would choose z to be your normal, and generate x and y vectors orthogonal to it.
A function for generating the coordinate system like this would be:
void GenerateCoordinateSystem(const Vector& normalized, Vector& outFirst, Vector& outSecond)
{
if (std::abs(normalized.x) > std::abs(normalized.y))
{
outFirst = Vector(-normalized.z, 0, normalized.x) /
std::sqrt(normalized.x * normalized.x + normalized.z * normalized.z);
}
else
{
outFirst = Vector(0, normalized.z, -normalized.y) /
std::sqrt(normalized.z * normalized.z + normalized.y * normalized.y);
}
outSecond = Cross(normalized, outFirst);
}
Where normalized is the normal (z vector) at the point, and outFirst and outSecond are your x and y vectors respectively.
Now that you have your tangent space vectors, you transform into them by (wo is in object space):
Vector x, y;
GenerateCoordinateSystem(normal, x, y);
Vector tangentWo = Vector(Dot(wo, x), Dot(wo, y), Dot(wo, normal));
You would then generate your wi as you do above.
Then, to get wi in object space, you would:
Vector objWi = wi.X * x + wi.Y * y + wi.Z * normal;
If you want them in world space, you would obviously have to multiply them by the object's transformation matrix.
Uniform hemisphere sampling does ensure that your wi is valid for any BSDF, however, you have to ensure that the pdf for the BSDF takes into account the distribution.
I have been reading a lot about the sutherland hodgman polygon clipping algorithm and understand the general idea. However, when I see the actual implementation of it (like the one below), I get confused about the coordinate comparisons such as those in the intersection and inside methods. Therefore, I was wondering if someone could elaborate on what and why? I see a ton of videos and articles explaining the general concepts but I really have trouble finding some explanation of the actual details regarding the implementation.
bool inside(b2Vec2 cp1, b2Vec2 cp2, b2Vec2 p) {
return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x);
}
b2Vec2 intersection(b2Vec2 cp1, b2Vec2 cp2, b2Vec2 s, b2Vec2 e) {
b2Vec2 dc( cp1.x - cp2.x, cp1.y - cp2.y );
b2Vec2 dp( s.x - e.x, s.y - e.y );
float n1 = cp1.x * cp2.y - cp1.y * cp2.x;
float n2 = s.x * e.y - s.y * e.x;
float n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x);
return b2Vec2( (n1*dp.x - n2*dc.x) * n3, (n1*dp.y - n2*dc.y) * n3);
}
//http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#JavaScript
//Note that this only works when fB is a convex polygon, but we know all
//fixtures in Box2D are convex, so that will not be a problem
bool findIntersectionOfFixtures(b2Fixture* fA, b2Fixture* fB, vector<b2Vec2>& outputVertices)
{
//currently this only handles polygon vs polygon
if ( fA->GetShape()->GetType() != b2Shape::e_polygon ||
fB->GetShape()->GetType() != b2Shape::e_polygon )
return false;
b2PolygonShape* polyA = (b2PolygonShape*)fA->GetShape();
b2PolygonShape* polyB = (b2PolygonShape*)fB->GetShape();
//fill subject polygon from fixtureA polygon
for (int i = 0; i < polyA->GetVertexCount(); i++)
outputVertices.push_back( fA->GetBody()->GetWorldPoint( polyA->GetVertex(i) ) );
//fill clip polygon from fixtureB polygon
vector<b2Vec2> clipPolygon;
for (int i = 0; i < polyB->GetVertexCount(); i++)
clipPolygon.push_back( fB->GetBody()->GetWorldPoint( polyB->GetVertex(i) ) );
b2Vec2 cp1 = clipPolygon[clipPolygon.size()-1];
for (int j = 0; j < clipPolygon.size(); j++) {
b2Vec2 cp2 = clipPolygon[j];
if ( outputVertices.empty() )
return false;
vector<b2Vec2> inputList = outputVertices;
outputVertices.clear();
b2Vec2 s = inputList[inputList.size() - 1]; //last on the input list
for (int i = 0; i < inputList.size(); i++) {
b2Vec2 e = inputList[i];
if (inside(cp1, cp2, e)) {
if (!inside(cp1, cp2, s)) {
outputVertices.push_back( intersection(cp1, cp2, s, e) );
}
outputVertices.push_back(e);
}
else if (inside(cp1, cp2, s)) {
outputVertices.push_back( intersection(cp1, cp2, s, e) );
}
s = e;
}
cp1 = cp2;
}
return !outputVertices.empty();
}
(code stolen from iforce2d :) )
You say you understand the general idea, presumably from reading something like Sutherland Hodgman Algorithm. That explains at a high level exactly what inside and intersection do.
As to the details of how they achieve their objectives, that is all just straight up text book linear algebra.
inside is testing the sign of (cp2 - cp2) cross (p - cp1) and returning true iff the sign is strictly greater than zero. You could rewrite the return statement as:
return (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x) > 0;
by moving the second term to the left of the > which gives you exactly the cross product on the left.
Note that a cross product is typically a vec3 cross vec3 operation and requires computation of all three terms. However, we're doing this in 2d meaning the vec3s have the form (x, y, 0). Therefore we only need to compute the z output term, since the cross must be perpendicular to the xy plane, and therefore be of the form (0, 0, value).
intersection finds the point at which two vectors intersect using exactly the algorithm listed here: Line Intersection from Two Points. In particular, we care about the formula immediately following the text "The determinants can be written out as:"
In the context of that formula n1 is (x1 y2 - y1 x2), n2 is (x3 y4 - y3 x4) and n3 is 1 / ((x1 - x2) (y3 - y4) - (y1 - y2) (x3 - x4))
-- EDIT --
To cover the issue raised in the comments, here is as full an explanation as I can give for why the return value from inside() is a test of the sign of the cross product.
I'm going to go off on a slight tangent, show my age, and note that the cross product formula has a very simple memory aid. You just need to remember the first magic word from Woods and Crowther's text adventure game Colossal Cave. xyzzy.
If you have two vectors in three dimensions: (x1, y1, z1) and (x2, y2, z2), their cross product (xc, yc, zc) is evaluated thus:
xc = y1 * z2 - z1 * y2;
yc = z1 * x2 - x1 * z2;
zc = x1 * y2 - y1 * x2;
Now, look at the first line, remove the c, 1 and 2 suffixes from the terms, all the spaces and operators and just look at the remaining letters. It's the magic word. Then you just go vertically down, replacing x with y, y with z and z with x as you go from line to line.
Getting back to business, both the terms on the right of the expansions of xc and yc contain either z1 or z2. But we know that both of these are zero since our input vectors are in the xy plane, and therefore have a zero z component. That's why we can completely elide computing those two terms, because we know they'll be zero.
This is 100% consistent with the definition of what the cross product does, the resulting vector is always perpendicular to both input vectors. Hence if both input vectors are in the xy plane, we know the output vector must be perpendicular to the xy plane, and therefore have the form (0, 0, z)
So, what do we have for the z term?
zc = x1 * y2 - y1 * x2;
in this case vector 1 is cp2-cp1 and vector 2 is p-cp1. So plugging that into the above we get:
zc = (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x);
But as noted, we don't care about its value, only it's sign. We want to know if that is greater than zero. Hence:
return (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x) > 0;
which is then rewritten as:
return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x);
Finally, what does the sign of that term have to do with whether the point p is inside or outside the clipping polygon? You are quite correct that all the clipping takes place in the 2d xy plane, so why are we involving 3d operations at all?
The important thing to realize is that the cross product formula in 3d is not commutative. The order of the two vector operands is significant, in terms of the angle between them. The first image on the Wikipedia page for Cross Product shows it perfectly. In that diagram, if you look down from above, when evaluating a cross b, the shortest angular direction from a to b is counter-clockwise. In that instance, that leads to a cross product with a positive z value, assuming positive z goes up the page. However if you evaluate b cross a, the shotest angular distance from b to a is clockwise, and the cross product has a negative z value.
Thinking back to the Wikipedia page for the algorithm itself, you've got the blue "clipping" line that works its way counter-clockwise around the clipping polygon. If you think of that vector as always having positive magnitude in the counter-clockwise direction it'll always be cp2 - cp1 for any pair of adjacent vertices in the clipping polygon.
Keeping this in mind, imagine what you'd see if you stood at cp1, with your nose pointed straight at cp2. The interior of the clipping polygon will be on your left, and the exterior on the right. Now consider two points p1 and p2. We'll say p1 is inside the clipping poly, and p2 is outside. That means that the quickest way to point yourt nose at p1 is to rotate counter-clockwise, and the quickest way to point at p2 is to rotate clockwise.
So by studying the sign of the cross product, we're really asking 'Do we rotate clockwise or counter-clockwise from the current edge to look at the point' which equates to asking if the point is inside the clipping polygon or outside.
I'll add one final suggestion. If you're at all interested in this sort of thing, or 3d rendering, or any programming that involves modelling the mathematical representation of the real world, taking a good solid course in linear algebra that covers the likes of cross products, dot products, vectors, matrices and the interactions between them all will be one of the best things you can ever do. It will provide a very strong foundation for a fair amount of what is done with computers.
I want to make a sphere without the gluSphere method, and I try to calculate the sphere with this code
void drawCircle(double x, double y, double r)
{
glBegin( GL_QUAD_STRIP );
for(int i=0;i<=360;i++){
glVertex3d(x+sin(i)*r,y+cos(i)*r,-5.0);
}
glEnd();
}
void drawSphere(double x,double y,double r){
glLoadIdentity();
glColor3d(1,0,0);
for(int j=0;j<180;j++){
glTranslated(0,0,r/180);
drawCircle(x,y,r*sin(j));
}
}
The result was like this
But the result was the circle that I made isn't aligned well. Is there any proper calculation so I can make the sphere right?
There is a difference between a Sphere and a Circle. a Circle is a 2 dimensional shape and Sphere is its 3D counterpart. from your code you are not generating points for a sphere but for a cylinder as the z is constant i.e. -0.5. for Sphere all 3 should change within the ranges
if the center is at (xc, yc, zc) then
x => (0-xc) < x < (0+xc)
y => (0-yc) < y < (0+yc)
z => (0-zc) < z < (0+zc)
A sphere may be defined parametrically in terms of (u,v)
x = xo + r cos(theta) cos(phi)
y = yo + r cos(theta) sin(phi)
z = zo + r sin(theta)
Your points should be a the valid combination of x, y z. that means your points can be generated using 3 loops. Only then you would have correct points for sphere.
Also since you ar eusing QuadStrip, the array you pass must have the order of points in counter clockwise or you will not be able to get the correct shape.
Instead of using sin() & cos() you should use the sphere equation: x*x + y*y + z*z = c*c
I agree with #SimpleFellow that another approach would be better. But let me show you what else is not quite how you wanted it in your code.
Your main problem is that you pass degrees to the sin and cos functions instead of radians.
The easiest remedy is to just define a function deg2rad or functions like
double sind(double val){return std::sin(val*M_PI / 180.0);}
double cosd(double val){return std::cos(val*M_PI / 180.0);}
Also, the distance between rings should be
//glTranslated(0,0,r/180); //equidistance will give you something like a pyramid
glTranslated(0, 0, r*(cosd(j)-cosd(j-1));
then the result from your code looks something like this:
When you reduce the sample points you see what's happening:
Then you'll see that you're connecting 4 successive points in a ring to a quad, which is not what you want, so if you change glBegin( GL_QUAD_STRIP ); to glBegin( GL_LINE_STRIP ) you'll see this:
If you increase the points to 360 again then you'll have this:
So now you have a sphere made of circles, but I assume that you wanted a sphere with a surface, for that have a look at this question.
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.
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 < -w : Point is outside the screen (left of the viewport)
x > W : Point is outside the screen (right of the viewport)
y < -w : Point is outside the screen (above the viewport)
y > w : Point is outside the screen (below the viewport)
z < -w : Point is outside the screen (beyond znear)
z > 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.