Files
OpenRA/OpenRA.Mods.Common/Graphics/UIModelRenderable.cs
Paul Chote dcd3e8d444 Ignore terrain slopes when calculating model shadows.
This is less realistic, but better matches the original
game and is the only practical way to reduce visual issues
caused by long shadows being cast over multiple cells.
2021-08-21 13:45:41 +02:00

155 lines
5.4 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 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 class UIModelRenderable : IRenderable, IPalettedRenderable
{
readonly IEnumerable<ModelAnimation> 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<ModelAnimation> models, WPos effectiveWorldPos, int2 screenPos, int zOffset,
in WRot camera, float scale, in 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 => effectiveWorldPos;
public PaletteReference Palette => palette;
public int ZOffset => zOffset;
public bool IsDecoration => false;
public IPalettedRenderable 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(in WVec vec) { return this; }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr)
{
return new FinalizedUIModelRenderable(wr, this);
}
class 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, WRot.None, 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, float3.Ones, 1f);
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).Bounds;
}
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 };
(Rectangle Bounds, float2 Z) 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 rotation = OpenRA.Graphics.Util.MakeFloatMatrix(v.RotationFunc().AsMatrix());
var worldTransform = OpenRA.Graphics.Util.MatrixMultiply(scaleTransform, rotation);
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 (Rectangle.FromLTRB((int)minX, (int)minY, (int)maxX, (int)maxY), new float2(minZ, maxZ));
}
}
}
}