diff --git a/src/Draco/IO/BitUtilities.cs b/src/Draco/IO/BitUtilities.cs new file mode 100644 index 0000000..429882f --- /dev/null +++ b/src/Draco/IO/BitUtilities.cs @@ -0,0 +1,53 @@ +namespace Draco.IO; + +internal class BitUtilities +{ + public static uint ReverseBits32(uint n) + { + n = ((n >> 1) & 0x55555555) | ((n & 0x55555555) << 1); + n = ((n >> 2) & 0x33333333) | ((n & 0x33333333) << 2); + n = ((n >> 4) & 0x0F0F0F0F) | ((n & 0x0F0F0F0F) << 4); + n = ((n >> 8) & 0x00FF00FF) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); + } + + public static ulong ConvertSignedIntToSymbol(long val) + { + if (val >= 0) + { + return (uint)(val << 1); + } + return ((ulong)(-val - 1) << 1) | 1; + } + + public static uint ConvertSignedIntToSymbol(int val) + { + if (val >= 0) + { + return (uint)(val << 1); + } + return ((uint)(-val - 1) << 1) | 1; + } + + public static long ConvertSymbolToSignedInt(ulong val) + { + var is_positive = (val & 1) != 0; + val >>= 1; + if (is_positive) + { + return (long)val; + } + return -(long)val - 1; + } + + public static int ConvertSymbolToSignedInt(uint val) + { + var is_positive = (val & 1) != 0; + val >>= 1; + if (is_positive) + { + return (int)val; + } + return -(int)val - 1; + } +} diff --git a/src/Draco/IO/Constants.cs b/src/Draco/IO/Constants.cs new file mode 100644 index 0000000..9541224 --- /dev/null +++ b/src/Draco/IO/Constants.cs @@ -0,0 +1,10 @@ +namespace Draco.IO; + +internal static class Constants +{ + public const string DracoMagic = "DRACO"; + public const byte MajorVersion = 2; + public const byte MinorVersion = 2; + + public static ushort BitStreamVersion(byte majorVersion, byte minorVersion) => (ushort)((majorVersion << 8) | minorVersion); +} diff --git a/src/Draco/IO/DecoderBuffer.cs b/src/Draco/IO/DecoderBuffer.cs new file mode 100644 index 0000000..1ab60ad --- /dev/null +++ b/src/Draco/IO/DecoderBuffer.cs @@ -0,0 +1,155 @@ +using System.Text; +using Draco.IO.Extensions; + +namespace Draco.IO; + +internal sealed class DecoderBuffer : IDisposable +{ + private bool _bitMode = false; + private byte _bitBuffer = 0; + private byte _bitBufferIndex = 0; + private readonly BinaryReader _binaryReader; + + public ushort BitStream_Version { get; set; } + + public DecoderBuffer(BinaryReader binaryReader) + { + _binaryReader = binaryReader; + } + + public DecoderBuffer(byte[] data, ushort bitStreamVersion) + { + _binaryReader = new BinaryReader(new MemoryStream(data)); + BitStream_Version = bitStreamVersion; + } + + public ulong DecodeVarIntUnsigned() + { + ulong result = 0; + byte shift = 0; + byte b; + while (true) + { + b = _binaryReader.ReadByte(); + result |= (ulong)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + { + break; + } + shift += 7; + } + return result; + } + + public long DecodeVarInt() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + var symbol = DecodeVarIntUnsigned(); + return BitUtilities.ConvertSymbolToSignedInt(symbol); + } + + public byte ReadByte() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadByte(); + } + + public ushort ReadUInt16() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadUInt16(); + } + + public uint ReadUInt32() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadUInt32(); + } + + public ulong ReadUInt64() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadUInt64(); + } + + public sbyte ReadSByte() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadSByte(); + } + + public int ReadInt32() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadInt32(); + } + + public float ReadSingle() + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadSingle(); + } + + public string ReadASCIIBytes(int count) + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return Encoding.ASCII.GetString(_binaryReader.ReadBytes(count)); + } + + public byte[] ReadBytes(int count) + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + return _binaryReader.ReadBytes(count); + } + + public sbyte[] ReadSBytes(int count) + { + Assertions.ThrowIf(_bitMode, "Cannot execute this whilst bit mode is on"); + sbyte[] values = new sbyte[count]; + for (int i = 0; i < count; i++) + { + values[i] = _binaryReader.ReadSByte(); + } + return values; + } + + public uint DecodeLeastSignificantBits32(byte count) + { + Assertions.ThrowIfNot(_bitMode, "Cannot execute this whilst bit mode is not on"); + uint value = 0; + + for (byte i = 0; i < count; i++) + { + if (_bitBufferIndex >= 8) + { + _bitBuffer = _binaryReader.ReadByte(); + _bitBufferIndex = 0; + } + value |= (byte)(((_bitBuffer >> _bitBufferIndex) & 1) << i); + _bitBufferIndex++; + } + return value; + } + + public void StartBitDecoding(bool decodeSize, out ulong size) + { + size = 0; + if (decodeSize) + { + size = BitStream_Version < Constants.BitStreamVersion(2, 2) ? _binaryReader.ReadUInt32() : DecodeVarIntUnsigned(); + } + _bitMode = true; + _bitBuffer = _binaryReader.ReadByte(); + _bitBufferIndex = 0; + } + + public void EndBitDecoding() + { + _bitMode = false; + } + + public void Dispose() + { + _binaryReader.Dispose(); + } +} diff --git a/src/Draco/IO/DracoDecoder.cs b/src/Draco/IO/DracoDecoder.cs new file mode 100644 index 0000000..8b1ced1 --- /dev/null +++ b/src/Draco/IO/DracoDecoder.cs @@ -0,0 +1,46 @@ +namespace Draco.IO; + +public class DracoDecoder +{ + public DracoHeader? Header { get; private set; } + + public void Decode(string path) + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); + Decode(new BinaryReader(fs)); + } + + public void Decode(Stream stream) + { + Decode(new BinaryReader(stream)); + } + + public void Decode(BinaryReader binaryReader) + { + using var buffer = new DecoderBuffer(binaryReader); + Header = ParseHeader(buffer); + buffer.BitStream_Version = Header.Version; + } + + private static DracoHeader ParseHeader(DecoderBuffer buffer) + { + var dracoMagic = buffer.ReadASCIIBytes(Constants.DracoMagic.Length); + if (dracoMagic != Constants.DracoMagic) + { + throw new InvalidDataException("Invalid Draco file."); + } + var majorVersion = buffer.ReadByte(); + var minorVersion = buffer.ReadByte(); + var encoderType = buffer.ReadByte(); + var encoderMethod = buffer.ReadByte(); + var flags = buffer.ReadUInt16(); + + return new DracoHeader( + majorVersion: majorVersion, + minorVersion: minorVersion, + encoderType: encoderType, + encoderMethod: encoderMethod, + flags: flags + ); + } +} diff --git a/src/Draco/IO/DracoHeader.cs b/src/Draco/IO/DracoHeader.cs new file mode 100644 index 0000000..e72f67e --- /dev/null +++ b/src/Draco/IO/DracoHeader.cs @@ -0,0 +1,11 @@ +namespace Draco.IO; + +public class DracoHeader(byte majorVersion, byte minorVersion, byte encoderType, byte encoderMethod, ushort flags) +{ + public byte MajorVersion { get; } = majorVersion; + public byte MinorVersion { get; } = minorVersion; + public ushort Version { get; } = Constants.BitStreamVersion(majorVersion, minorVersion); + public byte EncoderType { get; } = encoderType; + public byte EncoderMethod { get; } = encoderMethod; + public ushort Flags { get; } = flags; +} diff --git a/src/Draco/IO/Extensions/Assertions.cs b/src/Draco/IO/Extensions/Assertions.cs new file mode 100644 index 0000000..3a8c316 --- /dev/null +++ b/src/Draco/IO/Extensions/Assertions.cs @@ -0,0 +1,25 @@ +namespace Draco.IO.Extensions; + +internal static class Assertions +{ + public static void Throw(string reason = "") + { + throw new InvalidDataException(reason); + } + + public static void ThrowIf(bool condition, string reason = "") + { + if (condition) + { + throw new InvalidDataException(reason); + } + } + + public static void ThrowIfNot(bool condition, string reason = "") + { + if (!condition) + { + throw new InvalidDataException(reason); + } + } +}