#if UNITY_EDITOR using UnityEngine; using System; namespace GSpawn { public enum TileRuleMaskRotation { None = 0, R90, R180, R270 } public enum TileRuleMaskMirrorAxis { None = 0, X, Z } public enum TileRuleBitMaskId { ReqOn = 0, ReqOff } // Note: Maps to corresponding number of neighbors. public enum TileRuleNeighborRadius { One = 1, Two, [Obsolete] Three, } public struct TileRuleMaskMatchResult { public bool matched; public TileRuleMaskRotation maskRotation; public TileRuleMaskMirrorAxis maskMirrorAxis; public TileRuleMaskMatchResult(bool matched) { this.matched = matched; maskRotation = TileRuleMaskRotation.None; maskMirrorAxis = TileRuleMaskMirrorAxis.None; } public TileRuleMaskMatchResult(TileRuleMaskRotation maskRotation) { matched = true; this.maskRotation = maskRotation; maskMirrorAxis = TileRuleMaskMirrorAxis.None; } public TileRuleMaskMatchResult(TileRuleMaskMirrorAxis maskMirrorAxis) { matched = true; maskRotation = TileRuleMaskRotation.None; this.maskMirrorAxis = maskMirrorAxis; } public void reset() { matched = false; maskRotation = TileRuleMaskRotation.None; maskMirrorAxis = TileRuleMaskMirrorAxis.None; } } [Serializable] public class TileRuleMask { // Note: Maps to TileRuleMaskRotation. private static Vector3[] _rotationLookAxes = new Vector3[] { Vector3.forward, Vector3.right, Vector3.back, Vector3.left }; private const int _numAvailableBits = 49; private const int _bitRowSize = 7; private const int _numBitRows = 7; private const int _midBitIndex = 24; private const ulong _defaultReqOnMask = (1ul << 24); private const ulong _defaultReqOffMask = 0; [SerializeField] private ulong _reqOnMask = _defaultReqOnMask; [SerializeField] private ulong _rotatedReqOnMask_90 = _defaultReqOnMask; [SerializeField] private ulong _rotatedReqOnMask_180 = _defaultReqOnMask; [SerializeField] private ulong _rotatedReqOnMask_270 = _defaultReqOnMask; [SerializeField] private ulong _mirrXReqOnMask = _defaultReqOnMask; [SerializeField] private ulong _mirrZReqOnMask = _defaultReqOnMask; [SerializeField] private int _numReqOnBitsSet = 1; [SerializeField] private ulong _reqOffMask = _defaultReqOffMask; [SerializeField] private ulong _rotatedReqOffMask_90 = _defaultReqOffMask; [SerializeField] private ulong _rotatedReqOffMask_180 = _defaultReqOffMask; [SerializeField] private ulong _rotatedReqOffMask_270 = _defaultReqOffMask; [SerializeField] private ulong _mirrXReqOffMask = _defaultReqOffMask; [SerializeField] private ulong _mirrZReqOffMask = _defaultReqOffMask; [SerializeField] private int _numReqOffBitsSet = 0; public ulong reqOnMaskValue { get { return _reqOnMask; } set { _reqOnMask = value; _rotatedReqOnMask_90 = rotateMask_90(_reqOnMask); _rotatedReqOnMask_180 = rotateMask_180(_reqOnMask); _rotatedReqOnMask_270 = rotateMask_270(_reqOnMask); _mirrXReqOnMask = mirrorMaskX(_reqOnMask); _mirrZReqOnMask = mirrorMaskZ(_reqOnMask); _numReqOnBitsSet = countNumberOf1Bits(_reqOnMask); } } public int numReqOnBitsSet { get { return _numReqOnBitsSet; } } public ulong reqOffMaskValue { get { return _reqOffMask; } set { _reqOffMask = value; _rotatedReqOffMask_90 = rotateMask_90(_reqOffMask); _rotatedReqOffMask_180 = rotateMask_180(_reqOffMask); _rotatedReqOffMask_270 = rotateMask_270(_reqOffMask); _mirrXReqOffMask = mirrorMaskX(_reqOffMask); _mirrZReqOffMask = mirrorMaskZ(_reqOffMask); _numReqOffBitsSet = countNumberOf1Bits(_reqOffMask); } } public int numReqOffBitsSet { get { return _numReqOffBitsSet; } } public static int numAvailableBits { get { return _numAvailableBits; } } public static int numBitRows { get { return _numBitRows; } } public static int bitRowSize { get { return _bitRowSize; } } public static int middleBitRow { get { return 3; } } public static int middleBitCol { get { return 3; } } public static ulong defaultReqOnMask { get { return _defaultReqOnMask; } } public static Vector3 maskRotationToLookAxis(TileRuleMaskRotation maskRotation, Quaternion frameRotation) { return frameRotation * _rotationLookAxes[(int)maskRotation]; } public static void debugLog(ulong ruleMask) { string binaryString = Convert.ToString((long)ruleMask, 2).PadLeft(64, '0'); binaryString = binaryString.Substring(64 - _numAvailableBits, _numAvailableBits); Debug.Log("================Rule Mask================"); for (int i = 0; i < _numBitRows; ++i) Debug.Log("| " + binaryString.Substring(i * _bitRowSize, _bitRowSize) + " |"); } public static ulong setBit(ulong ruleMask, int row, int col) { #pragma warning disable 0612 int bitIndex = rowColToBitIndex(TileRuleNeighborRadius.Three, row, col); ulong bitMask = 1ul << bitIndex; return ruleMask | bitMask; #pragma warning restore 0612 } public static int countNumberOf1Bits(ulong ruleMask) { int count = 0; for (int i = 0; i < _numAvailableBits; ++i) { count += (int)(ruleMask & 0x1); ruleMask >>= 1; } return count; } public static int rowColToBitIndex(int row, int col) { return 48 - (row * _bitRowSize + col); } public static void bitIndexToRowCol(int bitIndex, out int row, out int col) { row = (48 - bitIndex) / _numBitRows; col = (48 - bitIndex) % _bitRowSize; } public static int rowColToBitIndex(TileRuleNeighborRadius radius, int row, int col) { #pragma warning disable 0612 switch (radius) { case TileRuleNeighborRadius.One: return 32 - (row * _bitRowSize + col); case TileRuleNeighborRadius.Two: return 40 - (row * _bitRowSize + col); case TileRuleNeighborRadius.Three: return 48 - (row * _bitRowSize + col); } #pragma warning restore 0612 return 0; } public static bool isMiddleBit(int row, int col) { #pragma warning disable 0612 int bitIndex = rowColToBitIndex(TileRuleNeighborRadius.Three, row, col); return bitIndex == _midBitIndex; #pragma warning restore 0612 } public static bool isBitInRadius(TileRuleNeighborRadius radius, int row, int col) { #pragma warning disable 0612 switch (radius) { case TileRuleNeighborRadius.Three: return true; case TileRuleNeighborRadius.Two: return row >= 1 && row <= 5 && col >= 1 && col <= 5; case TileRuleNeighborRadius.One: return row >= 2 && row <= 4 && col >= 2 && col <= 4; } #pragma warning restore 0612 return false; } // Note: The match function only produces the expected results as long as the tile rules // have been sorted in descending order of the total number of bits set (req on + req off). // (i.e from the most specific/restrictive to the least specific/restrictive). public TileRuleMaskMatchResult match(ulong ruleMask, TileRuleRotationMode rotationMode) { // Note: This is common to all rotation modes. if ((_reqOnMask & ruleMask) == _reqOnMask) { if ((_reqOffMask & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskRotation.None); } switch (rotationMode) { case TileRuleRotationMode.Fixed: // Note: Handled above. break; case TileRuleRotationMode.Rotated: if ((_rotatedReqOnMask_90 & ruleMask) == _rotatedReqOnMask_90) { if ((_rotatedReqOffMask_90 & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskRotation.R90); } if ((_rotatedReqOnMask_180 & ruleMask) == _rotatedReqOnMask_180) { if ((_rotatedReqOffMask_180 & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskRotation.R180); } if ((_rotatedReqOnMask_270 & ruleMask) == _rotatedReqOnMask_270) { if ((_rotatedReqOffMask_270 & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskRotation.R270); } break; #pragma warning disable 0612 case TileRuleRotationMode.MirrorX: if ((_mirrXReqOnMask & ruleMask) == _mirrXReqOnMask) { if ((_mirrXReqOffMask & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskMirrorAxis.X); } break; case TileRuleRotationMode.MirrorZ: if ((_mirrZReqOnMask & ruleMask) == _mirrZReqOnMask) { if ((_mirrZReqOffMask & ruleMask) != 0) return new TileRuleMaskMatchResult(false); return new TileRuleMaskMatchResult(TileRuleMaskMirrorAxis.Z); } break; #pragma warning restore 0612 } return new TileRuleMaskMatchResult(false); } public bool checkBit(int row, int col, TileRuleBitMaskId maskId) { #pragma warning disable 0612 int bitIndex = rowColToBitIndex(TileRuleNeighborRadius.Three, row, col); ulong bitMask = 1ul << bitIndex; switch (maskId) { case TileRuleBitMaskId.ReqOn: return (_reqOnMask & bitMask) != 0; case TileRuleBitMaskId.ReqOff: return (_reqOffMask & bitMask) != 0; } #pragma warning restore 0612 return false; } public void clearBit(int row, int col, TileRuleBitMaskId maskId) { #pragma warning disable 0612 int bitIndex = rowColToBitIndex(TileRuleNeighborRadius.Three, row, col); ulong bitMask = ~(1ul << bitIndex); switch (maskId) { case TileRuleBitMaskId.ReqOn: reqOnMaskValue = reqOnMaskValue & bitMask; break; case TileRuleBitMaskId.ReqOff: reqOffMaskValue = reqOffMaskValue & bitMask; break; } #pragma warning restore 0612 } public void setAllBits(TileRuleBitMaskId maskId) { switch (maskId) { case TileRuleBitMaskId.ReqOn: reqOnMaskValue = ~0ul; reqOffMaskValue = _defaultReqOffMask; break; case TileRuleBitMaskId.ReqOff: reqOffMaskValue = ~0ul; reqOffMaskValue &= ~((1ul << 24)); reqOnMaskValue = _defaultReqOnMask; break; } } public void setBit(int row, int col, TileRuleBitMaskId maskId) { #pragma warning disable 0612 int bitIndex = rowColToBitIndex(TileRuleNeighborRadius.Three, row, col); ulong bitMask = 1ul << bitIndex;; switch (maskId) { case TileRuleBitMaskId.ReqOn: reqOnMaskValue = reqOnMaskValue | bitMask; reqOffMaskValue = reqOffMaskValue & (~bitMask); break; case TileRuleBitMaskId.ReqOff: reqOffMaskValue = reqOffMaskValue | bitMask; reqOffMaskValue &= ~((1ul << 24)); reqOnMaskValue = reqOnMaskValue & (~bitMask); break; } #pragma warning restore 0612 } public void toggleBit(int row, int col, TileRuleBitMaskId maskId) { if (checkBit(row, col, maskId)) clearBit(row, col, maskId); else setBit(row, col, maskId); } public void useDefaultValue() { reqOnMaskValue = _defaultReqOnMask; reqOffMaskValue = _defaultReqOffMask; } private static ulong mirrorMaskX(ulong ruleMask) { int numColumns = _bitRowSize / 2; for (int r = 0; r < _numBitRows; ++r) { for (int c = 0; c < numColumns; ++c) { int p0 = rowColToBitIndex(r, c); int p1 = rowColToBitIndex(r, _bitRowSize - 1 - c); ruleMask = swapBits(ruleMask, p0, p1); } } return ruleMask; } private static ulong mirrorMaskZ(ulong ruleMask) { int numRows = _numBitRows / 2; for (int c = 0; c < _bitRowSize; ++c) { for (int r = 0; r < numRows; ++r) { int p0 = rowColToBitIndex(r, c); int p1 = rowColToBitIndex(_numBitRows - 1 - r, c); ruleMask = swapBits(ruleMask, p0, p1); } } return ruleMask; } private static ulong swapBits(ulong ruleMask, int p0, int p1) { if (p0 == p1) return ruleMask; // https://www.geeksforgeeks.org/how-to-swap-two-bits-in-a-given-integer/ ulong bit0 = (ruleMask >> p0) & 1; ulong bit1 = (ruleMask >> p1) & 1; ulong xor = (bit0 ^ bit1); xor = (xor << p0) | (xor << p1); return ruleMask ^ xor; } private static ulong rotateMask_90(ulong ruleMask) { ulong r0 = extractMaskRow(ruleMask, 0); ulong r1 = extractMaskRow(ruleMask, 1); ulong r2 = extractMaskRow(ruleMask, 2); ulong r3 = extractMaskRow(ruleMask, 3); ulong r4 = extractMaskRow(ruleMask, 4); ulong r5 = extractMaskRow(ruleMask, 5); ulong r6 = extractMaskRow(ruleMask, 6); ruleMask = setMaskColumn(ruleMask, 0, r6); ruleMask = setMaskColumn(ruleMask, 1, r5); ruleMask = setMaskColumn(ruleMask, 2, r4); ruleMask = setMaskColumn(ruleMask, 3, r3); ruleMask = setMaskColumn(ruleMask, 4, r2); ruleMask = setMaskColumn(ruleMask, 5, r1); ruleMask = setMaskColumn(ruleMask, 6, r0); return ruleMask; } private static ulong rotateMask_180(ulong ruleMask) { ruleMask = rotateMask_90(ruleMask); return rotateMask_90(ruleMask); } private static ulong rotateMask_270(ulong ruleMask) { ruleMask = rotateMask_90(ruleMask); ruleMask = rotateMask_90(ruleMask); return rotateMask_90(ruleMask); } private static ulong setMaskColumn(ulong ruleMask, int col, ulong val_7bit) { // Note: High bit in val_7bit maps to top most bit in column. Low bit // in val_7bit maps to bottom most bit in column. // Loop through each bit in the 7 bit value (0 = low bit; 7 = high bit) for (int bitIndex = 0; bitIndex < _bitRowSize; ++bitIndex) { // Clear bit in rule mask ruleMask &= ~((1ul << (_bitRowSize - 1 - col)) << (bitIndex * _bitRowSize)); // Extract bit from 7 bit value ulong bitMask = 1ul << bitIndex; ulong bit = (val_7bit & bitMask) >> bitIndex; // Store the bit in the rule mask ruleMask |= ((bit << (_bitRowSize - 1 - col)) << (bitIndex * _bitRowSize)); } return ruleMask; } private static ulong extractMaskRow(ulong ruleMask, int row) { int shift = (_numBitRows - 1 - row) * _bitRowSize; ulong bitMask = 0b1111111ul << shift; return (ruleMask & bitMask) >> shift; } } } #endif