Added rotation logic to the renderer to enable the use of Interpolated Facings.

This commit is contained in:
AspectInteractive2
2022-06-07 22:11:51 +02:00
committed by Matthias Mailänder
parent e060d6eb05
commit a1a50d6c98
11 changed files with 223 additions and 42 deletions

View File

@@ -50,37 +50,41 @@ namespace OpenRA.Graphics
} }
public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame; public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame;
public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc()); public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc());
public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette) public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette)
{ {
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None; var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
var alpha = CurrentSequence.GetAlpha(CurrentFrame); var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration); var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
rotation);
if (CurrentSequence.ShadowStart >= 0) if (CurrentSequence.ShadowStart >= 0)
{ {
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers, true); var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
true, rotation);
return new IRenderable[] { shadowRenderable, imageRenderable }; return new IRenderable[] { shadowRenderable, imageRenderable };
} }
return new IRenderable[] { imageRenderable }; return new IRenderable[] { imageRenderable };
} }
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f) public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f, float rotation = 0f)
{ {
scale *= CurrentSequence.Scale; scale *= CurrentSequence.Scale;
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2(); var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2)); var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
var alpha = CurrentSequence.GetAlpha(CurrentFrame); var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha); var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
if (CurrentSequence.ShadowStart >= 0) if (CurrentSequence.ShadowStart >= 0)
{ {
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2)); 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); var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation);
return new IRenderable[] { shadowRenderable, imageRenderable }; return new IRenderable[] { shadowRenderable, imageRenderable };
} }

View File

@@ -22,28 +22,28 @@ namespace OpenRA.Graphics
this.parent = parent; this.parent = parent;
} }
public void DrawSprite(Sprite s, in float3 location, in float3 scale) public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale); parent.DrawSprite(s, 0, location, scale, rotation);
} }
public void DrawSprite(Sprite s, in float3 location, float scale = 1f) public void DrawSprite(Sprite s, in float3 location, float scale = 1f, float rotation = 0f)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale); parent.DrawSprite(s, 0, location, scale, rotation);
} }
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha) public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha, float rotation = 0f)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, tint, alpha); parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation);
} }
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha) public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)

View File

@@ -26,6 +26,7 @@ namespace OpenRA.Graphics
int Length { get; } int Length { get; }
int Stride { get; } int Stride { get; }
int Facings { get; } int Facings { get; }
int InterpolatedFacings { get; }
int Tick { get; } int Tick { get; }
int ZOffset { get; } int ZOffset { get; }
int ShadowStart { get; } int ShadowStart { get; }
@@ -37,6 +38,7 @@ namespace OpenRA.Graphics
Sprite GetSprite(int frame); Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing); Sprite GetSprite(int frame, WAngle facing);
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing); Sprite GetShadow(int frame, WAngle facing);
float GetAlpha(int frame); float GetAlpha(int frame);
} }

View File

