[SOLVED] Problem with coordinates and screen size in landscape orientation in iOS

I’m using cocos2d-x 2.1.4 along with Marmalade 6.3.1 and this has been tested on the following: iPhone 4S, iPad 4, Blakberry Playbook and Xperia Play, but this issue only affects iOS devices.

In portrait orientation everything works as expected, but in landscape mode, it seems that width and height values for visible size are switched, as if the device was still in portrait mode, although visible origin has the correct values. This issue occurs both with CCDirector::sharedDirector()->getVisibleSize() and with CCEGLView::sharedOpenGLView()->getVisibleSize().

To make things clearer, I put together a scene with 5 sprites, one at the center of the screen and one on each corner, each with a different color (apologies for the blue one on the bottom right corner, it’s not easy to see it in the screenshots). To position them, I used the VisibleRect class from the TestCpp sample:

void VisibleRect::lazyInit()
{
    if (s_visibleRect.size.width == 0.0f && s_visibleRect.size.height == 0.0f)
    {
        s_visibleRect.origin = CCDirector::sharedDirector()->getVisibleOrigin();
        s_visibleRect.size = CCDirector::sharedDirector()->getVisibleSize();
    }
}

CCPoint VisibleRect::center()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}

// etc

Running the application on iOS devices, this is the result

Which looks a lot like swapped values between width and height, so I tried switching them manually as such:

void VisibleRect::lazyInit()
{
    if (s_visibleRect.size.width == 0.0f && s_visibleRect.size.height == 0.0f)
    {
        s_visibleRect.origin = CCDirector::sharedDirector()->getVisibleOrigin();
        s_visibleRect.size = CCSizeMake( CCDirector::sharedDirector()->getVisibleSize().height, CCDirector::sharedDirector()->getVisibleSize().width );
    }
}

And as a result I got this:

Which is what it should look like alright. The problem with this method is that the x and y values for touch events are also swapped, so if I were to use this workaround, I’d have to fiddle with touch values as well. Here’s my AppDelegate init code:

bool AppDelegate::applicationDidFinishLaunching() {
    // initialize director
    CCDirector *pDirector = CCDirector::sharedDirector();

    pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());

    CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
    CCSize designSize = CCSizeMake(1024, 768);
    std::vector searchPaths;

    if (screenSize.height > 320)
    {
        searchPaths.push_back("hd");
        searchPaths.push_back("sd");
        pDirector->setContentScaleFactor(768.0f/designSize.height);
    }
    else
    {
        searchPaths.push_back("sd");
        pDirector->setContentScaleFactor(320.0f/designSize.height);
    }

    CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);

    CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);

    // turn on display FPS
    pDirector->setDisplayStats(false);

    // set FPS. the default value is 1.0/60 if you don't call this
    pDirector->setAnimationInterval(1.0 / 60);

    // create a scene. it's an autorelease object
    CCScene *pScene = MenuScene::scene();

    // run
    pDirector->runWithScene(pScene);

    return true;
}

Is this a bug with visibleSize and landscape orientation or am I missing something? Since I’m using Marmalade, I set the orientation in the app.icf file as such:

[S3E]
DispFixRot="FixedLandscape"

The wiki has an article about [[Device_Orientation]] but it’s no longer up to date, so I don’t know if there’s anything there that could help me. Thanks in advance!

I managed to get it working on iOS by switching width and height on the visible rect size. As for touch input, I had to do something like this:

CCPoint location = touch->getLocationInView();
location.y = VisibleRect::getVisibleRect().height - location.y;

The getLocationInView() returns the correct touch location, albeit with inverted y coordinate.

This is becoming pretty choppy for a workaround, though. And a huge downside of this is that it only works when I’m the one dealing with touch values, but stuff like menu items, for example, aren’t working correctly. Can anyone shed some light on this issue?

For touch input try this.

