Custom iOS interface orientation change animation with OpenGL
Asked Answered
F

2

6

I have an iPad app and am drawing the main view with openGL and I want to animate my own change when the device is rotated.

Note that this app only draws occasionally, it is not constantly animating. Also my scene is a (very complex) 2D drawing, not 3D. I just want it to simply rotate around the display center during device orientation change, maintaining correct aspect ratio.

At the moment I just have the following code:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    // nothing yet
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    IMMainView *mv = (IMMainView *)self.view;
    [mv createRenderbuffer];
    [mv drawView];
}

I just re-create the OpenGL render buffer to match the new width and height once the rotation completes.

The default iOS behavior seems to rotate the view but also stretches it weirdly as the aspect ratio changes.

I can animate my drawing parameters to make something better appear during the transition, but I don't understand (1) how to stop iOS animating my layer and (2) how to set up an animation loop from these method calls that matches the iOS animation.

For example, during the animation, are the actual view width and height being changed progressively?

Also a possibly issue is the question of when to re-create the render buffer, because if the OpenGL buffer does not match the iOS view bounds then the pixel aspect ratios are not right and the drawing looks bad.

Any help would be appreciated.

Floodgate answered 28/2, 2013 at 0:31 Comment(1)
OK I think I understand part of the answer to my question, namely that I need to just keep presenting the render buffer during the orientation change to over-ride what is shown in the default case. At the moment I only present it when I need to change the view contents depending on a UI change.Floodgate
F
3

I have spent some time looking into reasonable ways of doing this correctly and have ended up going the simplest route which is just to clear the screen on willRotateToInterfaceOrientation and then to render the new content in the correct place in didRotateFromInterfaceOrientation. It just doesn't look so bad and the extra complexity for something better is not worth it IMHO.

Alternatively, while I didn't go for this solution, the best result with animation that I achieved with not too much work was something like this:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    IMMainView *mv = (IMMainView *)self.view;
    [mv drawBlankScreen];

    m_oldviewsize = self.view.bounds.size;
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
    IMMainView *mv = (IMMainView *)self.view;

    CGPoint neworigin = mv.origin;

    neworigin.x += 0.5f*(self.view.bounds.size.width - m_oldviewsize.width);
    neworigin.y += 0.5f*(self.view.bounds.size.height - m_oldviewsize.height);

    mv.origin = neworigin;

    [mv createRenderbuffer];
    [mv drawView];
}

The change of origin is intended to re-center the post-rotated drawing at the same location it was at before the rotation.

I found that willAnimateToInterfaceOrientation is called once after the new view bounds have been computed. Therefore I set up the new renderbuffer at that point because the distortion associated with the aspect change is not quite as noticeable as my original case. I also had to clear the screen in willRotateToInterfaceOrientation because there is a delay during which the distorted version of the original drawing is clearly visible.

The downsides to this are that the clearing of the screen causes a slight flash at the start of the animation, and also the stretching distortion is still present but is converging on the correct appearance, not diverging from the old appearance followed by a jump to the new appearance, so it doesn't look so bad. I suspect that any attempt to actually track the animation aspect ratio changes of the view with updating using my drawing function to continuously maintain the correct appearance would be quite complex to implement and likely to be very vulnerable to Apple changing things in the future.

Floodgate answered 28/2, 2013 at 21:52 Comment(0)
R
0

You can rotate the OpenGL output in the vertex shader as follows:

#version 300 es


in vec4 position;
in mediump vec4 texturecoordinate;

in vec4 color;

uniform float preferredRotation;

out mediump vec2 coordinate;

void main()
{
    //const float pi = 4.0 * atan(1.0);
    //float radians  = (( -90.0 ) / 180.0 * pi );

    // Preferred rotation of video acquired, for example, by:
    // AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
    // CGAffineTransform preferredTransform = [videoTrack preferredTransform];
    // self.glKitView.preferredRotation = -1 * atan2(preferredTransform.b, preferredTransform.a);

    // Preferred rotation for both portrait and landscape           
    mat4 rotationMatrix = mat4( cos(preferredRotation), -sin(preferredRotation), 0.0, 0.0,
                                sin(preferredRotation),  cos(preferredRotation), 0.0, 0.0,
                                0.0,                     0.0,                    1.0, 0.0,
                                0.0,                     0.0,                    0.0, 1.0);

    // Mirror vertical (portrait only)
    mat4 rotationMatrix = mat4( cos(preferredRotation),  sin(preferredRotation), 0.0, 0.0,
                               -sin(preferredRotation),  cos(preferredRotation), 0.0, 0.0,
                                0.0,           0.0,          1.0, 0.0,
                                0.0,           0.0,          0.0, 1.0);

    // Mirror horizontal (landscape only)
    mat4 rotationMatrix = mat4( 1.0, 0.0,                     0.0,                    0.0,
                                0.0, cos(preferredRotation), -sin(preferredRotation), 0.0,
                                0.0, sin(preferredRotation),  cos(preferredRotation), 0.0,
                                0.0, 0.0,                     0.0,                    1.0);

    // Mirror vertical (landscape only)
    mat4 rotationMatrix = mat4( cos(preferredRotation), 0.0, sin(preferredRotation), 0.0,
                                0.0,                    1.0, 0.0,                    0.0,
                               -sin(preferredRotation), 0.0, cos(preferredRotation), 0.0,
                                0.0,                    0.0, 0.0,                    1.0);

    gl_Position = position * rotationMatrix;
    coordinate = texturecoordinate.xy;
}

With each vSync, you can pass a new value for preferredRotation, which will rotate the view without stretching.

Obviously, you choose only one matrix4, depending on the orientation of the video, and then its rotation. Each matrix4 flips the video window -- not rotates it. For rotation, you have to first pick the matrix4 based on the orientation, and then substitute the preferredRotation variable with the number of degrees (in radians, the formula for which is also provided).

There are a lot of ways to rotate a view, layer, object, etc.; but, if you're rendering an image via OpenGL, then you should choose this method, and only this method.

Revolver answered 30/3, 2016 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.