@@ -25,12 +25,14 @@ namespace OpenRA.Graphics
readonly int zOffset; readonly int zOffset;
readonly PaletteReference palette; readonly PaletteReference palette;
readonly float scale; readonly float scale;
readonly WAngle rotation = WAngle.Zero;
readonly float3 tint; readonly float3 tint;
readonly TintModifiers tintModifiers; readonly TintModifiers tintModifiers;
readonly float alpha; readonly float alpha;
readonly bool isDecoration; readonly bool isDecoration;
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha, float3 tint, TintModifiers tintModifiers, bool isDecoration) public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
float3 tint, TintModifiers tintModifiers, bool isDecoration, WAngle rotation)
{ {
this.sprite = sprite; this.sprite = sprite;
this.pos = pos; this.pos = pos;
@@ -38,6 +40,7 @@ namespace OpenRA.Graphics
this.zOffset = zOffset; this.zOffset = zOffset;
this.palette = palette; this.palette = palette;
this.scale = scale; this.scale = scale;
this.rotation = rotation;
this.tint = tint; this.tint = tint;
this.isDecoration = isDecoration; this.isDecoration = isDecoration;
this.tintModifiers = tintModifiers; this.tintModifiers = tintModifiers;
@@ -50,6 +53,10 @@ namespace OpenRA.Graphics
this.palette = null; this.palette = null;
} }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
float3 tint, TintModifiers tintModifiers, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, WAngle.Zero) { }
public WPos Pos => pos + offset; public WPos Pos => pos + offset;
public WVec Offset => offset; public WVec Offset => offset;
public PaletteReference Palette => palette; public PaletteReference Palette => palette;
@@ -60,19 +67,34 @@ namespace OpenRA.Graphics
public float3 Tint => tint; public float3 Tint => tint;
public TintModifiers TintModifiers => tintModifiers; public TintModifiers TintModifiers => tintModifiers;
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, alpha, tint, tintModifiers, isDecoration); } public IPalettedRenderable WithPalette(PaletteReference newPalette)
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); } {
public IRenderable OffsetBy(in WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); } return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true); } }
public IRenderable WithZOffset(int newOffset)
{
return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
}
public IRenderable OffsetBy(in WVec vec)
{
return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
}
public IRenderable AsDecoration()
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true, rotation);
}
public IModifyableRenderable WithAlpha(float newAlpha) public IModifyableRenderable WithAlpha(float newAlpha)
{ {
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration); return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration, rotation);
} }
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers) public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{ {
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration); return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration, rotation);
} }
float3 ScreenPosition(WorldRenderer wr) float3 ScreenPosition(WorldRenderer wr)
@@ -94,7 +116,7 @@ namespace OpenRA.Graphics
if ((tintModifiers & TintModifiers.ReplaceColor) != 0) if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1; a *= -1;
wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a); wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
} }
public void RenderDebugGeometry(WorldRenderer wr) public void RenderDebugGeometry(WorldRenderer wr)
@@ -102,13 +124,16 @@ namespace OpenRA.Graphics
var pos = ScreenPosition(wr) + sprite.Offset; var pos = ScreenPosition(wr) + sprite.Offset;
var tl = wr.Viewport.WorldToViewPx(pos); var tl = wr.Viewport.WorldToViewPx(pos);
var br = wr.Viewport.WorldToViewPx(pos + sprite.Size); var br = wr.Viewport.WorldToViewPx(pos + sprite.Size);
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red); if (rotation == WAngle.Zero)
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(tl, br - tl, rotation.RendererRadians()), 1, Color.Red);
} }
public Rectangle ScreenBounds(WorldRenderer wr) public Rectangle ScreenBounds(WorldRenderer wr)
{ {
var screenOffset = ScreenPosition(wr) + sprite.Offset; var screenOffset = ScreenPosition(wr) + sprite.Offset;
return new Rectangle((int)screenOffset.X, (int)screenOffset.Y, (int)sprite.Size.X, (int)sprite.Size.Y); return Util.BoundingRectangle(screenOffset, sprite.Size, rotation.RendererRadians());
} }
} }
} }

View File

@@ -126,35 +126,40 @@ namespace OpenRA.Graphics
return pal.TextureIndex; return pal.TextureIndex;
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale) internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f); Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
nv += 6; nv += 6;
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale) internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f); Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
nv += 6; nv += 6;
} }
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f) public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
{ {
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale); DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha) internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
float rotation = 0f)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha); Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
rotation);
nv += 6; nv += 6;
} }
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha) public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
float rotation = 0f)
{ {
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha); DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha) internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)

View File

