#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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 OpenRA.FileFormats; using OpenRA.Primitives; namespace OpenRA.Graphics { public static class Util { // yes, our channel order is nuts. static readonly int[] ChannelMasks = { 2, 1, 0, 3 }; public static uint[] CreateQuadIndices(int quads) { var indices = new uint[quads * 6]; ReadOnlySpan cornerVertexMap = stackalloc uint[] { 0, 1, 2, 2, 3, 0 }; for (var i = 0; i < indices.Length; i++) indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6)); return indices; } public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, int paletteTextureIndex, int nv, in float3 size, in float3 tint, float alpha, float rotation = 0f) { float3 a, b, c, d; // Rotate sprite if rotation angle is not equal to 0 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, in float3 a, in float3 b, in float3 c, in float3 d, Sprite r, int2 samplers, int paletteTextureIndex, in float3 tint, float alpha, int nv) { float sl = 0; float st = 0; float sr = 0; float sb = 0; // See combined.vert for documentation on the channel attribute format var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01; attribC |= samplers.X << 6; if (r is SpriteWithSecondaryData ss) { sl = ss.SecondaryLeft; st = ss.SecondaryTop; sr = ss.SecondaryRight; sb = ss.SecondaryBottom; attribC |= ((byte)ss.SecondaryChannel) << 4 | 0x08; attribC |= samplers.Y << 9; } attribC |= (paletteTextureIndex & 0xFFFF) << 16; var uAttribC = (uint)attribC; vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, uAttribC, tint, alpha); vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, uAttribC, tint, alpha); vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, uAttribC, tint, alpha); vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, tint, alpha); } public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType, bool premultiplied = false) { var destData = dest.Sheet.GetData(); var width = dest.Bounds.Width; var height = dest.Bounds.Height; if (dest.Channel == TextureChannel.RGBA) { var destStride = dest.Sheet.Size.Width; unsafe { // Cast the data to an int array so we can copy the src data directly fixed (byte* bd = &destData[0]) { var data = (int*)bd; var x = dest.Bounds.Left; var y = dest.Bounds.Top; var k = 0; for (var j = 0; j < height; j++) { for (var i = 0; i < width; i++) { byte r, g, b, a; switch (srcType) { case SpriteFrameType.Bgra32: case SpriteFrameType.Bgr24: { b = src[k++]; g = src[k++]; r = src[k++]; a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255; break; } case SpriteFrameType.Rgba32: case SpriteFrameType.Rgb24: { r = src[k++]; g = src[k++]; b = src[k++]; a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255; break; } default: throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}"); } var cc = Color.FromArgb(a, r, g, b); if (premultiplied) data[(y + j) * destStride + x + i] = cc.ToArgb(); else data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); } } } } } else { var destStride = dest.Sheet.Size.Width * 4; var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel]; var destSkip = destStride - 4 * width; var srcOffset = 0; for (var j = 0; j < height; j++) { for (var i = 0; i < width; i++, srcOffset++) { destData[destOffset] = src[srcOffset]; destOffset += 4; } destOffset += destSkip; } } } public static void FastCopyIntoSprite(Sprite dest, Png src) { var destData = dest.Sheet.GetData(); var destStride = dest.Sheet.Size.Width; var width = dest.Bounds.Width; var height = dest.Bounds.Height; unsafe { // Cast the data to an int array so we can copy the src data directly fixed (byte* bd = &destData[0]) { var data = (int*)bd; var x = dest.Bounds.Left; var y = dest.Bounds.Top; var k = 0; for (var j = 0; j < height; j++) { for (var i = 0; i < width; i++) { Color cc; switch (src.Type) { case SpriteFrameType.Indexed8: { cc = src.Palette[src.Data[k++]]; break; } case SpriteFrameType.Rgba32: case SpriteFrameType.Rgb24: { var r = src.Data[k++]; var g = src.Data[k++]; var b = src.Data[k++]; var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255; cc = Color.FromArgb(a, r, g, b); break; } // Pngs don't support BGR[A], so no need to include them here default: throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}"); } data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); } } } } } /// Rotates a quad about its center in the x-y plane. /// The top left vertex of the quad. /// A float3 containing the X, Y, and Z lengths of the quad. /// The number of radians to rotate by. /// An array of four vertices representing the rotated quad (top-left, top-right, bottom-right, bottom-left). 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 }; } /// /// Returns the bounds of an object. Used for determining which objects need to be rendered on screen, and which do not. /// /// The top left vertex of the object. /// A float 3 containing the X, Y, and Z lengths of the object. /// The angle to rotate the object by (use 0f if there is no rotation). 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 = 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) { if (c.A == byte.MaxValue) return c; var a = c.A / 255f; return Color.FromArgb(c.A, (byte)(c.R * a + 0.5f), (byte)(c.G * a + 0.5f), (byte)(c.B * a + 0.5f)); } public static Color PremultipliedColorLerp(float t, Color c1, Color c2) { // Colors must be lerped in a non-multiplied color space var a1 = 255f / c1.A; var a2 = 255f / c2.A; return PremultiplyAlpha(Color.FromArgb( (int)(t * c2.A + (1 - t) * c1.A), (int)((byte)(t * a2 * c2.R + 0.5f) + (1 - t) * (byte)(a1 * c1.R + 0.5f)), (int)((byte)(t * a2 * c2.G + 0.5f) + (1 - t) * (byte)(a1 * c1.G + 0.5f)), (int)((byte)(t * a2 * c2.B + 0.5f) + (1 - t) * (byte)(a1 * c1.B + 0.5f)))); } } }