Topic: Drawing filled polygon in tileSet mode

I know that the documentation say that the background color of the polygon is not supported in tileset mode.

Is there any alternative solutions to display a filled polygon on the map ?

I'm thinking about creating a mesh and putting it just slightly over the map, but is there a better way ? I'm using elevation, so the mesh solution is more complex than i usually though. I want to explore all existing solution before doing that.


Re: Drawing filled polygon in tileSet mode


Unfortunately, there are no better ways.
Here, only suitable and not suitable ways:
1. OnlineMapsDrawingElement.checkMapBoundaries = false.
This mode works not intersect.
2. (Not recommended) Use Drawing as Overlay.
3. Draw a mesh over a map with elevations.
4. Draw a flat mesh and play with Material.renderQueue or Shader.renderQueue.

Kind Regards,
Infinity Code Team

Re: Drawing filled polygon in tileSet mode

So, i tried a different approach. I'trying to draw directly on the tileset shader.
Right now i'm trying to draw a filled circle with a border on the map.

So far it's working, except that i am not able to modify my shader variables and i really don't know why hmm

Here is the TilesetCutoutShader with the modification i added:

Shader "Custom/TilesetCutoutShaderCircleOnMap" {
        _Color("Main Color", Color) = (1,1,1,1)
        _MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
        _OverlayBackTex("Overlay Back Texture", 2D) = "black" {}
        _OverlayBackAlpha("Overlay Back Alpha", Range(0, 1)) = 1
        _TrafficTex("Traffic Texture", 2D) = "black" {}
        _OverlayFrontTex("Overlay Front Texture", 2D) = "black" {}
        _OverlayFrontAlpha("Overlay Front Alpha", Range(0, 1)) = 1
        //------------------------------------------------- DRAW A CIRCLE:
        _BorderColor("Border Color", Color) = (0, 0, 0,1)
        _FillColor("Fill Color",Color) = (1,1,1,1)
        _Center("Center", Vector) = (0,0,0,0)
        _Radius("Radius", Range(0, 1000)) = 500
        _Border("Border", Range(0, 100)) = 10


            Tags {"Queue" = "AlphaTest-300" "IgnoreProjector" = "False" "RenderType" = "TransparentCutout"}
            LOD 200

            #pragma surface surf Lambert alphatest:_Cutoff 

            sampler2D _MainTex;
            sampler2D _OverlayBackTex;
            half _OverlayBackAlpha;
            sampler2D _TrafficTex;
            sampler2D _OverlayFrontTex;
            half _OverlayFrontAlpha;
            fixed4 _Color;

            //------------------------------------------------- DRAW A CIRCLE:
            fixed4 _BorderColor;
            fixed4 _FillColor;
            float3 _Center;
            float _Border;
            float _Radius;

            struct Input
                float2 uv_MainTex;
                float2 uv_OverlayBackTex;
                float2 uv_TrafficTex;
                float2 uv_OverlayFrontTex;
                //------------------------------------------------- DRAW A CIRCLE:
                float3 worldPos;

            void surf(Input IN, inout SurfaceOutput o)
                fixed4 c = tex2D(_MainTex, IN.uv_MainTex);

                fixed4 t = tex2D(_OverlayBackTex, IN.uv_OverlayBackTex);
                fixed3 ct = lerp(c.rgb, t.rgb, t.a * _OverlayBackAlpha);

                t = tex2D(_TrafficTex, IN.uv_TrafficTex);
                ct = lerp(ct, t.rgb, t.a);

                t = tex2D(_OverlayFrontTex, IN.uv_OverlayFrontTex);
                ct = lerp(ct, t.rgb, t.a * _OverlayFrontAlpha);

                ct = ct * _Color;
                o.Albedo = ct;
                o.Alpha = c.a * _Color.a;
                //------------------------------------------------- DRAW A CIRCLE:

                float dist = distance(_Center, IN.worldPos);
                if (dist < _Radius)
                    o.Albedo = lerp(ct, _FillColor, _FillColor.a);
                else if (dist > _Radius && dist < (_Radius + _Border))
                    o.Albedo = lerp(ct, _BorderColor, _BorderColor.a);


            Fallback "Transparent/Cutout/Diffuse"

Result  :

Now, when i click on the map i try to do this :

//Keep trace of the last point clicked on the map
            OnlineMapsControlBase.instance.GetCoords(out lastClickLng, out lastClickLat);
            Vector3 p = OnlineMapsTileSetControl.instance.GetWorldPosition(lastClickLng, lastClickLat);
            Shader.SetGlobalFloat("_Radius", 1000);

But it doesn't affect the shader. Am i missing something ?

Re: Drawing filled polygon in tileSet mode

Also when i was testing a simple shader to draw my circle, i had to put it on a material and assign it to a gameobject (here, a plane). This allowed me to modify my variables in the inspector :

But with the map, i just assign the new shader in the inspector. Since there is no material involve i can't play with the properties.

How does it work if there is no material ? Could this explain why i can't get to modify my shader parameters ?

Re: Drawing filled polygon in tileSet mode

OK i solved it.

Shader.SetGlobal-whatever- will not be taken into account by the shader if the properties is defined in the shader.

So to get it to work, i simply had to comment those lines in the shader file:

        _Color("Main Color", Color) = (1,1,1,1)
        _MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
        _OverlayBackTex("Overlay Back Texture", 2D) = "black" {}
        _OverlayBackAlpha("Overlay Back Alpha", Range(0, 1)) = 1
        _TrafficTex("Traffic Texture", 2D) = "black" {}
        _OverlayFrontTex("Overlay Front Texture", 2D) = "black" {}
        _OverlayFrontAlpha("Overlay Front Alpha", Range(0, 1)) = 1
        //------------------------------------------------- DRAW A CIRCLE:
        _BorderColor("Border Color", Color) = (0, 0, 0,1)
        _FillColor("Fill Color",Color) = (1,1,1,1)
        //_Center("Center", Vector) = (0,0,0,0)
        //_Radius("Radius", Range(0, 1000)) = 500
        _Border("Border", Range(0, 100)) = 10


And now the global parameters are taken into account.

So i've got a lot of work to do do now, but it is going to work. I will draw my shapes directly in the shader, passing array of points. If i succeed i'll post a detailed example here

Re: Drawing filled polygon in tileSet mode

You have found an interesting and non-trivial solution.
I hope you can successfully complete this.

About your questions:
Materials are hidden because for Unity 2018.2 or later, a large number of materials can break the inspector.
This is some kind of bug in Unity Editor. So we just hide the materials and MeshRenderer.
You have many ways to get materials, for example from MeshRenderer (this is also hidden, but you can all get it through GetComponent).
Or you can get materials through OnlineMapsTileSetControl.OnDrawTile.

Kind Regards,
Infinity Code Team

Re: Drawing filled polygon in tileSet mode

So i tried to replace "Shader.SetGlobalVector("_Center", new Vector4(center.x, center.y, center.z));"

OnlineMaps.instance.GetComponent<MeshRenderer>().sharedMaterial.SetVector("_Center", new Vector4(center.x, center.y, center.z));

OnlineMapsTileSetControl.instance.GetComponent<MeshRenderer>().sharedMaterial.SetVector("_Center", new Vector4(center.x, center.y, center.z));

I even tried :

control.OnDrawTile += SetMat;

With :

    public void SetMat(OnlineMapsTile t,Material m)
        mat = m;

and :
mat.SetVector("_Center", new Vector4(center.x, center.y, center.z));

But nothing worked. I tried to uncomment the properties in my shader but that didn't change anything. So i will keep using the "SetGlobal-whatever- function, which is not very clean, unless you can point me another way to get a reference to the material ? tongue

Re: Drawing filled polygon in tileSet mode

So here is current progression: … /giphy.gif

As you can see, when the radius define a short distance, there is a big imprecision. This doesn't happen with a bigger radius.
So, i feel like i'm having the same troubles every week yikes

So basically i'm defining a circle with :

    Vector2Double circleCenter; // Vector2Double is a struct containing 2 double : longitute & latitude
    Vector2Double pointOnCircle;
    float circleRadius;

Then i subscribe to these events :

 control.OnMeshUpdated += UpdateCircle;
 control.OnSmoothZoomProcess += UpdateCircle;

And the weak point of my script, calculating the radius and updating the shader value:

 Vector3 realPosCenter = OnlineMapsTileSetControl.instance.GetWorldPosition(circleCenter.x, circleCenter.y);
 Vector3 realPosOnCircle = OnlineMapsTileSetControl.instance.GetWorldPosition(pointOnCircle.x, pointOnCircle.y);

circleRadius = Vector3.Magnitude(realPosOnCircle - realPosCenter);
DrawCircle(realPosCenter, circleRadius);

I'm sorry that i'm always getting confuse when achieving this kind of task (converting distances) and the more i try, the more i suck.

I just don't understand why the distance between my two marker world positions got wrong for small distances

Re: Drawing filled polygon in tileSet mode

This happens because you use GetWorldPosition and a map with elevations.
Use GetWorldPositionWithElevation.

double tlx, tly, brx, bry;
OnlineMaps.instance.GetCorners(out tlx, out tly, out brx, out bry);

Vector3 cp = control.GetWorldPositionWithElevation(centerPoint.x, centerPoint.y, tlx, tly, brx, bry);
Vector3 rp = control.GetWorldPositionWithElevation(radiusPoint.x, radiusPoint.y, tlx, tly, brx, bry);

Shader.SetGlobalVector("_Center", cp);
Shader.SetGlobalFloat("_Radius", (rp - cp).magnitude);
Kind Regards,
Infinity Code Team

Re: Drawing filled polygon in tileSet mode

So i finally found the time to continue this. Here is the update:

Good news: i made it work with polygons.
Bad news : it's killing the framerate on my tablet. I could probably optimize the shader a bit. I absolutly wanted an outline for the polygon, but without this fancy addition it's a bit smoother.
So everything is running fine on my computer, but a polygon (even with a small number of point) made this annoying to use on the tablet.

I will try to optimize the shader but i have the feeling that i will end up not using this solution after all sad

The effect is really cool though

Cherry on the cake is that i can set the outline width in meter smile

Re: Drawing filled polygon in tileSet mode

This is very cool.
Please, when you're ready, publish a shader for polygons.
Maybe I will find some way to optimize this.

Kind Regards,
Infinity Code Team

12 (edited by Thibault 2018-11-30 19:14:03)

Re: Drawing filled polygon in tileSet mode

I won't have time to set up a demo scene & unity package, so here are the files you need with a small explanation pdf.
So if anyone want to have fun with this experimental tool, here is my code:

Edit :

I forgot to add the c# file containing this function, which is called in DrawShapeManager :

/// <summary>
/// Return the Angle, clockwise from north (degree) of the vector AB
/// </summary>
/// <param name="lng">longitude of A</param>
/// <param name="lat">Latitude of A</param>
/// <param name="lng2">longitude of B</param>
/// <param name="lat2">Latitude of B</param>
public static float GetTileAngleFromNorth(double lng, double lat, double lng2, double lat2)
        double pointBCoordToTileX, pointBCoordToTileY, pointACoordToTileX, pointACoordToTileY;
        OnlineMaps.instance.projection.CoordinatesToTile(lng, lat, OnlineMaps.instance.zoom, out pointACoordToTileX, out pointACoordToTileY);
        OnlineMaps.instance.projection.CoordinatesToTile(lng2, lat2, OnlineMaps.instance.zoom, out pointBCoordToTileX, out pointBCoordToTileY);

        float angle = 90 + ((float)OnlineMapsUtils.Angle2D(pointACoordToTileX, pointACoordToTileY, pointBCoordToTileX, pointBCoordToTileY));
        if (angle < 0)
            angle = 360 + angle;
        return angle;

Re: Drawing filled polygon in tileSet mode

Thanks for your scripts.
A modified version of the shader is attached.

What has been changed:
1. Removed all properties except _MainTex. If you need something from this you can return it back.
2. Replaced Fallback "Transparent / Cutout / Diffuse" to "Unlit / Diffuse", because it is MUCH faster.
3. Polygon bounds have been added to speed up the calculation of a point outside the polygon.

private void EncapsulateBounds(ref Vector4 bounds, Vector3 posPolyPoint)
    if (bounds.x > posPolyPoint.x) bounds.x = posPolyPoint.x;
    if (bounds.y > posPolyPoint.z) bounds.y = posPolyPoint.z;
    if (bounds.z < posPolyPoint.x) bounds.z = posPolyPoint.x;
    if (bounds.w < posPolyPoint.z) bounds.w = posPolyPoint.z;

Vector4 bounds = new Vector4(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
EncapsulateBounds(ref bounds, worldPosPolyPoint); // For all points
Shader.SetGlobalVector("_PolyBounds", bounds);

4. Replaced isIntersecting to port of OnlineMapsUtils.IsPointInPolygon method, because it is much faster.
5. Additional small shader code optimizations.

It was tested on the Sony Xperia Z2 Tablet and works fine.

Possible future optimizations:
- For outline polygon: It is possible to replace checking of the point of the internal polygon by checking the distance from the outline segment to the point. This will allow you not to use _Points and maybe it will be faster.
- DrawShapeManager can be highly optimized, which will be good for performance.

Post's attachments

Attachment icon TilesetCutoutShaderCircleOnMap.shader 3.35 kb, 18 downloads since 2018-12-02 

Kind Regards,
Infinity Code Team

Re: Drawing filled polygon in tileSet mode

Thanks, it definitely made a difference.

My tablet is still suffering a bit but after all it's 4 years old now. I don't see big differences anymore in the framerate when drawing polygon / not drawing anything. So mission accomplish ! I'm glad that we came up with a solution to draw polygon in tileset mode smile

I know that i should spend some time to optimize my existing scripts. I'm aware of some basic tricks to optimize scripts but i probably need to learn a lot of stuff to do it efficiently. If you have any advices / tutorials or lectures on the subject i will gladly learn