@@ -22,8 +22,9 @@ namespace OpenRA.Graphics
readonly PaletteReference palette; readonly PaletteReference palette;
readonly float scale; readonly float scale;
readonly float alpha; readonly float alpha;
readonly float rotation = 0f;
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f) public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f)
{ {
this.sprite = sprite; this.sprite = sprite;
this.effectiveWorldPos = effectiveWorldPos; this.effectiveWorldPos = effectiveWorldPos;
@@ -32,6 +33,7 @@ namespace OpenRA.Graphics
this.palette = palette; this.palette = palette;
this.scale = scale; this.scale = scale;
this.alpha = alpha; this.alpha = alpha;
this.rotation = rotation;
// PERF: Remove useless palette assignments for RGBA sprites // PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences // HACK: This is working around the fact that palettes are defined on traits rather than sequences
@@ -48,7 +50,7 @@ namespace OpenRA.Graphics
public PaletteReference Palette => palette; public PaletteReference Palette => palette;
public int ZOffset => zOffset; public int ZOffset => zOffset;
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale, alpha); } public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale, alpha, rotation); }
public IRenderable WithZOffset(int newOffset) { return this; } public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec) { return this; } public IRenderable OffsetBy(in WVec vec) { return this; }
public IRenderable AsDecoration() { return this; } public IRenderable AsDecoration() { return this; }
@@ -56,19 +58,22 @@ namespace OpenRA.Graphics
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr) public void Render(WorldRenderer wr)
{ {
Game.Renderer.SpriteRenderer.DrawSprite(sprite, palette, screenPos, scale, float3.Ones, alpha); Game.Renderer.SpriteRenderer.DrawSprite(sprite, palette, screenPos, scale, float3.Ones, alpha, rotation);
} }
public void RenderDebugGeometry(WorldRenderer wr) public void RenderDebugGeometry(WorldRenderer wr)
{ {
var offset = screenPos + sprite.Offset.XY; var offset = screenPos + sprite.Offset.XY;
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red); if (rotation == 0f)
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(offset, sprite.Size, rotation), 1, Color.Red);
} }
public Rectangle ScreenBounds(WorldRenderer wr) public Rectangle ScreenBounds(WorldRenderer wr)
{ {
var offset = screenPos + sprite.Offset; var offset = screenPos + sprite.Offset;
return new Rectangle((int)offset.X, (int)offset.Y, (int)sprite.Size.X, (int)sprite.Size.Y); return Util.BoundingRectangle(offset, sprite.Size, rotation);
} }
} }
} }

View File

