Problem with FOV value

Started by sticksen, October 04, 2011, 02:56:52 PM

Previous topic - Next topic

sticksen

Hi there,

I´m developing an Android app and am currently facing the problem of converting screen in world(or scene) coordinates.
Anyways, I need to know the FOV value in degrees or maybe rad for this, but all jpct gives is a (proprietary?) FOV value that I can´t decode right. What exactly is this jpct FOV value and how can I calculate it back to deg or rad?
Additionally, it confuses me that jpct assigns a FOV to the x-axis. In standard OpenGL, the y-axis is been given the FOV and the x-axis FOV is calculated from it, so it´s exactly the other way round. Any ideas why?

Cheers,
Marc

EgonOlsen

The FOV is the tangens of the angle. While this isn't explicitly stated in the docs, the naming of the parameter "tanFOV" might be an indicator of this. It's not proprietary, it's pretty common.

The FOV is based on the X-axis, because that is what every game does and how people think (at least i do...). If i change the fov in CoD or HalfLife or Quake, i'll always change the horizontal one, not the vertical.

EgonOlsen

BTW: For converting screen to camera/world-space, this may help: http://www.jpct.net/wiki/index.php/Picking

sticksen

Hi,
thank you for your answer!
Ok, tan of the angle, now it makes sense. It´s only named tanFOV in the setter and I didn´t have a look at that. So, in actual degrees, the FOV is 51,34° as default?

Well, I had a look at Interact2D.reproject2D3DWS already, but somehow it doesn´t do what I need.

Example: I moved my finger on the screen by 17 Pixels in x-direction on the screen, indicating I want to translate the scene under my finger. Distance from camera to scene is 120. By what value do I have to translate my scene?

Answer: (tan(51,34°/2) * distance from camera to scene)*2 = width of current scene = ~115,35
Screen is 1280px wide, so every pixel on the screen corresponds to 0,09 voxel x-values in scene. Conversion should thus give me 0,09*17 = 1,53 in voxel x-direction.
The output from Interact2D always gives me negative values and also doesn´t deliver the right absolute values. Maybe I´m just getting this conversion functions wrong?

Cheers,
Marc

stownshend

I don't think you're after code examples - it sounds more like you want to understand how it works. However, just in case it is helpful here is how I implemented picking. I am using picking so that the player can select tiles within my game (laid out along the xz plane).


    public boolean onTouchEvent(MotionEvent event)
    {
    // Find the x and y positon of the touch event
    Integer xpos = new Float(event.getX()).intValue();
    Integer ypos = new Float(event.getY()).intValue();
   
    // Log the position
    Log.d("xpos", xpos.toString());
            Log.d("ypos", ypos.toString());
       
            // Find the direction vector that the camera is looking in and use that to find a vector
            // out from where the user has touched the screen.
SimpleVector dir=Interact2D.reproject2D3DWS(camera, frameBuffer, xpos, ypos).normalize();

    // Find the nearest object along that vector. res[0] is the distance, res[1] is the Object3D
    // which is null when there is no object.
    Object[] res=world.calcMinDistanceAndObject3D(camera.getPosition(), dir, 10000 /*or whatever*/);

    Log.d("distance", res[0].toString());

    if ( (Float) res[0] < 1000.0f )
    {
    Log.d("object", res[1].toString());

Object3D currentTile = (Object3D) res[1];

Log.d("Current Tile Centre X", new Float(currentTile.getTransformedCenter().x).toString());
Log.d("Current Tile Centre Y", new Float(currentTile.getTransformedCenter().y).toString());
Log.d("Current Tile Centre Z", new Float(currentTile.getTransformedCenter().z).toString());

// Save the x and z coordinates of the tile selected for future use.
destinationX = currentTile.getTransformedCenter().x;
destinationZ = currentTile.getTransformedCenter().z;

// TODO: Need to save the selected object in here if there is one?
    }

    touchEvent = true;

            return true;
    }


And of course any object you want to pick needs to have collision detection enabled:


// Sets the tile up so that collision events can occur so that it can be
// selected by the player.
honeyTile.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);


Like I said, I don't understand the intricacies of how it works, only that it does.

Knickedi

This thread is the first hit for jpct fov so I will contribute to this thread.

