Perspective matrix that fully encapsulates object - java

I would like to produce a projection matrix that will render an object from an arbitrary camera. I've managed to setup a viewMatrix that will look at the object from an arbitrary eye position, but I'm having difficulty setting up the projection matrix.
Given that an object centred at (x,y,z) who's furthest point is r from the centre can for any arbitrary orientation can be entirely enclosed within a sphere of radius r with origin (x,y,z), I calculated my perspective matrix as follows:
float dist2Object = (float) Math.sqrt(vec.lengthSquared());
float objectRadius = (float) Math.sqrt(3 * Math.pow(0.5, 2));
float far = dist2Object + objectRadius;
float near = dist2Object - objectRadius;
// fov_2 = FOV / 2
float fov_2 = (float) (Math.asin(objectRadius / dist2Object));
float r = (float) (Math.tan(fov_2));
float frustum_length = far - near;
depthProjectionMatrix.m00 = 1 / r;
depthProjectionMatrix.m11 = depthProjectionMatrix.m00;
depthProjectionMatrix.m22 = -((far + near) / frustum_length);
depthProjectionMatrix.m23 = -1;
depthProjectionMatrix.m32 = -((2 * near * far) / frustum_length);
depthProjectionMatrix.m33 = 0;
In my example:
vec is a vector from the camera to the object
the object is a cube who's furthest vertex is (0.5, 0.5, 0.5), giving a r of sqrt(0.75)
As far as I can tell, the geometry and trigonometry should be correct, but rendering the coordinates using the following fragment shader:
#version 150 core
in vec3 pCoord;
out vec4 out_Color;
void main(void) {
out_Color = vec4(0,1,0,1);
if(pCoord.x <= -1){
out_Color = vec4(1,0,0,1);
}
if(pCoord.x >= 1){
out_Color = vec4(0,0,1,1);
}
if(pCoord.z <= -1){
out_Color = vec4(1,0,1,1);
}
if(pCoord.z >= 1){
out_Color = vec4(1,0,1,1);
}
}
shown at image shows that the FOV is too narrow and that the near and far planes are also too narrow.
How can I fix this?

Haven't done this in a while, but it seem you are missing the viewport size in your calculation. Here is what I used for a project. Mind that the matrix here starts with _11 where you use m00:
double aspectRatio = (double)this.viewPort.Width /(double)this.viewPort.Height;
double fov = Math.toRadians(fieldOfView/2.0);
double size = nearClip * Math.tan(fov);
double left = -size* aspectRatio, right = size* aspectRatio, bottom = -size , top = size ;
projectionMatrix.resetToZero();
// the values in comments are for non symetrical frustrum
// First Column
projectionMatrix._11 = (float) (nearClip/right);//(float) ((2 * nearClip )/ (double)(right - left));
// Second Column
projectionMatrix._22 = (float)(nearClip/top);//(float) (2 * nearClip / (double)(top - bottom));
// Third Column
projectionMatrix._31 = 0;//(float) ((right + left) / (right - left));
projectionMatrix._32 = 0;//(float) ((top + bottom) / (top - bottom));
projectionMatrix._33 = -1*(farClip + nearClip) / (float)(farClip - nearClip);
projectionMatrix._34 = -1;
// Fourth Column
projectionMatrix._43 = -(2 * farClip * nearClip) / (float)(farClip - nearClip);

Related

Floorcasting not scrolling in raycasting engine?

