1 (edited by mikli1 2024-04-25 18:51:49)

Topic: Getting correct optimal zoom to fit all points within visible map area

Hey, this problem is similar to the https://forum.infinity-code.com/viewtopic.php?id=2209

I have in my app top panel, sidebar and timeline panel, which take away some space of the screen - see the attached image. The sidebar can change width.

The map takes the remaining part of the screen. I'm trying to center the map and calculate optimal zoom value to nicely see all of given points.

My map size is 2048x1536. In the tileset component size on the scene is also 2048x1536 - maybe that's a mistake? Can I change it in runtime?

My canvas is a screen space overlay canvas, with canvas scaler set to "Scale with screen size + reference resolution is 1920x1080".

How can I make it work on all canvas/screen sizes and different aspect ratios? I've noticed there is a Vector2 inset parametr in the OnlineMapsUtils.GetCenterPointAndZoom method, but I cannot make it work perfectly.

My current code:

Vector2 crop = Vector2.zero;
GameObject uiCanvas = GameObject.FindGameObjectWithTag(Global.Tags.UI_CANVAS);
GameObject sidebarGO = GameObject.FindGameObjectWithTag(Global.Tags.SIDEBAR_PANEL);
if (sidebarGO != null && uiCanvas != null)
{
    SidebarController sc = sidebarGO.GetComponentInChildren<SidebarController>(true);
    RectTransform canvasRT = uiCanvas.GetComponent<RectTransform>();

    if (sc != null && canvasRT != null)
    {
        crop = new Vector2(
            1.0f * (sc.expanded ? SIDEBAR_WIDTH_EXPANDED : SIDEBAR_WIDTH_COLLAPSED) / canvasRT.sizeDelta.x,
            1.0f * TIMELINE_HEIGHT_COLLAPSED / canvasRT.sizeDelta.y);

        crop /= canvasRT.localScale.x;
    }
}

OnlineMapsUtils.GetCenterPointAndZoom(positions.ToArray(), out Vector2 center, out int zoom, crop);
OnlineMapsControlBase.instance.map.SetPositionAndZoom(center.x, center.y, zoom);

How is the map positioned in the screen? Is the center of the map in the center of the screen?

Thanks for help.

Post's attachments

Attachment icon Bez tytułu.png 431.3 kb, 205 downloads since 2024-04-25 

2 (edited by mikli1 2024-04-26 06:34:08)

Re: Getting correct optimal zoom to fit all points within visible map area

Ok, I've managed to get it working.

I've added AdjustToScreen component, this way the tileset always covers my whole screen. I've fixed the calculations and I'm using the inset parameter to crop to view of the map. Unfortunately it crops the same ammount on left as on the right, but it is acceptable for me.

My code right now:

if (sidebarGO != null && uiCanvas != null)
{
    SidebarController sc = sidebarGO.GetComponentInChildren<SidebarController>(true);
    RectTransform canvasRT = uiCanvas.GetComponent<RectTransform>();

    if (sc != null && canvasRT != null)
    {
        Vector2 offset = new Vector2(
            1.0f * (sc.expanded ? Global.Values.SIDEBAR_WIDTH_EXPANDED : Global.Values.SIDEBAR_WIDTH_COLLAPSED),
            1.0f * Global.Values.TIMELINE_HEIGHT_COLLAPSED);

        Vector2 mapSize = new Vector2(OnlineMaps.instance.width, OnlineMaps.instance.height);

        crop = (((mapSize - canvasRT.sizeDelta) / 2) + offset) / mapSize;
    }
}

OnlineMapsUtils.GetCenterPointAndZoom(positions.ToArray(), out Vector2 center, out int zoom, crop);
OnlineMapsControlBase.instance.map.SetPositionAndZoom(center.x, center.y, zoom);

