Skip to content

Commit

Permalink
Merge pull request #2 from B3zaleel/parse-header
Browse files Browse the repository at this point in the history
Implement parsing of the Draco header
  • Loading branch information
B3zaleel authored Jul 14, 2024
2 parents 43c10bc + b6960a6 commit c31c3e9
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 0 deletions.
53 changes: 53 additions & 0 deletions src/Draco/IO/BitUtilities.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions src/Draco/IO/Constants.cs
Original file line number Diff line number Diff line change
@@ -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);
}
155 changes: 155 additions & 0 deletions src/Draco/IO/DecoderBuffer.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
46 changes: 46 additions & 0 deletions src/Draco/IO/DracoDecoder.cs
Original file line number Diff line number Diff line change
@@ -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
);
}
}
11 changes: 11 additions & 0 deletions src/Draco/IO/DracoHeader.cs
Original file line number Diff line number Diff line change
@@ -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;
}
25 changes: 25 additions & 0 deletions src/Draco/IO/Extensions/Assertions.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

0 comments on commit c31c3e9

Please sign in to comment.