Topic: Map with elevation has hickup

Hello Alex,

when using the map with the elevation feature (I am using the Fly scene again), it works fine in principle, but from time to time (after a certain distance on the map has been moved) the map stutters. With stutter, I mean that all vertices change their position from one frame to the next and mountains look completely different than before.

How to reproduce:
Load the Fly scene, check the Use Elevation feature and enter a Bing API Key.
Latitude is set at 46.1714515686035 and Longitude is set to 8.84636116027832, but I guess this is the default in the Fly scene.
Change the Aircraft Speed to e.g. 3000, so that the problem appears more often. It will happen all 5-6 seconds then.
Look at the mountains. They will hickup all 6 seconds.

I have the impression, that always, if a new tile update is downloaded from the server, the information is suddenly applied to the mesh and therefore it looks like a stutter / hickup.

I tried to solve the problem myself, by interpolating between the mesh manipulations, but I wasn't successful.
In OnlineMapsTileSetControl in Method UpdateMapMesh() there is a line that changes the mesh:

tilesetMesh.vertices = vertices;

I commented out this line and did the following instead:

Vector3[] changedVertices = vertices;
Vector3[] meshVerticesCache = tilesetMesh.vertices;
for (int ii = 0; ii < vertices.Length; ++ii)
{
    changedVertices[ii].y = Mathf.Lerp(meshVerticesCache[ii].y, vertices[ii].y, 0.1f);
}
tilesetMesh.vertices = changedVertices;

Now the changes were smoother in general, but the hickup was there anyway.

I hope you can reproduce and fix this issue.

Thank you again in advance
Clemens

Re: Map with elevation has hickup

Hello.

I know about this problem.

Why is this happening:
Bing Maps Elevation API can return up to 1024 (32x32) values per request.
When Bing Maps does not have value for some coordinate, it calculates this based on known neighboring values. Actually, this happens for almost every point.
Next, tileset does this a second time, because in 99.9% of cases the size of the map does not match the size of the elevation data.
The entire map is drawn from one set of elevation data, for performance reasons and careful use of Bing Maps quota (otherwise the map will quickly burn this quota).
So at each point of map you have an approximate-approximate value.
When you have any visual reference points on the map, updating the values of elevation data is very visible.

Is it possible to work around this using API?!
Most likely yes. But to be honest, I've never tried it.
If you want, I can try, but it will take some time.

Kind Regards,
Infinity Code Team

Re: Map with elevation has hickup

Hello Alex,

I absolutely can understand why you need to use the small resolution for the hight map and therefore need to interpolate the height of the vertices that do not have an actual hight value via their neighbours across multiple tile sets. And of course, the mesh changes drustically, if you receive a new update from bing.

What I do not get is, why I still see a hickup, if I only slightly change the vertices array or mesh.vertices data by only taking a small percentag of the changed data to my existing one (Interpolating from the existing mesh to the new wanted one). In the mean time I tried to do the interpolation on another (I guess better than in my first post) position in code.
In UpdateMapSubMesh() instead of doing

vertices[i] = new Vector3(fx, fy, fz);

I tried to do

float lerpedY = Mathf.Lerp(vertices[i].y, fy, 0.01f);
vertices[i] = new Vector3(fx, lerpedY, fz);

but it was not bringing the success I was hoping for. When I get an update from bing, I still can see a hickup.
I am wondering a little bit, why this update has such a massive affect on the mesh. Even if the whole mesh changes, I only take a small percantage of the change into account for changing the vertices array. So why do I still see a hickup?
I thought, there must be another part in the code, that changes the vertices array (or at least the y-values), but I could not find it.
Can you tell me where in the code I need to focus on to prevent the hickup?

I do not need a fix for accuracy, I just want to get rid of the hickup. It is perfectly fine for me, if the mountains are moving like waves because of the interpolation.

Yours
Clemens

Re: Map with elevation has hickup

Your way will not work, because the same vertice in different frames can refer to different tiles.
It's pretty hard to explain, so just take it for granted.

My way to work around the problem (without modifying the code):

using System;
using System.Reflection;
using UnityEngine;

public class ElevationHickup:MonoBehaviour
{
    public float duration = 1; // sec

    private OnlineMaps map;
    private OnlineMapsTileSetControl control;
    private MeshFilter meshFilter;
    private bool needImprove = false;
    private float t;
    private short[,] newElevations;
    private short[,] elevationData;
    private int elevationDataWidth = 32;
    private int elevationDataHeight = 32;
    private float elevationX1;
    private float elevationY1;
    private float elevationW;
    private float elevationH;
    private short elevationMinValue;
    private float newElevationX1;
    private float newElevationY1;
    private float newElevationW;
    private float newElevationH;

