From a8b1762464cecd3aa9beea323915b847bf8c9fbd Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Thu, 12 Sep 2019 21:23:10 +0100 Subject: [PATCH] Fix ActorPreviewWidget viewport manipulation. --- OpenRA.Game/Graphics/Animation.cs | 16 ++ OpenRA.Mods.Common/Graphics/ActorPreview.cs | 1 + .../Graphics/ModelActorPreview.cs | 13 +- .../Graphics/SpriteActorPreview.cs | 11 +- .../Graphics/UIModelRenderable.cs | 155 ++++++++++++++++++ .../Widgets/ActorPreviewWidget.cs | 22 +-- 6 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 OpenRA.Mods.Common/Graphics/UIModelRenderable.cs diff --git a/OpenRA.Game/Graphics/Animation.cs b/OpenRA.Game/Graphics/Animation.cs index 11d33ec8bf..2975b525ec 100644 --- a/OpenRA.Game/Graphics/Animation.cs +++ b/OpenRA.Game/Graphics/Animation.cs @@ -66,6 +66,22 @@ namespace OpenRA.Graphics return new IRenderable[] { imageRenderable }; } + public IRenderable[] RenderUI(int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale) + { + var imagePos = pos - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2)); + var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale); + + if (CurrentSequence.ShadowStart >= 0) + { + var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); + var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2)); + var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale); + return new IRenderable[] { shadowRenderable, imageRenderable }; + } + + return new IRenderable[] { imageRenderable }; + } + public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale) { var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset); diff --git a/OpenRA.Mods.Common/Graphics/ActorPreview.cs b/OpenRA.Mods.Common/Graphics/ActorPreview.cs index cf5c15dadd..b58180d80c 100644 --- a/OpenRA.Mods.Common/Graphics/ActorPreview.cs +++ b/OpenRA.Mods.Common/Graphics/ActorPreview.cs @@ -22,6 +22,7 @@ namespace OpenRA.Mods.Common.Graphics { void Tick(); IEnumerable Render(WorldRenderer wr, WPos pos); + IEnumerable RenderUI(WorldRenderer wr, int2 pos, float scale); IEnumerable ScreenBounds(WorldRenderer wr, WPos pos); } diff --git a/OpenRA.Mods.Common/Graphics/ModelActorPreview.cs b/OpenRA.Mods.Common/Graphics/ModelActorPreview.cs index 21f0075343..e5a36633fd 100644 --- a/OpenRA.Mods.Common/Graphics/ModelActorPreview.cs +++ b/OpenRA.Mods.Common/Graphics/ModelActorPreview.cs @@ -49,16 +49,23 @@ namespace OpenRA.Mods.Common.Graphics this.zOffset = zOffset; } - public void Tick() { /* not supported */ } + void IActorPreview.Tick() { /* not supported */ } - public IEnumerable Render(WorldRenderer wr, WPos pos) + IEnumerable IActorPreview.RenderUI(WorldRenderer wr, int2 pos, float scale) + { + yield return new UIModelRenderable(components, WPos.Zero + offset, pos, zOffset, camera, scale * this.scale, + lightSource, lightAmbientColor, lightDiffuseColor, + colorPalette, normalsPalette, shadowPalette); + } + + IEnumerable IActorPreview.Render(WorldRenderer wr, WPos pos) { yield return new ModelRenderable(components, pos + offset, zOffset, camera, scale, lightSource, lightAmbientColor, lightDiffuseColor, colorPalette, normalsPalette, shadowPalette); } - public IEnumerable ScreenBounds(WorldRenderer wr, WPos pos) + IEnumerable IActorPreview.ScreenBounds(WorldRenderer wr, WPos pos) { foreach (var c in components) yield return c.ScreenBounds(pos, wr, scale); diff --git a/OpenRA.Mods.Common/Graphics/SpriteActorPreview.cs b/OpenRA.Mods.Common/Graphics/SpriteActorPreview.cs index 4fa6af9f1a..16624e71e0 100644 --- a/OpenRA.Mods.Common/Graphics/SpriteActorPreview.cs +++ b/OpenRA.Mods.Common/Graphics/SpriteActorPreview.cs @@ -33,14 +33,19 @@ namespace OpenRA.Mods.Common.Graphics this.scale = scale; } - public void Tick() { animation.Tick(); } + void IActorPreview.Tick() { animation.Tick(); } - public IEnumerable Render(WorldRenderer wr, WPos pos) + IEnumerable IActorPreview.RenderUI(WorldRenderer wr, int2 pos, float scale) + { + return animation.RenderUI(pos, offset(), zOffset(), pr, scale); + } + + IEnumerable IActorPreview.Render(WorldRenderer wr, WPos pos) { return animation.Render(pos, offset(), zOffset(), pr, scale); } - public IEnumerable ScreenBounds(WorldRenderer wr, WPos pos) + IEnumerable IActorPreview.ScreenBounds(WorldRenderer wr, WPos pos) { yield return animation.ScreenBounds(wr, pos, offset(), scale); } diff --git a/OpenRA.Mods.Common/Graphics/UIModelRenderable.cs b/OpenRA.Mods.Common/Graphics/UIModelRenderable.cs new file mode 100644 index 0000000000..263a6f0f8a --- /dev/null +++ b/OpenRA.Mods.Common/Graphics/UIModelRenderable.cs @@ -0,0 +1,155 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Primitives; + +namespace OpenRA.Mods.Common.Graphics +{ + public struct UIModelRenderable : IRenderable + { + readonly IEnumerable models; + readonly WPos effectiveWorldPos; + readonly int2 screenPos; + readonly int zOffset; + readonly WRot camera; + readonly WRot lightSource; + readonly float[] lightAmbientColor; + readonly float[] lightDiffuseColor; + readonly PaletteReference palette; + readonly PaletteReference normalsPalette; + readonly PaletteReference shadowPalette; + readonly float scale; + + public UIModelRenderable( + IEnumerable models, WPos effectiveWorldPos, int2 screenPos, int zOffset, + WRot camera, float scale, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor, + PaletteReference color, PaletteReference normals, PaletteReference shadow) + { + this.models = models; + this.effectiveWorldPos = effectiveWorldPos; + this.screenPos = screenPos; + this.zOffset = zOffset; + this.scale = scale; + this.camera = camera; + this.lightSource = lightSource; + this.lightAmbientColor = lightAmbientColor; + this.lightDiffuseColor = lightDiffuseColor; + palette = color; + normalsPalette = normals; + shadowPalette = shadow; + } + + public WPos Pos { get { return effectiveWorldPos; } } + public PaletteReference Palette { get { return palette; } } + public int ZOffset { get { return zOffset; } } + public bool IsDecoration { get { return false; } } + + public IRenderable WithPalette(PaletteReference newPalette) + { + return new UIModelRenderable( + models, effectiveWorldPos, screenPos, zOffset, camera, scale, + lightSource, lightAmbientColor, lightDiffuseColor, + newPalette, normalsPalette, shadowPalette); + } + + public IRenderable WithZOffset(int newOffset) { return this; } + public IRenderable OffsetBy(WVec vec) { return this; } + public IRenderable AsDecoration() { return this; } + + static readonly float[] GroundNormal = { 0, 0, 1, 1 }; + public IFinalizedRenderable PrepareRender(WorldRenderer wr) + { + return new FinalizedUIModelRenderable(wr, this); + } + + struct FinalizedUIModelRenderable : IFinalizedRenderable + { + readonly UIModelRenderable model; + readonly ModelRenderProxy renderProxy; + + public FinalizedUIModelRenderable(WorldRenderer wr, UIModelRenderable model) + { + this.model = model; + var draw = model.models.Where(v => v.IsVisible); + + renderProxy = Game.Renderer.WorldModelRenderer.RenderAsync( + wr, draw, model.camera, model.scale, GroundNormal, model.lightSource, + model.lightAmbientColor, model.lightDiffuseColor, + model.palette, model.normalsPalette, model.shadowPalette); + } + + public void Render(WorldRenderer wr) + { + var pxOrigin = model.screenPos; + var psb = renderProxy.ProjectedShadowBounds; + var sa = pxOrigin + psb[0]; + var sb = pxOrigin + psb[2]; + var sc = pxOrigin + psb[1]; + var sd = pxOrigin + psb[3]; + Game.Renderer.RgbaSpriteRenderer.DrawSprite(renderProxy.ShadowSprite, sa, sb, sc, sd); + Game.Renderer.RgbaSpriteRenderer.DrawSprite(renderProxy.Sprite, pxOrigin - 0.5f * renderProxy.Sprite.Size); + } + + public void RenderDebugGeometry(WorldRenderer wr) { } + + public Rectangle ScreenBounds(WorldRenderer wr) + { + return Screen3DBounds(wr).First; + } + + static readonly uint[] CornerXIndex = { 0, 0, 0, 0, 3, 3, 3, 3 }; + static readonly uint[] CornerYIndex = { 1, 1, 4, 4, 1, 1, 4, 4 }; + static readonly uint[] CornerZIndex = { 2, 5, 2, 5, 2, 5, 2, 5 }; + Pair Screen3DBounds(WorldRenderer wr) + { + var pxOrigin = model.screenPos; + var draw = model.models.Where(v => v.IsVisible); + var scaleTransform = OpenRA.Graphics.Util.ScaleMatrix(model.scale, model.scale, model.scale); + var cameraTransform = OpenRA.Graphics.Util.MakeFloatMatrix(model.camera.AsMatrix()); + + var minX = float.MaxValue; + var minY = float.MaxValue; + var minZ = float.MaxValue; + var maxX = float.MinValue; + var maxY = float.MinValue; + var maxZ = float.MinValue; + + foreach (var v in draw) + { + var bounds = v.Model.Bounds(v.FrameFunc()); + var worldTransform = v.RotationFunc().Reverse().Aggregate(scaleTransform, + (x, y) => OpenRA.Graphics.Util.MatrixMultiply(x, OpenRA.Graphics.Util.MakeFloatMatrix(y.AsMatrix()))); + + var pxPos = pxOrigin + wr.ScreenVectorComponents(v.OffsetFunc()); + var screenTransform = OpenRA.Graphics.Util.MatrixMultiply(cameraTransform, worldTransform); + + for (var i = 0; i < 8; i++) + { + var vec = new float[] { bounds[CornerXIndex[i]], bounds[CornerYIndex[i]], bounds[CornerZIndex[i]], 1 }; + var screen = OpenRA.Graphics.Util.MatrixVectorMultiply(screenTransform, vec); + minX = Math.Min(minX, pxPos.X + screen[0]); + minY = Math.Min(minY, pxPos.Y + screen[1]); + minZ = Math.Min(minZ, pxPos.Z + screen[2]); + maxX = Math.Max(maxX, pxPos.X + screen[0]); + maxY = Math.Max(maxY, pxPos.Y + screen[1]); + maxZ = Math.Max(minZ, pxPos.Z + screen[2]); + } + } + + return Pair.New(Rectangle.FromLTRB((int)minX, (int)minY, (int)maxX, (int)maxY), new float2(minZ, maxZ)); + } + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs index decec547e5..f7923729a7 100644 --- a/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs @@ -72,8 +72,11 @@ namespace OpenRA.Mods.Common.Widgets IFinalizedRenderable[] renderables; public override void PrepareRenderables() { + var scale = GetScale(); + var origin = RenderOrigin + PreviewOffset + new int2(RenderBounds.Size.Width / 2, RenderBounds.Size.Height / 2); + renderables = preview - .SelectMany(p => p.Render(worldRenderer, WPos.Zero)) + .SelectMany(p => p.RenderUI(worldRenderer, origin, scale)) .OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey) .Select(r => r.PrepareRender(worldRenderer)) .ToArray(); @@ -81,25 +84,8 @@ namespace OpenRA.Mods.Common.Widgets 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()