Detect mouse click inside an area (polygon)

I would like to create a simple 2D clone of Europa Universalis (so essentially the good old Risk boardgame) and I need to figure out how to let the player select a province on the world map. Each province has a complicated shape, so rectangles or such won’t do.

I don’t even know where to start since I couldn’t find a tutorial even remotely touching this subject. Please refer me to a documentation or any place where I should start. Thanks.

Wikipedia mentions 2 methods for checking if a point in within a polygon (https://en.wikipedia.org/wiki/Point_in_polygon). I found an article which provides code for the raycasting method, which I have tested and can confirm that it works: https://lassieadventurestudio.wordpress.com/2012/03/20/polygon-hit-test/

This would require you to calculate EVERY vertex for each each country, which could be time consuming if it’s as complex as the example map you have provided. I’m also not sure how efficient the raycasting method would be if countries are comprised of many vertices each.

There are also 2 other methods I can think of:

  1. Have each country as a seperate image. When the screen is clicked/tapped, iterate through each country. For each country, check if the the touch/click is inside the country’s bounding box. If it is, do a test to see if you are touching a transparent pixel or an opaque pixel of that country. (Either check the pixel colour for the country’s texture, or render the image to a RenderTexture and read the pixel from there.)

  2. Have a second copy of the map where each country is filled in with a unique colour. Show the normal map and keep the coloured map hidden. When the screen is tapped/clicked, read the colour of the pixel on the coloured map at the tapped/clicked point.

Both 1 and 2 would use more memory than the polygon method, but they are probably easier to achieve than having to calculate the exact vertices for every country.

Most of the solutions covered by @grimfate could be optimized if you divide the map into smaller grids and remember which countries appear in which grid.

For a basic example, you can divide the map into east and west. Then you detect in which half of the map was the mouse clicked, leading you to save half of the calculation time. You could make smaller grids for faster calculations.

Keep in mind, though, that given that this would be a turn-based game, real-time is not really a concern, so actually having big calculations isn’t that big of an issue.

After a lot of googling I found out that this is more or less how Europa Universalis does it. I would have never been able to figure out how to do this, but luckily someone already has had a similar problem when checking for Pixel Perfect Collision Detection cocos2d-x v3 using glReadPixels():

This works perfectly! So I thanks to you and @ahlwong I found a solution to my original problem, but now I am facing several more. How do I highlight selected province, e.g. by making its border a different colour? At what coordinates do I place units, buildings, etc.

My only idea is to manually pinpoint each province border and store it as a polygon (so basically your other suggestion), but that would be time consuming, inaccurate, cumbersome, etc. etc.

This is how world map looks under the hood in Europa Universalis. Each province has a unique colour as @grimfate suggested. The gameplay map is drawn from other textures, but this province map is used to draw province borders dynamically in OpenGL.

The only solution I found to drawing borders between these provinces is to use OpenGL shaders. It turns out that using shaders is really simple in Cocos2d-x:

    auto sprite = Sprite::create("map.png");
    ...
    GLProgram* p = new GLProgram();
    p->initWithFilenames("outline.vsh", "outline.fsh");
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
    p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    p->link();
    p->updateUniforms();
    sprite->setGLProgram(p);

Writing shaders is not easy, however. I am completely sure that the solution would be to adapt shader from this article about LÖVE engine, but I lost my patience with it. I am giving up on this, but if any other amateur like me stumbles upon this post, I think these are the places to look into:

1 Like