diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index c82f1db633..abceca46dd 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -502,6 +502,10 @@ namespace OpenRA using (new PerfSample("render_widgets")) { + Game.Renderer.WorldVoxelRenderer.BeginFrame(); + Ui.PrepareRenderables(); + Game.Renderer.WorldVoxelRenderer.EndFrame(); + Ui.Draw(); if (ModData != null && ModData.CursorProvider != null) diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index 6880fa30df..19211ff5be 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -108,7 +108,11 @@ namespace OpenRA.Graphics public void BeginFrame(int2 scroll, float zoom) { Device.Clear(); + SetViewportParams(scroll, zoom); + } + public void SetViewportParams(int2 scroll, float zoom) + { var resolutionChanged = lastResolution != Resolution; if (resolutionChanged) { diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index 081df1efaa..8723bae336 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -72,6 +72,8 @@ namespace OpenRA.Widgets public static void Tick() { Root.TickOuter(); } + public static void PrepareRenderables() { Root.PrepareRenderablesOuter(); } + public static void Draw() { Root.DrawOuter(); } public static bool HandleInput(MouseInput mi) @@ -387,6 +389,18 @@ namespace OpenRA.Widgets return handled; } + public virtual void PrepareRenderables() { } + + public virtual void PrepareRenderablesOuter() + { + if (IsVisible()) + { + PrepareRenderables(); + foreach (var child in Children) + child.PrepareRenderablesOuter(); + } + } + public virtual void Draw() { } public virtual void DrawOuter() diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index e33514ee51..e9c28283cb 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -510,6 +510,7 @@ + diff --git a/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs new file mode 100644 index 0000000000..08eb24e567 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs @@ -0,0 +1,116 @@ +#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.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class ActorPreviewWidget : Widget + { + public bool Animate = false; + public Func GetScale = () => 1f; + + readonly WorldRenderer worldRenderer; + + IActorPreview[] preview = new IActorPreview[0]; + public int2 PreviewOffset { get; private set; } + public int2 IdealPreviewSize { get; private set; } + + [ObjectCreator.UseCtor] + public ActorPreviewWidget(WorldRenderer worldRenderer) + { + this.worldRenderer = worldRenderer; + } + + protected ActorPreviewWidget(ActorPreviewWidget other) + : base(other) + { + preview = other.preview; + worldRenderer = other.worldRenderer; + } + + public override Widget Clone() { return new ActorPreviewWidget(this); } + + public void SetPreview(ActorInfo actor, Player owner, TypeDictionary td) + { + var init = new ActorPreviewInitializer(actor, owner, worldRenderer, td); + preview = actor.Traits.WithInterface() + .SelectMany(rpi => rpi.RenderPreview(init)) + .ToArray(); + + // Calculate the preview bounds + PreviewOffset = int2.Zero; + IdealPreviewSize = int2.Zero; + + var r = preview + .SelectMany(p => p.Render(worldRenderer, WPos.Zero)) + .OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey) + .Select(rr => rr.PrepareRender(worldRenderer)); + + if (r.Any()) + { + var b = r.First().ScreenBounds(worldRenderer); + foreach (var rr in r.Skip(1)) + b = Rectangle.Union(b, rr.ScreenBounds(worldRenderer)); + + IdealPreviewSize = new int2(b.Width, b.Height); + PreviewOffset = -new int2(b.Left, b.Top) - IdealPreviewSize / 2; + } + } + + IFinalizedRenderable[] renderables; + public override void PrepareRenderables() + { + renderables = preview + .SelectMany(p => p.Render(worldRenderer, WPos.Zero)) + .OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey) + .Select(r => r.PrepareRender(worldRenderer)) + .ToArray(); + } + + public override void Draw() + { + // HACK: The split between world and UI shaders is a giant PITA because it isn't + // feasible to maintain two parallel sets of renderables for the two cases. + // Instead, we temporarily hijack the world rendering context and set the position + // and zoom values to give the desired screen position and size. + var scale = GetScale(); + var origin = RenderOrigin + new int2(RenderBounds.Size.Width / 2, RenderBounds.Size.Height / 2); + + // The scale affects world -> screen transform, which we don't want when drawing the (fixed) UI. + if (scale != 1f) + origin = (1f / scale * origin.ToFloat2()).ToInt2(); + + Game.Renderer.Flush(); + Game.Renderer.SetViewportParams(-origin - PreviewOffset, scale); + + foreach (var r in renderables) + r.Render(worldRenderer); + + Game.Renderer.Flush(); + Game.Renderer.SetViewportParams(worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.Zoom); + } + + public override void Tick() + { + if (Animate) + foreach (var p in preview) + p.Tick(); + } + } +}