Grid Programming: Selecting a tile returns a negative coordinates

et me explain what I am doing here. I created a class template named as PlayField where T is the type of the GridObject. I wrote this class using Cocos2dx.

The point of this class is to make it easy for me to create a grid for games like Tetris, Bejeweled-like games, Minesweeper etc. One of the main goal of this class is the ability to place it anywhere by a position offset by which at the time of the creation of each GridObject they will position themselves accordingly base on this offset. This way I could make the Grid center align perfectly or adjust the position horizontally or vertically depending on the layout.

I am able to do this easily on SFML since the coordinate system there is top-left, while the coordinates system on Cocos2dx is bottom left which has given me challenge and difficulties writing up a simple framework for creating grid.

I have certain requirements for this class and the smaller projects I made since the past few weeks was able to be developed without a hassle.

Clicking on each of grid object should only return positive coordinates so I could use this to access them via a 2d vector. I only notice this since I was placing the grid -74 pixels off the center. Clicking on the bottom-most part of the grid returns a negative coordinate for Y-axis. I think I made some wrong calculations. I was new to programming bottom-left coordinate and having difficult time doing so.

Can you please check what might cause this?

One of the key function on my class is the ability to convert the pixel position (the location of the mouse or touch) into grid position. The function returns the grid position of mouse wherever the grid is on the screen. (e.g. 245x245 returns grid position 4 x 4).

Here is the class I am talking about. I removed some part which is not related to my question:

(...)

template<class T>
PlayField<T>::PlayField(const cocos2d::Vec2 & position, const cocos2d::Size & gridSize, int offset)
    : mGridSize(gridSize)
    , mOffset(offset)
    , mGridPositionOffset(position)
    , mDrawNode(nullptr)
{
    mGridPositionOffset.y = mGridPositionOffset.y - (gridSize.height * offset);
}

template<class T>
PlayField<T>::~PlayField()
{

}

template<class T>
void PlayField<T>::initialize(cocos2d::Scene * scene)
{
    for (int y = 0; y < mGridSize.height; ++y)
    {
        mGrid.push_back(vector<unique_ptr<T>>());

        for (int x = 0; x < mGridSize.width; ++x)
        {               
            cocos2d::Vec2 actualPosition;
            actualPosition.x = (x * mOffset) + mGridPositionOffset.x + (mOffset / 2.0f);
            actualPosition.y = mGridPositionOffset.y + ((y + 1) * mOffset) + (mOffset / 2.0f);

            T * gridObject = createGridObject(cocos2d::Vec2(x , y), scene);
            mGrid[y].push_back(unique_ptr<T>(gridObject));

            onGridObjectCreated(gridObject, actualPosition , cocos2d::Vec2(x , y));
        }
    }

    onGridCreationFinished(mGrid);
}

template<class T>
bool PlayField<T>::isWithinBounds(const cocos2d::Vec2 & position)
{
    if ((position.x >= 0 && position.x < mGridSize.width)
        && (position.y >= 0 && position.y < mGridSize.height))
        return true;

    return false;
}

template<class T>
cocos2d::Vec2 PlayField<T>::convertToGridPosition(const cocos2d::Vec2 & position)
{       
    cocos2d::Size gridPixelSize = cocos2d::Size(mGridSize.width * mOffset, (mGridSize.height-1) * mOffset);

    cocos2d::Vec2 ipos;
    ipos.x = (mGridPositionOffset.x + gridPixelSize.width)  - position.x;
    ipos.y = (mGridPositionOffset.y + gridPixelSize.height) - position.y;

    cocos2d::Vec2 gpos;
    gpos.x = static_cast<int>(ipos.x / mOffset);
    gpos.y = static_cast<int>(ipos.y / mOffset);

    //gpos = cocos2d::Vec2(mGridSize.width - 1 , mGridSize.height - 1) - gpos;

    gpos.x = (mGridSize.width - 1) - gpos.x;

    return gpos;
}

template<class T>
int PlayField<T>::getOffset() const
{
    return mOffset;
}

(...)

I initialize the grid this way, I set a small size so that I can simplify testing:

mScreenSize = cocos2d::Director::getInstance()->getOpenGLView()->getFrameSize();
cocos2d::Size gridSize = cocos2d::Size(3, 3);

cocos2d::Vec2 actualPosition;
actualPosition.x = (mScreenSize.width / 2.0f) - ((gridSize.width * 50) / 2.0f);
actualPosition.y = ((mScreenSize.height / 2.0f) + (((gridSize.height-1) * 50) / 2.0f) - 74.0f);

mJewelBoard.reset(new JewelBoard(actualPosition, this));
The JewelBoard constructor automatically set the grid into (3,3) with a tilesize of 50 pixels (50x50) on its constructor. The size of the game screen is 600x550. I set the position of the JewelBoard to the center using the actualPosition variable. Notice that I subtracted 74.0f so it goes little bit downward to match the background borders.

That is where I notice that the grid coordinates being returned are negative on the y-axis. I must have missed something in calculation. Also take note that getting the position of touch and mouse location is starting top-left. Another note as well, I prefer my 0x0 to start at the bottom-most part as well to keep it aligned with my other code so I don’t have to do flip-axis conversions.

I really have my reservations on Cocos2dx choice of coordinate system. I think its just me being subjective. I admit I have a very messed up computation but I am trying my best to clean it up.

I already consulted this with game developer and they could not found any odd or mistake. I also tried this time and time again on a piece of paper and it works.

Can you help figure out what goes wrong? Clicking the bottom-most part return a negative coordinates for y axis. I cannot have negative coordinates obviously since I will use that to index my 2d container.

Thank you.

Here is the link to my SO question:

As I posted from the link. I’ve made my write up for my answer:

I just figured it out. The mGridPositionOffset is pointing to the top-most corner of the grid. So that makes it (grid.height + mGridPositionOffset.height). The mGridPositionOffset already accounted the height of the grid since it is on the top-left most corner. What I did is to make sure that the offset actually starts on the bottom-most part of the grid. So the coordinate system is consistent all over the place.

To make that happen, consider the changes I made:

template<class T>

cocos2d::Vec2 PlayField::convertToGridPosition(const cocos2d::Vec2 & position)
{
//if (!isWithinPixelBounds(position))
// return cocos2d::Vec2(-1, -1);

// TODO fix y-axis not aligning properly

cocos2d::Size gridPixelSize = cocos2d::Size(mGridSize.width * mOffset, mGridSize.height * mOffset);

cocos2d::Vec2 ipos;
ipos.x = (mGridPositionOffset.x + gridPixelSize.width ) - position.x;
ipos.y = ((mGridPositionOffset.y - gridPixelSize.height) + gridPixelSize.height) - position.y;

CCLOG("Position : %f , %f" , ipos.x , ipos.y);

cocos2d::Vec2 gpos;
gpos.x = static_cast<int>(ipos.x / mOffset);
gpos.y = static_cast<int>(ipos.y / mOffset);

gpos.x = (mGridSize.width - 1) - gpos.x;
gpos.y = (mGridSize.height - 1) - gpos.y;

return gpos;

}
Also, I made sure that the touch/mouse position actually starts on the bottom as well:

cocos2d::Vec2 touchPosition = touch->getLocationInView();
touchPosition = cocos2d::Director::getInstance()->convertToGL(touchPosition);
Now the grid can now throw the correct coordinates wherever the grid is on the screen!

Hope it helps.