#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; namespace OpenRA.Mods.Common.MapGenerator { /// /// A fixed-size 2D array that can be indexed either linearly or by coordinates. /// public sealed class Matrix { /// Underlying matrix data. public readonly T[] Data; /// Matrix dimensions. public readonly int2 Size; /// /// Create a new matrix with the given size and adopt a given array as its backing data. /// Matrix(int2 size, T[] data) { if (data.Length != size.X * size.Y) throw new ArgumentException("Matrix data length does not match size"); Data = data; Size = size; } /// Create a new matrix with the given size. public Matrix(int2 size) : this(size, new T[size.X * size.Y]) { } /// Create a new matrix with the given size. public Matrix(int x, int y) : this(new int2(x, y)) { } /// /// Convert a pair of coordinates into an index into Data. /// public int Index(int2 xy) => Index(xy.X, xy.Y); /// /// Convert a pair of coordinates into an index into Data. /// public int Index(int x, int y) { if (!ContainsXY(x, y)) ThrowIndexOutOfRangeException(x, y); return y * Size.X + x; } void ThrowIndexOutOfRangeException(int x, int y) { throw new IndexOutOfRangeException( $"({x}, {y}) is out of bounds for a matrix of size ({Size.X}, {Size.Y})"); } /// /// Convert a Data index into a pair of coordinates. /// public int2 XY(int index) { if (index < 0 || index >= Data.Length) throw new IndexOutOfRangeException( $"Index {index} is out of range for a matrix of size ({Size.X}, {Size.Y})"); var y = Math.DivRem(index, Size.X, out var x); return new int2(x, y); } public T this[int x, int y] { get => Data[Index(x, y)]; set => Data[Index(x, y)] = value; } public T this[int2 xy] { get => Data[Index(xy.X, xy.Y)]; set => Data[Index(xy.X, xy.Y)] = value; } /// Shorthand for Data[i]. public T this[int i] { get => Data[i]; set => Data[i] = value; } /// True iff xy is a valid index within the matrix. public bool ContainsXY(int2 xy) { return xy.X >= 0 && xy.X < Size.X && xy.Y >= 0 && xy.Y < Size.Y; } /// True iff (x, y) is a valid index within the matrix. public bool ContainsXY(int x, int y) { return x >= 0 && x < Size.X && y >= 0 && y < Size.Y; } public bool IsEdge(int x, int y) { return x == 0 || x == Size.X - 1 || y == 0 || y == Size.Y - 1; } public bool IsEdge(int2 xy) { return IsEdge(xy.X, xy.Y); } /// Clamp xy to be the closest index within the matrix. public int2 ClampXY(int2 xy) { var (nx, ny) = ClampXY(xy.X, xy.Y); return new int2(nx, ny); } /// Clamp (x, y) to be the closest index within the matrix. public (int Nx, int Ny) ClampXY(int x, int y) { if (x >= Size.X) x = Size.X - 1; if (x < 0) x = 0; if (y >= Size.Y) y = Size.Y - 1; if (y < 0) y = 0; return (x, y); } /// /// Creates a transposed (shallow) copy of the matrix. /// public Matrix Transpose() { var transposed = new Matrix(new int2(Size.Y, Size.X)); for (var y = 0; y < Size.Y; y++) for (var x = 0; x < Size.X; x++) transposed[y, x] = this[x, y]; return transposed; } /// /// Return a new matrix with the same shape as this one containing the values after being /// transformed by a mapping func. /// public Matrix Map(Func func) { var mapped = new Matrix(Size); for (var i = 0; i < Data.Length; i++) mapped.Data[i] = func(Data[i]); return mapped; } /// /// Replace all values in the matrix with a given value. Returns this. /// public Matrix Fill(T value) { Array.Fill(Data, value); return this; } /// /// Return a shallow clone of this matrix. /// public Matrix Clone() { return new Matrix(Size, (T[])Data.Clone()); } /// /// Combine two same-shape matrices into a new output matrix. /// The zipping function specifies how values are combined. /// public static Matrix Zip(Matrix a, Matrix b, Func func) { if (a.Size != b.Size) throw new ArgumentException("Input matrices to Zip must match in shape and size"); var matrix = new Matrix(a.Size); for (var i = 0; i < a.Data.Length; i++) matrix.Data[i] = func(a.Data[i], b.Data[i]); return matrix; } } }