void ccTouchesBegan(CCSet* touches, CCEvent event)
{
CCTouch
touch = (CCTouch*)(touches[0].anyObject());
CCPoint location = touch->getLocationInView();
location = CCDirector::sharedDirector()->convertToGL(location);
}

Zohaib Zaidi wrote:

For touch input try this.
>
void ccTouchesBegan(CCSet* touches, CCEvent event)
{
CCTouch
touch = (CCTouch**));
CCPoint location = touch->getLocationInView;
location = CCDirector::sharedDirector->convertToGL;
}
Sorry for the delayed answer. The convertToGL method doesn’t really work, because GL has width and height switched.
The VisibleRect workaround is starting to become a bit unmanageable though. I had to do some more ugly workarounds when I tried to add a CCFollow functionality to the player.
Is there a way to manually setting the GL View’s size? Stopping the problem at its source, maybe. Any help on this issue would be highly apreciated!
**Edit:* I should mention that I’ve also found out that this is somehow related to the resolution policy in the shared GL View. This happens with kResolutionNoBorder.

Alright, after more digging I finally came across a more definitive solution. It’s basically the solution described by Tim Closs in this forum post for rotating the projection matrix, with minor tweaks:

New method in AppDelegate:

    /**
     * Update the projection, based on an input angle, and store back the new w, h
     *
     * @param angle 0, 90, 180 or 270
     * @param w the new width of the virtual display
     * @param h the new height of the virtual display
     */
    virtual void updateProjection(float angle, float& w, float& h);

Method implementation:

void AppDelegate::updateProjection(float angle, float& w, float& h) {
    if (IwGLGetInt(IW_GL_VIRTUAL_WIDTH) >= 0) {
        w = (float)IwGLGetInt(IW_GL_VIRTUAL_WIDTH);
        h = (float)IwGLGetInt(IW_GL_VIRTUAL_HEIGHT);
    }
    else {
        w = (float)s3eSurfaceGetInt(S3E_SURFACE_WIDTH);
        h = (float)s3eSurfaceGetInt(S3E_SURFACE_HEIGHT);
    }

    if ((angle == 90.0f) || (angle == 270.0f)) {
        // Swap w and h
        float oh = h;
        h = w;
        w = oh;
    }

    CCEGLView::sharedOpenGLView()->setFrameSize(w, h);
    kmGLMatrixMode(KM_GL_PROJECTION);
    kmGLLoadIdentity();
    kmGLRotatef((float)angle, 0, 0, 1);
    kmMat4 orthoMatrix;
    kmMat4OrthographicProjection(&orthoMatrix, 0, w, 0, h, -1024, 1024 );
    kmGLMultMatrix(&orthoMatrix);
    kmGLMatrixMode(KM_GL_MODELVIEW);
    kmGLLoadIdentity();
}

Call to AppDelegate::updateProjection in AppDelegate::applicationDidFinishLaunching:

bool AppDelegate::applicationDidFinishLaunching() {
    // initialize director
    CCDirector *pDirector = CCDirector::sharedDirector();
    pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
    pDirector->setProjection(kCCDirectorProjection2D);

    // Set projection
    float w, h;
    float angle = s3eSurfaceGetInt(S3E_SURFACE_DEVICE_BLIT_DIRECTION) * 90.0f;

    // Workaround for ios devices, force rotation
    int os = s3eDeviceGetInt(S3E_DEVICE_OS);
    if (os == S3E_OS_ID_IPHONE || os == S3E_OS_ID_OSX) 
        angle = 90;

    updateProjection(angle, w, h);

    // Rest of method ...
}

The original solution also defined the virtual dimensions on marmalade’s icf file, thus overriding cocos2d-x’s scaling system, but I chose to stick with what cocos2d-x provides and it works just as well. The biggest difference was explicitely setting the angle to 90 for all iOS devices, since the original problem is that landscape orientation wasn’t being detected in the first place.

Also it should be noted that after implementing this solution, all my other workarounds for this issue became unnecessary, thus had to be removed for the correct behavior to take place.

Hope this can be of help to someone!