I am replacing my project's use of glRotatef because I need to be able to transform double matrices. glRotated is not an option because OpenGL does not guarantee the stored matrices or any operations performed to be double precision. However, my new implementation only rotates around the global axes, and does not give the same result as glRotatef.
I have looked at some implementations of glRotatef (like OpenGl rotate custom implementation) and don't see how they account for the initial transformation matrix's local axes when calculating the rotation matrix.
I have a generic rotate function, taken (with some changes) from https://community.khronos.org/t/implementing-rotation-function-like-glrotate/68603:
typedef double double_matrix_t[16];
void rotate_double_matrix(const double_matrix_t in, double angle, double x, double y, double z,
double_matrix_t out)
{
double sinAngle, cosAngle;
double mag = sqrt(x * x + y * y + z * z);
sinAngle = sin ( angle * M_PI / 180.0 );
cosAngle = cos ( angle * M_PI / 180.0 );
if ( mag > 0.0f )
{
double xx, yy, zz, xy, yz, zx, xs, ys, zs;
double oneMinusCos;
double_matrix_t rotMat;
x /= mag;
y /= mag;
z /= mag;
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0f - cosAngle;
rotMat[0] = (oneMinusCos * xx) + cosAngle;
rotMat[4] = (oneMinusCos * xy) - zs;
rotMat[8] = (oneMinusCos * zx) + ys;
rotMat[12] = 0.0F;
rotMat[1] = (oneMinusCos * xy) + zs;
rotMat[5] = (oneMinusCos * yy) + cosAngle;
rotMat[9] = (oneMinusCos * yz) - xs;
rotMat[13] = 0.0F;
rotMat[2] = (oneMinusCos * zx) - ys;
rotMat[6] = (oneMinusCos * yz) + xs;
rotMat[10] = (oneMinusCos * zz) + cosAngle;
rotMat[14] = 0.0F;
rotMat[3] = 0.0F;
rotMat[7] = 0.0F;
rotMat[11] = 0.0F;
rotMat[15] = 1.0F;
multiply_double_matrices(in, rotMat, out); // Generic matrix multiplication function.
}
}
I call this function with the same rotations I used to call glRotatef with and in the same order, but the result is different. All rotations are done around the global axes, while glRotatef would rotate around the local axis of in.
For example, I have a plane:
and I pitch up 90 degrees (this gives the expected result with both glRotatef and my rotation function) and persist the transformation:
If I bank 90 degrees with glRotatef (glRotatef(90, 0.0f, 0.0f, 1.0f)), the plane rotates around the transformation's local Z axis pointing out of the plane's nose, which is what I want:
But if I bank 90 degrees with my code (rotate_double_matrix(in, 90.0f, 0.0, 0.0, 1.0, out)), I get this:
The plane is still rotating around the global Z axis.
Similar issues happen if I change the order of rotations - the first rotation gives the expected result, but subsequent rotations still happen around the global axes.
How does glRotatef rotate around a matrix's local axes? What do I need to change in my code to get the same result? I assume rotate_double_matrix needs to modify the x, y, z values passed in based on the in matrix somehow, but I'm not sure.
You're probably multiplying the matrices in the wrong order. Try changing
multiply_double_matrices(in, rotMat, out);
to
multiply_double_matrices(rotMat, in, out);
I can never remember which way is right, and there's a reasonable chance multiply_double_matrices is backwards anyway (at least if I'd written it :)
The order you multiply matrices in matters. Since rotMat holds your rotation, and in holds the combination of all other matrices applied so far, i.e. "everything else", multiplying in the wrong order means that rotMat gets applied after everything else instead of before everything else. (And I didn't get that part backwards! If you want rotMat to be the "top of stack" transformation, that means you actually want it to be the first when your vertex coordinates are processed)
Another possibility is that you mixed up rows with columns. OpenGL matrices go down, then across, i.e.
matrix[0] matrix[4] matrix[8] matrix[12]
matrix[1] matrix[5] matrix[9] matrix[13]
matrix[2] matrix[6] matrix[10] matrix[14]
matrix[3] matrix[7] matrix[11] matrix[15]
even though 2D arrays are traditionally stored across, then down:
matrix[0] matrix[1] matrix[2] matrix[3]
matrix[4] matrix[5] matrix[6] matrix[7]
matrix[8] matrix[9] matrix[10] matrix[11]
matrix[12] matrix[13] matrix[14] matrix[15]
Getting this wrong can cause similar-looking, but mathematically different, issues
Related
Programming Language: C
I'm currently in the process of implementing a 3D wireframe model represented through isometric projection.
My current understanding of the project is to:
parse a text map containing the x,y,z coordinates of the wireframe model
Transforming the 3D coordinates to 2D using isometric projection
Drawing the line using the Bresenham Line Algo and a few functions out of my graphic library of choice.
I'm done with Step 1 however I've been stuck on Step 2 for the last few days.
I understand that isometric projection is the process of projecting a 2D plane in a angle that it looks like it's 3D even though we are only working with x,y when drawing the lines. That is def. not the best way of describing it and if I'm incorrect please correct me.
Example of a text map:
0 0 0
0 5 0
0 0 0
My data structure of choice (implemented as array of structs)
typedef struct point
{
float x;
float y;
float z;
bool is_last;
int color; // Implemented after mandatory part
} t_point;
I pretty much just read out the rows, column and values of the text map and store them in x,y,z values respectively.
Now that I have to transform them I've tried the following formulas:
const double angle = 30 * M_PI / 180.0;
void isometric(t_dot *dot, double angle)
{
dot->x = (dot->x - dot->y) * cos(angle);
dot->y = (dot->x + dot->y) * sin(angle) - dot->z;
}
static void iso(int x, int y, int z)
{
int previous_x;
int previous_y;
previous_x = x;
previous_y = y;
x = (previous_x - previous_y) * cos(0.523599);
y = -z + (previous_x + previous_y) * sin(0.523599);
}
t_point *calc_isometric(t_point *pts, int max_pts)
{
float x;
float y;
float z;
const double angle = 30 * M_PI / 180.0;
int num_pts;
num_pts = 0;
while (num_pts < max_pts)
{
x = pts[num_pts].x;
y = pts[num_pts].y;
z = pts[num_pts].z;
printf("x: %f y: %f z: %f\n", x, y, z);
pts[num_pts].x = (x - y) * cos(angle);
pts[num_pts].y = (x + y) * sin(angle) - z;
printf("x_iso %f\ty_iso %f\n\n", pts[num_pts].x, pts[num_pts].y);
num_pts++;
}
return (pts);
}
It spits out various things which makes no sense to me. I could just go one and try to implement the Line Algo. from here and hope for the best but I would like to understand what I'm actually doing here.
Next to that I learned through me research that I need to set up my camera in a certain way to create the projection.
All in all I'm just very lost and my question boils down to this.
Please help me understand the concept of isometric projection.
How to transform 3D coordinates (x,y,z) into coordinates using isometric projection.
I see it like this:
// constants:
float deg = M_PI/180.0;
float ax = 30*deg;
float ay =150*deg;
vec2 X = vec2(cos(ax),-sin(ax)); // x axis
vec2 Y = vec2(cos(ay),-sin(ay)); // y axis
vec2 Z = vec2( 0.0,- 1.0); // z axis
vec2 O = vec2(0,0); // position of point (0,0,0) on screen
// conversion:
vec3 p=vec3(?,?,?); // input point
vec2 q=O+(p.x*X)+(p.y*Y)+(p.z*Y); // output point
the coordinatewise version:
float Xx = cos(ax);
float Xy = -sin(ax);
float Yx = cos(ay);
float Yy = -sin(ay);
float Zx = 0.0;
float Zy = - 1.0;
float Ox = 0;
float Oy = 0;
// conversion:
float px=?,py=?,pz=?; // input point
float qx=Ox+(px*Xx)+(py*Yx)+(pz*Yx); // output point
float qy=Oy+(px*Xy)+(py*Yy)+(pz*Yy); // output point
Asuming x axis going to right and y axis going down ... the O is usually set to center of screen instead of (0,0) unless you add pan capabilities of your isometric world.
In case you want to add arbitrary rotations within the "3D" XY plane see this:
How can I warp a shader matrix to match isometric perspective in a 3d scene?
So you just compute the X,Y vectors on the ellipse (beware they will not be unit anymore!!!) So if I see it right it would be:
float ax=?,ay=ax+90*deg;
float Xx = cos(ax) ;
float Xy = -sin(ax)*0.5;
float Yx = cos(ay) ;
float Yy = -sin(ay)*0.5;
where ax is the rotation angle...
I'm trying to implement an atan2-like function to map two input sinusoidal signals of arbitrary relative phase shift to a single output signal that linearly goes from 0 to 2π. atan2 normally assumes two signals with a 90 deg phase shift.
Given y0(x) = sin(x) and y1 = sin(x + phase), where phase is a fixed non-zero value, how can I implement a way to return x modulo 2π?
atan2 returns the angle of a 2d vector. Your code does not handle such scaling properly. But no worries, it's actually very easy to reduce your problem to an atan2 that would handle everything nicely.
Notice that calculating sin(x) and sin(x + phase) is the same as projecting a point (cos(x), sin(x)) onto the axes (0, 1) and (sin(phase), cos(phase)). This is the same as taking dot products with those axes, or transforming the coordinate system from the standard orthogonal basis into the skewed one. This suggests a simple solution: inverse the transformation to get the coordinates in the orthogonal basis and then use atan2.
Here's a code that does that:
double super_atan2(double x0, double x1, double a0, double a1) {
double det = sin(a0 - a1);
double u = (x1*sin(a0) - x0*sin(a1))/det;
double v = (x0*cos(a1) - x1*cos(a0))/det;
return atan2(v, u);
}
double duper_atan2(double y0, double y1, double phase) {
const double tau = 6.28318530717958647692; // https://tauday.com/
return super_atan2(y0, y1, tau/4, tau/4 - phase);
}
super_atan2 gets the angles of the two projection axes, duper_atan2 solves the problem exactly as you stated.
Also notice that the calculation of det is not strictly necessary. It is possible to replace it by fmod and copysign (we still need the correct sign of u and v).
Derivation:
In code:
// assume phase != k * pi, for any integer k
double f (double y0, double y1, double phase)
{
double u = (- y0 * cos(phase) + y1) / sin(phase);
double v = y0;
double x = atan2 (v, u);
return (x < 0) ? (x + 2 * M_PI) : x;
}
I'm trying to do parallel projection in C.
My function:
void parallel_projection(int x, int y, int z, float angle);
It is necessary to pass 3D coordinates to 2D using the parallel projection with the parameters of the function.
What is the formula to use to find x and y? (Using cos, sin and tangent)
Parallel Projection - Wikipedia
In your image, x and wx is the same axis, and angle is in yoz plane. So wx = x.
projected y:
when y = 0,
wy = z * cos(pi/2 - α) = z * sin(α)
when y > 0 and z < 0,
wy = sqrt(y^2 + z^2) * cos(α + arctan(z / y) + pi)
otherwise,
wy = sqrt(y^2 + z^2) * cos(α + arctan(z / y))
Note that angle is given in degree, while in C, trigonometric functions accept radian.
Let's say I have 2 points in 3D space, one at:
x=2, y=3, z=5
and the second one at:
x=6, y=7, z=10
What is the fastest way, in code, to calculate the coordinates of a third point from extending (for example, doubling) the distance between those two points (relative to point one)?
If you want a point extended as far beyond (x2,y2,z2) as that is beyond (x1,y1,z1):
x3 = x2 + (x2 - x1) (= 10)
y3 = y2 + (y2 - y1) (= 11)
z3 = z2 + (z2 - z1) (= 15)
or:
(x2 * 2 - x1, y2 * 2 -y1, z2 * 2 - z1)
Simple as that.
If you want something other than double the length, you can scale the (x2 - x1)-type terms. For example, if you want it 50% longer than the current line, multiply them by 0.5 (+50%). If you want it three times longer, multiply them by two (+200%).
In terms of code that can perform this extension, something like this, which gives you an endpoint pDest that, along with, p1 forms a line percent times the size of p1-p2:
typedef struct {
double x;
double y;
double z;
} tPoint3d;
void extend (tPoint3d *p1, tPoint3d *p2, double percent, tPoint3d *pDest) {
percent -= 100.0; // what to ADD
percent /= 100.0; // make multiplier
pDest->x = p2->x + percent * (p2->x - p1->x); // scale each point
pDest->y = p2->y + percent * (p2->y - p1->y);
pDest->z = p2->z + percent * (p2->z - p1->z);
}
I have 2 points A and B in a plane. What I need to find is the points w, x, y and z so that I can have a uniform bounding box.
The conditions are a line formed by wx and yz are parallel to AB.
Similarly wBz and xAy are parallel must be parallel.
Also note that angle zwx and wxy are right angles. Basically wxyz has to be a square.
z
/ /
B /
/ /
w /
/ y
/ /
/ A
/ /
x
Basically finding w, x, y and z is easy if line AB is parallel to x-axis or if AB is parallel to y-axis. I'm having trouble determining the points w,x,y and z when line AB is in an angle with x-axis (slope of line AB could be positive or negative).
Any comments/suggestions is highly appreciated. Thanks!
Treat A and B as vectors in your plane, (xa, ya) and (xb, yb). Take the vector difference, to generate a vector, C, that points from A to B.
C = A - B = (xa - xb, ya - yb) = (xc, yc)
Rotate this vector 90 degrees in each direction, and scale by a half, to get D = (xd, yd) and E = (xe, ye).
D = (-yc/2, +xc/2)
E = -D = (+yc/2, -xc/2)
Use vector arithmetic to get the four points of the square.
w = B + D
x = A + D
y = A + E
z = B + E
EDIT: Fat fingers.
EDIT2: Forgot the factor of a half.
EDIT3: Vector rotation reference, as requested.
To figure out the vector rotation, one can, in general, perform multiplication with a rotation matrix. In this case, the sin and cos factors of +/- pi/2 end up being +/- 1.
If matrix multiplication isn't your thing, draw on paper (or just imagine) a sample vector in any quadrant. Now rotate the paper 90 deg in either direction and see how the x and y components get swapped around and negated.
neirbowjs answer translated to more optimized solution, if optimization floats your boat.
Vars you know (Ax, Ay, Bx, By);
Vars you solve for (Wx, Wy, Xx, Xy, Yx, Yy,Zx, Zy);
float dx = By - Ay / 2;
float dy = Bx - Ax / 2;
float Wx = Ax - dx;
float Wy = Ay + dy;
float Zx = Ax + dx;
float Zy = Ay - dy;
float Xx = Bx - dx;
float Xy = By + dy;
float Yx = Bx + dx;
float Yy = By - dy;