/// <summary>
            /// If you have moved the particle, call this instead of Enumerator.SetCurrent to update it.
            /// This ensures that particle partitioning keeps working.
            /// </summary>
            public void ParticleMoved(ref T particle, ref Vector2 oldPosition, ref Vector2 newPosition)
            {
                if (!SectorBounds.Contains(ref newPosition))
                {
                    var newIndex = System.Particles.GetIndexFromPoint(newPosition);
                    Enumerator.RemoveCurrent();

                    var newSector = System.Particles.GetSectorFromIndex(newIndex, true);
                    if (newSector.Count > System.SectorCapacityLimit)
                    {
                        System._ParticlesRemovedByLimit += 1;

                        if (System.RemoveParticlesWhenCapacityReached)
                        {
                            newSector.RemoveAt(System.RNG.Next(0, newSector.Count));
                        }
                        else
                        {
                            return;
                        }
                    }
                    newSector.Add(ref particle);
                }
                else
                {
                    Enumerator.SetCurrent(ref particle);
                }
            }
        private void DrawFlat(GraphicsDevice graphicsDevice, SectorBounds bounds, ISector rootSector)
        {
            RenderTarget2D renderTarget = renderTargets[rootSector];

            // Set the render target
            graphicsDevice.SetRenderTarget(renderTarget);
            graphicsDevice.DepthStencilState = new DepthStencilState()
            {
                DepthBufferEnable = true
            };
            graphicsDevice.Clear(Pallete.OCEAN_BLUE);

            double         relativeCameraZoom = camera.cameraZoom - Math.Log(ZCoords.GetSectorManager().GetTopmostOSMSectors().Count, 4) + (Game1.RECORDING ? 1 : 0);
            int            zoomLevel          = Math.Min(Math.Max((int)(relativeCameraZoom - 3), 0), ZCoords.GetSectorManager().GetHighestOSMZoom());
            List <ISector> containedSectors   = rootSector.GetSectorsInRange(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, zoomLevel);
            List <ISector> sorted             = containedSectors.Where(x => x.GetRoot().Equals(rootSector)).ToList();

            sorted.Sort((x, y) => x.Zoom.CompareTo(y.Zoom));
            foreach (var sector in sorted)
            {
                IGraphicsBuffer buffer = loadedMaps[sector];
                if (!(buffer is ImageTileBuffer))
                {
                    continue;
                }
                Matrixd       projection = Matrixd.CreateOrthographicOffCenter(bounds.minX * (1 << sector.Zoom) - sector.X, bounds.maxX * (1 << sector.Zoom) - sector.X, bounds.maxY * (1 << sector.Zoom) - sector.Y, bounds.minY * (1 << sector.Zoom) - sector.Y, -1, 0.01f); // TODO: why negative?
                RenderContext context    = new RenderContext(graphicsDevice, projection, bounds.minX * (1 << sector.Zoom) - sector.X, bounds.maxX * (1 << sector.Zoom) - sector.X, bounds.minY * (1 << sector.Zoom) - sector.Y, bounds.maxY * (1 << sector.Zoom) - sector.Y, camera.cameraZoom, RenderContext.LayerPass.MAIN_PASS);
                buffer.Draw(context);
            }
        }
        public override void Draw(RenderContext renderContext, GameTime gameTime)
        {
            if (renderContext.layerPass == RenderContext.LayerPass.MAIN_PASS && !Game1.DEFERRED_RENDERING)
            {
                var effect = this.GetDefaultEffect(renderContext.graphicsDevice);

                camera.ApplyMatrices(effect);
                float distance = (float)(9 * Math.Pow(0.5, camera.cameraZoom)); // TODO: this is hacky
                effect.View = CameraMatrixManager.GetWorldRelativeView(distance);

                effect.TextureEnabled = true;
                foreach (var rootSector in ZCoords.GetSectorManager().GetTopmostOSMSectors())
                {
                    SectorBounds       bounds = GetSectorBounds(renderContext.graphicsDevice, rootSector);
                    VertexIndiceBuffer sphere = SphereBuilder.MakeSphereSegExplicit(renderContext.graphicsDevice, rootSector, 2, bounds.minX, bounds.minY, bounds.maxX, bounds.maxY, camera);
                    effect.Texture = renderTargets[rootSector];
                    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                    {
                        pass.Apply();
                        renderContext.graphicsDevice.Indices = sphere.indices;
                        renderContext.graphicsDevice.SetVertexBuffer(sphere.vertices);
                        renderContext.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, sphere.indices.IndexCount / 3);
                    }
                    sphere.vertices.Dispose();
                    sphere.indices.Dispose();
                }
            }
            if (renderContext.layerPass == RenderContext.LayerPass.MAIN_PASS && Game1.DEFERRED_RENDERING)
            {
                var    effect   = GlobalContent.DeferredBasicNormalTextureShader;
                float  distance = (float)(9 * Math.Pow(0.5, camera.cameraZoom)); // TODO: this is hacky
                Matrix view     = CameraMatrixManager.GetWorldRelativeView(distance);
                effect.Parameters["WVP"].SetValue(camera.world * view * camera.projection);
                foreach (var rootSector in ZCoords.GetSectorManager().GetTopmostOSMSectors())
                {
                    SectorBounds       bounds = GetSectorBounds(renderContext.graphicsDevice, rootSector);
                    VertexIndiceBuffer sphere = SphereBuilder.MakeSphereSegExplicit(renderContext.graphicsDevice, rootSector, 2, bounds.minX, bounds.minY, bounds.maxX, bounds.maxY, camera);
                    effect.Parameters["Texture"].SetValue(renderTargets[rootSector]);
                    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                    {
                        pass.Apply();
                        renderContext.graphicsDevice.Indices = sphere.indices;
                        renderContext.graphicsDevice.SetVertexBuffer(sphere.vertices);
                        renderContext.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, sphere.indices.IndexCount / 3);
                    }
                    sphere.vertices.Dispose();
                    sphere.indices.Dispose();
                }
            }
            foreach (var rootSector in ZCoords.GetSectorManager().GetTopmostOSMSectors())
            {
                Draw3D(renderContext.graphicsDevice, allBounds[rootSector], rootSector, renderContext.layerPass);
            }
        }
        private void Draw3D(GraphicsDevice graphicsDevice, SectorBounds bounds, ISector rootSector, RenderContext.LayerPass layerPass)
        {
            double         relativeCameraZoom = camera.cameraZoom - Math.Log(ZCoords.GetSectorManager().GetTopmostOSMSectors().Count, 4) + (Game1.RECORDING ? 1 : 0);
            int            zoomLevel          = Math.Min(Math.Max((int)(relativeCameraZoom - 3), 0), ZCoords.GetSectorManager().GetHighestOSMZoom());
            List <ISector> containedSectors   = rootSector.GetSectorsInRange(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, zoomLevel);
            List <ISector> sorted             = containedSectors.Where(x => x.GetRoot().Equals(rootSector)).ToList();

            sorted.Sort((x, y) => x.Zoom.CompareTo(y.Zoom));
            foreach (var sector in sorted)
            {
                IGraphicsBuffer buffer = loadedMaps[sector];
                if (buffer is ImageTileBuffer)
                {
                    continue;
                }
                SectorBounds b           = new SectorBounds(bounds.minX * (1 << sector.Zoom) - sector.X, bounds.maxX * (1 << sector.Zoom) - sector.X, bounds.minY * (1 << sector.Zoom) - sector.Y, bounds.maxY * (1 << sector.Zoom) - sector.Y);
                SectorBounds limitedB    = new SectorBounds(Math.Max(0, Math.Min(1, b.minX)), Math.Max(0, Math.Min(1, b.maxX)), Math.Max(0, Math.Min(1, b.minY)), Math.Max(0, Math.Min(1, b.maxY)));
                BasicEffect  basicEffect = new BasicEffect(graphicsDevice);
                camera.ApplyMatrices(basicEffect);
                // going to make it easy and assume the shape is perfectly parallel (it's not)
                // the sector plane is constructed by flattening the visible portion of the sphere, basically
                Vector3d v1    = sector.ProjectToSphereCoordinates(new Vector2d(limitedB.minX, limitedB.minY));
                Vector3d v2    = sector.ProjectToSphereCoordinates(new Vector2d(limitedB.maxX, limitedB.minY));
                Vector3d v3    = sector.ProjectToSphereCoordinates(new Vector2d(limitedB.minX, limitedB.maxY));
                Vector3d xAxis = (v2 - v1) / (limitedB.maxX - limitedB.minX);
                Vector3d yAxis = (v3 - v1) / (limitedB.maxY - limitedB.minY);
                Vector3d start = v1 - xAxis * limitedB.minX - yAxis * limitedB.minY;
                Vector3d zAxis = start * (xAxis.Length() + yAxis.Length()) / start.Length() / 2;                                          // make this roughly the same length
                // matrixes copied over
                Matrixd       world           = Matrixd.CreateRotationZ(-camera.cameraRotX) * Matrixd.CreateRotationX(camera.cameraRotY); // eh.... think hard on this later
                double        distance        = 9 * Math.Pow(0.5, camera.cameraZoom);
                Matrixd       view            = CameraMatrixManager.GetWorldViewd(distance);
                Matrixd       projection      = CameraMatrixManager.GetWorldProjectiond(distance, graphicsDevice.Viewport.AspectRatio);
                Matrixd       transformMatrix = new Matrixd(xAxis.X, xAxis.Y, xAxis.Z, 0, yAxis.X, yAxis.Y, yAxis.Z, 0, zAxis.X, zAxis.Y, zAxis.Z, 0, start.X, start.Y, start.Z, 1); // turns our local coordinates into 3d spherical coordinates, based on the sector
                Matrixd       WVP             = Normalize(transformMatrix * world * view * projection);                                                                              // combine them all to allow for higher precision
                RenderContext context         = new RenderContext(graphicsDevice, WVP, b.minX, b.maxX, b.minY, b.maxY, camera.cameraZoom, layerPass);
                buffer.Draw(context);
            }
        }
        private void InitDraw(GraphicsDevice graphicsDevice, SectorBounds bounds, ISector rootSector)
        {
            double relativeCameraZoom = camera.cameraZoom - Math.Log(ZCoords.GetSectorManager().GetTopmostOSMSectors().Count, 4) + (Game1.RECORDING ? 1 : 0);
            // autoload stuff
            // TODO: move to update step?
            int            zoomLevel        = Math.Min(Math.Max((int)(relativeCameraZoom - 3), 0), ZCoords.GetSectorManager().GetHighestOSMZoom());
            List <ISector> containedSectors = rootSector.GetSectorsInRange(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, zoomLevel);

            foreach (var pair in loadedMaps.Where(x => AllowUnload(x.Key, rootSector, containedSectors)).ToList())
            {
                loadedMaps[pair.Key].Dispose();
                loadedMaps.Remove(pair.Key);
            }
            // end autoload stuff
            if (toLoad != null || Constants.TO_LOAD != null)
            {
                if (Constants.TO_LOAD != null)
                {
                    toLoad = ZCoords.GetSectorManager().FromString(Constants.TO_LOAD);
                }
                Stopwatch sw = new Stopwatch();
                sw.Start();
                foreach (var sector in toLoad.GetChildrenAtLevel(ZCoords.GetSectorManager().GetHighestOSMZoom()))
                {
                    osmSectorLoader.GetGraphicsBuffer(graphicsDevice, sector).Dispose();
                }
                Console.WriteLine($"Total load time for {toLoad} is {sw.Elapsed.TotalHours} h");
                toLoad = null;
                if (Constants.TO_LOAD != null)
                {
                    Constants.TERMINATE = true;
                    Constants.TO_LOAD   = null;
                }
            }
            bool loadCache = !(relativeCameraZoom - 4 > ZCoords.GetSectorManager().GetHighestOSMZoom());

            foreach (var l in containedSectors)
            {
                if (loadCache)
                {
                    if (!loadedMaps.ContainsKey(l))
                    {
                        loadedMaps[l] = osmSectorLoader.GetCacheBuffer(graphicsDevice, l);
                    }
                }
                else
                {
                    if (!loadedMaps.ContainsKey(l) || loadedMaps[l] is ImageTileBuffer)
                    {
                        if (loadedMaps.ContainsKey(l))
                        {
                            loadedMaps[l].Dispose();
                        }
                        loadedMaps[l] = osmSectorLoader.GetGraphicsBuffer(graphicsDevice, l);
                    }
                }
            }
            List <ISector> sorted = containedSectors.Where(x => x.GetRoot().Equals(rootSector)).ToList();

            sorted.Sort((x, y) => x.Zoom.CompareTo(y.Zoom));
            foreach (var sector in sorted)
            {
                IGraphicsBuffer buffer     = loadedMaps[sector];
                Matrixd         projection = Matrixd.CreateOrthographicOffCenter(bounds.minX * (1 << sector.Zoom) - sector.X, bounds.maxX * (1 << sector.Zoom) - sector.X, bounds.maxY * (1 << sector.Zoom) - sector.Y, bounds.minY * (1 << sector.Zoom) - sector.Y, -1, 0.01f); // TODO: why negative?
                RenderContext   context    = new RenderContext(graphicsDevice, projection, bounds.minX * (1 << sector.Zoom) - sector.X, bounds.maxX * (1 << sector.Zoom) - sector.X, bounds.minY * (1 << sector.Zoom) - sector.Y, bounds.maxY * (1 << sector.Zoom) - sector.Y, camera.cameraZoom, RenderContext.LayerPass.MAIN_PASS);
                buffer.InitDraw(context);
            }
        }