From 919181e04d2261a60c531c36b0aa48dfd5c03ce0 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 28 Sep 2013 10:21:23 +1200 Subject: [PATCH] Clean up viewport internals. --- OpenRA.Game/Game.cs | 4 +- OpenRA.Game/Graphics/ShroudRenderer.cs | 3 +- OpenRA.Game/Graphics/TerrainRenderer.cs | 30 +- OpenRA.Game/Graphics/TextRenderable.cs | 2 +- OpenRA.Game/Graphics/Viewport.cs | 283 ++++++++---------- OpenRA.Game/Graphics/WorldRenderer.cs | 9 +- OpenRA.Game/Traits/World/ResourceLayer.cs | 2 +- OpenRA.Game/Traits/World/ScreenShaker.cs | 2 +- .../Widgets/ViewportControllerWidget.cs | 4 +- .../WorldInteractionControllerWidget.cs | 8 +- OpenRA.Mods.RA/Widgets/RadarWidget.cs | 8 +- OpenRA.Mods.RA/World/DebugOverlay.cs | 3 +- OpenRA.Mods.RA/World/SmudgeLayer.cs | 2 +- 13 files changed, 153 insertions(+), 207 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index f95b5ddba1..e8a587982c 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -141,8 +141,8 @@ namespace OpenRA // worldRenderer is null during the initial install/download screen if (worldRenderer != null) { - Game.Renderer.BeginFrame(worldRenderer.Viewport.Location, Zoom); - Sound.SetListenerPosition(worldRenderer.Position(worldRenderer.Viewport.CenterLocation.ToInt2())); + Game.Renderer.BeginFrame(worldRenderer.Viewport.TopLeft.ToFloat2(), Zoom); + Sound.SetListenerPosition(worldRenderer.Position(worldRenderer.Viewport.CenterLocation)); worldRenderer.Draw(); } else diff --git a/OpenRA.Game/Graphics/ShroudRenderer.cs b/OpenRA.Game/Graphics/ShroudRenderer.cs index 0e244aa6ad..66a1c0e418 100644 --- a/OpenRA.Game/Graphics/ShroudRenderer.cs +++ b/OpenRA.Game/Graphics/ShroudRenderer.cs @@ -161,9 +161,8 @@ namespace OpenRA.Graphics GenerateSprites(shroud); - var clipRect = wr.Viewport.WorldBounds(wr.world); - // We draw the shroud when disabled to hide the sharp map edges + var clipRect = wr.Viewport.CellBounds; DrawShroud(wr, clipRect, sprites, shroudPalette); if (world.LobbyInfo.GlobalSettings.Fog) diff --git a/OpenRA.Game/Graphics/TerrainRenderer.cs b/OpenRA.Game/Graphics/TerrainRenderer.cs index 5891664df9..423f14aa5d 100644 --- a/OpenRA.Game/Graphics/TerrainRenderer.cs +++ b/OpenRA.Game/Graphics/TerrainRenderer.cs @@ -46,36 +46,14 @@ namespace OpenRA.Graphics public void Draw(WorldRenderer wr, Viewport viewport) { - int verticesPerRow = 4*map.Bounds.Width; - - int visibleRows = (int)(Game.Renderer.Resolution.Height * 1f / Game.CellSize / viewport.Zoom + 2); - - int firstRow = (int)(viewport.Location.Y * 1f / Game.CellSize - map.Bounds.Top); - int lastRow = firstRow + visibleRows; + var verticesPerRow = 4*map.Bounds.Width; + var bounds = viewport.CellBounds; + var firstRow = bounds.Top - map.Bounds.Top; + var lastRow = bounds.Bottom - map.Bounds.Top; if (lastRow < 0 || firstRow > map.Bounds.Height) return; - if (world.VisibleBounds.HasValue) - { - var r = world.VisibleBounds.Value; - if (firstRow < r.Top - map.Bounds.Top) - firstRow = r.Top - map.Bounds.Top; - - if (firstRow > r.Bottom - map.Bounds.Top) - firstRow = r.Bottom - map.Bounds.Top; - } - - // Sanity checking - if (firstRow < 0) - firstRow = 0; - - if (lastRow > map.Bounds.Height) - lastRow = map.Bounds.Height; - - if (lastRow < firstRow) - lastRow = firstRow; - Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( vertexBuffer, verticesPerRow * firstRow, verticesPerRow * (lastRow - firstRow), PrimitiveType.QuadList, wr.Theater.Sheet); diff --git a/OpenRA.Game/Graphics/TextRenderable.cs b/OpenRA.Game/Graphics/TextRenderable.cs index def2cd3533..299893cef5 100644 --- a/OpenRA.Game/Graphics/TextRenderable.cs +++ b/OpenRA.Game/Graphics/TextRenderable.cs @@ -46,7 +46,7 @@ namespace OpenRA.Graphics public void BeforeRender(WorldRenderer wr) {} public void Render(WorldRenderer wr) { - var screenPos = wr.Viewport.Zoom*(wr.ScreenPosition(pos) - wr.Viewport.Location) - 0.5f*font.Measure(text).ToFloat2(); + var screenPos = wr.Viewport.Zoom*(wr.ScreenPosition(pos) - wr.Viewport.TopLeft.ToFloat2()) - 0.5f*font.Measure(text).ToFloat2(); var screenPxPos = new float2((float)Math.Round(screenPos.X), (float)Math.Round(screenPos.Y)); font.DrawTextWithContrast(text, screenPxPos, color, Color.Black, 1); } diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index b613987f8b..e679a99d7b 100755 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -12,7 +12,6 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using OpenRA.Widgets; using OpenRA.Support; namespace OpenRA.Graphics @@ -20,157 +19,6 @@ namespace OpenRA.Graphics [Flags] public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 } - public class Viewport - { - readonly Rectangle mapBounds; - Rectangle scrollLimits; - int2 scrollPosition; - - // Top-left of the viewport, in world-px units - public float2 Location { get { return scrollPosition; } } - public float2 CenterLocation { get { return scrollPosition + 0.5f/Zoom * new float2(Game.Renderer.Resolution); } } - - public Rectangle WorldRect - { - get - { - return new Rectangle(scrollPosition.X / Game.CellSize, - scrollPosition.Y / Game.CellSize, - (int)(Game.Renderer.Resolution.Width / Zoom / Game.CellSize), - (int)(Game.Renderer.Resolution.Height / Zoom / Game.CellSize)); - } - } - - float zoom = 1f; - public float Zoom - { - get - { - return zoom; - } - set - { - var oldCenter = CenterLocation; - zoom = value; - - // Update scroll limits - var viewTL = (Game.CellSize*new float2(mapBounds.Left, mapBounds.Top)).ToInt2(); - var viewBR = (Game.CellSize*new float2(mapBounds.Right, mapBounds.Bottom)).ToInt2(); - var border = (.5f/Zoom * new float2(Game.Renderer.Resolution)).ToInt2(); - scrollLimits = Rectangle.FromLTRB(viewTL.X - border.X, - viewTL.Y - border.Y, - viewBR.X - border.X, - viewBR.Y - border.Y); - // Re-center viewport - scrollPosition = NormalizeScrollPosition((oldCenter - 0.5f / Zoom * new float2(Game.Renderer.Resolution)).ToInt2()); - } - } - - public static int TicksSinceLastMove = 0; - public static int2 LastMousePos; - - public void Scroll(float2 delta) - { - Scroll(delta, false); - } - - public void Scroll(float2 delta, bool ignoreBorders) - { - // Convert from world-px to viewport-px - var d = (1f/Zoom*delta).ToInt2(); - var newScrollPosition = scrollPosition + d; - - if(!ignoreBorders) - newScrollPosition = NormalizeScrollPosition(newScrollPosition); - - scrollPosition = newScrollPosition; - } - - int2 NormalizeScrollPosition(int2 newScrollPosition) - { - return newScrollPosition.Clamp(scrollLimits); - } - - public ScrollDirection GetBlockedDirections() - { - var ret = ScrollDirection.None; - if(scrollPosition.Y <= scrollLimits.Top) ret |= ScrollDirection.Up; - if(scrollPosition.X <= scrollLimits.Left) ret |= ScrollDirection.Left; - if(scrollPosition.Y >= scrollLimits.Bottom) ret |= ScrollDirection.Down; - if(scrollPosition.X >= scrollLimits.Right) ret |= ScrollDirection.Right; - return ret; - } - - public Viewport(Rectangle mapBounds) - { - this.mapBounds = mapBounds; - - Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1; - scrollPosition = new int2(scrollLimits.Location) + new int2(scrollLimits.Size)/2; - } - - // Convert from viewport coords to cell coords (not px) - public CPos ViewToWorld(MouseInput mi) { return ViewToWorld(mi.Location); } - public CPos ViewToWorld(int2 loc) - { - return (CPos)( (1f / Game.CellSize) * (1f/Zoom*loc.ToFloat2() + Location) ).ToInt2(); - } - - public int2 ViewToWorldPx(int2 loc) { return (1f/Zoom*loc.ToFloat2() + Location).ToInt2(); } - public int2 WorldToViewPx(int2 loc) { return (Zoom * (loc.ToFloat2() - Location)).ToInt2(); } - - public void Center(float2 loc) - { - scrollPosition = NormalizeScrollPosition((Game.CellSize * loc - 1f/(2*Zoom)*new float2(Game.Renderer.Resolution)).ToInt2()); - } - - public void Center(WPos pos) - { - Center(new float2(pos.X / 1024f, (pos.Y + pos.Z) / 1024f)); - } - - public void Center(IEnumerable actors) - { - if (!actors.Any()) - return; - - Center(actors.Select(a => a.CenterPosition).Average()); - } - - // Rectangle (in viewport coords) that contains things to be drawn - public Rectangle ViewBounds(World world) - { - var r = WorldBounds(world); - var origin = Location.ToInt2(); - var left = Math.Max(0, Game.CellSize * r.Left - origin.X)*Zoom; - var top = Math.Max(0, Game.CellSize * r.Top - origin.Y)*Zoom; - var right = Math.Min((Game.CellSize * r.Right - origin.X) * Zoom, Game.Renderer.Resolution.Width); - var bottom = Math.Min((Game.CellSize * r.Bottom - origin.Y) * Zoom, Game.Renderer.Resolution.Height); - - return Rectangle.FromLTRB((int)left, (int)top, (int)right, (int)bottom); - } - - int2 cachedScroll = new int2(int.MaxValue, int.MaxValue); - Rectangle cachedRect; - - // Rectangle (in cell coords) of cells that are currently visible on the screen - public Rectangle WorldBounds(World world) - { - if (cachedScroll != scrollPosition) - { - var boundary = new int2(1,1); // Add a curtain of cells around the viewport to account for rounding errors - var tl = ViewToWorld(int2.Zero).ToInt2() - boundary; - var br = ViewToWorld(new int2(Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height)).ToInt2() + boundary; - - cachedRect = Rectangle.Intersect(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y), world.Map.Bounds); - cachedScroll = scrollPosition; - } - - var b = world.VisibleBounds; - return (b.HasValue) ? Rectangle.Intersect(cachedRect, b.Value) : cachedRect; - } - } - public static class ViewportExts { public static bool Includes(this ScrollDirection d, ScrollDirection s) @@ -183,4 +31,135 @@ namespace OpenRA.Graphics return (d.Includes(s) != val) ? d ^ s : d; } } + + public class Viewport + { + readonly WorldRenderer worldRenderer; + + // Map bounds (world-px) + readonly Rectangle mapBounds; + + // Viewport geometry (world-px) + public int2 CenterLocation { get; private set; } + public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } } + public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } } + int2 viewportSize; + bool cellBoundsDirty = true; + + float zoom = 1f; + public float Zoom + { + get + { + return zoom; + } + + set + { + zoom = value; + viewportSize = (1f / zoom * new float2(Game.Renderer.Resolution)).ToInt2(); + cellBoundsDirty = true; + } + } + + public static int TicksSinceLastMove = 0; + public static int2 LastMousePos; + + public ScrollDirection GetBlockedDirections() + { + var ret = ScrollDirection.None; + if (CenterLocation.Y <= mapBounds.Top) + ret |= ScrollDirection.Up; + if (CenterLocation.X <= mapBounds.Left) + ret |= ScrollDirection.Left; + if (CenterLocation.Y >= mapBounds.Bottom) + ret |= ScrollDirection.Down; + if (CenterLocation.X >= mapBounds.Right) + ret |= ScrollDirection.Right; + + return ret; + } + + public Viewport(WorldRenderer wr, Map map) + { + worldRenderer = wr; + + // Calculate map bounds in world-px + var b = map.Bounds; + var tl = wr.ScreenPxPosition(new CPos(b.Left, b.Top).TopLeft); + var br = wr.ScreenPxPosition(new CPos(b.Right, b.Bottom).BottomRight); + mapBounds = Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); + + CenterLocation = (tl + br) / 2; + Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1; + } + + public CPos ViewToWorld(int2 view) + { + return worldRenderer.Position(ViewToWorldPx(view)).ToCPos(); + } + + public int2 ViewToWorldPx(int2 view) { return (1f / Zoom * view.ToFloat2()).ToInt2() + TopLeft; } + public int2 WorldToViewPx(int2 world) { return (Zoom * (world - TopLeft).ToFloat2()).ToInt2(); } + + public void Center(IEnumerable actors) + { + if (!actors.Any()) + return; + + Center(actors.Select(a => a.CenterPosition).Average()); + } + + public void Center(WPos pos) + { + CenterLocation = worldRenderer.ScreenPxPosition(pos).Clamp(mapBounds); + cellBoundsDirty = true; + } + + public void Scroll(float2 delta, bool ignoreBorders) + { + // Convert scroll delta from world-px to viewport-px + CenterLocation += (1f / Zoom * delta).ToInt2(); + cellBoundsDirty = true; + + if (!ignoreBorders) + CenterLocation = CenterLocation.Clamp(mapBounds); + } + + // Rectangle (in viewport coords) that contains things to be drawn + static readonly Rectangle ScreenClip = Rectangle.FromLTRB(0, 0, Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height); + public Rectangle ScissorBounds + { + get + { + var r = CellBounds; + var ctl = new CPos(r.Left, r.Top).TopLeft; + var cbr = new CPos(r.Right, r.Bottom).TopLeft; + var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl)).Clamp(ScreenClip); + var br = WorldToViewPx(worldRenderer.ScreenPxPosition(cbr)).Clamp(ScreenClip); + return Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); + } + } + + // Rectangle (in cell coords) of cells that are currently visible on the screen + Rectangle cachedRect; + public Rectangle CellBounds + { + get + { + if (cellBoundsDirty) + { + var boundary = new CVec(1, 1); + var tl = worldRenderer.Position(TopLeft).ToCPos() - boundary; + var br = worldRenderer.Position(BottomRight).ToCPos() + boundary; + + cachedRect = Rectangle.Intersect(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y), worldRenderer.world.Map.Bounds); + cellBoundsDirty = false; + } + + var b = worldRenderer.world.VisibleBounds; + return b.HasValue ? Rectangle.Intersect(cachedRect, b.Value) : cachedRect; + } + } + } } \ No newline at end of file diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 75899329d7..27101cd720 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -45,7 +45,7 @@ namespace OpenRA.Graphics internal WorldRenderer(World world) { this.world = world; - Viewport = new Viewport(world.Map.Bounds); + Viewport = new Viewport(this, world.Map); palette = new HardwarePalette(); palettes = new Cache(CreatePaletteReference); @@ -76,10 +76,7 @@ namespace OpenRA.Graphics List GenerateRenderables() { var comparer = new RenderableComparer(this); - var vb = Viewport.ViewBounds(world); - var tl = Viewport.ViewToWorldPx(new int2(vb.Left, vb.Top)); - var br = Viewport.ViewToWorldPx(new int2(vb.Right, vb.Bottom)); - var actors = world.ScreenMap.ActorsInBox(tl, br) + var actors = world.ScreenMap.ActorsInBox(Viewport.TopLeft, Viewport.BottomRight) .Append(world.WorldActor) .ToList(); @@ -117,7 +114,7 @@ namespace OpenRA.Graphics return; var renderables = GenerateRenderables(); - var bounds = Viewport.ViewBounds(world); + var bounds = Viewport.ScissorBounds; Game.Renderer.EnableScissor(bounds.Left, bounds.Top, bounds.Width, bounds.Height); terrainRenderer.Draw(this, Viewport); diff --git a/OpenRA.Game/Traits/World/ResourceLayer.cs b/OpenRA.Game/Traits/World/ResourceLayer.cs index 1f7da79740..2dcd437902 100644 --- a/OpenRA.Game/Traits/World/ResourceLayer.cs +++ b/OpenRA.Game/Traits/World/ResourceLayer.cs @@ -37,7 +37,7 @@ namespace OpenRA.Traits rt.info.PaletteRef = wr.Palette(rt.info.Palette); } - var clip = wr.Viewport.WorldBounds(world); + var clip = wr.Viewport.CellBounds; for (var x = clip.Left; x < clip.Right; x++) { for (var y = clip.Top; y < clip.Bottom; y++) diff --git a/OpenRA.Game/Traits/World/ScreenShaker.cs b/OpenRA.Game/Traits/World/ScreenShaker.cs index 4c5b06472e..5feca96cf9 100644 --- a/OpenRA.Game/Traits/World/ScreenShaker.cs +++ b/OpenRA.Game/Traits/World/ScreenShaker.cs @@ -50,7 +50,7 @@ namespace OpenRA.Traits float GetIntensity() { - var cp = worldRenderer.Position(worldRenderer.Viewport.CenterLocation.ToInt2()); + var cp = worldRenderer.Position(worldRenderer.Viewport.CenterLocation); var intensity = 100 * 1024 * 1024 * shakeEffects.Sum( e => (float)e.Intensity / (e.Position - cp).LengthSquared); diff --git a/OpenRA.Game/Widgets/ViewportControllerWidget.cs b/OpenRA.Game/Widgets/ViewportControllerWidget.cs index 9bcdd0f892..b6dd8ddfd6 100644 --- a/OpenRA.Game/Widgets/ViewportControllerWidget.cs +++ b/OpenRA.Game/Widgets/ViewportControllerWidget.cs @@ -152,7 +152,7 @@ namespace OpenRA.Widgets (mi.Button == MouseButton.Middle || mi.Button == (MouseButton.Left | MouseButton.Right))) { var d = scrolltype == MouseScrollType.Inverted ? -1 : 1; - worldRenderer.Viewport.Scroll((Viewport.LastMousePos - mi.Location) * d); + worldRenderer.Viewport.Scroll((Viewport.LastMousePos - mi.Location) * d, false); return true; } @@ -195,7 +195,7 @@ namespace OpenRA.Widgets var length = Math.Max(1, scroll.Length); scroll *= (1f / length) * Game.Settings.Game.ViewportEdgeScrollStep; - worldRenderer.Viewport.Scroll(scroll); + worldRenderer.Viewport.Scroll(scroll, false); } } diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index 0571914816..12ac02159c 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -90,12 +90,8 @@ namespace OpenRA.Widgets if (MultiClick) { var unit = world.ScreenMap.ActorsAt(xy).FirstOrDefault(); - - var visibleWorld = worldRenderer.Viewport.ViewBounds(world); - var topLeft = worldRenderer.Viewport.ViewToWorldPx(new int2(visibleWorld.Left, visibleWorld.Top)); - var bottomRight = worldRenderer.Viewport.ViewToWorldPx(new int2(visibleWorld.Right, visibleWorld.Bottom)); - var newSelection2= SelectActorsInBox(world, topLeft, bottomRight, - a => unit != null && a.Info.Name == unit.Info.Name && a.Owner == unit.Owner); + var newSelection2 = SelectActorsInBox(world, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, + a => unit != null && a.Info.Name == unit.Info.Name && a.Owner == unit.Owner); world.Selection.Combine(world, newSelection2, true, false); } diff --git a/OpenRA.Mods.RA/Widgets/RadarWidget.cs b/OpenRA.Mods.RA/Widgets/RadarWidget.cs index 1226cb0f36..ad14b6865e 100755 --- a/OpenRA.Mods.RA/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.RA/Widgets/RadarWidget.cs @@ -110,7 +110,7 @@ namespace OpenRA.Mods.RA.Widgets var cell = MinimapPixelToCell(mi.Location); var pos = cell.CenterPosition; if ((mi.Event == MouseInputEvent.Down || mi.Event == MouseInputEvent.Move) && mi.Button == MouseButton.Left) - worldRenderer.Viewport.Center(cell.ToFloat2()); + worldRenderer.Viewport.Center(cell.CenterPosition); if (mi.Event == MouseInputEvent.Down && mi.Button == MouseButton.Right) { @@ -153,10 +153,8 @@ namespace OpenRA.Mods.RA.Widgets // Draw viewport rect if (hasRadar) { - var wr = worldRenderer.Viewport.WorldRect; - var wro = new CPos(wr.X, wr.Y); - var tl = CellToMinimapPixel(wro); - var br = CellToMinimapPixel(wro + new CVec(wr.Width, wr.Height)); + var tl = CellToMinimapPixel(worldRenderer.Position(worldRenderer.Viewport.TopLeft).ToCPos()); + var br = CellToMinimapPixel(worldRenderer.Position(worldRenderer.Viewport.BottomRight).ToCPos()); Game.Renderer.EnableScissor(mapRect.Left, mapRect.Top, mapRect.Width, mapRect.Height); Game.Renderer.LineRenderer.DrawRect(tl, br, Color.White); diff --git a/OpenRA.Mods.RA/World/DebugOverlay.cs b/OpenRA.Mods.RA/World/DebugOverlay.cs index 7b31457525..e3258f74a7 100644 --- a/OpenRA.Mods.RA/World/DebugOverlay.cs +++ b/OpenRA.Mods.RA/World/DebugOverlay.cs @@ -53,8 +53,7 @@ namespace OpenRA.Mods.RA var doDim = refreshTick - world.FrameNumber <= 0; if (doDim) refreshTick = world.FrameNumber + 20; - var viewBounds = wr.Viewport.WorldBounds(world); - + var viewBounds = wr.Viewport.CellBounds; foreach (var pair in layers) { var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise; diff --git a/OpenRA.Mods.RA/World/SmudgeLayer.cs b/OpenRA.Mods.RA/World/SmudgeLayer.cs index 7cd119157f..0c33aafe2a 100644 --- a/OpenRA.Mods.RA/World/SmudgeLayer.cs +++ b/OpenRA.Mods.RA/World/SmudgeLayer.cs @@ -95,7 +95,7 @@ namespace OpenRA.Mods.RA public void Render(WorldRenderer wr) { - var cliprect = wr.Viewport.WorldBounds(world); + var cliprect = wr.Viewport.CellBounds; var pal = wr.Palette("terrain"); foreach (var kv in tiles)