This one was driving me insane too.
I was sure that my math is right but the results were still wrong.
I needed the exact vertical FOV but couldn't get the right value.

After a long day, irritating information and source decompiling that's what I got:
1.25 is actually not simply the tangent of the FOV and the horizontal angle is not ~51,34° it's rather ~64.01°
The formula is tan(fov / 2) * 2 = 1.25

Here's the formula for the vertical FOV angle (if you can't figure it out):
fovy = 2 * atan(getFOV() / 2 * screenHeight / screenWidth)
The result is the full vertical FOV in radian.

Seems trivial when you know it but actually that's something I would never expect to get as result from a function named getFOV (without a formula hint in the documentation).


No offence, still a great library and a great job on that ;-)

Regards
Knickedi

EgonOlsen

You are right and i agree that this can be confusing. What makes this feel strange is the additional *2 that is applied to the tan(angle/2) value. The rest is pretty much natural IMHO, but this *2...to be honest, i'm not sure why it's actually there. If i would do this method again, i would simply drop it. However, it's too late for that...so i decided to add the formula to the documentation as well as two new methods called setFovAngle(<float>) and setYFovAngle(<float>), which take the angle directly and hide the formula.

Knickedi

I really appreciate your hard work! Thank you!

I don't want to highjack this thread but I want to add something here:
I started to experiment with this library and I m impressed.
I'm not an expert in engines, but i gave it a go and compared it to a unity project and have to say: You library performs way better, I'll stick to that.

It's a kind of 3D Tetris clone (I know boring, but it was a gift for my dad to christmas ;-)
I hope that I can release the game soon. It uses the google play game services too.
I'll share it on github and in your forum when I make progress.

If you have some kind of logo which represents your engine, I'd be happy to integrate it into the splash screen.

Keep up the good work. This library has great potential!

nnyerges

#8
Knickedi,

Dear Knickedi,