@@ -20,12 +20,44 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts. // yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 }; static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint, float alpha) public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
in float3 size, in float3 tint, float alpha, float rotation = 0f)
{ {
var b = new float3(o.X + size.X, o.Y, o.Z); float3 a, b, c, d;
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z); // Rotate sprite if rotation angle is not equal to 0
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv); if (rotation != 0f)
{
var center = o + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
a = center - ra;
b = center + rb;
c = center + ra;
d = center - rb;
}
else
{
a = o;
b = new float3(o.X + size.X, o.Y, o.Z);
c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
}
FastCreateQuad(vertices, a, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
} }
public static void FastCreateQuad(Vertex[] vertices, public static void FastCreateQuad(Vertex[] vertices,
@@ -191,6 +223,69 @@ namespace OpenRA.Graphics
} }
} }
/// <summary>Rotates a quad about its center in the x-y plane.</summary>
/// <param name="tl">The top left vertex of the quad</param>
/// <param name="size">A float3 containing the X, Y, and Z lengths of the quad</param>
/// <param name="rotation">The number of radians to rotate by</param>
/// <returns>An array of four vertices representing the rotated quad (top-left, top-right, bottom-right, bottom-left)</returns>
public static float3[] RotateQuad(float3 tl, float3 size, float rotation)
{
var center = tl + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
return new float3[]
{
center - ra,
center + rb,
center + ra,
center - rb
};
}
/// <summary>
/// Returns the bounds of an object. Used for determining which objects need to be rendered on screen, and which do not.
/// </summary>
/// <param name="offset">The top left vertex of the object</param>
/// <param name="size">A float 3 containing the X, Y, and Z lengths of the object</param>
/// <param name="rotation">The angle to rotate the object by (use 0f if there is no rotation)</param>
public static Rectangle BoundingRectangle(float3 offset, float3 size, float rotation)
{
if (rotation == 0f)
return new Rectangle((int)offset.X, (int)offset.Y, (int)size.X, (int)size.Y);
var rotatedQuad = Util.RotateQuad(offset, size, rotation);
var minX = rotatedQuad[0].X;
var maxX = rotatedQuad[0].X;
var minY = rotatedQuad[0].Y;
var maxY = rotatedQuad[0].Y;
for (var i = 1; i < rotatedQuad.Length; i++)
{
minX = Math.Min(rotatedQuad[i].X, minX);
maxX = Math.Max(rotatedQuad[i].X, maxX);
minY = Math.Min(rotatedQuad[i].Y, minY);
maxY = Math.Max(rotatedQuad[i].Y, maxY);
}
return new Rectangle(
(int)minX,
(int)minY,
(int)Math.Ceiling(maxX) - (int)minX,
(int)Math.Ceiling(maxY) - (int)minY);
}
public static Color PremultiplyAlpha(Color c) public static Color PremultiplyAlpha(Color c)
{ {
if (c.A == byte.MaxValue) if (c.A == byte.MaxValue)

View File

@@ -108,6 +108,7 @@ namespace OpenRA.Mods.Common.Graphics
int ISpriteSequence.Length => throw exception; int ISpriteSequence.Length => throw exception;
int ISpriteSequence.Stride => throw exception; int ISpriteSequence.Stride => throw exception;
int ISpriteSequence.Facings => throw exception; int ISpriteSequence.Facings => throw exception;
int ISpriteSequence.InterpolatedFacings => throw exception;
int ISpriteSequence.Tick => throw exception; int ISpriteSequence.Tick => throw exception;
int ISpriteSequence.ZOffset => throw exception; int ISpriteSequence.ZOffset => throw exception;
int ISpriteSequence.ShadowStart => throw exception; int ISpriteSequence.ShadowStart => throw exception;
@@ -118,6 +119,7 @@ namespace OpenRA.Mods.Common.Graphics
float ISpriteSequence.Scale => throw exception; float ISpriteSequence.Scale => throw exception;
Sprite ISpriteSequence.GetSprite(int frame) { throw exception; } Sprite ISpriteSequence.GetSprite(int frame) { throw exception; }
Sprite ISpriteSequence.GetSprite(int frame, WAngle facing) { throw exception; } Sprite ISpriteSequence.GetSprite(int frame, WAngle facing) { throw exception; }
(Sprite, WAngle) ISpriteSequence.GetSpriteWithRotation(int frame, WAngle facing) { throw exception; }
Sprite ISpriteSequence.GetShadow(int frame, WAngle facing) { throw exception; } Sprite ISpriteSequence.GetShadow(int frame, WAngle facing) { throw exception; }
float ISpriteSequence.GetAlpha(int frame) { throw exception; } float ISpriteSequence.GetAlpha(int frame) { throw exception; }
} }
@@ -167,6 +169,11 @@ namespace OpenRA.Mods.Common.Graphics
int ISpriteSequence.Facings => facings; int ISpriteSequence.Facings => facings;
protected int facings; protected int facings;
[Desc("The amount of directions the unit faces. Use negative values to rotate counter-clockwise.")]
static readonly SpriteSequenceField<int> InterpolatedFacings = new SpriteSequenceField<int>(nameof(InterpolatedFacings), 1);
int ISpriteSequence.InterpolatedFacings => interpolatedFacings;
protected int interpolatedFacings;
[Desc("Time (in milliseconds at default game speed) to wait until playing the next frame in the animation.")] [Desc("Time (in milliseconds at default game speed) to wait until playing the next frame in the animation.")]
static readonly SpriteSequenceField<int> Tick = new SpriteSequenceField<int>(nameof(Tick), 40); static readonly SpriteSequenceField<int> Tick = new SpriteSequenceField<int>(nameof(Tick), 40);
int ISpriteSequence.Tick => tick; int ISpriteSequence.Tick => tick;
@@ -305,6 +312,11 @@ namespace OpenRA.Mods.Common.Graphics
var zRamp = LoadField(d, ZRamp); var zRamp = LoadField(d, ZRamp);
facings = LoadField(d, Facings); facings = LoadField(d, Facings);
interpolatedFacings = LoadField(d, nameof(InterpolatedFacings), -1);
if (interpolatedFacings != -1 && (interpolatedFacings <= 1 || interpolatedFacings <= Math.Abs(facings) || interpolatedFacings > 1024
|| !Exts.IsPowerOf2(interpolatedFacings)))
throw new YamlException($"InterpolatedFacings must be greater than Facings, within the range of 2 to 1024, and a power of 2.");
if (facings < 0) if (facings < 0)
{ {
reverseFacings = true; reverseFacings = true;
@@ -531,6 +543,17 @@ namespace OpenRA.Mods.Common.Graphics
return GetSprite(start, frame, facing); return GetSprite(start, frame, facing);
} }
public (Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing)
{
var rotation = WAngle.Zero;
// Note: Error checking is not done here as it is done on load
if (interpolatedFacings != -1)
rotation = Util.GetInterpolatedFacing(facing, Math.Abs(facings), interpolatedFacings);
return (GetSprite(start, frame, facing), rotation);
}
public Sprite GetShadow(int frame, WAngle facing) public Sprite GetShadow(int frame, WAngle facing)
{ {
return shadowStart >= 0 ? GetSprite(shadowStart, frame, facing) : null; return shadowStart >= 0 ? GetSprite(shadowStart, frame, facing) : null;

View File

@@ -29,7 +29,8 @@ namespace OpenRA.Mods.Common.Traits
throw new InvalidOperationException("Actor " + ai.Name + " is missing sequence to quantize facings from."); throw new InvalidOperationException("Actor " + ai.Name + " is missing sequence to quantize facings from.");
var rsi = ai.TraitInfo<RenderSpritesInfo>(); var rsi = ai.TraitInfo<RenderSpritesInfo>();
return sequenceProvider.GetSequence(rsi.GetImage(ai, race), Sequence).Facings; var seq = sequenceProvider.GetSequence(rsi.GetImage(ai, race), Sequence);
return seq.InterpolatedFacings == -1 ? seq.Facings : seq.InterpolatedFacings;
} }
public override object Create(ActorInitializer init) { return new QuantizeFacingsFromSequence(this); } public override object Create(ActorInitializer init) { return new QuantizeFacingsFromSequence(this); }

