Moving 3d objects to a location on the screen

I have a requirement to “zoom in” on an object by pulling it out of the scene and moving it up in front of the camera.



The basics of doing so are quite simple and are working fine. What I was wondering though was if anyone know any good ways to select the destination for the “zoomed” object so that it takes up the desired section of the 2d display window no matter what resolution/aspect ratio/etc the display window might have.



The naive implementation:

Take the current camera position. Move X in front of it. Move the zoomed object to that location. Rotate object to face towards the camera.



This is fine in the general case but I’m trying to consider different aspect ratio monitors and in particular android screens where you might have extremely wide screens (800*480 for example). Does anyone know of a good algorithm/resource/utility/whatever that helps me handle that?



If all else fails I’ll just do a bit of trial and error to find settings that work (probably adjust the distance from camera forwards/backwards based on aspect ratio) but I was hoping someone knew a better way.



Thanks,

Zarch

cam.screenToWorld and worldToScreen, obviously you cannot know about the depth from a 2d pick, so you have to decide how you handle that yourself.

1 Like

Yep, I was thinking about it some more while in the gymn (nothing better to do on that treadmill!) and came up with the following algorithm. It won’t be super-fast but it only needs doing if/when the resolution changes so should be acceptable. (It would need to be rotated/translated with the camera of course as the camera moves but the distance and angle would stay the same just need adjusting as per the camera’s movement and rotation)



Inputs: x,y co-ords of the top left and bottom right corners of the screen into which the item will be fitted. width and height the item needs.


  1. Work out size required

    1.1. Compare image and screen resolution.

    1.2. Work out “bounding rectangle” at screen aspect ratio that contains desired object. I.e. a 4:3 object on a 3:4 screen needs a 4:4 box (in screen res) to contain it.

    1.3. Work out diagonal length of bounding rectangle



    2 Cast rays out from the 2 diagonals

    2.1 Use the same mouse pick algorithms to cast the ray.


  2. Select points on the two rays

    3.1 Follow each ray one world unit.

    3.2 Measure the diagonal length between those two positions

    3.3 Ratio of length at 1 wu and length from 1.3 is how many world units away are needed.

    3.4 Follow each ray that many world units.

    3.5 Select the point half way between the two positions just found.





    The point at 3.5 should be the new center point for your object.





    Unless I missed anything this should position any arbitrarily sized object onto any arbitrarily sized screen so that it always fits into the given window and uses as much of it as possible.



    Anyone see any mistakes/suggestions for improvement/etc ?



    Thanks,

    Zarch

Thanks for pointing me at getWorldCoordinates, I’d used it for mouse picking but not really looked in detail before. However I just looked at it and I can just do getWorldCoordinates(x,y,1f) to do 2 to 3.1 in one step and then use the calculated ratio and the same function to then get the extended points in a second step. Very nice.

Ok, I’m getting a strange result here:



http://www.jmonkeyengine.com/doc/com/jme/renderer/Camera.html#getWorldCoordinates(com.jme.math.Vector2f, float)



My reading of this is that if I select a point on the screen and supply a number then it gives me the co-ordinate



Cam at 0,0,10, Dir 0,0,-1



Coords 806x432



Distance = 95:



Result = 0.002, 0.0, 10.01



Same settings but Distance = -95



Result = 0.002, 0, 9.989







Clearly I’ve misunderstood something about how the function is working here. I was expecting to see something with x and y both being close to 0 (which is what I see) but the z should have been a long way away, instead it is virtually on top of the camera (in fact it is so close the camera goes inside it and it never shows). Additionally negative distance seems to move out in front of the camera and positive distance behind it…



What units is “distance” in? Is it correct that I should need a negative distance to move forwards?



Thanks,

Zarch

distance is the frustum range from 0-1

1 Like

Ahha! Thanks, that explains it. The results work nicely now in a few basic tests, will have to see how it works out when things get more complicated :slight_smile:





[java]public class FitWorldObjectToScreenBox {



private FitWorldObjectToScreenBox() {



}



/**

  • Calculate the center for an object which will be displayed in the specified rectangle from
  • the camera position.

    *
  • The maximum width and height in world units of the object to be positioned in the box formed

    *
  • @param cam The camera to calculate the distance for
  • @param x1 The top left corner of the box (0 to 1 across the screen)
  • @param y1 The top left corner of the box (0 to 1 up the screen)
  • @param x2 The bottom right corner of the box (0 to 1 across the screen)
  • @param y2 The bottom right corner of the box (0 to 1 across the screen)
  • @param width The widest width of the object to fit in the box
  • @param height The hightest height of the object to fit in the box
  • @return The distance away to place the object to have it fit inside the box

    /

    public static float getDistanceToCenterFor(Camera cam, float x1, float y1, float x2, float y2, float width, float height) {

    Vector3f topLeft = getPointAtRangeFromScreen(cam, x1, y1, 1);

    Vector3f bottomRight = getPointAtRangeFromScreen(cam, x2, y2, 1);



    float length = topLeft.subtractLocal(bottomRight).length();



    float cameraAspect = ((float)cam.getWidth())/cam.getHeight();

    float objectApect = width/height;



    float objectLength;



    // Camera more widescreen than object - so limit by object height

    if (cameraAspect > objectApect) {

    objectLength = FastMath.sqrt(FastMath.sqr(width
    cameraAspect/objectApect) + FastMath.sqr(height));

    } else {

    objectLength = FastMath.sqrt(FastMath.sqr(width) + FastMath.sqr(heightobjectApect/cameraAspect));

    }



    return objectLength / length;

    }



    public static Vector3f getPointAtRangeFromScreen(Camera cam, float x, float y, float distance) {



    Vector2f position = new Vector2f(x
    cam.getWidth(), y*cam.getHeight());

    Vector3f origin = cam.getWorldCoordinates(position, 0f).clone();

    Vector3f dir = cam.getWorldCoordinates(position, 1f).subtractLocal(origin);

    dir.normalizeLocal();

    dir.multLocal(distance);



    return origin.addLocal(dir);

    }



    }[/java]