Maybe my trigonometry is wrong (and excuse me if I'm wrong). No matter the value that jPCT gets out for FOV, using basic trigonometry the value of YFOV angle is given by:

  YFOV = 2 * asin (sin (FOV / 2) * height / width)

There is a simple test: the distance of the camera focal point (Z axis) must be the same on the YZ plane or the XZ plane:

Z (YZ plane) = Math.sqrt (Math.pow (x / Math.tan (0.5 * FOV), 2) - Math.pow (y, 2))
Z (XZ plane) = Math.sqrt (Math.pow (y / Math.tan (0.5 * YFOV), 2) - Math.pow (x, 2))

where x = width/2 and y = height /2,  the Z value must be equal  Z (yz) = Z (xz)

You write:
Quote from: Knickedi on December 27, 2013, 02:46:34 AM
..fovy = 2 * atan(getFOV() / 2 * screenHeight / screenWidth).
The result is the full vertical FOV in radian.

if you apply your YFOV formula, you will see that Z (yz) != Z (xz)

Please correct the formula. Maybe this help others not to fight hours trying to get the correct values like me LOL.

  EDITED: SORRY IS MY MISTAKE (see above post #12)

Chears

EgonOlsen

#9
I'm not sure if i can follow you here...well, no...i'm sure...i can't... ;)

In jPCT, you give the fov as "tan(angle / 2) * 2"...as weird as it is. That's true for xfov as well as for yfov. If you don't set yfov and haven't changed the default configuration, there's no explicit yfov but it will be derived from the xfov instead to keep the proportions (assuming that the pixels are square). If you set yfov explicitly, it's the same formula as xfov. I don't see why it should depend on the xfov like it does in your formula. Or did it i get that part wrong?

The both ...Angle-methods simply hide this calculation. I'm confused now about what you think exactly that is wrong and what should be corrected!?

nnyerges

Separating the fact from the yFOV error, I want to solve my problem:
- Suppose I have a 1280 (w) x 800 (h) screen
-Draw a cross using two Polylines, one horizontal of 400 pixels vector (- 200.0.0) - (200,0,0) and the vertical of 400 pixels vector  (0, - 200.0) - (0,200,0).
- I want to place the camera in the coordinates (0, 0, Z), looking at the center (0, 0, 0), in such a way, that the cross is seen in perspective exactly of 400px (w) x 400px (h).
How I calculate the values of Z, FOB and YFOV?
With the present functions of Set/ Get of FOB and YFOV and using your formulas or my trigonometric formulas, I have not managed to do it. Emphasize the fact, that for values of Z greater than approximately 1100, the camera disappears.

nnyerges

#11
SORRY I MISTAKE ....

Forget the 1st and 3rd question (point 1 and 3 of my last post) and the other last post. I have the wrong yFOV formulas and  the Knickedi formula ITS CORRECT. Sorry y should need some sleep..!

Resume:

function getFOV returns = tan(fov / 2) * 2 = tanFOV, so

FOV = 2 * atan (getFOV/2)
YFOV = 2 * atan(getFOV() / 2 * screenHeight / screenWidth)



I still have a question about yFOV:
Why setting any value to YFOV using setYFovAngle, gets a different output? setting cam.setYFovAngle = 0.6 returns aprox. 0.7 with getYFOV ?



chears


EgonOlsen

That's because setYFovAngle just hides the actual formula that the docs mention. Internally, it applies that formula to the given value and calls setYFOV with it. The value that getYFOV returns is that value, not the one given to setYFovAngle.

nnyerges

Im getting a perfect aspect radio of the "green scuare" (see attached image), but still can't see the scuare to be 640x400 pixels in the screen.
Please help!

This is my code:


@Override
public void onSurfaceChanged(GL10 gl, int w, int h) {

// -------------------------------------------------------
// Setup 2D View's
// -------------------------------------------------------

// draw's the background grid as reference (you can discard this from the code)
framel.newImageView((graphics.newImage("xtemppatron.png", ImageFormat.ARGB4444)).getBitmap(), 0, 000);

// -------------------------------------------------------
// Setup OpenGL objects
// -------------------------------------------------------
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(gl, w, h);

if (master == null) {

world = new World();
world.setAmbientLight(20, 20, 20);

// Place camera according with device screen
cam = world.getCamera();
float YFOV = (float) (2 * Math.atan(cam.getFOV()/2 * h / w)); // <<---OK
cam.setYFovAngle(YFOV);
float z = (float) (0.5 * h / Math.tan(YFOV/2));
cam.setPosition(0, 0, -z);
cam.lookAt(SimpleVector.ORIGIN);

// custom class to print logs to lower left corner on my screen (you can discard this and use Logger)
//debug("w= " + w + " h= " + h + " FOV= " + (float) ((2 * Math.atan(cam.getFOV()/2) *180f) / Math.PI) +  " YFOV= " + (float) ((YFOV *180f) / Math.PI) + " CameraZ= " + z);
Logger.log("w= " + w + " h= " + h + " FOV= " + (float) ((2 * Math.atan(cam.getFOV()/2) *180f) / Math.PI) +  " YFOV= " + (float) ((YFOV *180f) / Math.PI) + " CameraZ= " + z);

sun = new Light(world);
sun.setIntensity(2250, 2250, 2250);
sv.set(0, 0,0);
sun.setPosition(sv);

SimpleVector v1 = SimpleVector.create(-320, -200, 0);
SimpleVector v2 = SimpleVector.create(320, -200, 0);
SimpleVector v3 = SimpleVector.create(320, 200, 0);
SimpleVector v4 = SimpleVector.create(-320, 200, 0);
SimpleVector v5 = SimpleVector.create(-320, -200, 0);
SimpleVector[] vec = new SimpleVector[] { v1,v2,v3,v4,v5 };
Polyline line1 = new Polyline(vec, RGBColor.GREEN);
world.addPolyline(line1);

MemoryHelper.compact();

if (master == null) {
master = ActivityCarpa.this;
}
}
}

nnyerges

#14
[ SOLVED ]
What a fool I am, It took a whole night breaking my head, and I did not realize I have to put the scaled values ​​of the rectangle. I just see it as soon i write you.!

            SimpleVector v1 = SimpleVector.create(-320/scaleX, -200/scaleY, 0);
            SimpleVector v2 = SimpleVector.create(320/scaleX, -200/scaleY, 0);
            SimpleVector v3 = SimpleVector.create(320/scaleX, 200/scaleY, 0);
            SimpleVector v4 = SimpleVector.create(-320/scaleX, 200/scaleY, 0);
            SimpleVector v5 = SimpleVector.create(-320/scaleX, -200/scaleY, 0);

..thanks for your patience. Excellent work you do. Congratulations