context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using OpenRA.Traits;
  
  namespace OpenRA.Graphics
  {
  	public sealed class WorldRenderer : IDisposable
  	{
  		public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
  			r => ZPosition(r.Pos, r.ZOffset);
  
  		const int RangeCircleSegments = 32;
  		static readonly int[][] RangeCircleStartRotations = Exts.MakeArray(RangeCircleSegments, i => WRot.FromFacing(8 * i).AsMatrix());
  		static readonly int[][] RangeCircleEndRotations = Exts.MakeArray(RangeCircleSegments, i => WRot.FromFacing(8 * i + 6).AsMatrix());
  
  		public readonly Size TileSize;
  		public readonly World World;
  		public readonly Theater Theater;
  		public Viewport Viewport { get; private set; }
  
  		public event Action PaletteInvalidated = null;
  
  		readonly HardwarePalette palette = new HardwarePalette();
  		readonly Dictionary<string, PaletteReference> palettes = new Dictionary<string, PaletteReference>();
  		readonly TerrainRenderer terrainRenderer;
  		readonly Lazy<DeveloperMode> devTrait;
  		readonly Func<string, PaletteReference> createPaletteReference;
  
  		internal WorldRenderer(World world)
  		{
  			World = world;
  			TileSize = World.Map.Grid.TileSize;
  			Viewport = new Viewport(this, world.Map);
  
  			createPaletteReference = CreatePaletteReference;
  
  			foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
  				pal.Trait.LoadPalettes(this);
  
  			foreach (var p in world.Players)
  				UpdatePalettesForPlayer(p.InternalName, p.Color, false);
  
  			palette.Initialize();
  
  			Theater = new Theater(world.TileSet);
  			terrainRenderer = new TerrainRenderer(world, this);
  
  			devTrait = Exts.Lazy(() => world.LocalPlayer != null ? world.LocalPlayer.PlayerActor.Trait<DeveloperMode>() : null);
  		}
  
  		public void UpdatePalettesForPlayer(string internalName, HSLColor color, bool replaceExisting)
  		{
  			foreach (var pal in World.WorldActor.TraitsImplementing<ILoadsPlayerPalettes>())
  				pal.LoadPlayerPalettes(this, internalName, color, replaceExisting);
  		}
  
  		PaletteReference CreatePaletteReference(string name)
  		{
  			var pal = palette.GetPalette(name);
  			return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
  		}
  
  		public PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
  		public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
  		{
  			if (allowOverwrite && palette.Contains(name))
  				ReplacePalette(name, pal);
  			else
  			{
  				var oldHeight = palette.Height;
  				palette.AddPalette(name, pal, allowModifiers);
  
  				if (oldHeight != palette.Height && PaletteInvalidated != null)
  					PaletteInvalidated();
  			}
  		}
  
  		public void ReplacePalette(string name, IPalette pal)
  		{
  			palette.ReplacePalette(name, pal);
  
  			// Update cached PlayerReference if one exists
  			if (palettes.ContainsKey(name))
  				palettes[name].Palette = pal;
  		}
  
  		List<IFinalizedRenderable> GenerateRenderables()
  		{
  			var actors = World.ScreenMap.ActorsInBox(Viewport.TopLeft, Viewport.BottomRight)
  				.Append(World.WorldActor);
  
  			// Include player actor for the rendered player
  			if (World.RenderPlayer != null)
  				actors = actors.Append(World.RenderPlayer.PlayerActor);
  
  			var worldRenderables = actors.SelectMany(a => a.Render(this));
  			if (World.OrderGenerator != null)
  				worldRenderables = worldRenderables.Concat(World.OrderGenerator.Render(this, World));
  
  			worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
  
  			// Effects are drawn on top of all actors
  			// HACK: Effects aren't interleaved with actors.
  			var effectRenderables = World.Effects
  				.SelectMany(e => e.Render(this));
  
  			if (World.OrderGenerator != null)
  				effectRenderables = effectRenderables.Concat(World.OrderGenerator.RenderAfterWorld(this, World));
  
  			Game.Renderer.WorldVoxelRenderer.BeginFrame();
  			var renderables = worldRenderables.Concat(effectRenderables)
  				.Select(r => r.PrepareRender(this)).ToList();
  			Game.Renderer.WorldVoxelRenderer.EndFrame();
  
  			return renderables;
  		}
  
  		public void Draw()
  		{
  			if (World.WorldActor.Disposed)
  				return;
  
  			RefreshPalette();
  
  			if (World.Type == WorldType.Shellmap && !Game.Settings.Game.ShowShellmap)
  				return;
  
  			var renderables = GenerateRenderables();
  			var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
  			Game.Renderer.EnableScissor(bounds);
  
  			terrainRenderer.Draw(this, Viewport);
  			Game.Renderer.Flush();
  
  			for (var i = 0; i < renderables.Count; i++)
  				renderables[i].Render(this);
  
  			foreach (var a in World.ActorsWithTrait<IPostRender>())
  				if (a.Actor.IsInWorld && !a.Actor.Disposed)
  					a.Trait.RenderAfterWorld(this, a.Actor);
  
  			var renderShroud = World.RenderPlayer != null ? World.RenderPlayer.Shroud : null;
  
  			foreach (var a in World.ActorsWithTrait<IRenderShroud>())
  				a.Trait.RenderShroud(this, renderShroud);
  
  			if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry)
  				for (var i = 0; i < renderables.Count; i++)
  					renderables[i].RenderDebugGeometry(this);
  
  			Game.Renderer.DisableScissor();
  
  			var overlayRenderables = World.Selection.Actors.Where(a => !a.Disposed)
  				.SelectMany(a => a.TraitsImplementing<IPostRenderSelection>())
  				.SelectMany(t => t.RenderAfterWorld(this));
  
  			Game.Renderer.WorldVoxelRenderer.BeginFrame();
  			var finalOverlayRenderables = overlayRenderables.Select(r => r.PrepareRender(this));
  			Game.Renderer.WorldVoxelRenderer.EndFrame();
  
  			// HACK: Keep old grouping behaviour
  			foreach (var g in finalOverlayRenderables.GroupBy(prs => prs.GetType()))
  				foreach (var r in g)
  					r.Render(this);
  
  			if (devTrait.Value != null && devTrait.Value.ShowDebugGeometry)
  				foreach (var g in finalOverlayRenderables.GroupBy(prs => prs.GetType()))
  					foreach (var r in g)
  						r.RenderDebugGeometry(this);
  
  			if (World.Type == WorldType.Regular && Game.Settings.Game.AlwaysShowStatusBars)
  			{
  				foreach (var g in World.Actors.Where(a => !a.Disposed
  					&& a.Info.HasTraitInfo<SelectableInfo>()
  					&& !World.FogObscures(a)
  					&& !World.Selection.Actors.Contains(a)))
  
  					DrawRollover(g);
  			}
  
  			Game.Renderer.Flush();
  		}
  
  		public void DrawRollover(Actor unit)
  		{
  			if (unit.Info.HasTraitInfo<SelectableInfo>())
  				new SelectionBarsRenderable(unit).Render(this);
  		}
  
  		public void DrawRangeCircle(WPos pos, WDist range, Color c)
  		{
  			var offset = new WVec(range.Length, 0, 0);
  			for (var i = 0; i < RangeCircleSegments; i++)
  			{
  				var pa = pos + offset.Rotate(RangeCircleStartRotations[i]);
  				var pb = pos + offset.Rotate(RangeCircleEndRotations[i]);
  				Game.Renderer.WorldLineRenderer.DrawLine(ScreenPosition(pa), ScreenPosition(pb), c);
  			}
  		}
  
  		public void DrawTargetMarker(Color c, float2 location)
  		{
  			var tl = new float2(-1 / Viewport.Zoom, -1 / Viewport.Zoom);
  			var br = new float2(1 / Viewport.Zoom, 1 / Viewport.Zoom);
  			var bl = new float2(tl.X, br.Y);
  			var tr = new float2(br.X, tl.Y);
  
  			var wlr = Game.Renderer.WorldLineRenderer;
  			wlr.DrawLine(location + tl, location + tr, c);
  			wlr.DrawLine(location + tr, location + br, c);
  
gt: 			wlr.DrawLine(location + br, location + bl, c);