Skip to content

Instantly share code, notes, and snippets.

@fabiodr
Forked from gafferongames/delta_compression.cpp
Created June 3, 2022 19:04
Show Gist options
  • Save fabiodr/93f2d1683c107db2a702b2239583bfda to your computer and use it in GitHub Desktop.
Save fabiodr/93f2d1683c107db2a702b2239583bfda to your computer and use it in GitHub Desktop.

Revisions

  1. @gafferongames gafferongames revised this gist Jun 12, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -395,7 +395,7 @@ class BitReader

    if ( m_bitsRead + bytes * 8 >= m_numBits )
    {
    memset( data, bytes, 0 );
    memset( data, 0, bytes );
    m_overflow = true;
    return;
    }
  2. @gafferongames gafferongames revised this gist Mar 19, 2015. 1 changed file with 54 additions and 74 deletions.
    128 changes: 54 additions & 74 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -222,8 +222,6 @@ class BitWriter
    return;
    }

    // head

    assert( m_bitIndex == 0 || m_bitIndex == 8 || m_bitIndex == 16 || m_bitIndex == 24 );

    int headBytes = ( 4 - m_bitIndex / 8 ) % 4;
    @@ -236,8 +234,6 @@ class BitWriter

    assert( GetAlignBits() == 0 );

    // words

    int numWords = ( bytes - headBytes ) / 4;
    if ( numWords > 0 )
    {
    @@ -250,8 +246,6 @@ class BitWriter

    assert( GetAlignBits() == 0 );

    // tail

    int tailStart = headBytes + numWords * 4;
    int tailBytes = bytes - tailStart;
    assert( tailBytes >= 0 && tailBytes < 4 );
    @@ -406,8 +400,6 @@ class BitReader
    return;
    }

    // head

    assert( m_bitIndex == 0 || m_bitIndex == 8 || m_bitIndex == 16 || m_bitIndex == 24 );

    int headBytes = ( 4 - m_bitIndex / 8 ) % 4;
    @@ -420,8 +412,6 @@ class BitReader

    assert( GetAlignBits() == 0 );

    // words

    int numWords = ( bytes - headBytes ) / 4;
    if ( numWords > 0 )
    {
    @@ -434,8 +424,6 @@ class BitReader

    assert( GetAlignBits() == 0 );

    // tail

    int tailStart = headBytes + numWords * 4;
    int tailBytes = bytes - tailStart;
    assert( tailBytes >= 0 && tailBytes < 4 );
    @@ -1448,41 +1436,44 @@ struct QuantizedSnapshot
    }
    };

    int signed_to_unsigned( int signed_value )
    inline int signed_to_unsigned( int n )
    {
    if ( signed_value >= 0 )
    return signed_value * 2;
    else
    return abs( signed_value * 2 ) - 1;
    return ( n << 1 ) ^ ( n >> 31 );
    }

    int unsigned_to_signed( uint32_t unsigned_value )
    inline int unsigned_to_signed( uint32_t n )
    {
    if ( unsigned_value % 2 )
    return - ( ( unsigned_value + 1 ) / 2 );
    else
    return unsigned_value / 2;
    return ( n >> 1 ) ^ ( -( n & 1 ) );
    }

    template <typename Stream> void serialize_relative_position_component( Stream & stream, uint32_t & component )
    template <typename Stream> void serialize_unsigned_range( Stream & stream, uint32_t & value, int num_ranges, const int * range_bits )
    {
    bool a = component <= 15;
    serialize_bool( stream, a );
    if ( a )
    {
    serialize_int( stream, component, 0, 15 );
    return;
    }
    assert( num_ranges > 0 );

    bool b = component <= 16 + 63;
    serialize_bool( stream, b );
    if ( b )
    int range_min = 0;

    for ( int i = 0; i < num_ranges - 1; ++i )
    {
    serialize_int( stream, component, 16, 16 + 63 );
    return;
    const int range_max = range_min + ( ( 1 << range_bits[i] ) - 1 );
    bool in_range = Stream::IsWriting && value <= range_max;
    serialize_bool( stream, in_range );
    if ( in_range )
    {
    serialize_int( stream, value, range_min, range_max );
    return;
    }
    range_min += ( 1 << range_bits[i] );
    }

    serialize_int( stream, component, 16 + 64, 16 + 64 + 255 );
    serialize_int( stream, value, range_min, range_min + ( ( 1 << range_bits[num_ranges-1] ) - 1 ) );
    }

    inline int unsigned_range_limit( int num_ranges, const int * range_bits )
    {
    int range_limit = 0;
    for ( int i = 0; i < num_ranges; ++i )
    range_limit += ( 1 << range_bits[i] );
    return range_limit;
    }

    template <typename Stream> void serialize_relative_position( Stream & stream,
    @@ -1497,8 +1488,13 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    bool too_large;
    uint32_t dx,dy,dz;

    const int range_bits[] = { 5, 6, 7 };
    const int num_ranges = sizeof( range_bits ) / sizeof( int );

    const int small_limit = 15;
    const int large_limit = 16 + 64 + 256;
    const int large_limit = unsigned_range_limit( num_ranges, range_bits );

    const int max_delta = 2047;

    if ( Stream::IsWriting )
    {
    @@ -1523,15 +1519,15 @@ template <typename Stream> void serialize_relative_position( Stream & stream,

    if ( !too_large )
    {
    serialize_relative_position_component( stream, dx );
    serialize_relative_position_component( stream, dy );
    serialize_relative_position_component( stream, dz );
    serialize_unsigned_range( stream, dx, num_ranges, range_bits );
    serialize_unsigned_range( stream, dy, num_ranges, range_bits );
    serialize_unsigned_range( stream, dz, num_ranges, range_bits );
    }
    else
    {
    serialize_int( stream, dx, 0, 2047 );
    serialize_int( stream, dy, 0, 2047 );
    serialize_int( stream, dz, 0, 2047 );
    serialize_int( stream, dx, 0, max_delta );
    serialize_int( stream, dy, 0, max_delta );
    serialize_int( stream, dz, 0, max_delta );
    }
    }

    @@ -1547,33 +1543,15 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    }
    }

    template <typename Stream> void serialize_relative_orientation_component( Stream & stream, uint32_t & component )
    {
    bool a = component <= 15;
    serialize_bool( stream, a );
    if ( a )
    {
    serialize_int( stream, component, 0, 15 );
    return;
    }

    bool b = component <= 16 + 31;
    serialize_bool( stream, b );
    if ( b )
    {
    serialize_int( stream, component, 16, 16 + 31 );
    return;
    }

    serialize_int( stream, component, 16 + 32, 16 + 32 + 127 );
    }

    template <typename Stream> void serialize_relative_orientation( Stream & stream,
    compressed_quaternion<OrientationBits> & orientation,
    const compressed_quaternion<OrientationBits> & base_orientation )
    {
    const int range_bits[] = { 4, 5, 7 };
    const int num_ranges = sizeof( range_bits ) / sizeof( int );

    const int small_limit = 3;
    const int large_limit = 16 + 32 + 127;
    const int large_limit = unsigned_range_limit( num_ranges, range_bits );

    uint32_t da,db,dc;
    bool all_small = false;
    @@ -1584,7 +1562,9 @@ template <typename Stream> void serialize_relative_orientation( Stream & stream,
    da = signed_to_unsigned( orientation.integer_a - base_orientation.integer_a );
    db = signed_to_unsigned( orientation.integer_b - base_orientation.integer_b );
    dc = signed_to_unsigned( orientation.integer_c - base_orientation.integer_c );

    all_small = da <= small_limit && db <= small_limit && dc <= small_limit;

    relative_orientation = da < large_limit && db < large_limit && dc < large_limit;
    }

    @@ -1602,9 +1582,9 @@ template <typename Stream> void serialize_relative_orientation( Stream & stream,
    }
    else
    {
    serialize_relative_orientation_component( stream, da );
    serialize_relative_orientation_component( stream, db );
    serialize_relative_orientation_component( stream, dc );
    serialize_unsigned_range( stream, da, num_ranges, range_bits );
    serialize_unsigned_range( stream, db, num_ranges, range_bits );
    serialize_unsigned_range( stream, dc, num_ranges, range_bits );
    }

    if ( Stream::IsReading )
    @@ -1946,7 +1926,7 @@ int main( int argc, char ** argv )
    printf( "average bytes per-packet: %f\n", total_bytes / double(num_packets) );
    printf( "average bytes per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 );
    printf( "average kilobits per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 / 1000.0 );
    printf( "compression ratio: %.2f%% of original size\n", total_bytes / ( num_packets * ( ( 4 * 32 + 3 * 32 + 1 ) * NumCubes ) / 8.0 ) * 100.0 );
    printf( "compression ratio: %.2f%% of original size\n", total_bytes / ( num_packets * ( 4 + 3 + 3 ) * 32 * NumCubes / 8.0 ) * 100.0 );

    // clean up everything

    @@ -1962,9 +1942,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1510957
    average bytes per-packet: 533.718474
    average bytes per-second: 256184.867538
    average kilobits per-second: 256.184868
    compression ratio: 2.11% of original size
    total packet bytes: 1505956
    average bytes per-packet: 531.951960
    average bytes per-second: 255336.941010
    average kilobits per-second: 255.336941
    compression ratio: 1.48% of original size
    */
  3. @gafferongames gafferongames revised this gist Mar 18, 2015. 1 changed file with 17 additions and 6 deletions.
    23 changes: 17 additions & 6 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1638,7 +1638,18 @@ template <typename Stream> void serialize_cube_relative_to_base( Stream & stream

    if ( position_changed )
    {
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x + base_dx, base.position_y + base_dy, max( base.position_z + base_dz, 105 ) );
    const int gravity = 3;
    const int ground_limit = 105;

    const int drag_x = - ceil( base_dx * 0.062f );
    const int drag_y = - ceil( base_dy * 0.062f );
    const int drag_z = - ceil( base_dz * 0.062f );

    const int position_estimate_x = base.position_x + base_dx + drag_x;
    const int position_estimate_y = base.position_y + base_dy + drag_y;
    const int position_estimate_z = max( base.position_z + base_dz - gravity + drag_z, ground_limit );

    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, position_estimate_x, position_estimate_y, position_estimate_z );
    }
    else if ( Stream::IsReading )
    {
    @@ -1951,9 +1962,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1538037
    average bytes per-packet: 543.283999
    average bytes per-second: 260776.319322
    average kilobits per-second: 260.776319
    compression ratio: 2.14% of original size
    total packet bytes: 1510957
    average bytes per-packet: 533.718474
    average bytes per-second: 256184.867538
    average kilobits per-second: 256.184868
    compression ratio: 2.11% of original size
    */
  4. @gafferongames gafferongames revised this gist Mar 18, 2015. 1 changed file with 9 additions and 9 deletions.
    18 changes: 9 additions & 9 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1638,7 +1638,7 @@ template <typename Stream> void serialize_cube_relative_to_base( Stream & stream

    if ( position_changed )
    {
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x + base_dx, base.position_y + base_dy, base.position_z + base_dz );
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x + base_dx, base.position_y + base_dy, max( base.position_z + base_dz, 105 ) );
    }
    else if ( Stream::IsReading )
    {
    @@ -1661,9 +1661,9 @@ void calculate_compression_state( CompressionState & compression_state, Quantize
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    compression_state.delta_x[i] = ( current_snapshot.cubes[i].position_x - baseline_snapshot.cubes[i].position_x ) / 6.0;
    compression_state.delta_y[i] = ( current_snapshot.cubes[i].position_y - baseline_snapshot.cubes[i].position_y ) / 6.0;
    compression_state.delta_z[i] = ( current_snapshot.cubes[i].position_z - baseline_snapshot.cubes[i].position_z ) / 6.0;
    compression_state.delta_x[i] = current_snapshot.cubes[i].position_x - baseline_snapshot.cubes[i].position_x;
    compression_state.delta_y[i] = current_snapshot.cubes[i].position_y - baseline_snapshot.cubes[i].position_y;
    compression_state.delta_z[i] = current_snapshot.cubes[i].position_z - baseline_snapshot.cubes[i].position_z;
    }
    }

    @@ -1951,9 +1951,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1581540
    average bytes per-packet: 558.650653
    average bytes per-second: 268152.313670
    average kilobits per-second: 268.152314
    compression ratio: 2.20% of original size
    total packet bytes: 1538037
    average bytes per-packet: 543.283999
    average bytes per-second: 260776.319322
    average kilobits per-second: 260.776319
    compression ratio: 2.14% of original size
    */
  5. @gafferongames gafferongames revised this gist Mar 18, 2015. 1 changed file with 137 additions and 255 deletions.
    392 changes: 137 additions & 255 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1435,10 +1435,6 @@ struct QuantizedSnapshot
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    if ( cubes[i] != other.cubes[i] )
    {
    printf( "mismatch cube %d\n", i );
    }
    if ( cubes[i] != other.cubes[i] )
    return false;
    }
    @@ -1452,73 +1448,41 @@ struct QuantizedSnapshot
    }
    };

    template <typename Stream> void serialize_cube_changed( Stream & stream, QuantizedCubeState & cube, const QuantizedCubeState & base )
    int signed_to_unsigned( int signed_value )
    {
    serialize_bool( stream, cube.interacting );

    bool position_changed;
    bool orientation_changed;

    if ( Stream::IsWriting )
    {
    position_changed = cube.position_x != base.position_x || cube.position_y != base.position_y || cube.position_z != base.position_z;
    orientation_changed = cube.orientation != base.orientation;
    }

    serialize_bool( stream, position_changed );
    serialize_bool( stream, orientation_changed );

    if ( position_changed )
    {
    serialize_int( stream, cube.position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, cube.position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, cube.position_z, 0, +QuantizedPositionBoundZ - 1 );
    }
    if ( signed_value >= 0 )
    return signed_value * 2;
    else
    {
    cube.position_x = base.position_x;
    cube.position_y = base.position_y;
    cube.position_z = base.position_z;
    }
    return abs( signed_value * 2 ) - 1;
    }

    if ( orientation_changed )
    serialize_object( stream, cube.orientation );
    int unsigned_to_signed( uint32_t unsigned_value )
    {
    if ( unsigned_value % 2 )
    return - ( ( unsigned_value + 1 ) / 2 );
    else
    cube.orientation = base.orientation;
    return unsigned_value / 2;
    }

    template <typename Stream> void serialize_offset( Stream & stream, int & offset, int small_bound, int large_bound )
    template <typename Stream> void serialize_relative_position_component( Stream & stream, uint32_t & component )
    {
    if ( Stream::IsWriting )
    bool a = component <= 15;
    serialize_bool( stream, a );
    if ( a )
    {
    assert( offset >= small_bound - 1 || offset <= - small_bound );

    if ( offset > 0 )
    {
    offset -= small_bound - 1;
    }
    else
    {
    offset += small_bound - 1;
    assert( offset < 0 ); // note: otherwise two offset values end up sharing the zero value
    }

    assert( offset >= -large_bound );
    assert( offset <= +large_bound - 1 );
    serialize_int( stream, component, 0, 15 );
    return;
    }

    serialize_int( stream,
    offset,
    -large_bound,
    large_bound - 1 );

    if ( Stream::IsReading )
    bool b = component <= 16 + 63;
    serialize_bool( stream, b );
    if ( b )
    {
    if ( offset >= 0 )
    offset += small_bound - 1;
    else
    offset -= small_bound - 1;
    serialize_int( stream, component, 16, 16 + 63 );
    return;
    }

    serialize_int( stream, component, 16 + 64, 16 + 64 + 255 );
    }

    template <typename Stream> void serialize_relative_position( Stream & stream,
    @@ -1529,232 +1493,152 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    int base_position_y,
    int base_position_z )
    {
    const int RelativePositionBound_Small = 64;
    const int RelativePositionBound_Large = 512;
    bool all_small;
    bool too_large;
    uint32_t dx,dy,dz;

    bool relative_position = false;
    bool relative_position_small_x = false;
    bool relative_position_small_y = false;
    bool relative_position_small_z = false;
    const int small_limit = 15;
    const int large_limit = 16 + 64 + 256;

    if ( Stream::IsWriting )
    {
    const int dx = position_x - base_position_x;
    const int dy = position_y - base_position_y;
    const int dz = position_z - base_position_z;

    const int relative_min = -RelativePositionBound_Large - ( RelativePositionBound_Small - 1 );
    const int relative_max = RelativePositionBound_Large - 1 + ( RelativePositionBound_Small - 1 );

    relative_position = dx >= relative_min && dx <= relative_max &&
    dy >= relative_min && dy <= relative_max &&
    dz >= relative_min && dz <= relative_max;

    if ( relative_position )
    {
    relative_position_small_x = dx >= -RelativePositionBound_Small && dx < RelativePositionBound_Small;
    relative_position_small_y = dy >= -RelativePositionBound_Small && dy < RelativePositionBound_Small;
    relative_position_small_z = dz >= -RelativePositionBound_Small && dz < RelativePositionBound_Small;
    }
    dx = signed_to_unsigned( position_x - base_position_x );
    dy = signed_to_unsigned( position_y - base_position_y );
    dz = signed_to_unsigned( position_z - base_position_z );
    all_small = dx <= small_limit && dy <= small_limit && dz <= small_limit;
    too_large = dx >= large_limit || dy >= large_limit || dz >= large_limit;
    }

    bool all_small = Stream::IsWriting && relative_position_small_x && relative_position_small_y && relative_position_small_z;

    serialize_bool( stream, all_small );

    if ( all_small )
    {
    int offset_x, offset_y, offset_z;

    if ( Stream::IsWriting )
    {
    offset_x = position_x - base_position_x;
    offset_y = position_y - base_position_y;
    offset_z = position_z - base_position_z;
    }

    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );

    if ( Stream::IsReading )
    {
    position_x = base_position_x + offset_x;
    position_y = base_position_y + offset_y;
    position_z = base_position_z + offset_z;
    }
    serialize_int( stream, dx, 0, small_limit );
    serialize_int( stream, dy, 0, small_limit );
    serialize_int( stream, dz, 0, small_limit );
    }
    else
    {
    serialize_bool( stream, relative_position );
    serialize_bool( stream, too_large );

    if ( relative_position )
    if ( !too_large )
    {
    serialize_bool( stream, relative_position_small_x );
    serialize_bool( stream, relative_position_small_y );
    serialize_bool( stream, relative_position_small_z );

    int offset_x, offset_y, offset_z;

    if ( Stream::IsWriting )
    {
    offset_x = position_x - base_position_x;
    offset_y = position_y - base_position_y;
    offset_z = position_z - base_position_z;
    }

    if ( relative_position_small_x )
    {
    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_x, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_y )
    {
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_y, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_z )
    {
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_z, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( Stream::IsReading )
    {
    position_x = base_position_x + offset_x;
    position_y = base_position_y + offset_y;
    position_z = base_position_z + offset_z;
    }
    serialize_relative_position_component( stream, dx );
    serialize_relative_position_component( stream, dy );
    serialize_relative_position_component( stream, dz );
    }
    else
    {
    serialize_int( stream, position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_z, 0, +QuantizedPositionBoundZ - 1 );
    serialize_int( stream, dx, 0, 2047 );
    serialize_int( stream, dy, 0, 2047 );
    serialize_int( stream, dz, 0, 2047 );
    }
    }

    if ( Stream::IsReading )
    {
    int signed_dx = unsigned_to_signed( dx );
    int signed_dy = unsigned_to_signed( dy );
    int signed_dz = unsigned_to_signed( dz );

    position_x = base_position_x + signed_dx;
    position_y = base_position_y + signed_dy;
    position_z = base_position_z + signed_dz;
    }
    }

    template <typename Stream> void serialize_relative_orientation( Stream & stream, compressed_quaternion<OrientationBits> & orientation, const compressed_quaternion<OrientationBits> & base_orientation )
    template <typename Stream> void serialize_relative_orientation_component( Stream & stream, uint32_t & component )
    {
    const int RelativeOrientationBound_Small = 4;
    const int RelativeOrientationBound_Large = 64;

    bool relative_orientation = false;
    bool small_a = false;
    bool small_b = false;
    bool small_c = false;
    bool a = component <= 15;
    serialize_bool( stream, a );
    if ( a )
    {
    serialize_int( stream, component, 0, 15 );
    return;
    }

    if ( Stream::IsWriting )
    bool b = component <= 16 + 31;
    serialize_bool( stream, b );
    if ( b )
    {
    const int da = orientation.integer_a - base_orientation.integer_a;
    const int db = orientation.integer_b - base_orientation.integer_b;
    const int dc = orientation.integer_c - base_orientation.integer_c;
    serialize_int( stream, component, 16, 16 + 31 );
    return;
    }

    const int relative_min = -RelativeOrientationBound_Large - ( RelativeOrientationBound_Small - 1 );
    const int relative_max = RelativeOrientationBound_Large - 1 + ( RelativeOrientationBound_Small - 1 );
    serialize_int( stream, component, 16 + 32, 16 + 32 + 127 );
    }

    if ( orientation.largest == base_orientation.largest &&
    da >= relative_min && da < relative_max &&
    db >= relative_min && db < relative_max &&
    dc >= relative_min && dc < relative_max )
    {
    relative_orientation = true;
    template <typename Stream> void serialize_relative_orientation( Stream & stream,
    compressed_quaternion<OrientationBits> & orientation,
    const compressed_quaternion<OrientationBits> & base_orientation )
    {
    const int small_limit = 3;
    const int large_limit = 16 + 32 + 127;

    small_a = da >= -RelativeOrientationBound_Small && da < RelativeOrientationBound_Small;
    small_b = db >= -RelativeOrientationBound_Small && db < RelativeOrientationBound_Small;
    small_c = dc >= -RelativeOrientationBound_Small && dc < RelativeOrientationBound_Small;
    }
    uint32_t da,db,dc;
    bool all_small = false;
    bool relative_orientation = false;

    if ( Stream::IsWriting && orientation.largest == base_orientation.largest )
    {
    da = signed_to_unsigned( orientation.integer_a - base_orientation.integer_a );
    db = signed_to_unsigned( orientation.integer_b - base_orientation.integer_b );
    dc = signed_to_unsigned( orientation.integer_c - base_orientation.integer_c );
    all_small = da <= small_limit && db <= small_limit && dc <= small_limit;
    relative_orientation = da < large_limit && db < large_limit && dc < large_limit;
    }

    serialize_bool( stream, relative_orientation );

    if ( relative_orientation )
    {
    serialize_bool( stream, small_a );
    serialize_bool( stream, small_b );
    serialize_bool( stream, small_c );

    int offset_a, offset_b, offset_c;
    serialize_bool( stream, all_small );

    if ( Stream::IsWriting )
    if ( all_small )
    {
    offset_a = orientation.integer_a - base_orientation.integer_a;
    offset_b = orientation.integer_b - base_orientation.integer_b;
    offset_c = orientation.integer_c - base_orientation.integer_c;
    }

    if ( small_a )
    {
    serialize_int( stream, offset_a, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    serialize_int( stream, da, 0, small_limit );
    serialize_int( stream, db, 0, small_limit );
    serialize_int( stream, dc, 0, small_limit );
    }
    else
    {
    serialize_offset( stream, offset_a, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    }

    if ( small_b )
    {
    serialize_int( stream, offset_b, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_b, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    }

    if ( small_c )
    {
    serialize_int( stream, offset_c, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_c, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    serialize_relative_orientation_component( stream, da );
    serialize_relative_orientation_component( stream, db );
    serialize_relative_orientation_component( stream, dc );
    }

    if ( Stream::IsReading )
    {
    int signed_da = unsigned_to_signed( da );
    int signed_db = unsigned_to_signed( db );
    int signed_dc = unsigned_to_signed( dc );

    orientation.largest = base_orientation.largest;
    orientation.integer_a = base_orientation.integer_a + offset_a;
    orientation.integer_b = base_orientation.integer_b + offset_b;
    orientation.integer_c = base_orientation.integer_c + offset_c;
    orientation.integer_a = base_orientation.integer_a + signed_da;
    orientation.integer_b = base_orientation.integer_b + signed_db;
    orientation.integer_c = base_orientation.integer_c + signed_dc;
    }
    }
    else
    else
    {
    serialize_object( stream, orientation );
    }
    }

    template <typename Stream> void serialize_cube_relative_to_base( Stream & stream, QuantizedCubeState & cube, const QuantizedCubeState & base )
    template <typename Stream> void serialize_cube_relative_to_base( Stream & stream, QuantizedCubeState & cube, const QuantizedCubeState & base, int base_dx, int base_dy, int base_dz )
    {
    serialize_bool( stream, cube.interacting );

    bool position_changed;
    bool orientation_changed;

    if ( Stream::IsWriting )
    {
    position_changed = cube.position_x != base.position_x || cube.position_y != base.position_y || cube.position_z != base.position_z;
    orientation_changed = cube.orientation != base.orientation;
    }

    serialize_bool( stream, position_changed );
    serialize_bool( stream, orientation_changed );

    if ( position_changed )
    {
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x, base.position_y, base.position_z );
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x + base_dx, base.position_y + base_dy, base.position_z + base_dz );
    }
    else if ( Stream::IsReading )
    {
    @@ -1763,17 +1647,27 @@ template <typename Stream> void serialize_cube_relative_to_base( Stream & stream
    cube.position_z = base.position_z;
    }

    if ( orientation_changed )
    {
    serialize_relative_orientation( stream, cube.orientation, base.orientation );
    }
    else
    serialize_relative_orientation( stream, cube.orientation, base.orientation );
    }

    struct CompressionState
    {
    float delta_x[NumCubes];
    float delta_y[NumCubes];
    float delta_z[NumCubes];
    };

    void calculate_compression_state( CompressionState & compression_state, QuantizedSnapshot & current_snapshot, QuantizedSnapshot & baseline_snapshot )
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    cube.orientation = base.orientation;
    compression_state.delta_x[i] = ( current_snapshot.cubes[i].position_x - baseline_snapshot.cubes[i].position_x ) / 6.0;
    compression_state.delta_y[i] = ( current_snapshot.cubes[i].position_y - baseline_snapshot.cubes[i].position_y ) / 6.0;
    compression_state.delta_z[i] = ( current_snapshot.cubes[i].position_z - baseline_snapshot.cubes[i].position_z ) / 6.0;
    }
    }

    template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream & stream, QuantizedSnapshot & current_snapshot, QuantizedSnapshot & baseline_snapshot )
    template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream & stream, CompressionState & compression_state, QuantizedSnapshot & current_snapshot, QuantizedSnapshot & baseline_snapshot )
    {
    QuantizedCubeState * quantized_cubes = &current_snapshot.cubes[0];
    QuantizedCubeState * quantized_base_cubes = &baseline_snapshot.cubes[0];
    @@ -1800,22 +1694,6 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    }
    }

    bool nothing_changed;
    if ( Stream::IsWriting )
    nothing_changed = num_changed == 0;

    serialize_bool( stream, nothing_changed );

    if ( nothing_changed )
    {
    if ( Stream::IsReading )
    {
    for ( int i = 0; i < NumCubes; ++i )
    memcpy( &quantized_cubes[i], &quantized_base_cubes[i], sizeof( QuantizedCubeState ) );
    }
    return;
    }

    serialize_bool( stream, use_indices );

    if ( use_indices )
    @@ -1843,7 +1721,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    serialize_relative_index( stream, previous_index, i );
    }

    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );
    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i], compression_state.delta_x[i], compression_state.delta_y[i], compression_state.delta_z[i] );

    num_written++;

    @@ -1867,7 +1745,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    else
    serialize_relative_index( stream, previous_index, i );

    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );
    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i], compression_state.delta_x[i], compression_state.delta_y[i], compression_state.delta_z[i] );

    changed[i] = true;

    @@ -1889,7 +1767,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream

    if ( changed[i] )
    {
    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );
    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i], compression_state.delta_x[i], compression_state.delta_y[i], compression_state.delta_z[i] );
    }
    else if ( Stream::IsReading )
    {
    @@ -1999,6 +1877,8 @@ int main( int argc, char ** argv )
    printf( "writing %d packets\n", num_packets );
    assert( num_packets > 0 );

    CompressionState * compression_state = new CompressionState[num_frames];

    int packet_index = 0;
    Packet * packets = new Packet[num_packets];
    uint64_t total_bytes = 0;
    @@ -2011,7 +1891,9 @@ int main( int argc, char ** argv )
    QuantizedSnapshot & current_snapshot = snapshots[i];
    QuantizedSnapshot & baseline_snapshot = snapshots[i-6];

    serialize_snapshot_relative_to_baseline( stream, current_snapshot, baseline_snapshot );
    calculate_compression_state( compression_state[i], current_snapshot, baseline_snapshot );

    serialize_snapshot_relative_to_baseline( stream, compression_state[i-6], current_snapshot, baseline_snapshot );

    stream.Flush();

    @@ -2041,7 +1923,7 @@ int main( int argc, char ** argv )
    QuantizedSnapshot current_snapshot;
    QuantizedSnapshot & baseline_snapshot = snapshots[i];

    serialize_snapshot_relative_to_baseline( stream, current_snapshot, baseline_snapshot );
    serialize_snapshot_relative_to_baseline( stream, compression_state[i], current_snapshot, baseline_snapshot );

    assert( current_snapshot == snapshots[i+6] );
    }
    @@ -2069,9 +1951,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1652471
    average bytes per-packet: 583.705758
    average bytes per-second: 280178.763688
    average kilobits per-second: 280.178764
    compression ratio: 2.30% of original size
    total packet bytes: 1581540
    average bytes per-packet: 558.650653
    average bytes per-second: 268152.313670
    average kilobits per-second: 268.152314
    compression ratio: 2.20% of original size
    */
  6. @gafferongames gafferongames revised this gist Mar 18, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -90,7 +90,7 @@ inline int bits_required( uint32_t min, uint32_t max )

    template <typename T> const T & min( const T & a, const T & b )
    {
    return ( a > b ) ? a : b;
    return ( a < b ) ? a : b;
    }

    template <typename T> const T & max( const T & a, const T & b )
  7. @gafferongames gafferongames revised this gist Mar 17, 2015. 1 changed file with 9 additions and 6 deletions.
    15 changes: 9 additions & 6 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1985,6 +1985,7 @@ int main( int argc, char ** argv )
    Frame * frames = new Frame[num_frames];
    uint64_t frames_read = fread( frames, sizeof( Frame ), num_frames, file );
    assert( frames_read == num_frames );
    fclose( file );

    // convert frames to snapshots

    @@ -2017,9 +2018,11 @@ int main( int argc, char ** argv )
    int bits_written = stream.GetBitsProcessed();
    int bytes_written = ( bits_written / 8 ) + ( ( bits_written % 8 ) ? 1 : 0 );

    while ( packet.data[bytes_written] == 0 )
    while ( packet.data[bytes_written] == 0 && bytes_written > 0 )
    --bytes_written;

    assert( bytes_written >= 0 );

    packet.size = bytes_written;
    total_bytes += bytes_written;

    @@ -2066,9 +2069,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1504995
    average bytes per-packet: 531.612504
    average bytes per-second: 255174.002119
    average kilobits per-second: 255.174002
    compression ratio: 2.10% of original size
    total packet bytes: 1652471
    average bytes per-packet: 583.705758
    average bytes per-second: 280178.763688
    average kilobits per-second: 280.178764
    compression ratio: 2.30% of original size
    */
  8. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -2026,7 +2026,7 @@ int main( int argc, char ** argv )
    ++packet_index;
    }

    // read packets and verify reconstruction of snapshot matches exactly
    // read packets and verify reconstruction of snapshot

    printf( "reading %d packets\n", num_packets );
    for ( int i = 0; i < num_packets; ++i )
  9. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 70 additions and 41 deletions.
    111 changes: 70 additions & 41 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1529,7 +1529,7 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    int base_position_y,
    int base_position_z )
    {
    const int RelativePositionBound_Small = 32;
    const int RelativePositionBound_Small = 64;
    const int RelativePositionBound_Large = 512;

    bool relative_position = false;
    @@ -1558,14 +1558,12 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    }
    }

    serialize_bool( stream, relative_position );
    bool all_small = Stream::IsWriting && relative_position_small_x && relative_position_small_y && relative_position_small_z;

    if ( relative_position )
    {
    serialize_bool( stream, relative_position_small_x );
    serialize_bool( stream, relative_position_small_y );
    serialize_bool( stream, relative_position_small_z );
    serialize_bool( stream, all_small );

    if ( all_small )
    {
    int offset_x, offset_y, offset_z;

    if ( Stream::IsWriting )
    @@ -1575,32 +1573,9 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    offset_z = position_z - base_position_z;
    }

    if ( relative_position_small_x )
    {
    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_x, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_y )
    {
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_y, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_z )
    {
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_z, RelativePositionBound_Small, RelativePositionBound_Large );
    }
    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );

    if ( Stream::IsReading )
    {
    @@ -1611,9 +1586,63 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    }
    else
    {
    serialize_int( stream, position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_z, 0, +QuantizedPositionBoundZ - 1 );
    serialize_bool( stream, relative_position );

    if ( relative_position )
    {
    serialize_bool( stream, relative_position_small_x );
    serialize_bool( stream, relative_position_small_y );
    serialize_bool( stream, relative_position_small_z );

    int offset_x, offset_y, offset_z;

    if ( Stream::IsWriting )
    {
    offset_x = position_x - base_position_x;
    offset_y = position_y - base_position_y;
    offset_z = position_z - base_position_z;
    }

    if ( relative_position_small_x )
    {
    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_x, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_y )
    {
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_y, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_z )
    {
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_z, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( Stream::IsReading )
    {
    position_x = base_position_x + offset_x;
    position_y = base_position_y + offset_y;
    position_z = base_position_z + offset_z;
    }
    }
    else
    {
    serialize_int( stream, position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_z, 0, +QuantizedPositionBoundZ - 1 );
    }
    }
    }

    @@ -2037,9 +2066,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1520166
    average bytes per-packet: 536.971388
    average bytes per-second: 257746.266337
    average kilobits per-second: 257.746266
    compression ratio: 2.12% of original size
    total packet bytes: 1504995
    average bytes per-packet: 531.612504
    average bytes per-second: 255174.002119
    average kilobits per-second: 255.174002
    compression ratio: 2.10% of original size
    */
  10. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 44 additions and 20 deletions.
    64 changes: 44 additions & 20 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -169,6 +169,7 @@ class BitWriter
    m_bitIndex = 0;
    m_wordIndex = 0;
    m_overflow = false;
    memset( m_data, 0, bytes );
    }

    void WriteBits( uint32_t value, int bits )
    @@ -1528,8 +1529,8 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    int base_position_y,
    int base_position_z )
    {
    const int RelativePositionBound_Small = 16;
    const int RelativePositionBound_Large = 256;
    const int RelativePositionBound_Small = 32;
    const int RelativePositionBound_Large = 512;

    bool relative_position = false;
    bool relative_position_small_x = false;
    @@ -1542,8 +1543,8 @@ template <typename Stream> void serialize_relative_position( Stream & stream,
    const int dy = position_y - base_position_y;
    const int dz = position_z - base_position_z;

    const int relative_min = -RelativePositionBound_Large - ( RelativePositionBound_Small - 1 ); // -256 - 15 = -271
    const int relative_max = RelativePositionBound_Large - 1 + ( RelativePositionBound_Small - 1 ); // +255 + 15 = 270
    const int relative_min = -RelativePositionBound_Large - ( RelativePositionBound_Small - 1 );
    const int relative_max = RelativePositionBound_Large - 1 + ( RelativePositionBound_Small - 1 );

    relative_position = dx >= relative_min && dx <= relative_max &&
    dy >= relative_min && dy <= relative_max &&
    @@ -1618,8 +1619,8 @@ template <typename Stream> void serialize_relative_position( Stream & stream,

    template <typename Stream> void serialize_relative_orientation( Stream & stream, compressed_quaternion<OrientationBits> & orientation, const compressed_quaternion<OrientationBits> & base_orientation )
    {
    const int RelativeOrientationBound_Small = 16;
    const int RelativeOrientationBound_Large = 128;
    const int RelativeOrientationBound_Small = 4;
    const int RelativeOrientationBound_Large = 64;

    bool relative_orientation = false;
    bool small_a = false;
    @@ -1632,8 +1633,8 @@ template <typename Stream> void serialize_relative_orientation( Stream & stream,
    const int db = orientation.integer_b - base_orientation.integer_b;
    const int dc = orientation.integer_c - base_orientation.integer_c;

    const int relative_min = -RelativeOrientationBound_Large - ( RelativeOrientationBound_Small - 1 ); // -256 - 15 = -271
    const int relative_max = RelativeOrientationBound_Large - 1 + ( RelativeOrientationBound_Small - 1 ); // +255 + 15 = 270
    const int relative_min = -RelativeOrientationBound_Large - ( RelativeOrientationBound_Small - 1 );
    const int relative_max = RelativeOrientationBound_Large - 1 + ( RelativeOrientationBound_Small - 1 );

    if ( orientation.largest == base_orientation.largest &&
    da >= relative_min && da < relative_max &&
    @@ -1748,7 +1749,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    QuantizedCubeState * quantized_cubes = &current_snapshot.cubes[0];
    QuantizedCubeState * quantized_base_cubes = &baseline_snapshot.cubes[0];

    const int MaxIndex = 255;
    const int MaxChanged = 256;

    int num_changed = 0;
    bool use_indices = false;
    @@ -1761,16 +1762,36 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    if ( changed[i] )
    num_changed++;
    }
    int relative_index_bits = count_relative_index_bits( changed );
    if ( relative_index_bits <= NumCubes )
    use_indices = true;

    if ( num_changed > 0 )
    {
    int relative_index_bits = count_relative_index_bits( changed );
    if ( num_changed <= MaxChanged && relative_index_bits <= NumCubes )
    use_indices = true;
    }
    }

    bool nothing_changed;
    if ( Stream::IsWriting )
    nothing_changed = num_changed == 0;

    serialize_bool( stream, nothing_changed );

    if ( nothing_changed )
    {
    if ( Stream::IsReading )
    {
    for ( int i = 0; i < NumCubes; ++i )
    memcpy( &quantized_cubes[i], &quantized_base_cubes[i], sizeof( QuantizedCubeState ) );
    }
    return;
    }

    serialize_bool( stream, use_indices );

    if ( use_indices )
    {
    serialize_int( stream, num_changed, 0, MaxIndex );
    serialize_int( stream, num_changed, 1, MaxChanged );

    if ( Stream::IsWriting )
    {
    @@ -1964,8 +1985,11 @@ int main( int argc, char ** argv )

    stream.Flush();

    const int bits_written = stream.GetBitsProcessed();
    const int bytes_written = ( bits_written / 8 ) + ( ( bits_written % 8 ) ? 1 : 0 );
    int bits_written = stream.GetBitsProcessed();
    int bytes_written = ( bits_written / 8 ) + ( ( bits_written % 8 ) ? 1 : 0 );

    while ( packet.data[bytes_written] == 0 )
    --bytes_written;

    packet.size = bytes_written;
    total_bytes += bytes_written;
    @@ -2013,9 +2037,9 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1714231
    average bytes per-packet: 605.521371
    average bytes per-second: 290650.257859
    average kilobits per-second: 290.650258
    compression ratio: 2.39% of original size
    total packet bytes: 1520166
    average bytes per-packet: 536.971388
    average bytes per-second: 257746.266337
    average kilobits per-second: 257.746266
    compression ratio: 2.12% of original size
    */
  11. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    /*
    Snapshot Delta Compression by Glenn Fiedler.
    Delta Compression by Glenn Fiedler.
    This source code is placed in the public domain.
    http://gafferongames.com/2015/03/14/the-networked-physics-data-compression-challenge/
    */
  12. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 0 additions and 87 deletions.
    87 changes: 0 additions & 87 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1001,93 +1001,6 @@ template <typename Stream> void serialize_string( Stream & stream, char * string
    string[length] = '\0';
    }

    template <typename Stream, typename T> void serialize_int_relative( Stream & stream, T previous, T & current )
    {
    uint32_t difference;
    if ( Stream::IsWriting )
    {
    assert( previous < current );
    difference = current - previous;
    assert( difference >= 0 );
    }

    bool oneBit;
    if ( Stream::IsWriting )
    oneBit = difference == 1;
    serialize_bool( stream, oneBit );
    if ( oneBit )
    {
    if ( Stream::IsReading )
    current = previous + 1;
    return;
    }

    bool twoBits;
    if ( Stream::IsWriting )
    twoBits = difference == difference <= 4;
    serialize_bool( stream, twoBits );
    if ( twoBits )
    {
    serialize_int( stream, difference, 1, 4 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool fourBits;
    if ( Stream::IsWriting )
    fourBits = difference == difference <= 16;
    serialize_bool( stream, fourBits );
    if ( fourBits )
    {
    serialize_int( stream, difference, 1, 16 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool eightBits;
    if ( Stream::IsWriting )
    eightBits = difference == difference <= 256;
    serialize_bool( stream, eightBits );
    if ( eightBits )
    {
    serialize_int( stream, difference, 1, 256 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool twelveBits;
    if ( Stream::IsWriting )
    twelveBits = difference <= 4096;
    serialize_bool( stream, twelveBits );
    if ( twelveBits )
    {
    serialize_int( stream, difference, 1, 4096 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool sixteenBits;
    if ( Stream::IsWriting )
    sixteenBits = difference <= 65535;
    serialize_bool( stream, sixteenBits );
    if ( sixteenBits )
    {
    serialize_int( stream, difference, 1, 65536 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    uint32_t value = current;
    serialize_uint32( stream, value );
    if ( Stream::IsReading )
    current = (decltype(current)) value;
    }

    template <typename Stream> bool serialize_check( Stream & stream, uint32_t magic )
    {
    return stream.Check( magic );
  13. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -2084,6 +2084,7 @@ int main( int argc, char ** argv )
    printf( "average bytes per-packet: %f\n", total_bytes / double(num_packets) );
    printf( "average bytes per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 );
    printf( "average kilobits per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 / 1000.0 );
    printf( "compression ratio: %.2f%% of original size\n", total_bytes / ( num_packets * ( ( 4 * 32 + 3 * 32 + 1 ) * NumCubes ) / 8.0 ) * 100.0 );

    // clean up everything

    @@ -2103,4 +2104,5 @@ int main( int argc, char ** argv )
    average bytes per-packet: 605.521371
    average bytes per-second: 290650.257859
    average kilobits per-second: 290.650258
    compression ratio: 2.39% of original size
    */
  14. @gafferongames gafferongames revised this gist Mar 16, 2015. 1 changed file with 9 additions and 9 deletions.
    18 changes: 9 additions & 9 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -1323,7 +1323,7 @@ template <int bits> struct compressed_quaternion

    inline int count_relative_index_bits( bool * changed )
    {
    int bits = 7; // 0..127 num changed
    int bits = 8; // 0..255 num changed
    bool first = true;
    int previous_index = 0;

    @@ -1446,11 +1446,11 @@ template <typename Stream> void serialize_relative_index( Stream & stream, int p

    bool fiveBits;
    if ( Stream::IsWriting )
    fiveBits = difference <= 64;
    fiveBits = difference <= 62;
    serialize_bool( stream, fiveBits );
    if ( fiveBits )
    {
    serialize_int( stream, difference, 31, 64 );
    serialize_int( stream, difference, 31, 62 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    @@ -1835,7 +1835,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream
    QuantizedCubeState * quantized_cubes = &current_snapshot.cubes[0];
    QuantizedCubeState * quantized_base_cubes = &baseline_snapshot.cubes[0];

    const int MaxIndex = 254;
    const int MaxIndex = 255;

    int num_changed = 0;
    bool use_indices = false;
    @@ -1857,7 +1857,7 @@ template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream

    if ( use_indices )
    {
    serialize_int( stream, num_changed, 0, MaxIndex + 1 );
    serialize_int( stream, num_changed, 0, MaxIndex );

    if ( Stream::IsWriting )
    {
    @@ -2099,8 +2099,8 @@ int main( int argc, char ** argv )
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1715020
    average bytes per-packet: 605.800071
    average bytes per-second: 290784.033910
    average kilobits per-second: 290.784034
    total packet bytes: 1714231
    average bytes per-packet: 605.521371
    average bytes per-second: 290650.257859
    average kilobits per-second: 290.650258
    */
  15. @gafferongames gafferongames created this gist Mar 16, 2015.
    2,106 changes: 2,106 additions & 0 deletions delta_compression.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2106 @@
    /*
    Snapshot Delta Compression by Glenn Fiedler.
    This source code is placed in the public domain.
    http://gafferongames.com/2015/03/14/the-networked-physics-data-compression-challenge/
    */

    #include <stdint.h>
    #include <stdio.h>
    #include <assert.h>
    #include <string.h>
    #include <math.h>

    static const int MaxContexts = 8;
    static const int NumCubes = 901;
    static const int MaxPacketSize = 4 * 1024;
    static const int UnitsPerMeter = 512;
    static const int OrientationBits = 9;
    static const int PositionBoundXY = 32;
    static const int PositionBoundZ = 16;
    static const int QuantizedPositionBoundXY = UnitsPerMeter * PositionBoundXY - 1;
    static const int QuantizedPositionBoundZ = UnitsPerMeter * PositionBoundZ - 1;

    template <uint32_t x> struct PopCount
    {
    enum { a = x - ( ( x >> 1 ) & 0x55555555 ),
    b = ( ( ( a >> 2 ) & 0x33333333 ) + ( a & 0x33333333 ) ),
    c = ( ( ( b >> 4 ) + b ) & 0x0f0f0f0f ),
    d = c + ( c >> 8 ),
    e = d + ( d >> 16 ),

    result = e & 0x0000003f
    };
    };

    template <uint32_t x> struct Log2
    {
    enum { a = x | ( x >> 1 ),
    b = a | ( a >> 2 ),
    c = b | ( b >> 4 ),
    d = c | ( c >> 8 ),
    e = d | ( d >> 16 ),
    f = e >> 1,

    result = PopCount<f>::result
    };
    };

    template <int64_t min, int64_t max> struct BitsRequired
    {
    static const uint32_t result = ( min == max ) ? 0 : Log2<uint32_t(max-min)>::result + 1;
    };

    inline uint32_t popcount( uint32_t x )
    {
    const uint32_t a = x - ( ( x >> 1 ) & 0x55555555 );
    const uint32_t b = ( ( ( a >> 2 ) & 0x33333333 ) + ( a & 0x33333333 ) );
    const uint32_t c = ( ( ( b >> 4 ) + b ) & 0x0f0f0f0f );
    const uint32_t d = c + ( c >> 8 );
    const uint32_t e = d + ( d >> 16 );
    const uint32_t result = e & 0x0000003f;
    return result;
    }

    #ifdef __GNUC__

    inline int bits_required( uint32_t min, uint32_t max )
    {
    return 32 - __builtin_clz( max - min );
    }

    #else

    inline uint32_t log2( uint32_t x )
    {
    const uint32_t a = x | ( x >> 1 );
    const uint32_t b = a | ( a >> 2 );
    const uint32_t c = b | ( b >> 4 );
    const uint32_t d = c | ( c >> 8 );
    const uint32_t e = d | ( d >> 16 );
    const uint32_t f = e >> 1;
    return popcount( f );
    }

    inline int bits_required( uint32_t min, uint32_t max )
    {
    return ( min == max ) ? 0 : log2( max-min ) + 1;
    }

    #endif

    template <typename T> const T & min( const T & a, const T & b )
    {
    return ( a > b ) ? a : b;
    }

    template <typename T> const T & max( const T & a, const T & b )
    {
    return ( a > b ) ? a : b;
    }

    template <typename T> T clamp( const T & value, const T & min, const T & max )
    {
    if ( value < min )
    return min;
    else if ( value > max )
    return max;
    else
    return value;
    }

    template <typename T> void swap( T & a, T & b )
    {
    T tmp = a;
    a = b;
    b = tmp;
    };

    template <typename T> T abs( const T & value )
    {
    return ( value < 0 ) ? -value : value;
    }

    #define CPU_LITTLE_ENDIAN 1
    #define CPU_BIG_ENDIAN 2

    #if defined(__386__) || defined(i386) || defined(__i386__) \
    || defined(__X86) || defined(_M_IX86) \
    || defined(_M_X64) || defined(__x86_64__) \
    || defined(alpha) || defined(__alpha) || defined(__alpha__) \
    || defined(_M_ALPHA) \
    || defined(ARM) || defined(_ARM) || defined(__arm__) \
    || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \
    || defined(_WIN32_WCE) || defined(__NT__) \
    || defined(__MIPSEL__)
    #define CPU_ENDIAN CPU_LITTLE_ENDIAN
    #else
    #define CPU_ENDIAN CPU_BIG_ENDIAN
    #endif

    inline uint32_t host_to_network( uint32_t value )
    {
    #if CPU_ENDIAN == CPU_BIG_ENDIAN
    return __builtin_bswap32( value );
    #else
    return value;
    #endif
    }

    inline uint32_t network_to_host( uint32_t value )
    {
    #if CPU_ENDIAN == CPU_BIG_ENDIAN
    return __builtin_bswap32( value );
    #else
    return value;
    #endif
    }

    class BitWriter
    {
    public:

    BitWriter( void * data, int bytes ) : m_data( (uint32_t*)data ), m_numWords( bytes / 4 )
    {
    assert( data );
    assert( ( bytes % 4 ) == 0 ); // IMPORTANT: buffer size must be a multiple of four!
    m_numBits = m_numWords * 32;
    m_bitsWritten = 0;
    m_scratch = 0;
    m_bitIndex = 0;
    m_wordIndex = 0;
    m_overflow = false;
    }

    void WriteBits( uint32_t value, int bits )
    {
    assert( bits > 0 );
    assert( bits <= 32 );
    assert( m_bitsWritten + bits <= m_numBits );

    if ( m_bitsWritten + bits > m_numBits )
    {
    m_overflow = true;
    return;
    }

    value &= ( uint64_t( 1 ) << bits ) - 1;

    m_scratch |= uint64_t( value ) << ( 64 - m_bitIndex - bits );

    m_bitIndex += bits;

    if ( m_bitIndex >= 32 )
    {
    assert( m_wordIndex < m_numWords );
    m_data[m_wordIndex] = host_to_network( uint32_t( m_scratch >> 32 ) );
    m_scratch <<= 32;
    m_bitIndex -= 32;
    m_wordIndex++;
    }

    m_bitsWritten += bits;
    }

    void WriteAlign()
    {
    const int remainderBits = m_bitsWritten % 8;
    if ( remainderBits != 0 )
    {
    uint32_t zero = 0;
    WriteBits( zero, 8 - remainderBits );
    assert( m_bitsWritten % 8 == 0 );
    }
    }

    void WriteBytes( const uint8_t * data, int bytes )
    {
    assert( GetAlignBits() == 0 );
    if ( m_bitsWritten + bytes * 8 >= m_numBits )
    {
    m_overflow = true;
    return;
    }

    // head

    assert( m_bitIndex == 0 || m_bitIndex == 8 || m_bitIndex == 16 || m_bitIndex == 24 );

    int headBytes = ( 4 - m_bitIndex / 8 ) % 4;
    if ( headBytes > bytes )
    headBytes = bytes;
    for ( int i = 0; i < headBytes; ++i )
    WriteBits( data[i], 8 );
    if ( headBytes == bytes )
    return;

    assert( GetAlignBits() == 0 );

    // words

    int numWords = ( bytes - headBytes ) / 4;
    if ( numWords > 0 )
    {
    assert( m_bitIndex == 0 );
    memcpy( &m_data[m_wordIndex], data + headBytes, numWords * 4 );
    m_bitsWritten += numWords * 32;
    m_wordIndex += numWords;
    m_scratch = 0;
    }

    assert( GetAlignBits() == 0 );

    // tail

    int tailStart = headBytes + numWords * 4;
    int tailBytes = bytes - tailStart;
    assert( tailBytes >= 0 && tailBytes < 4 );
    for ( int i = 0; i < tailBytes; ++i )
    WriteBits( data[tailStart+i], 8 );

    assert( GetAlignBits() == 0 );

    assert( headBytes + numWords * 4 + tailBytes == bytes );
    }

    void FlushBits()
    {
    if ( m_bitIndex != 0 )
    {
    assert( m_wordIndex < m_numWords );
    if ( m_wordIndex >= m_numWords )
    {
    m_overflow = true;
    return;
    }
    m_data[m_wordIndex++] = host_to_network( uint32_t( m_scratch >> 32 ) );
    }
    }

    int GetAlignBits() const
    {
    return ( 8 - m_bitsWritten % 8 ) % 8;
    }

    int GetBitsWritten() const
    {
    return m_bitsWritten;
    }

    int GetBitsAvailable() const
    {
    return m_numBits - m_bitsWritten;
    }

    const uint8_t * GetData() const
    {
    return (uint8_t*) m_data;
    }

    int GetBytesWritten() const
    {
    return m_wordIndex * 4;
    }

    int GetTotalBytes() const
    {
    return m_numWords * 4;
    }

    bool IsOverflow() const
    {
    return m_overflow;
    }

    private:

    uint32_t * m_data;
    uint64_t m_scratch;
    int m_numBits;
    int m_numWords;
    int m_bitsWritten;
    int m_bitIndex;
    int m_wordIndex;
    bool m_overflow;
    };

    class BitReader
    {
    public:

    BitReader( const void * data, int bytes ) : m_data( (const uint32_t*)data ), m_numWords( bytes / 4 )
    {
    assert( data );
    assert( ( bytes % 4 ) == 0 ); // IMPORTANT: buffer size must be a multiple of four!
    m_numBits = m_numWords * 32;
    m_bitsRead = 0;
    m_bitIndex = 0;
    m_wordIndex = 0;
    m_scratch = network_to_host( m_data[0] );
    m_overflow = false;
    }

    uint32_t ReadBits( int bits )
    {
    assert( bits > 0 );
    assert( bits <= 32 );
    assert( m_bitsRead + bits <= m_numBits );

    if ( m_bitsRead + bits > m_numBits )
    {
    m_overflow = true;
    return 0;
    }

    m_bitsRead += bits;

    assert( m_bitIndex < 32 );

    if ( m_bitIndex + bits < 32 )
    {
    m_scratch <<= bits;
    m_bitIndex += bits;
    }
    else
    {
    m_wordIndex++;
    assert( m_wordIndex < m_numWords );
    const uint32_t a = 32 - m_bitIndex;
    const uint32_t b = bits - a;
    m_scratch <<= a;
    m_scratch |= network_to_host( m_data[m_wordIndex] );
    m_scratch <<= b;
    m_bitIndex = b;
    }

    const uint32_t output = uint32_t( m_scratch >> 32 );

    m_scratch &= 0xFFFFFFFF;

    return output;
    }

    void ReadAlign()
    {
    const int remainderBits = m_bitsRead % 8;
    if ( remainderBits != 0 )
    {
    #ifdef NDEBUG
    ReadBits( 8 - remainderBits );
    #else
    uint32_t value = ReadBits( 8 - remainderBits );
    assert( value == 0 );
    assert( m_bitsRead % 8 == 0 );
    #endif
    }
    }

    void ReadBytes( uint8_t * data, int bytes )
    {
    assert( GetAlignBits() == 0 );

    if ( m_bitsRead + bytes * 8 >= m_numBits )
    {
    memset( data, bytes, 0 );
    m_overflow = true;
    return;
    }

    // head

    assert( m_bitIndex == 0 || m_bitIndex == 8 || m_bitIndex == 16 || m_bitIndex == 24 );

    int headBytes = ( 4 - m_bitIndex / 8 ) % 4;
    if ( headBytes > bytes )
    headBytes = bytes;
    for ( int i = 0; i < headBytes; ++i )
    data[i] = ReadBits( 8 );
    if ( headBytes == bytes )
    return;

    assert( GetAlignBits() == 0 );

    // words

    int numWords = ( bytes - headBytes ) / 4;
    if ( numWords > 0 )
    {
    assert( m_bitIndex == 0 );
    memcpy( data + headBytes, &m_data[m_wordIndex], numWords * 4 );
    m_bitsRead += numWords * 32;
    m_wordIndex += numWords;
    m_scratch = network_to_host( m_data[m_wordIndex] );
    }

    assert( GetAlignBits() == 0 );

    // tail

    int tailStart = headBytes + numWords * 4;
    int tailBytes = bytes - tailStart;
    assert( tailBytes >= 0 && tailBytes < 4 );
    for ( int i = 0; i < tailBytes; ++i )
    data[tailStart+i] = ReadBits( 8 );

    assert( GetAlignBits() == 0 );

    assert( headBytes + numWords * 4 + tailBytes == bytes );
    }

    int GetAlignBits() const
    {
    return ( 8 - m_bitsRead % 8 ) % 8;
    }

    int GetBitsRead() const
    {
    return m_bitsRead;
    }

    int GetBytesRead() const
    {
    return ( m_wordIndex + 1 ) * 4;
    }

    int GetBitsRemaining() const
    {
    return m_numBits - m_bitsRead;
    }

    int GetTotalBits() const
    {
    return m_numBits;
    }

    int GetTotalBytes() const
    {
    return m_numBits * 8;
    }

    bool IsOverflow() const
    {
    return m_overflow;
    }

    private:

    const uint32_t * m_data;
    uint64_t m_scratch;
    int m_numBits;
    int m_numWords;
    int m_bitsRead;
    int m_bitIndex;
    int m_wordIndex;
    bool m_overflow;
    };

    class WriteStream
    {
    public:

    enum { IsWriting = 1 };
    enum { IsReading = 0 };

    WriteStream( uint8_t * buffer, int bytes ) : m_writer( buffer, bytes ), m_context( nullptr ), m_aborted( false ) {}

    void SerializeInteger( int32_t value, int32_t min, int32_t max )
    {
    assert( min < max );
    assert( value >= min );
    assert( value <= max );
    const int bits = bits_required( min, max );
    uint32_t unsigned_value = value - min;
    m_writer.WriteBits( unsigned_value, bits );
    }

    void SerializeBits( uint32_t value, int bits )
    {
    assert( bits > 0 );
    assert( bits <= 32 );
    m_writer.WriteBits( value, bits );
    }

    void SerializeBytes( const uint8_t * data, int bytes )
    {
    Align();
    m_writer.WriteBytes( data, bytes );
    }

    void Align()
    {
    m_writer.WriteAlign();
    }

    int GetAlignBits() const
    {
    return m_writer.GetAlignBits();
    }

    bool Check( uint32_t magic )
    {
    Align();
    SerializeBits( magic, 32 );
    return true;
    }

    void Flush()
    {
    m_writer.FlushBits();
    }

    const uint8_t * GetData() const
    {
    return m_writer.GetData();
    }

    int GetBytesProcessed() const
    {
    return m_writer.GetBytesWritten();
    }

    int GetBitsProcessed() const
    {
    return m_writer.GetBitsWritten();
    }

    int GetBitsRemaining() const
    {
    return GetTotalBits() - GetBitsProcessed();
    }

    int GetTotalBits() const
    {
    return m_writer.GetTotalBytes() * 8;
    }

    int GetTotalBytes() const
    {
    return m_writer.GetTotalBytes();
    }

    bool IsOverflow() const
    {
    return m_writer.IsOverflow();
    }

    void SetContext( const void ** context )
    {
    m_context = context;
    }

    const void * GetContext( int index ) const
    {
    assert( index >= 0 );
    assert( index < MaxContexts );
    return m_context ? m_context[index] : nullptr;
    }

    void Abort()
    {
    m_aborted = true;
    }

    bool Aborted() const
    {
    return m_aborted;
    }

    private:

    BitWriter m_writer;
    const void ** m_context;
    bool m_aborted;
    };

    class ReadStream
    {
    public:

    enum { IsWriting = 0 };
    enum { IsReading = 1 };

    ReadStream( uint8_t * buffer, int bytes ) : m_bitsRead(0), m_reader( buffer, bytes ), m_context( nullptr ), m_aborted( false ) {}

    void SerializeInteger( int32_t & value, int32_t min, int32_t max )
    {
    assert( min < max );
    const int bits = bits_required( min, max );
    uint32_t unsigned_value = m_reader.ReadBits( bits );
    value = (int32_t) unsigned_value + min;
    m_bitsRead += bits;
    }

    void SerializeBits( uint32_t & value, int bits )
    {
    assert( bits > 0 );
    assert( bits <= 32 );
    uint32_t read_value = m_reader.ReadBits( bits );
    value = read_value;
    m_bitsRead += bits;
    }

    void SerializeBytes( uint8_t * data, int bytes )
    {
    Align();
    m_reader.ReadBytes( data, bytes );
    m_bitsRead += bytes * 8;
    }

    void Align()
    {
    m_reader.ReadAlign();
    }

    int GetAlignBits() const
    {
    return m_reader.GetAlignBits();
    }

    bool Check( uint32_t magic )
    {
    Align();
    uint32_t value = 0;
    SerializeBits( value, 32 );
    assert( value == magic );
    return value == magic;
    }

    int GetBitsProcessed() const
    {
    return m_bitsRead;
    }

    int GetBytesProcessed() const
    {
    return m_bitsRead / 8 + ( m_bitsRead % 8 ? 1 : 0 );
    }

    bool IsOverflow() const
    {
    return m_reader.IsOverflow();
    }

    void SetContext( const void ** context )
    {
    m_context = context;
    }

    const void * GetContext( int index ) const
    {
    assert( index >= 0 );
    assert( index < MaxContexts );
    return m_context ? m_context[index] : nullptr;
    }

    void Abort()
    {
    m_aborted = true;
    }

    bool Aborted() const
    {
    return m_aborted;
    }

    int GetBytesRead() const
    {
    return m_reader.GetBytesRead();
    }

    private:

    int m_bitsRead;
    BitReader m_reader;
    const void ** m_context;
    bool m_aborted;
    };

    class MeasureStream
    {
    public:

    enum { IsWriting = 1 };
    enum { IsReading = 0 };

    MeasureStream( int bytes ) : m_totalBytes( bytes ), m_bitsWritten(0), m_context( nullptr ), m_aborted( false ) {}

    void SerializeInteger( int32_t value, int32_t min, int32_t max )
    {
    assert( min < max );
    assert( value >= min );
    assert( value <= max );
    const int bits = bits_required( min, max );
    m_bitsWritten += bits;
    }

    void SerializeBits( uint32_t value, int bits )
    {
    assert( bits > 0 );
    assert( bits <= 32 );
    m_bitsWritten += bits;
    }

    void SerializeBytes( const uint8_t * data, int bytes )
    {
    Align();
    m_bitsWritten += bytes * 8;
    }

    void Align()
    {
    const int alignBits = GetAlignBits();
    m_bitsWritten += alignBits;
    }

    int GetAlignBits() const
    {
    return 7; // worst case
    }

    bool Check( uint32_t magic )
    {
    Align();
    m_bitsWritten += 32;
    return true;
    }

    int GetBitsProcessed() const
    {
    return m_bitsWritten;
    }

    int GetBytesProcessed() const
    {
    return m_bitsWritten / 8 + ( m_bitsWritten % 8 ? 1 : 0 );
    }

    int GetTotalBytes() const
    {
    return m_totalBytes;
    }

    int GetTotalBits() const
    {
    return m_totalBytes * 8;
    }

    bool IsOverflow() const
    {
    return m_bitsWritten > m_totalBytes * 8;
    }

    void SetContext( const void ** context )
    {
    m_context = context;
    }

    const void * GetContext( int index ) const
    {
    assert( index >= 0 );
    assert( index < MaxContexts );
    return m_context ? m_context[index] : nullptr;
    }

    void Abort()
    {
    m_aborted = true;
    }

    bool Aborted() const
    {
    return m_aborted;
    }

    private:

    int m_totalBytes;
    int m_bitsWritten;
    const void ** m_context;
    bool m_aborted;
    };

    template <typename T> void serialize_object( ReadStream & stream, T & object )
    {
    object.SerializeRead( stream );
    }

    template <typename T> void serialize_object( WriteStream & stream, T & object )
    {
    object.SerializeWrite( stream );
    }

    template <typename T> void serialize_object( MeasureStream & stream, T & object )
    {
    object.SerializeMeasure( stream );
    }

    #define serialize_int( stream, value, min, max ) \
    do \
    { \
    assert( min < max ); \
    int32_t int32_value; \
    if ( Stream::IsWriting ) \
    { \
    assert( value >= min ); \
    assert( value <= max ); \
    int32_value = (int32_t) value; \
    } \
    stream.SerializeInteger( int32_value, min, max ); \
    if ( Stream::IsReading ) \
    { \
    value = (decltype(value)) int32_value; \
    assert( value >= min ); \
    assert( value <= max ); \
    } \
    } while (0)

    #define serialize_bits( stream, value, bits ) \
    do \
    { \
    assert( bits > 0 ); \
    assert( bits <= 32 ); \
    uint32_t uint32_value; \
    if ( Stream::IsWriting ) \
    uint32_value = (uint32_t) value; \
    stream.SerializeBits( uint32_value, bits ); \
    if ( Stream::IsReading ) \
    value = (decltype(value)) uint32_value; \
    } while (0)

    #define serialize_bool( stream, value ) serialize_bits( stream, value, 1 )

    template <typename Stream> void serialize_uint16( Stream & stream, uint16_t & value )
    {
    serialize_bits( stream, value, 16 );
    }

    template <typename Stream> void serialize_uint32( Stream & stream, uint32_t & value )
    {
    serialize_bits( stream, value, 32 );
    }

    template <typename Stream> void serialize_uint64( Stream & stream, uint64_t & value )
    {
    uint32_t hi,lo;
    if ( Stream::IsWriting )
    {
    lo = value & 0xFFFFFFFF;
    hi = value >> 32;
    }
    serialize_bits( stream, lo, 32 );
    serialize_bits( stream, hi, 32 );
    if ( Stream::IsReading )
    value = ( uint64_t(hi) << 32 ) | lo;
    }

    template <typename Stream> void serialize_int16( Stream & stream, int16_t & value )
    {
    serialize_bits( stream, value, 16 );
    }

    template <typename Stream> void serialize_int32( Stream & stream, int32_t & value )
    {
    serialize_bits( stream, value, 32 );
    }

    template <typename Stream> void serialize_int64( Stream & stream, int64_t & value )
    {
    uint32_t hi,lo;
    if ( Stream::IsWriting )
    {
    lo = uint64_t(value) & 0xFFFFFFFF;
    hi = uint64_t(value) >> 32;
    }
    serialize_bits( stream, lo, 32 );
    serialize_bits( stream, hi, 32 );
    if ( Stream::IsReading )
    value = ( int64_t(hi) << 32 ) | lo;
    }

    template <typename Stream> void serialize_float( Stream & stream, float & value )
    {
    union FloatInt
    {
    float float_value;
    uint32_t int_value;
    };

    FloatInt tmp;
    if ( Stream::IsWriting )
    tmp.float_value = value;

    serialize_uint32( stream, tmp.int_value );

    if ( Stream::IsReading )
    value = tmp.float_value;
    }

    template <typename Stream> inline void internal_serialize_float( Stream & stream, float & value, float min, float max, float res )
    {
    const float delta = max - min;
    const float values = delta / res;
    const uint32_t maxIntegerValue = (uint32_t) ceil( values );
    const int bits = bits_required( 0, maxIntegerValue );

    uint32_t integerValue = 0;

    if ( Stream::IsWriting )
    {
    float normalizedValue = clamp( ( value - min ) / delta, 0.0f, 1.0f );
    integerValue = (uint32_t) floor( normalizedValue * maxIntegerValue + 0.5f );
    }

    stream.SerializeBits( integerValue, bits );

    if ( Stream::IsReading )
    {
    const float normalizedValue = integerValue / float( maxIntegerValue );
    value = normalizedValue * delta + min;
    }
    }

    #define serialize_compressed_float( stream, value, min, max, res ) \
    do \
    { \
    internal_serialize_float( stream, value, min, max, res ); \
    } \
    while(0)

    template <typename Stream> void serialize_double( Stream & stream, double & value )
    {
    union DoubleInt
    {
    double double_value;
    uint64_t int_value;
    };

    DoubleInt tmp;
    if ( Stream::IsWriting )
    tmp.double_value = value;

    serialize_uint64( stream, tmp.int_value );

    if ( Stream::IsReading )
    value = tmp.double_value;
    }

    template <typename Stream> void serialize_bytes( Stream & stream, uint8_t * data, int bytes )
    {
    stream.SerializeBytes( data, bytes );
    }

    template <typename Stream> void serialize_string( Stream & stream, char * string, int buffer_size )
    {
    uint32_t length;
    if ( Stream::IsWriting )
    length = strlen( string );
    stream.Align();
    stream.SerializeBits( length, 32 );
    assert( length < buffer_size - 1 );
    stream.SerializeBytes( (uint8_t*)string, length );
    if ( Stream::IsReading )
    string[length] = '\0';
    }

    template <typename Stream, typename T> void serialize_int_relative( Stream & stream, T previous, T & current )
    {
    uint32_t difference;
    if ( Stream::IsWriting )
    {
    assert( previous < current );
    difference = current - previous;
    assert( difference >= 0 );
    }

    bool oneBit;
    if ( Stream::IsWriting )
    oneBit = difference == 1;
    serialize_bool( stream, oneBit );
    if ( oneBit )
    {
    if ( Stream::IsReading )
    current = previous + 1;
    return;
    }

    bool twoBits;
    if ( Stream::IsWriting )
    twoBits = difference == difference <= 4;
    serialize_bool( stream, twoBits );
    if ( twoBits )
    {
    serialize_int( stream, difference, 1, 4 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool fourBits;
    if ( Stream::IsWriting )
    fourBits = difference == difference <= 16;
    serialize_bool( stream, fourBits );
    if ( fourBits )
    {
    serialize_int( stream, difference, 1, 16 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool eightBits;
    if ( Stream::IsWriting )
    eightBits = difference == difference <= 256;
    serialize_bool( stream, eightBits );
    if ( eightBits )
    {
    serialize_int( stream, difference, 1, 256 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool twelveBits;
    if ( Stream::IsWriting )
    twelveBits = difference <= 4096;
    serialize_bool( stream, twelveBits );
    if ( twelveBits )
    {
    serialize_int( stream, difference, 1, 4096 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    bool sixteenBits;
    if ( Stream::IsWriting )
    sixteenBits = difference <= 65535;
    serialize_bool( stream, sixteenBits );
    if ( sixteenBits )
    {
    serialize_int( stream, difference, 1, 65536 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    uint32_t value = current;
    serialize_uint32( stream, value );
    if ( Stream::IsReading )
    current = (decltype(current)) value;
    }

    template <typename Stream> bool serialize_check( Stream & stream, uint32_t magic )
    {
    return stream.Check( magic );
    }

    #define SERIALIZE_OBJECT( stream ) \
    void SerializeRead( class ReadStream & stream ) { Serialize( stream ); }; \
    void SerializeWrite( class WriteStream & stream ) { Serialize( stream ); }; \
    void SerializeMeasure( class MeasureStream & stream ) { Serialize( stream ); }; \
    template <typename Stream> void Serialize( Stream & stream )

    template <int bits> struct compressed_quaternion
    {
    enum { max_value = (1<<bits)-1 };

    uint32_t largest : 2;
    uint32_t integer_a : bits;
    uint32_t integer_b : bits;
    uint32_t integer_c : bits;

    void Load( float x, float y, float z, float w )
    {
    assert( bits > 1 );
    assert( bits <= 10 );

    const float minimum = - 1.0f / 1.414214f; // 1.0f / sqrt(2)
    const float maximum = + 1.0f / 1.414214f;

    const float scale = float( ( 1 << bits ) - 1 );

    const float abs_x = fabs( x );
    const float abs_y = fabs( y );
    const float abs_z = fabs( z );
    const float abs_w = fabs( w );

    largest = 0;
    float largest_value = abs_x;

    if ( abs_y > largest_value )
    {
    largest = 1;
    largest_value = abs_y;
    }

    if ( abs_z > largest_value )
    {
    largest = 2;
    largest_value = abs_z;
    }

    if ( abs_w > largest_value )
    {
    largest = 3;
    largest_value = abs_w;
    }

    float a = 0;
    float b = 0;
    float c = 0;

    switch ( largest )
    {
    case 0:
    if ( x >= 0 )
    {
    a = y;
    b = z;
    c = w;
    }
    else
    {
    a = -y;
    b = -z;
    c = -w;
    }
    break;

    case 1:
    if ( y >= 0 )
    {
    a = x;
    b = z;
    c = w;
    }
    else
    {
    a = -x;
    b = -z;
    c = -w;
    }
    break;

    case 2:
    if ( z >= 0 )
    {
    a = x;
    b = y;
    c = w;
    }
    else
    {
    a = -x;
    b = -y;
    c = -w;
    }
    break;

    case 3:
    if ( w >= 0 )
    {
    a = x;
    b = y;
    c = z;
    }
    else
    {
    a = -x;
    b = -y;
    c = -z;
    }
    break;

    default:
    assert( false );
    }

    const float normal_a = ( a - minimum ) / ( maximum - minimum );
    const float normal_b = ( b - minimum ) / ( maximum - minimum );
    const float normal_c = ( c - minimum ) / ( maximum - minimum );

    integer_a = floor( normal_a * scale + 0.5f );
    integer_b = floor( normal_b * scale + 0.5f );
    integer_c = floor( normal_c * scale + 0.5f );
    }

    void Save( float & x, float & y, float & z, float & w ) const
    {
    // note: you're going to want to normalize the quaternion returned from this function

    assert( bits > 1 );
    assert( bits <= 10 );

    const float minimum = - 1.0f / 1.414214f; // 1.0f / sqrt(2)
    const float maximum = + 1.0f / 1.414214f;

    const float scale = float( ( 1 << bits ) - 1 );

    const float inverse_scale = 1.0f / scale;

    const float a = integer_a * inverse_scale * ( maximum - minimum ) + minimum;
    const float b = integer_b * inverse_scale * ( maximum - minimum ) + minimum;
    const float c = integer_c * inverse_scale * ( maximum - minimum ) + minimum;

    switch ( largest )
    {
    case 0:
    {
    x = sqrtf( 1 - a*a - b*b - c*c );
    y = a;
    z = b;
    w = c;
    }
    break;

    case 1:
    {
    x = a;
    y = sqrtf( 1 - a*a - b*b - c*c );
    z = b;
    w = c;
    }
    break;

    case 2:
    {
    x = a;
    y = b;
    z = sqrtf( 1 - a*a - b*b - c*c );
    w = c;
    }
    break;

    case 3:
    {
    x = a;
    y = b;
    z = c;
    w = sqrtf( 1 - a*a - b*b - c*c );
    }
    break;

    default:
    {
    assert( false );
    x = 0;
    y = 0;
    z = 0;
    w = 1;
    }
    }
    }

    SERIALIZE_OBJECT( stream )
    {
    serialize_bits( stream, largest, 2 );
    serialize_bits( stream, integer_a, bits );
    serialize_bits( stream, integer_b, bits );
    serialize_bits( stream, integer_c, bits );
    }

    bool operator == ( const compressed_quaternion & other ) const
    {
    if ( largest != other.largest )
    return false;

    if ( integer_a != other.integer_a )
    return false;

    if ( integer_b != other.integer_b )
    return false;

    if ( integer_c != other.integer_c )
    return false;

    return true;
    }

    bool operator != ( const compressed_quaternion & other ) const
    {
    return ! ( *this == other );
    }
    };

    inline int count_relative_index_bits( bool * changed )
    {
    int bits = 7; // 0..127 num changed
    bool first = true;
    int previous_index = 0;

    for ( int i = 0; i < NumCubes; ++i )
    {
    if ( !changed[i] )
    continue;

    if ( first )
    {
    bits += 10;
    first = false;
    previous_index = i;
    }
    else
    {
    const int difference = i - previous_index;

    if ( difference == 1 )
    {
    bits += 1;
    }
    else if ( difference <= 6 )
    {
    bits += 1 + 1 + 2;
    }
    else if ( difference <= 14 )
    {
    bits += 1 + 1 + 1 + 3;
    }
    else if ( difference <= 30 )
    {
    bits += 1 + 1 + 1 + 1 + 4;
    }
    else if ( difference <= 62 )
    {
    bits += 1 + 1 + 1 + 1 + 1 + 5;
    }
    else if ( difference <= 126 )
    {
    bits += 1 + 1 + 1 + 1 + 1 + 1 + 6;
    }
    else
    {
    bits += 1 + 1 + 1 + 1 + 1 + 1 + 1 + 10;
    }

    previous_index = i;
    }
    }

    return bits;
    }

    template <typename Stream> void serialize_relative_index( Stream & stream, int previous, int & current )
    {
    uint32_t difference;
    if ( Stream::IsWriting )
    {
    assert( previous < current );
    difference = current - previous;
    assert( difference > 0 );
    }

    // +1 (1 bit)

    bool plusOne;
    if ( Stream::IsWriting )
    plusOne = difference == 1;
    serialize_bool( stream, plusOne );
    if ( plusOne )
    {
    current = previous + 1;
    return;
    }

    // [+2,6] (2 bits)

    bool twoBits;
    if ( Stream::IsWriting )
    twoBits = difference <= 6;
    serialize_bool( stream, twoBits );
    if ( twoBits )
    {
    serialize_int( stream, difference, 2, 6 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    // [7,14] -> [0,7] (3 bits)

    bool threeBits;
    if ( Stream::IsWriting )
    threeBits = difference <= 14;
    serialize_bool( stream, threeBits );
    if ( threeBits )
    {
    serialize_int( stream, difference, 7, 14 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    // [15,30] -> [0,15] (4 bits)

    bool fourBits;
    if ( Stream::IsWriting )
    fourBits = difference <= 30;
    serialize_bool( stream, fourBits );
    if ( fourBits )
    {
    serialize_int( stream, difference, 15, 30 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    // [31,62] -> [0,31] (5 bits)

    bool fiveBits;
    if ( Stream::IsWriting )
    fiveBits = difference <= 64;
    serialize_bool( stream, fiveBits );
    if ( fiveBits )
    {
    serialize_int( stream, difference, 31, 64 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    // [63,126] -> [0,63] (6 bits)

    bool sixBits;
    if ( Stream::IsWriting )
    sixBits = difference <= 126;
    serialize_bool( stream, sixBits );
    if ( sixBits )
    {
    serialize_int( stream, difference, 63, 126 );
    if ( Stream::IsReading )
    current = previous + difference;
    return;
    }

    // [127,NumCubes]

    serialize_int( stream, difference, 127, NumCubes - 1 );
    if ( Stream::IsReading )
    current = previous + difference;
    }

    struct QuantizedCubeState
    {
    bool interacting;

    int position_x;
    int position_y;
    int position_z;

    compressed_quaternion<OrientationBits> orientation;

    bool operator == ( const QuantizedCubeState & other ) const
    {
    if ( interacting != other.interacting )
    return false;

    if ( position_x != other.position_x )
    return false;

    if ( position_y != other.position_y )
    return false;

    if ( position_z != other.position_z )
    return false;

    if ( orientation != other.orientation )
    return false;

    return true;
    }

    bool operator != ( const QuantizedCubeState & other ) const
    {
    return ! ( *this == other );
    }
    };

    struct QuantizedSnapshot
    {
    QuantizedCubeState cubes[NumCubes];

    bool operator == ( const QuantizedSnapshot & other ) const
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    if ( cubes[i] != other.cubes[i] )
    {
    printf( "mismatch cube %d\n", i );
    }
    if ( cubes[i] != other.cubes[i] )
    return false;
    }

    return true;
    }

    bool operator != ( const QuantizedSnapshot & other ) const
    {
    return ! ( *this == other );
    }
    };

    template <typename Stream> void serialize_cube_changed( Stream & stream, QuantizedCubeState & cube, const QuantizedCubeState & base )
    {
    serialize_bool( stream, cube.interacting );

    bool position_changed;
    bool orientation_changed;

    if ( Stream::IsWriting )
    {
    position_changed = cube.position_x != base.position_x || cube.position_y != base.position_y || cube.position_z != base.position_z;
    orientation_changed = cube.orientation != base.orientation;
    }

    serialize_bool( stream, position_changed );
    serialize_bool( stream, orientation_changed );

    if ( position_changed )
    {
    serialize_int( stream, cube.position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, cube.position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, cube.position_z, 0, +QuantizedPositionBoundZ - 1 );
    }
    else
    {
    cube.position_x = base.position_x;
    cube.position_y = base.position_y;
    cube.position_z = base.position_z;
    }

    if ( orientation_changed )
    serialize_object( stream, cube.orientation );
    else
    cube.orientation = base.orientation;
    }

    template <typename Stream> void serialize_offset( Stream & stream, int & offset, int small_bound, int large_bound )
    {
    if ( Stream::IsWriting )
    {
    assert( offset >= small_bound - 1 || offset <= - small_bound );

    if ( offset > 0 )
    {
    offset -= small_bound - 1;
    }
    else
    {
    offset += small_bound - 1;
    assert( offset < 0 ); // note: otherwise two offset values end up sharing the zero value
    }

    assert( offset >= -large_bound );
    assert( offset <= +large_bound - 1 );
    }

    serialize_int( stream,
    offset,
    -large_bound,
    large_bound - 1 );

    if ( Stream::IsReading )
    {
    if ( offset >= 0 )
    offset += small_bound - 1;
    else
    offset -= small_bound - 1;
    }
    }

    template <typename Stream> void serialize_relative_position( Stream & stream,
    int & position_x,
    int & position_y,
    int & position_z,
    int base_position_x,
    int base_position_y,
    int base_position_z )
    {
    const int RelativePositionBound_Small = 16;
    const int RelativePositionBound_Large = 256;

    bool relative_position = false;
    bool relative_position_small_x = false;
    bool relative_position_small_y = false;
    bool relative_position_small_z = false;

    if ( Stream::IsWriting )
    {
    const int dx = position_x - base_position_x;
    const int dy = position_y - base_position_y;
    const int dz = position_z - base_position_z;

    const int relative_min = -RelativePositionBound_Large - ( RelativePositionBound_Small - 1 ); // -256 - 15 = -271
    const int relative_max = RelativePositionBound_Large - 1 + ( RelativePositionBound_Small - 1 ); // +255 + 15 = 270

    relative_position = dx >= relative_min && dx <= relative_max &&
    dy >= relative_min && dy <= relative_max &&
    dz >= relative_min && dz <= relative_max;

    if ( relative_position )
    {
    relative_position_small_x = dx >= -RelativePositionBound_Small && dx < RelativePositionBound_Small;
    relative_position_small_y = dy >= -RelativePositionBound_Small && dy < RelativePositionBound_Small;
    relative_position_small_z = dz >= -RelativePositionBound_Small && dz < RelativePositionBound_Small;
    }
    }

    serialize_bool( stream, relative_position );

    if ( relative_position )
    {
    serialize_bool( stream, relative_position_small_x );
    serialize_bool( stream, relative_position_small_y );
    serialize_bool( stream, relative_position_small_z );

    int offset_x, offset_y, offset_z;

    if ( Stream::IsWriting )
    {
    offset_x = position_x - base_position_x;
    offset_y = position_y - base_position_y;
    offset_z = position_z - base_position_z;
    }

    if ( relative_position_small_x )
    {
    serialize_int( stream, offset_x, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_x, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_y )
    {
    serialize_int( stream, offset_y, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_y, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( relative_position_small_z )
    {
    serialize_int( stream, offset_z, -RelativePositionBound_Small, RelativePositionBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_z, RelativePositionBound_Small, RelativePositionBound_Large );
    }

    if ( Stream::IsReading )
    {
    position_x = base_position_x + offset_x;
    position_y = base_position_y + offset_y;
    position_z = base_position_z + offset_z;
    }
    }
    else
    {
    serialize_int( stream, position_x, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_y, -QuantizedPositionBoundXY, +QuantizedPositionBoundXY - 1 );
    serialize_int( stream, position_z, 0, +QuantizedPositionBoundZ - 1 );
    }
    }

    template <typename Stream> void serialize_relative_orientation( Stream & stream, compressed_quaternion<OrientationBits> & orientation, const compressed_quaternion<OrientationBits> & base_orientation )
    {
    const int RelativeOrientationBound_Small = 16;
    const int RelativeOrientationBound_Large = 128;

    bool relative_orientation = false;
    bool small_a = false;
    bool small_b = false;
    bool small_c = false;

    if ( Stream::IsWriting )
    {
    const int da = orientation.integer_a - base_orientation.integer_a;
    const int db = orientation.integer_b - base_orientation.integer_b;
    const int dc = orientation.integer_c - base_orientation.integer_c;

    const int relative_min = -RelativeOrientationBound_Large - ( RelativeOrientationBound_Small - 1 ); // -256 - 15 = -271
    const int relative_max = RelativeOrientationBound_Large - 1 + ( RelativeOrientationBound_Small - 1 ); // +255 + 15 = 270

    if ( orientation.largest == base_orientation.largest &&
    da >= relative_min && da < relative_max &&
    db >= relative_min && db < relative_max &&
    dc >= relative_min && dc < relative_max )
    {
    relative_orientation = true;

    small_a = da >= -RelativeOrientationBound_Small && da < RelativeOrientationBound_Small;
    small_b = db >= -RelativeOrientationBound_Small && db < RelativeOrientationBound_Small;
    small_c = dc >= -RelativeOrientationBound_Small && dc < RelativeOrientationBound_Small;
    }
    }

    serialize_bool( stream, relative_orientation );

    if ( relative_orientation )
    {
    serialize_bool( stream, small_a );
    serialize_bool( stream, small_b );
    serialize_bool( stream, small_c );

    int offset_a, offset_b, offset_c;

    if ( Stream::IsWriting )
    {
    offset_a = orientation.integer_a - base_orientation.integer_a;
    offset_b = orientation.integer_b - base_orientation.integer_b;
    offset_c = orientation.integer_c - base_orientation.integer_c;
    }

    if ( small_a )
    {
    serialize_int( stream, offset_a, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_a, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    }

    if ( small_b )
    {
    serialize_int( stream, offset_b, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_b, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    }

    if ( small_c )
    {
    serialize_int( stream, offset_c, -RelativeOrientationBound_Small, RelativeOrientationBound_Small - 1 );
    }
    else
    {
    serialize_offset( stream, offset_c, RelativeOrientationBound_Small, RelativeOrientationBound_Large );
    }

    if ( Stream::IsReading )
    {
    orientation.largest = base_orientation.largest;
    orientation.integer_a = base_orientation.integer_a + offset_a;
    orientation.integer_b = base_orientation.integer_b + offset_b;
    orientation.integer_c = base_orientation.integer_c + offset_c;
    }
    }
    else
    {
    serialize_object( stream, orientation );
    }
    }

    template <typename Stream> void serialize_cube_relative_to_base( Stream & stream, QuantizedCubeState & cube, const QuantizedCubeState & base )
    {
    serialize_bool( stream, cube.interacting );

    bool position_changed;
    bool orientation_changed;

    if ( Stream::IsWriting )
    {
    position_changed = cube.position_x != base.position_x || cube.position_y != base.position_y || cube.position_z != base.position_z;
    orientation_changed = cube.orientation != base.orientation;
    }

    serialize_bool( stream, position_changed );
    serialize_bool( stream, orientation_changed );

    if ( position_changed )
    {
    serialize_relative_position( stream, cube.position_x, cube.position_y, cube.position_z, base.position_x, base.position_y, base.position_z );
    }
    else if ( Stream::IsReading )
    {
    cube.position_x = base.position_x;
    cube.position_y = base.position_y;
    cube.position_z = base.position_z;
    }

    if ( orientation_changed )
    {
    serialize_relative_orientation( stream, cube.orientation, base.orientation );
    }
    else
    {
    cube.orientation = base.orientation;
    }
    }

    template <typename Stream> void serialize_snapshot_relative_to_baseline( Stream & stream, QuantizedSnapshot & current_snapshot, QuantizedSnapshot & baseline_snapshot )
    {
    QuantizedCubeState * quantized_cubes = &current_snapshot.cubes[0];
    QuantizedCubeState * quantized_base_cubes = &baseline_snapshot.cubes[0];

    const int MaxIndex = 254;

    int num_changed = 0;
    bool use_indices = false;
    bool changed[NumCubes];
    if ( Stream::IsWriting )
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    changed[i] = quantized_cubes[i] != quantized_base_cubes[i];
    if ( changed[i] )
    num_changed++;
    }
    int relative_index_bits = count_relative_index_bits( changed );
    if ( relative_index_bits <= NumCubes )
    use_indices = true;
    }

    serialize_bool( stream, use_indices );

    if ( use_indices )
    {
    serialize_int( stream, num_changed, 0, MaxIndex + 1 );

    if ( Stream::IsWriting )
    {
    int num_written = 0;

    bool first = true;
    int previous_index = 0;

    for ( int i = 0; i < NumCubes; ++i )
    {
    if ( changed[i] )
    {
    if ( first )
    {
    serialize_int( stream, i, 0, NumCubes - 1 );
    first = false;
    }
    else
    {
    serialize_relative_index( stream, previous_index, i );
    }

    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );

    num_written++;

    previous_index = i;
    }
    }

    assert( num_written == num_changed );
    }
    else
    {
    memset( changed, 0, sizeof( changed ) );

    int previous_index = 0;

    for ( int j = 0; j < num_changed; ++j )
    {
    int i;
    if ( j == 0 )
    serialize_int( stream, i, 0, NumCubes - 1 );
    else
    serialize_relative_index( stream, previous_index, i );

    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );

    changed[i] = true;

    previous_index = i;
    }

    for ( int i = 0; i < NumCubes; ++i )
    {
    if ( !changed[i] )
    memcpy( &quantized_cubes[i], &quantized_base_cubes[i], sizeof( QuantizedCubeState ) );
    }
    }
    }
    else
    {
    for ( int i = 0; i < NumCubes; ++i )
    {
    serialize_bool( stream, changed[i] );

    if ( changed[i] )
    {
    serialize_cube_relative_to_base( stream, quantized_cubes[i], quantized_base_cubes[i] );
    }
    else if ( Stream::IsReading )
    {
    memcpy( &quantized_cubes[i], &quantized_base_cubes[i], sizeof( QuantizedCubeState ) );
    }
    }
    }
    }

    struct FrameCubeData
    {
    int orientation_largest;
    int orientation_a;
    int orientation_b;
    int orientation_c;
    int position_x;
    int position_y;
    int position_z;
    int interacting;
    };

    struct Frame
    {
    FrameCubeData cubes[NumCubes];
    };

    struct Packet
    {
    int size;
    uint8_t data[MaxPacketSize];
    };

    void convert_frame_to_snapshot( const Frame & frame, QuantizedSnapshot & snapshot )
    {
    for ( int j = 0; j < NumCubes; ++j )
    {
    assert( frame.cubes[j].orientation_largest >= 0 );
    assert( frame.cubes[j].orientation_largest <= 3 );

    snapshot.cubes[j].orientation.largest = frame.cubes[j].orientation_largest;

    assert( frame.cubes[j].orientation_a >= 0 );
    assert( frame.cubes[j].orientation_b >= 0 );
    assert( frame.cubes[j].orientation_c >= 0 );

    assert( frame.cubes[j].orientation_a <= ( 1 << OrientationBits ) - 1 );
    assert( frame.cubes[j].orientation_b <= ( 1 << OrientationBits ) - 1 );
    assert( frame.cubes[j].orientation_c <= ( 1 << OrientationBits ) - 1 );

    snapshot.cubes[j].orientation.integer_a = frame.cubes[j].orientation_a;
    snapshot.cubes[j].orientation.integer_b = frame.cubes[j].orientation_b;
    snapshot.cubes[j].orientation.integer_c = frame.cubes[j].orientation_c;

    assert( frame.cubes[j].position_x >= -QuantizedPositionBoundXY );
    assert( frame.cubes[j].position_y >= -QuantizedPositionBoundXY );
    assert( frame.cubes[j].position_z >= 0 );

    assert( frame.cubes[j].position_x <= QuantizedPositionBoundXY );
    assert( frame.cubes[j].position_y <= QuantizedPositionBoundXY );
    assert( frame.cubes[j].position_z <= QuantizedPositionBoundZ );

    snapshot.cubes[j].position_x = frame.cubes[j].position_x;
    snapshot.cubes[j].position_y = frame.cubes[j].position_y;
    snapshot.cubes[j].position_z = frame.cubes[j].position_z;

    assert( frame.cubes[j].interacting == 0 || frame.cubes[j].interacting == 1 );

    snapshot.cubes[j].interacting = frame.cubes[j].interacting;
    }
    }

    int main( int argc, char ** argv )
    {
    FILE * file = fopen( "delta_data.bin", "rb" );
    if ( !file )
    {
    printf( "error: can't open file\n" );
    return 1;
    }

    // count number of frames in file

    fseek( file, 0L, SEEK_END );
    uint64_t file_size = ftell( file );
    fseek( file, 0L, SEEK_SET );

    const int num_frames = (int) floor( double(file_size) / sizeof( Frame ) );
    printf( "%d input frames\n", num_frames );
    assert( num_frames > 6 );

    // read in frames

    Frame * frames = new Frame[num_frames];
    uint64_t frames_read = fread( frames, sizeof( Frame ), num_frames, file );
    assert( frames_read == num_frames );

    // convert frames to snapshots

    QuantizedSnapshot * snapshots = new QuantizedSnapshot[num_frames];
    for ( int i = 0; i < num_frames; ++i )
    convert_frame_to_snapshot( frames[i], snapshots[i] );

    // write packets

    const int num_packets = num_frames - 6;
    printf( "writing %d packets\n", num_packets );
    assert( num_packets > 0 );

    int packet_index = 0;
    Packet * packets = new Packet[num_packets];
    uint64_t total_bytes = 0;
    for ( int i = 6; i < num_frames; ++i )
    {
    Packet & packet = packets[packet_index];

    WriteStream stream( packet.data, MaxPacketSize );

    QuantizedSnapshot & current_snapshot = snapshots[i];
    QuantizedSnapshot & baseline_snapshot = snapshots[i-6];

    serialize_snapshot_relative_to_baseline( stream, current_snapshot, baseline_snapshot );

    stream.Flush();

    const int bits_written = stream.GetBitsProcessed();
    const int bytes_written = ( bits_written / 8 ) + ( ( bits_written % 8 ) ? 1 : 0 );

    packet.size = bytes_written;
    total_bytes += bytes_written;

    ++packet_index;
    }

    // read packets and verify reconstruction of snapshot matches exactly

    printf( "reading %d packets\n", num_packets );
    for ( int i = 0; i < num_packets; ++i )
    {
    Packet & packet = packets[i];

    ReadStream stream( packet.data, MaxPacketSize );

    QuantizedSnapshot current_snapshot;
    QuantizedSnapshot & baseline_snapshot = snapshots[i];

    serialize_snapshot_relative_to_baseline( stream, current_snapshot, baseline_snapshot );

    assert( current_snapshot == snapshots[i+6] );
    }
    printf( "all packets verify ok!\n" );

    // print results

    printf( "total packet bytes: %llu\n", total_bytes );
    printf( "average bytes per-packet: %f\n", total_bytes / double(num_packets) );
    printf( "average bytes per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 );
    printf( "average kilobits per-second: %f\n", total_bytes / double(num_packets) * 60 * 8 / 1000.0 );

    // clean up everything

    delete [] snapshots;
    delete [] packets;
    delete [] frames;

    return 0;
    }

    /*
    2837 input frames
    writing 2831 packets
    reading 2831 packets
    all packets verify ok!
    total packet bytes: 1715020
    average bytes per-packet: 605.800071
    average bytes per-second: 290784.033910
    average kilobits per-second: 290.784034
    */