I'm currently working on a raycaster in Java, and so far, I have the floor correctly textured. The problem, however, is that the floor doesn't scroll. In other words, when I move the camera in the projection, the floor stays the same, yet the walls move as expected. I'm really not sure what I'm doing wrong. I took almost all the code from this reference. Note that I took some liberties when pasting the code in that I used some pseudocode.
I tried applying a player offset to the tileX and tileY variables, e.g., tileX += player.x, and all I got was a floor that scrolls far too quickly and incorrectly.
for every ray:
... // other stuff relating to the walls above here.
int start = (int)(wallY + wallHeight + 1);
double directionCos = cos(rad(ray.getAngle()));
double directionSin = sin(rad(ray.getAngle()));
int textureDim = 16;
for (int y = start; y < screenHeight; y++) {
double distance = screenHeight / (2.f * y - screenHeight);
distance /= cos(rad(player.getAngle()) - rad(ray.getAngle()));
// The source I grabbed the code from actually appends the player's x and y to the tileX and tileY variables, but this completely messes up the textures when I try to.
double tileX = distance * directionCos;
double tileY = distance * directionSin;
int textureX = Math.floorMod((int)(tileX * textureDim), textureDim);
int textureY = Math.floorMod((int)(tileY * textureDim), textureDim);
int rgb = floorTexture.getRGB(textureX, textureY);
projectionFloor.setRGB((int)wallX, y, rgb);
}
Below is an image of the floor.
Below is an animation visualizing the problem.
Below is an animation visualizing what happens if I try to apply a player position offset:
Fixed it on my own. Turns out that, yes, you do have to account for the player's position (shocker!); the source I got the code from just didn't do it correctly.
DTPP = distance to projection plane.
for every pixel y from wallY + wallHeight + 1 to projectionHeight:
double r = y - this.getPreferredSize().height / 2.f;
double d = (CAMERA_HEIGHT * DTPP / r) / ANGLE;
double tileX = CAMERA_X + d * RAY_COSANGLE;
double tileY = CAMERA_Y + d * RAY_SINANGLE;
int textureX = Math.floorMod((int) (tileX * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
int textureY = Math.floorMod((int) (tileY * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
... (drawing occurs here)

Bilinear interpolation anomaly

I wrote a function that takes the subpixels of an image for the purpose of upscaling, and the subpixel is generated by bilinear interpolation, but I am having some weird artifacts.
Here is my code:
public static int getSubPixel(BufferedImage bi, double x, double y) {
float[] topleft = new Color(bi.getRGB((int) Math.floor(x), (int) Math.floor(y))).getColorComponents(null);
float[] topright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), (int) Math.floor(y))).getColorComponents(null);
float[] bottomleft = new Color(bi.getRGB((int) Math.floor(x), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
float[] bottomright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
for (int i = 0; i < 3; i++) {
topleft[i] *= topleft[i];
topright[i] *= topright[i];
bottomleft[i] *= bottomleft[i];
bottomright[i] *= bottomright[i];
}
double decX = x % 1;
double decY = y % 1;
double inv_DecX = 1 - decX;
double inv_DecY = 1 - decY;
float red = (float) Math.sqrt((topleft[0] * inv_DecX + topright[0] * decX) * inv_DecY + (bottomleft[0] * inv_DecX + bottomright[0] * decX) * decY);
float green = (float) Math.sqrt((topleft[1] * inv_DecX + topright[1] * decX) * inv_DecY + (bottomleft[1] * inv_DecX + bottomright[1] * decX) * decY);
float blue = (float) Math.sqrt((topleft[2] * inv_DecX + topright[2] * decX) * inv_DecY + (bottomleft[2] * inv_DecX + bottomright[2] * decX) * decY);
return new Color(red, green, blue).getRGB();
}
This is the result of scaling up a 16x16 image 20 times:
As you can see, there is weird streaking going on. I did go out of my way to square the colors before averaging, then taking the square root of the result, but something does not seem right here. Any insight?
PS: I understand functions already exist to do this. This is an educational exercise. I am trying to understand the process by doing it on my own.
The stripe artifacts that you are seeing are caused by the linear interpolation scheme. Your implementation is correct (except for the squaring, which is unnecessary and causes the stripes to be stronger in darker regions of the image). This is what I'm seeing with a correct linear interpolation (16x instead of 20x as in the OP, I goofed) but without squaring (note less stripes in the dark blue parts):
If you want to get rid of the stripes, use a better interpolation scheme, such as cubic spline interpolation:

Draw an arc in opengl GL10

I want to draw an arc using center point,starting point,ending point on opengl surfaceview.I have tried this given below code so far. This function draws the expected arc if we give the value for start_line_angle and end_line_angle manually (like start_line_angle=0 and end_line_angle=90) in degree.
But I need to draw an arc with the given co-ordinates(center point,starting point,ending point) and calculating the start_line_angle and end_line_angle programatically.
This given function draws an arc with the given parameters but not giving the desire result. I've wasted my 2 days for this. Thanks in advance.
private void drawArc(GL10 gl, float radius, float cx, float cy, float start_point_x, float start_point_y, float end_point_x, float end_point_y) {
gl.glLineWidth(1);
int start_line_angle;
double sLine = Math.toDegrees(Math.atan((cy - start_point_y) / (cx - start_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line first
double eLine = Math.toDegrees(Math.atan((cy - end_point_y) / (cx - end_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line second
//cast from double to int after round
int start_line_Slope = (int) (sLine + 0.5);
/**
* mapping the tiriogonometric angle system to glsurfaceview angle system
* since angle system in trigonometric system starts in anti clockwise
* but in opengl glsurfaceview angle system starts in clock wise and the starting angle is 90 degree of general trigonometric angle system
**/
if (start_line_Slope <= 90) {
start_line_angle = 90 - start_line_Slope;
} else {
start_line_angle = 360 - start_line_Slope + 90;
}
// int start_line_angle = 270;
// int end_line_angle = 36;
//casting from double to int
int end_line_angle = (int) (eLine + 0.5);
if (start_line_angle > end_line_angle) {
start_line_angle = start_line_angle - 360;
}
int nCount = 0;
float[] stVertexArray = new float[2 * (end_line_angle - start_line_angle)];
float[] newStVertextArray;
FloatBuffer sampleBuffer;
// stVertexArray[0] = cx;
// stVertexArray[1] = cy;
for (int nR = start_line_angle; nR < end_line_angle; nR++) {
float fX = (float) (cx + radius * Math.sin((float) nR * (1 * (Math.PI / 180))));
float fY = (float) (cy + radius * Math.cos((float) nR * (1 * (Math.PI / 180))));
stVertexArray[nCount * 2] = fX;
stVertexArray[nCount * 2 + 1] = fY;
nCount++;
}
//taking making the stVertextArray's data in reverse order
reverseArray = new float[stVertexArray.length];//-2 so that no repeatation occurs of first value and end value
int count = 0;
for (int i = (stVertexArray.length) / 2; i > 0; i--) {
reverseArray[count] = stVertexArray[(i - 1) * 2 + 0];
count++;
reverseArray[count] = stVertexArray[(i - 1) * 2 + 1];
count++;
}
//reseting the counter to initial value
count = 0;
int finalArraySize = stVertexArray.length + reverseArray.length;
newStVertextArray = new float[finalArraySize];
/**Now adding all the values to the single newStVertextArray to draw an arc**/
//adding stVertextArray to newStVertextArray
for (float d : stVertexArray) {
newStVertextArray[count++] = d;
}
//adding reverseArray to newStVertextArray
for (float d : reverseArray) {
newStVertextArray[count++] = d;
}
Log.d("stArray", stVertexArray.length + "");
Log.d("reverseArray", reverseArray.length + "");
Log.d("newStArray", newStVertextArray.length + "");
ByteBuffer bBuff = ByteBuffer.allocateDirect(newStVertextArray.length * 4);
bBuff.order(ByteOrder.nativeOrder());
sampleBuffer = bBuff.asFloatBuffer();
sampleBuffer.put(newStVertextArray);
sampleBuffer.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, sampleBuffer);
gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, nCount * 2);
gl.glLineWidth(1);
}
To begin with the trigonometry you may not simply use the atan to find degrees of the angle. You need to check what quadrant the vector is in and increase or decrease the result you get from atan. Better yet use atan2 which should include both dx and dy and do the job for you.
You seem to create the buffer so that a point is created per degree. This is not the best solution as for large radius that might be too small and for small radius this is way too much. Tessellation should include the radius as well such that number of points N is N = abs((int)(deltaAngle*radius*tessellationFactor)) then use angleFragment = deltaAngle/N but make sure that N is greater then 0 (N = N?N:1). The buffer size is then 2*(N+1) of floats and the iteration if for(int i=0; i<=N; i++) angle = startAngle + angleFragment*i;.
As already pointed out you need to define the radius of the arc. It is quite normal to use an outside source the way you do and simply force it to that value but use the 3 points for center and the two borders. Some other options that usually make sense are:
getting the radius from the start line
getting the radius from the shorter of the two lines
getting the average of the two
interpolate the two to get an elliptic curve (explained below)
To interpolate the radius you need to get the two radiuses startRadius and endRadius. Then you need to find the overall radius which was already used as deltaAngle above (watch out when computing this one, it is more complicated as it seems, for instance drawing from 320 degrees to 10 degrees results in deltaAngle = 50). Anyway the radius for a specific point is then simply radius = startRadius + (endRadius-startRadius)*abs((angleFragment*i)/deltaAngle). This represents a simple linear interpolation in polar coordinate system which is usually used to interpolate vector in matrices and is the core functionality to get nice animations.
There are some other ways of getting the arc points which may be better performance wise but I would not suggest them unless and until you need to optimize your code which should be very late in production. You may simply keep stepping toward the next point and correcting the radius (this is only a concept):
vec2 start, end, center; // input values
float radius; // input value
// making the start and end relative to center
start -= center;
end -= center;
vec2 current = start/length(start) * radius; // current position starts in first vector
vec2 target = end/length(end) * radius; // should be the last point
outputBuffer[0] = current+center; // insert the first point
for(int i=1;; i++) { // "break" will need to exit the loop, we need index only for the buffer
vec2 step = vec2(current.y, -(current.x)); // a tangential vector from current start point according to center
step = step/length(step) / tessellationScale; // normalize and apply tessellation
vec2 next = current + step; // move tangentially
next = next/length(next) * radius; // normalize and set the
if(dot(current-target, next-target) > .0) { // when we passed the target vector
current = next; // set the current point
outputBuffer[i] = current+center; // insert into buffer
}
else {
current = target; // simply use the target now
outputBuffer[i] = current+center; // insert into buffer
break; // exit
}
}

Orthogonal Projection - Fit Object to Screen?

Im programming with opengl (lwjgl) and building my own mini-library. My Camera, which takes the projection type, builds its projection matrix like this:
this.aspect = (float) Display.getWidth() / (float) Display.getHeight();
this.top = (float) (Math.tan(Math.toRadians(fov) / 2));
this.bottom = -top;
this.right = top * aspect;
this.left = -right;
if(type == AGLProjectionType.PERSPECTIVE){
float aspect = 800.0f / 600.0f;
final double f = (1.0 / Math.tan(Math.toRadians(fov / 2.0)));
projection = new Matrix4f();
projection.m00 = (float) (f / aspect);
projection.m11 = (float) f;
projection.m22 = (far + near) / (near - far);
projection.m23 = -1;
projection.m32 = (2 * far + near) / (near - far);
projection.m33 = 0;
}
else if(type == AGLProjectionType.ORTHOGONAL){
projection.m00 = 2 / (right - left);
projection.m03 = -(right + left) / (right - left);
projection.m11 = 2 / (top - bottom);
projection.m13 = -(top + bottom) / (top - bottom);
projection.m22 = -2 / (far - near);
}
So far so good.
Now, the VBO input, so the raw meshes of objects - for example a quad - i keep in the normalized dimension, so values in the range of [ -1 | 1 ].
If i want to scale it, i scale the model matrix to a value, and to move it i translate the model matrix.
My Problem is: That are all relative values. If i say "matrix.scale(0.5f, 0.5f, 0.5f)" the object will take the half of its previous size. But what if for example i want to have an object with 500 pixel width? How can i calculate this? Or if i want the object to be Screen.width / heiht, and x = -Screen.width * 0.5 and y = -Screen.height * 0.5 - so an object wich fills out the screen and has his position in the upper left corner of the screen? I have to calculate something with help of the projection matrix - right? But how?
Not exactly what you are asking, but maybe it helps. With this code the camera is set so that screen coordinates match world coordinates and the lower left corner of the viewport is zero for X and Y. Orthogonal projection.
case TwoD:
{
projectionMatrix.resetToZero();
projectionMatrix._11 = 2.0f/(float)this.viewPort.Width;
projectionMatrix._22 = 2.0f/(float)this.viewPort.Height;
projectionMatrix._33 = -2.0f/(this.farClip-this.nearClip);
projectionMatrix._43 = -1* this.nearClip;
projectionMatrix._44 = 1.0f;
float tx = -0.5f* (float)this.viewPort.Width;
float ty = -0.5f* (float)this.viewPort.Height;
float tz = this.nearClip +0.1f; //why +0.1f >> so that an object with Z = 0 is still displayed
viewMatrix.setIdetity();
viewMatrix._22 = 1.0f;
viewMatrix._41 = tx;
viewMatrix._42 = ty;
viewMatrix._43 = -tz;
break;
}
As for your question: You would have to put your desired screen coordinates trough the inverse of the view-projection matrix. And you would have to add the depth information on the way as you are going from 2D to 3D. I am sorry, but I cant help you with the math for that.

Java OpenGL: Mouse picking in 3D Space

I am trying to make some sort of 3D Editor with Java and OpenGL. And now I'm implementing the basic functions of an 3D Editor like rotating the camera around a specific Position and zooming. Next I want to do a 3D Picking to select Objects,Lines and Vertices in 3D-Space with the Mouse. I thought this is gonna to be easy because I can already select Objects when the Camera is focusing them.
Here is the example of the Selection of Objects with the Camera focus:
In the Class Camera there is this Method:
public boolean isVecInFocus(Vec3 vec) {
//returns the distance between camera and target
float c = new Vec3(posX,posY,posZ).getDistanceTo(vec);
// returns a Vector by drawing an imiginary line with the length of c and the position and rotation of the camera
Vec3 target = getFocusedPoint(c);
//checks if the calculated Vector is near to the target
if(target.x > vec.x - 0.05f && target.x < vec.x + 0.05f && target.y > vec.y - 0.05f && target.y < vec.y + 0.05f && target.z > vec.z - 0.05f && target.z < vec.z + 0.05f) {
return true;
} else {
return false;
}
}
Now, I want to do the same with the Mouse input:
//Mouse positions
float mX = Mouse.getX();
float mY = Mouse.getY();
//My test Vector
Vec3 vec = new Vec3(-5,5,-8);
//Camera Position
Vec3 camV = new Vec3(cam.getPosX(),cam.getPosY(),cam.getPosZ());
//Distance from Test Vector to Camera
float c = camV.getDistanceTo(vec);
//Calculating of the aspect between width and height (Because fov_x and fov_y are different because of the Screen Resolution, I think)
float aspect = (float) sb.getDisplayWidth() / (float) sb.getDisplayHeight();
//Normal fov refers to fov_y, so here is the fov_x
float fovx = cam.fov * aspect;
//Changing the Rotations to calculate the target Vector with the values of the Mouse position and rotations , not the Camera
float rotY = cam.getRotationY() + (fovx / (float) sb.getDisplayWidth()) * (mX) - (fovx / 2F);
float rotX = cam.getRotationX() + (cam.fov / (float) sb.getDisplayHeight()) * ((float) sb.getDisplayHeight() - mY) - (cam.fov / 2F);
//Calculating the target Vector with simple Math ...
double xDis = c * Math.sin(Math.toRadians(rotY)) * Math.cos(Math.toRadians(rotX));
double yDis = c * Math.sin(Math.toRadians(rotX));
double zDis = c * Math.cos(Math.toRadians(rotY)) * Math.cos(Math.toRadians(rotX));
float posX = (float) (camV.x + xDis);
float posY = (float) (camV.y - yDis);
float posZ = (float) (camV.z - zDis);
Vec3 target = new Vec3(posX,posY,posZ);
//Check if the target Vector and the Test Vector are the same.
If I use this Code, and point with my Mouse at the Test-Vector, the result is not right. The accuracy of the Point gets lower, the bigger the difference between Screen-middle and Mouse position is.
I think it has something to do with the OpenGL Perspective, but I'm not sure ...

Categories