If you know how can I do it better (to use the space on the right, please let me know. Thanks.

Post's attachments

Attachment icon Bez tytułu.png 446.48 kb, 212 downloads since 2024-04-26 

3 (edited by mikli1 2024-05-16 13:45:58)

Re: Getting correct optimal zoom to fit all points within visible map area

Ok, so the previous solution wasnt quite working for all resolutions and aspect ratios. I've expanded the GetCenterPointAndZoom method to include both bottomLeft offset and topRight offset. And the condition looks like this:

if (px < xLeftTileOffset || py < yBottomTileOffset || px > width - xRightTileOffset || py > height - yTopTileOffset)
{
    success = false;
    break;
}

And in my code I've included another call to set position when I calculate the offset as in the https://forum.infinity-code.com/viewtopic.php?id=2209:

private void SetMapPositionAndZoom(List<Vector2> positions)
{
    Vector2 bottomLeftCrop = Vector2.zero;
    Vector2 topRightCrop = Vector2.zero;
    GameObject uiCanvas = GameObject.FindGameObjectWithTag(Global.Tags.UI_CANVAS);
    GameObject sidebarGO = GameObject.FindGameObjectWithTag(Global.Tags.SIDEBAR_PANEL);
    GameObject timelineGO = GameObject.FindGameObjectWithTag(Global.Tags.TIMELINE_PANEL);
    GameObject topPanelGO = GameObject.FindGameObjectWithTag(Global.Tags.TOP_PANEL);

    if (sidebarGO != null && timelineGO != null && uiCanvas != null && topPanelGO != null)
    {
        SidebarController sc = sidebarGO.GetComponentInChildren<SidebarController>(true);
        TimelineController tc = timelineGO.GetComponentInChildren<TimelineController>(true);
        TopPanelController tpc = topPanelGO.GetComponentInChildren<TopPanelController>(true);
        RectTransform canvasRT = uiCanvas.GetComponent<RectTransform>();

        if (sc != null && tc != null && canvasRT != null)
        {
            Vector2 bottomLeftOffset = new Vector2(
               1.0f * (sc.expanded ? Global.Values.SIDEBAR_WIDTH_EXPANDED : Global.Values.SIDEBAR_WIDTH_COLLAPSED),
               1.0f * tc.myRT.sizeDelta.y);

            Vector2 topRightOffset = new Vector2(0f, tpc.GetComponent<RectTransform>().sizeDelta.y);

            Vector2 mapSize = new Vector2(OnlineMaps.instance.width, OnlineMaps.instance.height);

            bottomLeftCrop = (((mapSize - (canvasRT.sizeDelta * canvasRT.localScale)) / 2) + (bottomLeftOffset * canvasRT.localScale)) / mapSize;
            topRightCrop = (((mapSize - (canvasRT.sizeDelta * canvasRT.localScale)) / 2) + (topRightOffset * canvasRT.localScale)) / mapSize;
        }
    }

    OnlineMapsUtils.GetCenterPointAndZoom(positions.ToArray(), out Vector2 center, out int zoom, bottomLeftCrop, topRightCrop);
    OnlineMapsControlBase.instance.map.SetPositionAndZoom(center.x, center.y, zoom);

    SetMapPosition(bottomLeftCrop, topRightCrop);
}

private void SetMapPosition(Vector2 bottomLeftCrop, Vector2 topRightCrop)
{
    OnlineMaps.instance.projection.CoordinatesToTile(OnlineMaps.instance.position.x, OnlineMaps.instance.position.y, OnlineMaps.instance.zoom, out double tx, out double ty);
    tx -= (bottomLeftCrop.x - topRightCrop.x) * 0.5d * OnlineMaps.instance.width / OnlineMapsUtils.tileSize * OnlineMaps.instance.zoomFactor;
    ty += (bottomLeftCrop.y - topRightCrop.y) * 0.5d * OnlineMaps.instance.height / OnlineMapsUtils.tileSize * OnlineMaps.instance.zoomFactor;

    OnlineMaps.instance.SetTilePosition(tx, ty);
}

This is still not working perfectly, sometimes it returns smaller zoom than it could (6 instead of 7). The reason is that the map position for the zoom calculation is just (min+max)/2, which is not correct in my application. The side offsets should be calculated into that.

If you have any idea if this can be done simpler/better please let me know. Thanks.