Skip to content

Instantly share code, notes, and snippets.

@StagPoint
Last active December 2, 2024 07:38
Show Gist options
  • Save StagPoint/bb7edf61c2e97ce54e3e4561627f6582 to your computer and use it in GitHub Desktop.
Save StagPoint/bb7edf61c2e97ce54e3e4561627f6582 to your computer and use it in GitHub Desktop.

Revisions

  1. StagPoint revised this gist Apr 25, 2016. 1 changed file with 31 additions and 4 deletions.
    35 changes: 31 additions & 4 deletions QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -58,6 +58,18 @@ public static void WriteCompressedRotation( this NetworkWriter writer, Quaternio
    }
    }

    // If the maximum value is approximately 1f (such as Quaternion.identity [0,0,0,1]), then we can
    // reduce storage even further due to the fact that all other fields must be 0f by definition, so
    // we only need to send the index of the largest field.
    if( Mathf.Approximately( maxValue, 1f ) )
    {
    // Again, don't need to transmit the sign since in quaternion space (x,y,z,w) and (-x,-y,-z,-w)
    // represent the same rotation. We only need to send the index of the single element whose value
    // is 1f in order to recreate an equivalent rotation on the receiver.
    writer.Write( maxIndex + 4 );
    return;
    }

    var a = (short)0;
    var b = (short)0;
    var c = (short)0;
    @@ -106,17 +118,32 @@ public static void WriteCompressedRotation( this NetworkWriter writer, Quaternio
    /// <returns>Returns the uncompressed rotation value as a Quaternion.</returns>
    public static Quaternion ReadCompressedRotation( this NetworkReader reader )
    {
    var flag = reader.ReadByte();
    // Read the index of the omitted field from the stream.
    var maxIndex = reader.ReadByte();

    // Values between 4 and 7 indicate that only the index of the single field whose value is 1f was
    // sent, and (maxIndex - 4) is the correct index for that field.
    if( maxIndex >= 4 && maxIndex <= 7 )
    {
    var x = ( maxIndex == 4 ) ? 1f : 0f;
    var y = ( maxIndex == 5 ) ? 1f : 0f;
    var z = ( maxIndex == 6 ) ? 1f : 0f;
    var w = ( maxIndex == 7 ) ? 1f : 0f;

    return new Quaternion( x, y, z, w );
    }

    // Read the other three fields and derive the value of the omitted field
    var a = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var b = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var c = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var d = Mathf.Sqrt( 1f - ( a * a + b * b + c * c ) );

    if( flag == 0 )
    if( maxIndex == 0 )
    return new Quaternion( d, a, b, c );
    else if( flag == 1 )
    else if( maxIndex == 1 )
    return new Quaternion( a, d, b, c );
    else if( flag == 2 )
    else if( maxIndex == 2 )
    return new Quaternion( a, b, d, c );

    return new Quaternion( a, b, c, d );
  2. StagPoint revised this gist Apr 19, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -100,7 +100,7 @@ public static void WriteCompressedRotation( this NetworkWriter writer, Quaternio

    /// <summary>
    /// Reads a compressed rotation value from the network stream. This value must have been previously written
    /// with WriteRotation() in order to be properly decompressed.
    /// with WriteCompressedRotation() in order to be properly decompressed.
    /// </summary>
    /// <param name="reader">The network stream to read the compressed rotation value from.</param>
    /// <returns>Returns the uncompressed rotation value as a Quaternion.</returns>
  3. StagPoint revised this gist Apr 19, 2016. No changes.
  4. StagPoint revised this gist Apr 19, 2016. No changes.
  5. StagPoint revised this gist Apr 19, 2016. No changes.
  6. StagPoint revised this gist Apr 19, 2016. 1 changed file with 36 additions and 38 deletions.
    74 changes: 36 additions & 38 deletions QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -1,29 +1,39 @@
    // Copyright (c) 2016 StagPoint Software
    namespace StagPoint.Networking
    {
    using System;

    using UnityEngine;
    using UnityEngine.Networking;

    /// <summary>
    /// Provides some commonly-used
    /// Provides some commonly-used functions for transferring compressed data over the network using
    /// Unity's UNET networking library.
    /// </summary>
    public static class NetworkStreamExtensions
    public static class NetworkingExtensions
    {
    #region Constants and static variables

    /// <summary>
    /// Used for Quaternion compression (see the WriteRotation() function)
    /// Used when compressing float values, where the decimal portion of the floating point value
    /// is multiplied by this number prior to storing the result in an Int16. Doing this allows
    /// us to retain five decimal places, which for many purposes is more than adequate.
    /// </summary>
    private const float QUAT_PRECISION_MULT = 10000f;
    private const float FLOAT_PRECISION_MULT = 10000f;

    #endregion

    #region NetworkReader and NetworkWriter extension methods

    /// <summary>
    /// Writes a compressed Quaternion value to the network stream. This function uses the "smallest three"
    /// method, which is well summarized here: http://gafferongames.com/networked-physics/snapshot-compression/
    /// </summary>
    /// <param name="writer">The stream to write the compressed rotation to.</param>
    /// <param name="rotation">The rotation value to be written to the stream.</param>
    public static void WriteRotation( this NetworkWriter writer, Quaternion rotation )
    public static void WriteCompressedRotation( this NetworkWriter writer, Quaternion rotation )
    {
    var max = Mathf.Max( rotation.x, rotation.y, rotation.z, rotation.w );
    var maxIndex = (byte)0;
    var maxIndex = (byte)0;
    var maxValue = float.MinValue;
    var sign = 1f;

    @@ -39,7 +49,7 @@ public static void WriteRotation( this NetworkWriter writer, Quaternion rotation
    // We don't need to explicitly transmit the sign bit of the omitted element because you
    // can make the omitted element always positive by negating the entire quaternion if
    // the omitted element is negative (in quaternion space (x,y,z,w) and (-x,-y,-z,-w)
    // represent the same rotation), but we need to keep track of the sign for use below.
    // represent the same rotation.), but we need to keep track of the sign for use below.
    sign = ( element < 0 ) ? -1 : 1;

    // Keep track of the index of the largest element
    @@ -59,27 +69,27 @@ public static void WriteRotation( this NetworkWriter writer, Quaternion rotation

    if( maxIndex == 0 )
    {
    a = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    a = (short)( rotation.y * sign * FLOAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * FLOAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * FLOAT_PRECISION_MULT );
    }
    else if( maxIndex == 1 )
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    a = (short)( rotation.x * sign * FLOAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * FLOAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * FLOAT_PRECISION_MULT );
    }
    else if( maxIndex == 2 )
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    a = (short)( rotation.x * sign * FLOAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * FLOAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * FLOAT_PRECISION_MULT );
    }
    else
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    a = (short)( rotation.x * sign * FLOAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * FLOAT_PRECISION_MULT );
    c = (short)( rotation.z * sign * FLOAT_PRECISION_MULT );
    }

    writer.Write( maxIndex );
    @@ -94,26 +104,12 @@ public static void WriteRotation( this NetworkWriter writer, Quaternion rotation
    /// </summary>
    /// <param name="reader">The network stream to read the compressed rotation value from.</param>
    /// <returns>Returns the uncompressed rotation value as a Quaternion.</returns>
    public static Quaternion ReadRotation( this NetworkReader reader )
    public static Quaternion ReadCompressedRotation( this NetworkReader reader )
    {
    var flag = reader.ReadByte();
    if( flag == 4 )
    {
    return Quaternion.Euler( reader.ReadVector3() );
    }

    var a = reader.ReadInt16();
    var b = reader.ReadInt16();
    var c = reader.ReadInt16();

    return decompressQuaternion( flag, a, b, c );
    }

    private static Quaternion decompressQuaternion( byte flag, short s1, short s2, short s3 )
    {
    var a = (float)s1 / QUAT_PRECISION_MULT;
    var b = (float)s2 / QUAT_PRECISION_MULT;
    var c = (float)s3 / QUAT_PRECISION_MULT;
    var a = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var b = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var c = (float)reader.ReadInt16() / FLOAT_PRECISION_MULT;
    var d = Mathf.Sqrt( 1f - ( a * a + b * b + c * c ) );

    if( flag == 0 )
    @@ -125,5 +121,7 @@ private static Quaternion decompressQuaternion( byte flag, short s1, short s2, s

    return new Quaternion( a, b, c, d );
    }

    #endregion
    }
    }
  7. StagPoint revised this gist Apr 19, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // Copyright (c) StagPoint Software
    // Copyright (c) 2016 StagPoint Software
    namespace StagPoint.Networking
    {
    using UnityEngine;
  8. StagPoint revised this gist Apr 19, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    // Copyright (c) StagPoint Software
    namespace StagPoint.Networking
    {
    using UnityEngine;
  9. StagPoint revised this gist Apr 18, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion QuaternionCompression.cs
    Original file line number Diff line number Diff line change
    @@ -38,7 +38,7 @@ public static void WriteRotation( this NetworkWriter writer, Quaternion rotation
    // We don't need to explicitly transmit the sign bit of the omitted element because you
    // can make the omitted element always positive by negating the entire quaternion if
    // the omitted element is negative (in quaternion space (x,y,z,w) and (-x,-y,-z,-w)
    // represent the same rotation.), but we need to keep track of the sign for use below.
    // represent the same rotation), but we need to keep track of the sign for use below.
    sign = ( element < 0 ) ? -1 : 1;

    // Keep track of the index of the largest element
  10. StagPoint renamed this gist Apr 18, 2016. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  11. StagPoint created this gist Apr 18, 2016.
    128 changes: 128 additions & 0 deletions UNetExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,128 @@
    namespace StagPoint.Networking
    {
    using UnityEngine;
    using UnityEngine.Networking;

    /// <summary>
    /// Provides some commonly-used
    /// </summary>
    public static class NetworkStreamExtensions
    {
    /// <summary>
    /// Used for Quaternion compression (see the WriteRotation() function)
    /// </summary>
    private const float QUAT_PRECISION_MULT = 10000f;

    /// <summary>
    /// Writes a compressed Quaternion value to the network stream. This function uses the "smallest three"
    /// method, which is well summarized here: http://gafferongames.com/networked-physics/snapshot-compression/
    /// </summary>
    /// <param name="writer">The stream to write the compressed rotation to.</param>
    /// <param name="rotation">The rotation value to be written to the stream.</param>
    public static void WriteRotation( this NetworkWriter writer, Quaternion rotation )
    {
    var max = Mathf.Max( rotation.x, rotation.y, rotation.z, rotation.w );
    var maxIndex = (byte)0;
    var maxValue = float.MinValue;
    var sign = 1f;

    // Determine the index of the largest (absolute value) element in the Quaternion.
    // We will transmit only the three smallest elements, and reconstruct the largest
    // element during decoding.
    for( int i = 0; i < 4; i++ )
    {
    var element = rotation[ i ];
    var abs = Mathf.Abs( rotation[ i ] );
    if( abs > maxValue )
    {
    // We don't need to explicitly transmit the sign bit of the omitted element because you
    // can make the omitted element always positive by negating the entire quaternion if
    // the omitted element is negative (in quaternion space (x,y,z,w) and (-x,-y,-z,-w)
    // represent the same rotation.), but we need to keep track of the sign for use below.
    sign = ( element < 0 ) ? -1 : 1;

    // Keep track of the index of the largest element
    maxIndex = (byte)i;
    maxValue = abs;
    }
    }

    var a = (short)0;
    var b = (short)0;
    var c = (short)0;

    // We multiply the value of each element by QUAT_PRECISION_MULT before converting to 16-bit integer
    // in order to maintain precision. This is necessary since by definition each of the three smallest
    // elements are less than 1.0, and the conversion to 16-bit integer would otherwise truncate everything
    // to the right of the decimal place. This allows us to keep five decimal places.

    if( maxIndex == 0 )
    {
    a = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    }
    else if( maxIndex == 1 )
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    }
    else if( maxIndex == 2 )
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.w * sign * QUAT_PRECISION_MULT );
    }
    else
    {
    a = (short)( rotation.x * sign * QUAT_PRECISION_MULT );
    b = (short)( rotation.y * sign * QUAT_PRECISION_MULT );
    c = (short)( rotation.z * sign * QUAT_PRECISION_MULT );
    }

    writer.Write( maxIndex );
    writer.Write( a );
    writer.Write( b );
    writer.Write( c );
    }

    /// <summary>
    /// Reads a compressed rotation value from the network stream. This value must have been previously written
    /// with WriteRotation() in order to be properly decompressed.
    /// </summary>
    /// <param name="reader">The network stream to read the compressed rotation value from.</param>
    /// <returns>Returns the uncompressed rotation value as a Quaternion.</returns>
    public static Quaternion ReadRotation( this NetworkReader reader )
    {
    var flag = reader.ReadByte();
    if( flag == 4 )
    {
    return Quaternion.Euler( reader.ReadVector3() );
    }

    var a = reader.ReadInt16();
    var b = reader.ReadInt16();
    var c = reader.ReadInt16();

    return decompressQuaternion( flag, a, b, c );
    }

    private static Quaternion decompressQuaternion( byte flag, short s1, short s2, short s3 )
    {
    var a = (float)s1 / QUAT_PRECISION_MULT;
    var b = (float)s2 / QUAT_PRECISION_MULT;
    var c = (float)s3 / QUAT_PRECISION_MULT;
    var d = Mathf.Sqrt( 1f - ( a * a + b * b + c * c ) );

    if( flag == 0 )
    return new Quaternion( d, a, b, c );
    else if( flag == 1 )
    return new Quaternion( a, d, b, c );
    else if( flag == 2 )
    return new Quaternion( a, b, d, c );

    return new Quaternion( a, b, c, d );
    }
    }
    }