View File

@@ -67,11 +67,28 @@ namespace OpenRA.Mods.Common
/// </summary> /// </summary>
public static int IndexFacing(WAngle facing, int numFrames) public static int IndexFacing(WAngle facing, int numFrames)
{ {
// 1024 here is the max angle, so we divide the max angle by the total number of facings (numFrames)
var step = 1024 / numFrames; var step = 1024 / numFrames;
var a = (facing.Angle + step / 2) & 1023; var a = (facing.Angle + step / 2) & 1023;
return a / step; return a / step;
} }
/// <summary>
/// Returns the remainder angle after rounding to the nearest whole step / facing
/// </summary>
public static WAngle AngleDiffToStep(WAngle facing, int numFrames)
{
var step = 1024 / numFrames;
var a = (facing.Angle + step / 2) & 1023;
return new WAngle(a % step - step / 2);
}
public static WAngle GetInterpolatedFacing(WAngle facing, int facings, int interpolatedFacings)
{
var step = 1024 / interpolatedFacings;
return new WAngle(AngleDiffToStep(facing, facings).Angle / step * step);
}
/// <summary>Rounds the given facing value to the nearest quantized step.</summary> /// <summary>Rounds the given facing value to the nearest quantized step.</summary>
public static WAngle QuantizeFacing(WAngle facing, int steps) public static WAngle QuantizeFacing(WAngle facing, int steps)
{ {

View File

@@ -1,11 +1,13 @@
mig: mig:
idle: idle:
Facings: 16 Facings: 16
InterpolatedFacings: 64
icon: migicon icon: migicon
yak: yak:
idle: idle:
Facings: 16 Facings: 16
InterpolatedFacings: 64
muzzle: minigun muzzle: minigun
Length: 6 Length: 6
Facings: 8 Facings: 8
@@ -66,10 +68,12 @@ tran2husk:
u2: u2:
idle: idle:
Facings: 16 Facings: 16
InterpolatedFacings: 64
badr: badr:
idle: idle:
Facings: 16 Facings: 16
InterpolatedFacings: 64
mh60: mh60:
idle: idle: