Issue with non square aspect ratio and resizing

When using the Model Viewer Starter kit, there is code that checks the window resize, and changes the horizontalFov property based on whether the height is greater than the width ( so, a square comparison ).

This works fine.

If the content to be framed isn’t square, there is an issue.

I’ve set up a project here to show what’s happening.

https://playcanvas.com/editor/scene/777791

If you run that scene, and resize the window until you’re about to touch the side of the plane object, you’ll notice that the horizontalFov is now based on an aspect ratio of the camera.

The switch to horizontalFov occurs correctly, but now there is a jump rather than a smooth transfer between the two. The code using horizontalFov must be assuming that the framed content is still square.

If anyone knows how to get this working, so that when the horizontalFov is flipped the transfer is visually seamless, that would be great!

When I originally wrote it, it was done under the assumption that it was for when the user would rotate their phone/device rather than resizing a window. This way, it would use the shortest edge to consider the framing.

In your case, it sounds like you don’t want to flip it at all unless the user resets the view to frame the content.

An alternative idea is to move the camera back/forward when resizing the window instead of changing the fov.

Hi Yaustar,

Thanks for the info.

I’ve captured two animations showing what I’m trying to achieve in that demo, basically keep the border around the rectangle as small as possible in either of the axis.

Here’s the normal way, which is seamless, but leaves a large border around the rectangle when the window is thinner. This border space is wasted, as it could be used to show more of the model. At the thinnest point of the animation below, the model could nearly be twice as large / show twice as much detail - see the two images at the bottom of the post.

This second one shows where the code has been changed to maximise the “flip” until the aspect ratio matches the rectangular shape, but the resulting flipped camera setup now jumps further away from the rectangle, rather than being seamless.

Having to hardcode a new camera position could work, but there has to be an automated way of doing this. I’ll try to get time this weekend to see how other apps deal with this, there might be something that we can borrow.

vs

Yeah, the reason for that is we assume that the longest size of the AABB of the model might be along the shortest edge of the screen pending on the camera angle.

You could get the screen space of the AABB’s corners and use that as the aspect ratio check the camera ratio to? That might work. However, I can see that ‘breaking’ if you resize the window, rotate the camera/model and resize the window again. It would have the same ‘snap’ problem.

Edit: Ah, I see you have tried that already with a hard coded aspect ratio.

The only solution I can see here is to move the camera back/forward and not flip the FOV axis.

Hi @Mal_Duffin, Have you tried just setting this.entity.camera.horizontalFov = false; ?

The link above is now working, here’s it in action…

I asked Ariel of Flare3d ( https://www.facebook.com/flare3djs/ - I had previously used the Flash version of this software for client projects ) if he knew how to solve this issue, and he got back within a few minutes with a working solution, so the credit goes to him!

At the moment the value for the transition is hard-coded into the formula, but it should be possible to make a generic solution for this, so that everyone can take advantage of it for their own particular project needs.

Here is the monkey-code that fixes the issue - updating, so that I can link to this post for a related topic.

var requiredAspectRatio = 0.55;

pc.Mat4.prototype.setPerspective = 
    function(fov, aspect, znear, zfar, fovIsHorizontal) 
    {
        var xmax, ymax;
        if (!fovIsHorizontal) 
        {
            ymax = znear * Math.tan(fov * Math.PI / 360);
            xmax = ymax * aspect;
        } 
        else
        {                
            xmax = znear * Math.tan(fov * Math.PI / 360);
            xmax *= requiredAspectRatio;
            ymax = xmax / aspect;
        }

        return this.setFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
    };

};

How is requiredAspectRatio calculated/what does it represent?

1 Like

requiredAspectRatio was set to the amount that worked for me - at the time I just tweaked it in code, I’m assuming it’s the aspect ratio that would be required to keep the rectangle in view ( it’s roughly twice as high as it is wide, so 1 / 2-ish would give the 0.5-ish value )

I’ve had another look at this and it looks like it can be tidied up into a nice utility script without needing to patch a core math function.

The camera component has projection matrix calculation override property: https://developer.playcanvas.com/en/api/pc.CameraComponent.html#calculateProjection

We can do this in the orbit camera script (in this instance):

this.entity.camera.calculateProjection = function(mat4, view) {
        var fov = this._fov;
        var aspect = this._aspectRatio;
        var znear = this._nearClip;
        var zfar = this._farClip;
        var fovIsHorizontal =  this._horizontalFov;
        var requiredAspectRatio = 0.55;
        
        var xmax = 0; 
        var ymax = 0;
        if (!fovIsHorizontal) 
        {
            ymax = znear * Math.tan(fov * Math.PI / 360);
            xmax = ymax * aspect;
        } 
        else
        {                
            xmax = znear * Math.tan(fov * Math.PI / 360);
            xmax *= requiredAspectRatio;
            ymax = xmax / aspect;
        }

        return mat4.setFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
    };

Made it into a generic script here: https://playcanvas.com/editor/scene/1141210

Thinking about how to demo and frame this though :thinking: I might use the game board idea that you showed earlier Mal

1 Like