/// <summary>
 /// Inverse (Go from lat/lon to specified projection)
 /// </summary>
 /// <param name="uv"></param>
 /// <returns></returns>
 public UV Inverse(UV uv)
 {
     return pj_inv(uv, projPJ);
 }
 /// <summary>
 /// Forward (Go from specified projection to lat/lon)
 /// </summary>
 /// <param name="uv"></param>
 /// <returns></returns>
 public UV Forward(UV uv)
 {
     return pj_fwd(uv, projPJ);
 }
 static extern UV pj_inv(UV uv, IntPtr projPJ);
 static extern UV pj_fwd(UV uv, IntPtr projPJ);
        //NOTE this is a mix from Mashi's Reproject and WW for terrain
        public void CreateMesh(byte opacity, float verticalExaggeration)
        {
            this.vertEx = verticalExaggeration;

            int opacityColor = System.Drawing.Color.FromArgb(opacity, 0, 0, 0).ToArgb();

            meshPointCount = 32; //64; //96 // How many vertices for each direction in mesh (total: n^2)
            //vertices = new CustomVertex.PositionColoredTextured[meshPointCount * meshPointCount];

            // Build mesh with one extra row and col around the terrain for normal computation and struts
            vertices = new CustomVertex.PositionNormalTextured[(meshPointCount + 2) * (meshPointCount + 2)];

            int upperBound = meshPointCount - 1;
            float scaleFactor = (float)1 / upperBound;
            //using(Projection proj = new Projection(m_projectionParameters))
            //{
            double uStep = (UR.U - UL.U) / upperBound;
            double vStep = (UL.V - LL.V) / upperBound;
            UV curUnprojected = new UV(UL.U - uStep, UL.V + vStep);

            // figure out latrange (for terrain detail)
            UV geoUL = _proj.Inverse(m_ul);
            UV geoLR = _proj.Inverse(m_lr);
            double latRange = (geoUL.U - geoLR.U) * 180 / Math.PI;

            North = geoUL.V * 180 / Math.PI;
            South = geoLR.V * 180 / Math.PI;
            West = geoUL.U * 180 / Math.PI;
            East = geoLR.U * 180 / Math.PI;

            float meshBaseRadius = (float)_layerRadius;
            /*          float[,] heightData = null;
                        if (_terrainAccessor != null && _veForm.IsTerrainOn == true)
                        {
                            //does the +1 to help against tears between elevated tiles? - made it worse
                            //TODO not sure how to fix the tear between tiles caused by elevation?

                    // Get elevation data with one extra row and col all around the terrain
                    double degreePerSample = Math.Abs(latRange / (meshPointCount - 1));
                            TerrainTile tile = _terrainAccessor.GetElevationArray(North + degreePerSample, South - degreePerSample, West - degreePerSample, East + degreePerSample, meshPointCount + 2);
                            heightData = tile.ElevationData;
                            tile.Dispose();
                            tile = null;

                
                            // Calculate mesh base radius (bottom vertices)
                            float minimumElevation = float.MaxValue;
                            float maximumElevation = float.MinValue;
			
                            // Find minimum elevation to account for possible bathymetry
                            foreach(float _height in heightData)
                            {
                                if(_height < minimumElevation)
                                    minimumElevation = _height;
                                if(_height > maximumElevation)
                                    maximumElevation = _height;
                            }
                            minimumElevation *= verticalExaggeration;
                            maximumElevation *= verticalExaggeration;

                            if(minimumElevation > maximumElevation)
                            {
                                // Compensate for negative vertical exaggeration
                                float tmp = minimumElevation;
                                minimumElevation = maximumElevation;
                                maximumElevation = tmp;
                            }

                            float overlap = 500 * verticalExaggeration; // 500m high tiles
			
                            // Radius of mesh bottom grid
                            meshBaseRadius = (float) _layerRadius + minimumElevation - overlap;
                
                        }
            */
            UV geo;
            Vector3 pos;
            double height = 0;
            for (int i = 0; i < meshPointCount + 2; i++)
            {
                for (int j = 0; j < meshPointCount + 2; j++)
                {
                    geo = _proj.Inverse(curUnprojected);

                    // Radians -> Degrees
                    geo.U *= 180 / Math.PI;
                    geo.V *= 180 / Math.PI;

                    if (_terrainAccessor != null)
                    {
                        if (_veForm.IsTerrainOn == true)
                        {
                            //height = heightData[i, j] * verticalExaggeration;
                            //original : need to fetch altitude on a per vertex basis (in VE space) to have matching tile borders (note PM)
                            height = verticalExaggeration * _terrainAccessor.GetElevationAt(geo.V, geo.U, Math.Abs(upperBound / latRange));
                        }
                        else
                        {
                            height = 0;
                        }
                    }

                    pos = MathEngine.SphericalToCartesian(
                        geo.V,
                        geo.U,
                        _layerRadius + height);
                    int idx = i * (meshPointCount + 2) + j;
                    vertices[idx].X = pos.X;
                    vertices[idx].Y = pos.Y;
                    vertices[idx].Z = pos.Z;
                    //double sinLat = Math.Sin(geo.V);
                    //vertices[idx].Z = (float) (pos.Z * sinLat);

                    vertices[idx].Tu = (j - 1) * scaleFactor;
                    vertices[idx].Tv = (i - 1) * scaleFactor;
                    //vertices[idx].Color = opacityColor;
                    curUnprojected.U += uStep;
                }
                curUnprojected.U = UL.U - uStep;
                curUnprojected.V -= vStep;
            }
            //}

            int slices = meshPointCount + 1;
            indices = new short[2 * slices * slices * 3];
            for (int i = 0; i < slices; i++)
            {
                for (int j = 0; j < slices; j++)
                {
                    indices[(2 * 3 * i * slices) + 6 * j] = (short)(i * (meshPointCount + 2) + j);
                    indices[(2 * 3 * i * slices) + 6 * j + 1] = (short)((i + 1) * (meshPointCount + 2) + j);
                    indices[(2 * 3 * i * slices) + 6 * j + 2] = (short)(i * (meshPointCount + 2) + j + 1);

                    indices[(2 * 3 * i * slices) + 6 * j + 3] = (short)(i * (meshPointCount + 2) + j + 1);
                    indices[(2 * 3 * i * slices) + 6 * j + 4] = (short)((i + 1) * (meshPointCount + 2) + j);
                    indices[(2 * 3 * i * slices) + 6 * j + 5] = (short)((i + 1) * (meshPointCount + 2) + j + 1);
                }
            }

            // Compute normals and fold struts
            calculate_normals();
            fold_struts(false, meshBaseRadius);
        }
        /// <summary>
        /// Update layer (called from worker thread)
        /// </summary>
        public override void Update(DrawArgs drawArgs)
        {

            try
            {
                if (this.isOn == false)
                {
                    return;
                }

                //NOTE for some reason Initialize is not getting called from the Plugin Menu Load/Unload
                //it does get called when the plugin loads from Startup
                //not sure what is going on, so i'll just call it manually
                if (this.isInitialized == false)
                {
                    this.Initialize(drawArgs);
                    return;
                }

                //get lat, lon
                double lat = drawArgs.WorldCamera.Latitude.Degrees;
                double lon = drawArgs.WorldCamera.Longitude.Degrees;
                double tilt = drawArgs.WorldCamera.Tilt.Degrees;
                //determine zoom level
                double alt = drawArgs.WorldCamera.Altitude;
                //could go off distance, but this changes when view angle changes
                //Angle fov = drawArgs.WorldCamera.Fov; //stays at 45 degress
                //Angle viewRange = drawArgs.WorldCamera.ViewRange; //off of distance, same as TVR but changes when view angle changes
                Angle tvr = drawArgs.WorldCamera.TrueViewRange; //off of altitude
                //smallest altitude = 100m
                //tvr = .00179663198575926
                //start altitude = 12756273m
                //tvr = 180

                //WW _levelZeroTileSizeDegrees
                //180 90 45 22.5 11.25 5.625 2.8125 1.40625 .703125 .3515625 .17578125 .087890625 0.0439453125 0.02197265625 0.010986328125 0.0054931640625
                int zoomLevel = GetZoomLevelByTrueViewRange(tvr.Degrees);
                //dont start VE tiles until a certain zoom level
                if (zoomLevel < veForm.StartZoomLevel)
                {
                    this.RemoveAllTiles();
		    this.ForceRefresh();
                    return;
                }

                //WW tiles
                //double tileDegrees = GetLevelDegrees(zoomLevel);
                //int row = MathEngine.GetRowFromLatitude(lat, tileDegrees);
                //int col = MathEngine.GetColFromLongitude(lon, tileDegrees);

                //VE tiles
                double metersY;
                double yMeters;
                int yMetersPerPixel;
                int row;
                /*
                //WRONG - doesn't stay centered away from equator
                //int yMeters = LatitudeToYAtZoom(lat, zoomLevel); //1024
                double sinLat = Math.Sin(DegToRad(lat));
                metersY = earthRadius / 2 * Math.Log((1 + sinLat) / (1 - sinLat)); //0
                yMeters = earthHalfCirc - metersY; //20037508.342789244
                yMetersPerPixel = (int) Math.Round(yMeters / MetersPerPixel(zoomLevel));
                row = yMetersPerPixel / pixelsPerTile;
                */
                //CORRECT
                //int xMeters = LongitudeToXAtZoom(lon, zoomLevel); //1024
                double metersX = earthRadius * DegToRad(lon); //0
                double xMeters = earthHalfCirc + metersX; //20037508.342789244
                int xMetersPerPixel = (int)Math.Round(xMeters / MetersPerPixel(zoomLevel));
                int col = xMetersPerPixel / pixelsPerTile;

                //reproject - overrides row above
                //this correctly keeps me on the current tile that is being viewed
                UV uvCurrent = new UV(DegToRad(lon), DegToRad(lat));
                uvCurrent = proj.Forward(uvCurrent);
                metersY = uvCurrent.V;
                yMeters = earthHalfCirc - metersY;
                yMetersPerPixel = (int)Math.Round(yMeters / MetersPerPixel(zoomLevel));
                row = yMetersPerPixel / pixelsPerTile;

                //update mesh if VertEx changes
                if (prevVe != World.Settings.VerticalExaggeration)
                {
                    lock (veTiles.SyncRoot)
                    {
                        VeTile veTile;
                        for (int i = 0; i < veTiles.Count; i++)
                        {
                            veTile = (VeTile)veTiles[i];
                            if (veTile.VertEx != World.Settings.VerticalExaggeration)
                            {
                                veTile.CreateMesh(this.Opacity, World.Settings.VerticalExaggeration);
                            }
                        }
                    }
                }
                prevVe = World.Settings.VerticalExaggeration;

                //if within previous bounds and same zoom level, then exit
                if (row == prevRow && col == prevCol && zoomLevel == prevLvl && tilt == preTilt)
                {
                    return;
                }

                //System.Diagnostics.Debug.WriteLine("CHANGE");

                lock (veTiles.SyncRoot)
                {
                    VeTile veTile;
                    for (int i = 0; i < veTiles.Count; i++)
                    {
                        veTile = (VeTile)veTiles[i];
                        veTile.IsNeeded = false;
                    }
                }

                //metadata
                ArrayList alMetadata = null;
                if (veForm.IsDebug == true)
                {
                    alMetadata = new ArrayList();
                    alMetadata.Add("yMeters " + yMeters.ToString());
                    alMetadata.Add("metersY " + metersY.ToString());
                    alMetadata.Add("yMeters2 " + yMeters.ToString());
                    alMetadata.Add("vLat " + uvCurrent.V.ToString());
                    //alMetadata.Add("xMeters " + xMeters.ToString());
                    //alMetadata.Add("metersX " + metersX.ToString());
                    //alMetadata.Add("uLon " + uvCurrent.U.ToString());
                }

                //add current tiles first
                AddVeTile(drawArgs, row, col, zoomLevel, alMetadata);
                //then add other tiles outwards in surrounding circles
                AddNeighborTiles(drawArgs, row, col, zoomLevel, null, 1);
                AddNeighborTiles(drawArgs, row, col, zoomLevel, null, 2);
                AddNeighborTiles(drawArgs, row, col, zoomLevel, null, 3);
		// Extend tile grid if camera tilt above some values
                if(tilt > 45) AddNeighborTiles(drawArgs, row, col, zoomLevel, null, 4);
                if(tilt > 60) AddNeighborTiles(drawArgs, row, col, zoomLevel, null, 5);


                //if(prevLvl > zoomLevel) //zooming out
                //{
                //}			

                lock (veTiles.SyncRoot)
                {
                    VeTile veTile;
                    for (int i = 0; i < veTiles.Count; i++)
                    {
                        veTile = (VeTile)veTiles[i];
                        if (veTile.IsNeeded == false)
                        {
                            veTile.Dispose();
                            veTiles.RemoveAt(i);
			    i--;
                        }
                    }
                }

                prevRow = row;
                prevCol = col;
                prevLvl = zoomLevel;
		preTilt = tilt;
            }
            catch (Exception ex)
            {
                Utility.Log.Write(ex);
            }



        }