Like the masochistic I am, I'm trying to learn all the matrix math behind creating modelview and perspective matrices so that I can write my own functions for generating them without the use of JS libraries.
I understand the concept of the matrices, but not how to actually generate them. I've been looking very closely at the glMatrix library, and I have the following questions:
1) What is going on in the following mat4.perspecive method?
/**
* Generates a perspective projection matrix with the given bounds
*
* #param {mat4} out mat4 frustum matrix will be written into
* #param {number} fovy Vertical field of view in radians
* #param {number} aspect Aspect ratio. typically viewport width/height
* #param {number} near Near bound of the frustum
* #param {number} far Far bound of the frustum
* #returns {mat4} out
*/
mat4.perspective = function (out, fovy, aspect, near, far) {
var f = 1.0 / Math.tan(fovy / 2),
nf = 1 / (near - far);
out[0] = f / aspect;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = f;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = (far + near) * nf;
out[11] = -1;
out[12] = 0;
out[13] = 0;
out[14] = (2 * far * near) * nf;
out[15] = 0;
return out;
};
Specifically, I get what Math.tan(fovy / 2) is calculating, but why take the inverse of it? Likewise, why take the inverse of the difference between the near boundary and the far boundary? Also, why is out[11] set to -1 and what is the value stored in out[14] for?
2) The following mat4.lookAt method in the library is also confusing me:
/**
* Generates a look-at matrix with the given eye position, focal point,
* and up axis
*
* #param {mat4} out mat4 frustum matrix will be written into
* #param {vec3} eye Position of the viewer
* #param {vec3} center Point the viewer is looking at
* #param {vec3} up vec3 pointing up
* #returns {mat4} out
*/
mat4.lookAt = function (out, eye, center, up) {
var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
eyex = eye[0],
eyey = eye[1],
eyez = eye[2],
upx = up[0],
upy = up[1],
upz = up[2],
centerx = center[0],
centery = center[1],
centerz = center[2];
if (Math.abs(eyex - centerx) < GLMAT_EPSILON &&
Math.abs(eyey - centery) < GLMAT_EPSILON &&
Math.abs(eyez - centerz) < GLMAT_EPSILON) {
return mat4.identity(out);
}
z0 = eyex - centerx;
z1 = eyey - centery;
z2 = eyez - centerz;
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
z0 *= len;
z1 *= len;
z2 *= len;
x0 = upy * z2 - upz * z1;
x1 = upz * z0 - upx * z2;
x2 = upx * z1 - upy * z0;
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
if (!len) {
x0 = 0;
x1 = 0;
x2 = 0;
} else {
len = 1 / len;
x0 *= len;
x1 *= len;
x2 *= len;
}
y0 = z1 * x2 - z2 * x1;
y1 = z2 * x0 - z0 * x2;
y2 = z0 * x1 - z1 * x0;
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
if (!len) {
y0 = 0;
y1 = 0;
y2 = 0;
} else {
len = 1 / len;
y0 *= len;
y1 *= len;
y2 *= len;
}
out[0] = x0;
out[1] = y0;
out[2] = z0;
out[3] = 0;
out[4] = x1;
out[5] = y1;
out[6] = z1;
out[7] = 0;
out[8] = x2;
out[9] = y2;
out[10] = z2;
out[11] = 0;
out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
out[15] = 1;
return out;
};
Similar to the mat4.perspecive method, why is the inverse of the length of the vector being calculated? Also, why is that value then multiplied by the z0, z1 and z2 values? The same thing is being done for the x0-x2 variables and the y0-y2 variables. Why? Lastly, what is the meaning of the values set for out[12]-out[14]?
3) Lastly, I have a few questions about the mat4.translate method. Specifically, I bought the book Professional WebGL Programming: Developing 3D Graphics for the Web, and it says that the following 4x4 matrix is used to translate a vertex:
1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1
However, when I look at the following mat4.translate method in the glMatrix library, I see that out[12]-out[15] are set via some complex equations. Why are these values set at all?
/**
* Translate a mat4 by the given vector
*
* #param {mat4} out the receiving matrix
* #param {mat4} a the matrix to translate
* #param {vec3} v vector to translate by
* #returns {mat4} out
*/
mat4.translate = function (out, a, v) {
var x = v[0], y = v[1], z = v[2],
a00, a01, a02, a03,
a10, a11, a12, a13,
a20, a21, a22, a23;
if (a === out) {
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
} else {
a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
out[12] = a00 * x + a10 * y + a20 * z + a[12];
out[13] = a01 * x + a11 * y + a21 * z + a[13];
out[14] = a02 * x + a12 * y + a22 * z + a[14];
out[15] = a03 * x + a13 * y + a23 * z + a[15];
}
return out;
};
Thank you all for your time, and sorry for all the questions. I come from a JS background, not an OpenGL/3D programming background, so it's hard for me to understand the math behind all the matrices.
If there are any great resources out there that explain the math used for these equations/methods, then that would be great too. Thanks.
Specifically, I get what Math.tan(fovy / 2) is calculating, but why
take the inverse of it?
Because the focal distance d comes from the formula
Math.tan(fovy / 2) = y / d
to get the focal length you need to multiply by
1 / Math.tan(fovy / 2)
why take the inverse of the difference between the near boundary and
the far boundary? Also, why is out[11] set to -1 and what is the value
stored in out[14] for?
You can project (x,y,z) into (x*d/z, y*d/z) using the focal distance d. This is enough but OpenGL requires a linear transformation to (x,y,z) such as the projection gives coordinates in [-1,1]. Such normalized coordinates simplify clipping and retain the z information used to remove hidden surfaces.
out[11] is set to -1 because there's no linear transformation that gives normalized coordinates unless a reflection is applied. This -1 causes the handedness of the system to be switched with normalized coordinates.
out[14] is used with out[10] to transform z from [-n -f] to [-1 1] after projection.
Similar to the mat4.perspecive method, why is the inverse of the
length of the vector being calculated? Also, why is that value then
multiplied by the z0, z1 and z2 values? The same thing is being done
for the x0-x2 variables and the y0-y2 variables. Why?
To normalize the vectors x, y and z
what is the meaning of the values set for out[12]-out[14]?
A camera is composed of a base of vectors and a position.
out[12]-out[14] apply an inverse translation to set the camera position.
However, when I look at the following mat4.translate method in the
glMatrix library, I see that out[12]-out[15] are set via some complex
equations. Why are these values set at all?
The equations look complex because it's a product of a translation matrix and an existing matrix a.
Professional WebGL Programming: Developing 3D Graphics for the Web
I don't know this book, it might explain some math but if you need detailed explanation you should consider Eric Lengyel's book that explains and derivates the important math used in 3d raster graphics.
Related
The above expression was taken from the below method.
I know that to rotate a point around the center we have to
Move the point to the origin
Make the rotation and
Move the point back
But the pieces I don't get my head around are:
r[0] = x * Math.cos(angle) - y * Math.sin(angle);
^
|
why we use the minus sign here?
r[1] = x * Math.sin(angle) + y * Math.cos(angle);
^
|
And why here we use plus sign instead of minus?
Vec2.prototype.rotate = function (center, angle) {
//rotate in counterclockwise
var r = [];
var x = this.x - center.x;
var y = this.y - center.y;
r[0] = x * Math.cos(angle) - y * Math.sin(angle);
r[1] = x * Math.sin(angle) + y * Math.cos(angle);
r[0] += center.x;
r[1] += center.y;
return new Vec2(r[0], r[1]);
};
The book was to be great but it doesn't explain most of the code it simply spits out.
I got it! Just saw a video of Dr Peyam on transformation matrix.
To get x' and y' we multiply the transformation matrix by the current coordinate (x,y)
enter image description here
I have a point:
x1 = 100;
z1 = 100;
y1 = 100;
And 2 directions:
xDir = Math.PI;
yDir = 0.11;
And a range:
range = 100;
How can I calculate the end point when constructing a ray from this information. I have tried the following:
x2 = x1 + (range * Math.cos(xDir))
z2 = z1 + (range * Math.sin(xDir))
y2 = y1 + (range * Math.cos(yDir))
But this doesn't work when the ray is pointing anywhere but at a perfect horizontal direction. Because I don't take yDir into account in the first 2 lines. How can I find the correct end point?
Seems you want to work with azimuth/inclination angles in spherical coordinate system (specific system - with OXZ equatorial plane).
In this case you need to account for Y when working with X/Z:
x2 = x1 + (range * Math.cos(xDir) * Math.sin(yDir))
z2 = z1 + (range * Math.sin(xDir) * Math.sin(yDir))
y2 = y1 + (range * Math.cos(yDir))
I have two points(x1,y1 and x2,y2) on a circle and the center (c1,c2) of a circle
and need javascript code to calcuate the intersection point of two tangent lines thru points x1,y1 and x2,y2.
I am using it to convert from a circle (really an arc defined by the above points) to a quadratic bezier curve.
The normals of the tangents are:
n1x = x1 - c1
n1y = y1 - c2
n2x = x2 - c1
n2y = y2 - c2
Using the following parameters:
d1 = n1x * x1 + n1y * y1
d2 = n2x * x2 + n2y * y2
the equations of the tangents can be written as:
x * n1x + y * n1y = d1
x * n2x + y * n2y = d2
Solving the linear equation system yields the following result in general case:
x = (d2 * n1y - d1 * n2y) / (n1y * n2x - n1x * n2y)
y = (d1 * n2x - d2 * n1x) / (n1y * n2x - n1x * n2y)
In javascript:
var x1,y1,x2,y2,c1,c2; // inputs
var x, y; // outputs
... get the parameters somehow
var n1x = x1 - c1;
var n1y = y1 - c2;
var n2x = x2 - c1;
var n2y = y2 - c2;
var d1 = n1x * x1 + n1y * y1;
var d2 = n2x * x2 + n2y * y2;
var det = n1y * n2x - n1x * n2y;
if (det === 0) {
// The lines are parallel
} else {
x = (d2 * n1y - d1 * n2y) / det;
y = (d1 * n2x - d2 * n1x) / det;
}
The vector from the center (c₁, c₂) to a point (xᵢ, yᵢ) on a circumference is (xᵢ-c₁, yᵢ-c₂).
That means the line through (c₁, c₂) and (xᵢ, yᵢ) has slope sᵢ = (yᵢ-c₂)/(xᵢ-c₁).
Let tᵢ be the slope of the tangent line on (xᵢ, yᵢ). tᵢ = -1/sᵢ = (c₁-xᵢ)/(yᵢ-c₂).
Let oᵢ = yᵢ-tᵢxᵢ. Then the tangent line through (xᵢ, yᵢ) is
y = tᵢ(x-xᵢ) + yᵢ = tᵢx + oᵢ
Doing this for i = 1, i = 2 produces a linear equation system.
y = t₁x + o₁
y = t₂x + o₂
Solving it gives the intersection of the tangent lines
o₁-o₂
x = ─────
t₂-t₁
o₁-o₂
y = t₁ ───── + o₁
t₂-t₁
So I have an object rotating around an origin point. Once I rotate and then change the origin point. My object seems to jump positions. After the jump it rotates fine... Need help finding the pattern/why it's jumping and what I need to do to stop it.
Here's the rotation code:
adjustMapTransform = function (_x, _y) {
var x = _x + (map.width/2);
var y = _y + (map.height/2);
//apply scale here
var originPoint = {
x:originXInt,
y:originYInt
};
var mapOrigin = {
x:map.x + (map.width/2),
y:map.y + (map.height/2)
};
//at scale 1
var difference = {
x:mapOrigin.x - originPoint.x,
y:mapOrigin.y - originPoint.y
};
x += (difference.x * scale) - difference.x;
y += (difference.y * scale) - difference.y;
var viewportMapCentre = {
x: originXInt,
y: originYInt
}
var rotatedPoint = {};
var angle = (rotation) * Math.PI / 180.0;
var s = Math.sin(angle);
var c = Math.cos(angle);
// translate point back to origin:
x -= viewportMapCentre.x;
y -= viewportMapCentre.y;
// rotate point
var xnew = x * c - y * s;
var ynew = x * s + y * c;
// translate point back:
x = xnew + viewportMapCentre.x - (map.width/2);
y = ynew + viewportMapCentre.y - (map.height/2);
var coords = {
x:x,
y:y
};
return coords;
}
Also here is a JS Fiddle project that you can play around in to give you a better idea of what's happening.
EDITED LINK - Got rid of the originY bug and scaling bug
https://jsfiddle.net/fionoble/6k8sfkdL/13/
Thanks!
The direction of rotation is a consequence of the sign you pick for the elements in your rotation matrix. [This is Rodrigues formula for rotation in two dimensions]. So to rotate in the opposite direction simply subtract your y cosine term rather than your y sine term.
Also you might try looking at different potential representations of your data.
If you use the symmetric representation of the line between your points you can avoid shifting and instead simply transform your coordinates.
Take your origin [with respect to your rotation], c_0, to be the constant offset in the symmetric form.
You have for a point p relative to c_0:
var A = (p.x - c_0.x);
var B = (p.y - c_0.y);
//This is the symmetric form.
(p.x - c_0.x)/A = (p.y - c_0.y)/B
which will be true under a change of coordinates and for any point on the line (which also takes care of scaling/dilation).
Then after the change of coordinates for rotation you have [noting that this rotation has the opposite sense, not the same as yours].
//This is the symmetric form of the line incident on your rotated point
//and on the center of its rotation
((p.x - c_0.x) * c + (p.y - c_0.y) * s)/A = ((p.x - c_0.x) * s - (p.y - c_0.y) * c)/B
so, multiplying out we get
(pn.x - c_0.x) * B * c + (pn.y - c_0.y) * B * s = (pn.x - c_0.x) * A * s - (pn.y - c_0.y) * A * c
rearrangement gives
(pn.x - c_0.x) * (B * c - A * s) = - (pn.y - c_0.y) * (B * s + A * c)
pn.y = -(pn.x - c_0.x) * (B * c - A * s) / (B * s + A * c) + c_0.y;
for any scaling.
What I have:
A lot of bubbles. But to make it more simple, let's say I have two. When they meet each other they collide and change the direction.
var xVelocityBubble1 = Math.random();
var yVelocityBubble1 = Math.random();
var xVelocityBubble2 = Math.random();
var yVelocityBubble2 = Math.random();
moveBubbles = function() {
xbubble1 += xVelocityBubble1;
ybubble1 += yVelocityBubble1;
xbubble2 -= xVelocityBubble2;
xbubble2 -= yVelocityBubble2;
if (Math.sqrt(Math.pow(xbubble1 - xbubble2, 2) + Math.pow(ybubble1 - ybubble2, 2)) < radius * 2) {
xVelocityBubble1 *= -1;
yVelocityBubble1 *= -1;
xVelocityBubble2 *= -1;
yVelocityBubble2 *= -1;
}
}
What I want:
I do not want the circles to simply change the direction, because that looks strange and boring. So I want to calculate the angle where the circle meet, and from that I need to calculate how much momentum they exchange and how that affects each circle.
My problem:
I really do not know how to calculate the angle and the momentum! Any hints?
To get the angle between those two bubbles if they collide do as follows:
get the direction vector in which one of those bubbles were moving
direction = {x: Math.abs(xVelocityBubble1), y: Math.abs(yVelocityBubble1)};
Then normalize that vector (divide it's x and y components by it's length)
After doing that you'll have the cosine of the angle as the x component and the sine as the y, just use any of them in Math.acos or Math.asin and you'll have the angle in which they collided.
This code shows collision of asteroids:
for (var i = 0; i < asteroidsLength; i++) {
var tmpAsteroid = asteroids[i];
for (var j = i + 1; j < asteroidsLength; j++) {
var tmpAsteroidB = asteroids[j];
var dX = tmpAsteroidB.x - tmpAsteroid.x;
var dY = tmpAsteroidB.y - tmpAsteroid.y;
var distance = Math.sqrt((dX * dX) + (dY * dY));
if (distance < tmpAsteroid.radius + tmpAsteroidB.radius) {
var angle = Math.atan2(dY, dX);
var sine = Math.sin(angle);
var cosine = Math.cos(angle);
// Rotate asteroid position
var x = 0;
var y = 0;
// Rotate asteroidB position
var xB = dX * cosine + dY * sine;
var yB = dY * cosine - dX * sine;
// Rotate asteroid velocity
var vX = tmpAsteroid.vX * cosine + tmpAsteroid.vY * sine;
var vY = tmpAsteroid.vY * cosine - tmpAsteroid.vX * sine;
// Rotate asteroidB velocity
var vXb = tmpAsteroidB.vX * cosine + tmpAsteroidB.vY * sine;
var vYb = tmpAsteroidB.vY * cosine - tmpAsteroidB.vX * sine;
// Conserve momentum
var vTotal = vX - vXb;
vX = ((tmpAsteroid.mass - tmpAsteroidB.mass) * vX + 2 * tmpAsteroidB.mass * vXb) / (tmpAsteroid.mass + tmpAsteroidB.mass);
vXb = vTotal + vX;
// Move asteroids apart
xB = x + (tmpAsteroid.radius + tmpAsteroidB.radius);
// Rotate asteroid positions back
tmpAsteroid.x = tmpAsteroid.x + (x * cosine - y * sine);
tmpAsteroid.y = tmpAsteroid.y + (y * cosine + x * sine);
tmpAsteroidB.x = tmpAsteroid.x + (xB * cosine - yB * sine);
tmpAsteroidB.y = tmpAsteroid.y + (yB * cosine + xB * sine);
// Rotate asteroid velocities back
tmpAsteroid.vX = vX * cosine - vY * sine;
tmpAsteroid.vY = vY * cosine + vX * sine;
tmpAsteroidB.vX = vXb * cosine - vYb * sine;
tmpAsteroidB.vY = vYb * cosine + vXb * sine;
};
};