    private float GetElevationValue(double x, double z, float yScale, double tlx, double tly, double brx, double bry)
    {
        if (elevationData == null) return float.MinValue;

        x = x / -map.tilesetSize.x;
        z = z / map.tilesetSize.y;

        int ew = elevationDataWidth - 1;
        int eh = elevationDataHeight - 1;

        if (x < 0) return float.MinValue;
        else if (x > 1) return float.MinValue;

        if (z < 0) return float.MinValue;
        else if (z > 1) return float.MinValue;

        double cx = (brx - tlx) * x + tlx;
        double cz = (bry - tly) * z + tly;

        float rx = (float)((cx - elevationX1) / elevationW * ew);
        float ry = (float)((cz - elevationY1) / elevationH * eh);

        if (rx < 0) return float.MinValue;
        if (rx > ew) return float.MinValue;

        if (ry < 0) return float.MinValue;
        if (ry > eh) return float.MinValue;

        int x1 = (int)rx;
        int x2 = x1 + 1;
        int y1 = (int)ry;
        int y2 = y1 + 1;
        if (x2 > ew) x2 = ew;
        if (y2 > eh) y2 = eh;

        float p1 = (elevationData[x2, eh - y1] - elevationData[x1, eh - y1]) * (rx - x1) + elevationData[x1, eh - y1];
        float p2 = (elevationData[x2, eh - y2] - elevationData[x1, eh - y2]) * (rx - x1) + elevationData[x1, eh - y2];

        float v = (p2 - p1) * (ry - y1) + p1;
        if (control.elevationBottomMode == OnlineMapsTileSetControl.ElevationBottomMode.minValue) v -= elevationMinValue;
        return v * yScale * control.elevationScale;
    }

    private void OnElevationUpdated()
    {
        elevationData = newElevations;
        elevationX1 = newElevationX1;
        elevationY1 = newElevationY1;
        elevationW = newElevationW;
        elevationH = newElevationH;

        Type type = control.GetType();

        FieldInfo field = type.GetField("elevationData", BindingFlags.Instance | BindingFlags.NonPublic);
        short[,] ned = field.GetValue(control) as short[,];
        if (ned != null)
        {
            newElevations = new short[ned.GetLength(0), ned.GetLength(1)];

            for (int i = 0; i < ned.GetLength(0); i++)
            {
                for (int j = 0; j < ned.GetLength(1); j++) newElevations[i, j] = ned[i, j];
            }
        }

        field = type.GetField("elevationX1", BindingFlags.Instance | BindingFlags.NonPublic);
        newElevationX1 = (float)field.GetValue(control);

        field = type.GetField("elevationY1", BindingFlags.Instance | BindingFlags.NonPublic);
        newElevationY1 = (float)field.GetValue(control);

        field = type.GetField("elevationW", BindingFlags.Instance | BindingFlags.NonPublic);
        newElevationW = (float)field.GetValue(control);

        field = type.GetField("elevationH", BindingFlags.Instance | BindingFlags.NonPublic);
        newElevationH = (float)field.GetValue(control);

        UpdateElevationMinMax();

        needImprove = true;
    }

    private void OnMeshUpdated()
    {
        if (!needImprove || elevationData == null) return;

        Mesh mesh = meshFilter.sharedMesh;
        Vector3[] vertices = mesh.vertices;

        t += Time.deltaTime / duration;
        if (t >= 1)
        {
            t = 0;
            needImprove = false;
            return;
        }

        double tlx, tly, brx, bry;
        map.GetCorners(out tlx, out tly, out brx, out bry);
        float yScale = control.GetBestElevationYScale(tlx, tly, brx, bry);

        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 v = vertices[i];
            float y = GetElevationValue(v.x, v.z, yScale, tlx, tly, brx, bry);
            if (Math.Abs(y - float.MinValue) > float.Epsilon)
            {
                v.y = Mathf.Lerp(y, v.y, t);
                vertices[i] = v;
            }
        }

        mesh.vertices = vertices;
    }

    private void Start()
    {
        map = OnlineMaps.instance;
        control = OnlineMapsTileSetControl.instance;
        control.OnElevationUpdated += OnElevationUpdated;
        control.OnMeshUpdated += OnMeshUpdated;

        meshFilter = control.GetComponent<MeshFilter>();
    }

    private void UpdateElevationMinMax()
    {
        elevationMinValue = short.MaxValue;

        if (elevationData == null) return;

        int s1 = elevationData.GetLength(0);
        int s2 = elevationData.GetLength(1);

        for (int i = 0; i < s1; i++)
        {
            for (int j = 0; j < s2; j++)
            {
                short v = elevationData[i, j];
                if (v < elevationMinValue) elevationMinValue = v;
            }
        }
    }
}
Kind Regards,
Infinity Code Team

Re: Map with elevation has hickup

Thank you very much, Alex!
This extra class solves the issue for me.

All the best
Clemens