diff --git a/Source.Common.Tests/BitBufferTests.cs b/Source.Common.Tests/BitBufferTests.cs new file mode 100644 index 00000000..2143b0ef --- /dev/null +++ b/Source.Common.Tests/BitBufferTests.cs @@ -0,0 +1,328 @@ +using Source.Common.Bitbuffers; + +using System.Numerics; +using System.Text; + +using static Source.Common.CoordSize; + +namespace Source.Common.Tests; + +public class BitBufferTests +{ + const int BufferBytes = 16; + const byte Marker = 0x5A; + + static uint Mask(int bits) => bits == 32 ? uint.MaxValue : (1u << bits) - 1; + + static string Written(bf_write writer, byte[] data) => Convert.ToHexString(data, 0, BitBuffer.BitByte(writer.BitsWritten)); + + static int SetBits(byte[] data) => data.Sum(b => BitOperations.PopCount(b)); + + static bf_read StringThenMarker(string text) { + byte[] data = new byte[64]; + bf_write writer = new(data, data.Length); + writer.WriteString(text); + writer.WriteByte(Marker); + return new bf_read(data, data.Length); + } + + [Fact] + public void ReadUBitLongReadsWhatWasWrittenAtEveryOffsetAndWidth() { + Random random = new(1234); + byte[] data = new byte[BufferBytes]; + + for (int offset = 0; offset < 32; offset++) { + for (int bits = 1; bits <= 32; bits++) { + uint value = (uint)random.NextInt64() & Mask(bits); + + Array.Clear(data); + bf_write writer = new(data, data.Length); + writer.Seek(offset); + writer.WriteUBitLong(value, bits); + + bf_read reader = new(data, data.Length); + reader.Seek(offset); + Assert.Equal(value, reader.ReadUBitLong(bits)); + Assert.Equal(offset + bits, reader.BitsRead); + } + } + } + + [Fact] + public void ReadUBitLongOnlyReturnsTheRequestedBits() { + byte[] data = new byte[BufferBytes]; + Array.Fill(data, (byte)0xFF); + + for (int offset = 0; offset < 32; offset++) { + for (int bits = 1; bits <= 32; bits++) { + bf_read reader = new(data, data.Length); + reader.Seek(offset); + Assert.Equal(Mask(bits), reader.ReadUBitLong(bits)); + } + } + } + + [Fact] + public void ReadUBitLongPastTheEndOverflows() { + byte[] data = new byte[4]; + bf_read reader = new(data, data.Length); + reader.ReadUBitLong(20); + + Assert.Equal(0u, reader.ReadUBitLong(13)); + Assert.True(reader.Overflowed); + Assert.Equal(32, reader.BitsRead); + } + + [Theory] + [InlineData(0x12345678u, 0x12345678u, false)] + [InlineData(0x12345678u, 0x12345679u, true)] + [InlineData(0x80000000u, 0x00000000u, true)] + public void CompareBitsReportsDifferenceAndAdvancesBothReaders(uint first, uint second, bool different) { + for (int offset = 0; offset < 32; offset++) { + byte[] data1 = new byte[BufferBytes]; + byte[] data2 = new byte[BufferBytes]; + + bf_write writer1 = new(data1, data1.Length); + writer1.Seek(offset); + writer1.WriteUBitLong(first, 32); + bf_write writer2 = new(data2, data2.Length); + writer2.Seek(offset); + writer2.WriteUBitLong(second, 32); + + bf_read reader1 = new(data1, data1.Length); + bf_read reader2 = new(data2, data2.Length); + reader1.Seek(offset); + reader2.Seek(offset); + + Assert.Equal(different, reader1.CompareBits(reader2, 32)); + Assert.Equal(offset + 32, reader1.BitsRead); + Assert.Equal(offset + 32, reader2.BitsRead); + } + } + + [Theory] + [InlineData(0, 1)] + [InlineData(3, 7)] + [InlineData(13, 19)] + [InlineData(29, 9)] + [InlineData(31, 31)] + public void WritesOnlyTouchTheRequestedBits(int offset, int bits) { + Action[] writes = [ + w => w.WriteUBitLong(uint.MaxValue, bits, false), + w => w.WriteSBitLong(-1, bits), + w => w.WriteBits(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, bits) + ]; + + foreach (var write in writes) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.Seek(offset); + write(writer); + + Assert.Equal(offset + bits, writer.BitsWritten); + Assert.Equal(bits, SetBits(data)); + bf_read reader = new(data, data.Length); + reader.Seek(offset); + Assert.Equal(Mask(bits), reader.ReadUBitLong(bits)); + } + } + + [Fact] + public void NamedReaderConstructorDefaultsToTheWholeBuffer() { + byte[] data = [0x78, 0x56, 0x34, 0x12, 0, 0, 0, 0]; + bf_read reader = new("named", data, data.Length); + + Assert.Equal((nint)64, reader.MaxBits); + Assert.Equal(0x12345678u, reader.ReadUBitLong(32)); + Assert.False(reader.Overflowed); + } + + [Theory] + [InlineData(0x0123456789ABCDEFL)] + [InlineData(-1L)] + [InlineData(long.MinValue)] + public void ReadLongLongReadsTwoDwords(long value) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.Seek(3); + writer.WriteLongLong(value); + + bf_read reader = new(data, data.Length); + reader.Seek(3); + Assert.Equal(value, reader.ReadLongLong()); + Assert.Equal(67, reader.BitsRead); + Assert.False(reader.Overflowed); + } + + [Fact] + public void TruncatedStringReadsSkipTheRestOfTheString() { + bf_read reader = StringThenMarker("hello world"); + Assert.False(reader.ReadString(out string? str, 5)); + Assert.Equal("hell", str); + Assert.Equal(Marker, reader.ReadByte()); + + reader = StringThenMarker("hello world"); + Assert.Equal("hell", reader.ReadString(5)); + Assert.Equal(Marker, reader.ReadByte()); + + reader = StringThenMarker("hello world"); + char[] chars = new char[5]; + Assert.Equal(4, reader.ReadString(chars.AsSpan())); + Assert.Equal("hell\0", new string(chars)); + Assert.Equal(Marker, reader.ReadByte()); + + reader = StringThenMarker("hello world"); + byte[] bytes = new byte[5]; + Assert.Equal(5, reader.ReadString(bytes.AsSpan())); + Assert.Equal("hello", Encoding.ASCII.GetString(bytes)); + Assert.Equal(Marker, reader.ReadByte()); + } + + [Theory] + [InlineData(1f)] + [InlineData(-0.5f)] + [InlineData(-3.5f)] + [InlineData(16384f)] + public void ReadBitCoordBitsReadsTheWholeCoord(float value) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitCoord(value); + + bf_read raw = new(data, data.Length); + uint expected = raw.ReadUBitLong(writer.BitsWritten); + bf_read reader = new(data, data.Length); + Assert.Equal(expected, reader.ReadBitCoordBits()); + Assert.Equal(writer.BitsWritten, reader.BitsRead); + } + + [Theory] + [InlineData(-1)] + [InlineData(int.MinValue)] + public void SingleBitWritesTreatNegativeValuesAsSet(int value) { + byte[] data = new byte[4]; + bf_write writer = new(data, data.Length); + writer.WriteOneBit(value); + writer.WriteOneBitNoCheck(value); + writer.WriteOneBitAt(5, value); + + Assert.Equal(0x23, data[0]); + } + + [Fact] + public void WriteStringReturnsWhetherItFit() { + byte[] data = new byte[8]; + Assert.True(new bf_write(data, data.Length).WriteString("hello")); + Assert.False(new bf_write(data, 4).WriteString("hello")); + } + + [Theory] + [InlineData("é", "E900")] + [InlineData("a´ÿ", "61B4FF00")] + [InlineData("\0abc", "00")] + public void WriteStringWritesBytesLikeGMod(string text, string expected) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteString(text); + Assert.Equal(expected, Written(writer, data)); + } + + [Fact] + public void StringReadsDoNotSignExtendHighBytes() { + byte[] data = [0x61, 0xB4, 0xFF, 0x00]; + char[] chars = new char[8]; + Assert.True(new bf_read(data, data.Length).ReadString(chars.AsSpan(), false, out int count)); + Assert.Equal("a´ÿ", new string(chars, 0, count)); + Assert.Equal("a´ÿ", new bf_read(data, data.Length).ReadAndAllocateString()); + } + + [Theory] + [InlineData(5u, 0x3B201403u)] + [InlineData(320u, 0x3E201403u)] + [InlineData(1280u, 0x3F201403u)] + public void ReadBitNormalRoundsLikeGMod(uint fraction, uint expectedBits) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteOneBit(0); + writer.WriteUBitLong(fraction, NORMAL_FRACTIONAL_BITS); + + float value = new bf_read(data, data.Length).ReadBitNormal(); + Assert.Equal(expectedBits, BitConverter.SingleToUInt32Bits(value)); + } + + [Fact] + public void WriteBitNormalKeepsThePositiveSignAtNegativeFloatResolution() { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitNormal(-(1f / NORMAL_DENOMINATOR)); + Assert.Equal("0200", Written(writer, data)); + } + + [Theory] + [InlineData(float.NaN, "FDFF01")] + [InlineData(float.NegativeInfinity, "FDFF01")] + [InlineData(3e9f, "F9FF01")] + public void WriteBitCoordEncodesOutOfRangeValuesLikeGMod(float value, string expected) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitCoord(value); + Assert.Equal(expected, Written(writer, data)); + } + + [Theory] + [InlineData(float.NaN, false, false, "FFFF07")] + [InlineData(float.NegativeInfinity, true, true, "FF3F")] + [InlineData(3e9f, false, true, "FBFF01")] + public void WriteBitCoordMPEncodesOutOfRangeValuesLikeGMod(float value, bool integral, bool lowPrecision, string expected) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitCoordMP(value, integral, lowPrecision); + Assert.Equal(expected, Written(writer, data)); + } + + [Theory] + [InlineData(float.NaN, "FF0F")] + [InlineData(float.NegativeInfinity, "FF0F")] + [InlineData(-1e10f, "FF0F")] + public void WriteBitNormalEncodesOutOfRangeValuesLikeGMod(float value, string expected) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitNormal(value); + Assert.Equal(expected, Written(writer, data)); + } + + [Fact] + public void WriteBitVec3CoordEncodesOutOfRangeValuesLikeGMod() { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitVec3Coord(new Vector3(float.NaN, float.NegativeInfinity, 1e10f)); + Assert.Equal(54, writer.BitsWritten); + Assert.Equal("EFFFDFFF3FFF3F", Written(writer, data)); + } + + [Theory] + [InlineData(-258.23f, 31, 607081600u)] + [InlineData(558.71f, 28, 148168928u)] + [InlineData(-604.07f, 21, 675341u)] + [InlineData(678.54f, 31, 0u)] + public void WriteBitAngleEncodesLikeGMod(float angle, int bits, uint expected) { + byte[] data = new byte[BufferBytes]; + bf_write writer = new(data, data.Length); + writer.WriteBitAngle(angle, bits); + Assert.Equal(expected, new bf_read(data, data.Length).ReadUBitLong(bits)); + } + + [Fact] + public void ReadBitsAlignsTheOutputBeforeReadingDwords() { + byte[] data = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0, 0, 0, 0]; + bf_read reader = new(data, 8); + reader.Seek(4); + byte[] output = new byte[12]; + Array.Fill(output, (byte)0xCC); + + reader.ReadBits(output.AsSpan(1), 72); + + Assert.True(reader.Overflowed); + Assert.Equal(64, reader.BitsRead); + Assert.Equal("CC213243546576870000CCCC", Convert.ToHexString(output)); + } +} diff --git a/Source.Common/Bitbuffers/bf_read.cs b/Source.Common/Bitbuffers/bf_read.cs index 85d8d58b..aafe2dd5 100644 --- a/Source.Common/Bitbuffers/bf_read.cs +++ b/Source.Common/Bitbuffers/bf_read.cs @@ -80,7 +80,7 @@ public bf_read(byte[]? pData, int bytes, int bits) { StartReading(pData, bytes, 0, bits); } - public bf_read(string name, byte[]? pData, int bytes, int bits = 0) { + public bf_read(string name, byte[]? pData, int bytes, int bits = -1) { debugName = name; StartReading(pData, bytes, 0, bits); } @@ -144,6 +144,12 @@ public void ReadBits(Span pOutData, int nBits) { byte* pOut = pOutRO; int nBitsLeft = nBits; + while (((nint)pOut & 3) != 0 && nBitsLeft >= 8) { + *pOut = (byte)ReadUBitLong(8); + ++pOut; + nBitsLeft -= 8; + } + // read dwords while (nBitsLeft >= 32) { *(uint*)pOut = ReadUBitLong(32); @@ -399,7 +405,7 @@ public uint ReadBitCoordBits() { if (flags == 0) return 0; - return ReadUBitLong(ReadBitCoordMP_numbits_table[flags - 1]) * 4 + flags; + return ReadUBitLong(ReadBitCoordBits_numbits_table[flags - 1]) * 4 + flags; } enum ReadBitCoordMPBitsFlags { INBOUNDS = 1, INTVAL = 2 }; @@ -468,7 +474,7 @@ public float ReadBitNormal() { uint fractval = ReadUBitLong(NORMAL_FRACTIONAL_BITS); // Calculate the correct floating point value - float value = fractval * NORMAL_RESOLUTION; + float value = (float)(fractval * NORMAL_RESOLUTION); // Fixup the sign if negative. if (signbit > 0) @@ -525,8 +531,8 @@ public long ReadLongLong() { // Read the two DWORDs according to network endian short endianIndex = 0x0100; byte* idx = (byte*)&endianIndex; - pLongs[*idx++] = ReadUBitLong(sizeof(long) << 3); - pLongs[*idx] = ReadUBitLong(sizeof(long) << 3); + pLongs[*idx++] = ReadUBitLong(sizeof(int) << 3); + pLongs[*idx] = ReadUBitLong(sizeof(int) << 3); return retval; } @@ -566,8 +572,10 @@ public unsafe bool ReadInto(out T output) where T : unmanaged { } public bool ReadString(out string? str, int maxLen, bool bLine = false) { - str = ReadString(maxLen, bLine); - return !Overflowed; + Span buffer = stackalloc byte[maxLen]; + bool fits = ReadString(buffer, bLine, out int chars); + str = Encoding.ASCII.GetString(buffer[..chars]); + return fits; } public bool ReadString(Span str, bool bLine, out int outNumChars) { Assert(!str.IsEmpty); @@ -576,7 +584,7 @@ public bool ReadString(Span str, bool bLine, out int outNumChars) { bool bTooSmall = false; int iChar = 0; while (true) { - char val = (char)ReadChar(); + char val = (char)ReadByte(); if (val == 0) break; else if (bLine && val == '\n') @@ -640,23 +648,22 @@ public string ReadAndAllocateString(out bool overflow){ public int ReadString(Span target, bool bLine = false) { if (target.Length < 1) return 0; Span data = stackalloc byte[target.Length]; - int bytes = ReadString(data, bLine); - int chars = Encoding.ASCII.GetChars(data[..bytes], target[..^1]); + ReadString(data, bLine, out int bytes); + int chars = Encoding.ASCII.GetChars(data[..bytes], target); target[chars] = '\0'; return chars; } public int ReadString(Span target, bool bLine = false) { Assert(target.Length != 0); int i = 0; - while (target.Length > 0 && !Overflowed) { + while (!Overflowed) { byte val = ReadByte(); if (val == 0) break; else if (bLine && val == '\n') break; - target[0] = val; - target = target[1..]; - i++; + if (i < target.Length) + target[i++] = val; } return i; } @@ -665,20 +672,8 @@ public int ReadString(Span target, bool bLine = false) { return string.Empty; Span writeTarget = stackalloc byte[maxLen]; - - int iChar = 0; - while (iChar < writeTarget.Length && !Overflowed) { - byte val = ReadByte(); - - if (val == 0) - break; - else if (bLine && val == '\n') - break; - - writeTarget[iChar++] = val; - } - - return Encoding.ASCII.GetString(writeTarget[..iChar]); + ReadString(writeTarget, bLine, out int chars); + return Encoding.ASCII.GetString(writeTarget[..chars]); } public void ExciseBits(int startbit, int bitstoremove) { diff --git a/Source.Common/Bitbuffers/bf_write.cs b/Source.Common/Bitbuffers/bf_write.cs index 4bb1bfce..183486db 100644 --- a/Source.Common/Bitbuffers/bf_write.cs +++ b/Source.Common/Bitbuffers/bf_write.cs @@ -1,5 +1,4 @@ -#define NEW_BITBUFFER - + using Source.Common.Mathematics; using System.Diagnostics; @@ -318,7 +317,7 @@ public void WriteBitLong(uint data, int numbits, bool signed) { public void WriteOneBitNoCheck(int bit) { fixed (byte* dataB = data) { uint* data = (uint*)dataB; - if (bit > 0) + if (bit != 0) data[curBit >> 5] |= g_LittleBits[curBit & 31]; else data[curBit >> 5] &= ~g_LittleBits[curBit & 31]; @@ -419,24 +418,24 @@ public bool WriteBitsFromBuffer(bf_read pIn, int nBits) { public void WriteBitAngle(float fAngle, int numbits) { int d; - int mask; - int shift; + uint mask; + uint shift; - shift = (int)BitForBitnum(numbits); + shift = BitForBitnum(numbits); mask = shift - 1; - d = (int)(fAngle / 360.0 * shift); - d &= mask; + d = MathLib.TruncateOrMinValue(fAngle * (1f / 360f) * shift); + d &= (int)mask; WriteUBitLong((uint)d, numbits); } public void WriteBitCoordMP(float f, bool bIntegral, bool bLowPrecision) { - int signbit = f <= -(bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION) ? 1 : 0; - int intval = (int)MathF.Abs(f); + int signbit = MathLib.NotGreater(f, -(bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION)) ? 1 : 0; + int intval = MathLib.TruncateOrMinValue(MathF.Abs(f)); int fractval = bLowPrecision ? - Math.Abs((int)(f * COORD_DENOMINATOR_LOWPRECISION)) & COORD_DENOMINATOR_LOWPRECISION - 1 : - Math.Abs((int)(f * COORD_DENOMINATOR)) & COORD_DENOMINATOR - 1; + MathLib.AbsWrapping(MathLib.TruncateOrMinValue(f * COORD_DENOMINATOR_LOWPRECISION)) & COORD_DENOMINATOR_LOWPRECISION - 1 : + MathLib.AbsWrapping(MathLib.TruncateOrMinValue(f * COORD_DENOMINATOR)) & COORD_DENOMINATOR - 1; int bInBounds = intval < 1 << (int)COORD_INTEGER_BITS_MP ? 1 : 0; @@ -444,7 +443,7 @@ public void WriteBitCoordMP(float f, bool bIntegral, bool bLowPrecision) { if (bIntegral) { // Integer encoding: in-bounds bit, nonzero bit, optional sign bit + integer value bits - if (intval > 0) { + if (intval != 0) { // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] --intval; bits = (uint)(intval * 8 + signbit * 4 + 2 + bInBounds); @@ -457,7 +456,7 @@ public void WriteBitCoordMP(float f, bool bIntegral, bool bLowPrecision) { } else { // Float encoding: in-bounds bit, integer bit, sign bit, fraction value bits, optional integer value bits - if (intval > 0) { + if (intval != 0) { // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] --intval; bits = (uint)(intval * 8 + signbit * 4 + 2 + bInBounds); @@ -475,28 +474,28 @@ public void WriteBitCoordMP(float f, bool bIntegral, bool bLowPrecision) { } public void WriteBitCoord(float f) { - int signbit = f <= -COORD_RESOLUTION ? 1 : 0; - int intval = (int)Math.Abs(f); - int fractval = Math.Abs((int)(f * COORD_DENOMINATOR)) & COORD_DENOMINATOR - 1; + int signbit = MathLib.NotGreater(f, -COORD_RESOLUTION) ? 1 : 0; + int intval = MathLib.TruncateOrMinValue(MathF.Abs(f)); + int fractval = MathLib.AbsWrapping(MathLib.TruncateOrMinValue(f * COORD_DENOMINATOR)) & COORD_DENOMINATOR - 1; // Send the bit flags that indicate whether we have an integer part and/or a fraction part. WriteOneBit(intval); WriteOneBit(fractval); - if (intval > 0 || fractval > 0) { + if (intval != 0 || fractval != 0) { // Send the sign bit WriteOneBit(signbit); // Send the integer if we have one. - if (intval > 0) { + if (intval != 0) { // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] intval--; WriteUBitLong((uint)intval, (int)COORD_INTEGER_BITS); } // Send the fraction if we have one - if (fractval > 0) { + if (fractval != 0) { WriteUBitLong((uint)fractval, (int)COORD_FRACTIONAL_BITS); } } @@ -505,9 +504,9 @@ public void WriteBitCoord(float f) { public void WriteBitVec3Coord(Vector3 fa) { int xflag, yflag, zflag; - xflag = fa[0] >= COORD_RESOLUTION || fa[0] <= -COORD_RESOLUTION ? 1 : 0; - yflag = fa[1] >= COORD_RESOLUTION || fa[1] <= -COORD_RESOLUTION ? 1 : 0; - zflag = fa[2] >= COORD_RESOLUTION || fa[2] <= -COORD_RESOLUTION ? 1 : 0; + xflag = MathLib.NotLess(fa[0], COORD_RESOLUTION) || MathLib.NotGreater(fa[0], -COORD_RESOLUTION) ? 1 : 0; + yflag = MathLib.NotLess(fa[1], COORD_RESOLUTION) || MathLib.NotGreater(fa[1], -COORD_RESOLUTION) ? 1 : 0; + zflag = MathLib.NotLess(fa[2], COORD_RESOLUTION) || MathLib.NotGreater(fa[2], -COORD_RESOLUTION) ? 1 : 0; WriteOneBit(xflag); WriteOneBit(yflag); @@ -522,10 +521,10 @@ public void WriteBitVec3Coord(Vector3 fa) { } public void WriteBitNormal(float f) { - int signbit = f <= -NORMAL_RESOLUTION ? 1 : 0; + int signbit = MathLib.NotGreater(f, -NORMAL_RESOLUTION) ? 1 : 0; // NOTE: Since +/-1 are valid values for a normal, I'm going to encode that as all ones - uint fractval = (uint)Math.Abs((int)(f * NORMAL_DENOMINATOR)); + uint fractval = (uint)MathLib.AbsWrapping(MathLib.TruncateOrMinValue(f * NORMAL_DENOMINATOR)); // clamp.. if (fractval > NORMAL_DENOMINATOR) @@ -632,11 +631,9 @@ public bool WriteString(ReadOnlySpan str, bool nullTerminate = true, int l if (!str.IsEmpty) { var i = 0; - if (str.Length != 0) { - do { - WriteChar(str[i]); - i++; - } while (i < str.Length && str[i] != '\0'); // Stop at any null terminator + while (i < str.Length && str[i] != '\0') { // Stop at any null terminator + WriteByte((byte)str[i]); + i++; } if (nullTerminate) WriteChar(0); // null terminate @@ -646,7 +643,7 @@ public bool WriteString(ReadOnlySpan str, bool nullTerminate = true, int l WriteChar(0); } - return Overflowed; + return !Overflowed; } public void WriteOneBit(int bit) { @@ -666,7 +663,7 @@ public void WriteOneBitAt(int bit, int value) { fixed (byte* dataB = data) { uint* data = (uint*)dataB; - if (value > 0) + if (value != 0) data[bit >> 5] |= g_LittleBits[bit & 31]; else data[bit >> 5] &= ~g_LittleBits[bit & 31]; diff --git a/Source.Common/CoordSize.cs b/Source.Common/CoordSize.cs index baf29458..0dfc16e3 100644 --- a/Source.Common/CoordSize.cs +++ b/Source.Common/CoordSize.cs @@ -20,7 +20,7 @@ public static class CoordSize public const int NORMAL_FRACTIONAL_BITS = 11; public const int NORMAL_DENOMINATOR = (1 << NORMAL_FRACTIONAL_BITS) - 1; - public const float NORMAL_RESOLUTION = 1.0f / NORMAL_DENOMINATOR; + public const double NORMAL_RESOLUTION = 1.0 / NORMAL_DENOMINATOR; /// /// This is limited by the network fractional bits used for coords, because net coords will be only be accurate to 5 bits fractional. diff --git a/Source.Common/Mathematics/MathLib.cs b/Source.Common/Mathematics/MathLib.cs index 773ab692..35267d4a 100644 --- a/Source.Common/Mathematics/MathLib.cs +++ b/Source.Common/Mathematics/MathLib.cs @@ -366,6 +366,18 @@ public static void Init(float gamma, float texGamma, float brightness, int overb BuildGammaTable(gamma, texGamma, brightness, overbright); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TruncateOrMinValue(float f) => f >= -2147483648f && f < 2147483648f ? (int)f : int.MinValue; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int AbsWrapping(int n) => n < 0 ? unchecked(-n) : n; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool NotGreater(float a, double b) => !(a > b); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool NotLess(float a, double b) => !(a < b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SimpleSplineRemapValClamped(float val, float A, float B, float C, float D) { if (A == B)