|
| 1 | +############################################################################### |
| 2 | +# Copyright (c) 2019-2020 Yuri Sarudiansky |
| 3 | +# |
| 4 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | +# of this software and associated documentation files (the "Software"), to deal |
| 6 | +# in the Software without restriction, including without limitation the rights |
| 7 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 8 | +# copies of the Software, and to permit persons to whom the Software is |
| 9 | +# furnished to do so, subject to the following conditions: |
| 10 | +# |
| 11 | +# The above copyright notice and this permission notice shall be included in |
| 12 | +# all copies or substantial portions of the Software. |
| 13 | +# |
| 14 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 20 | +# THE SOFTWARE. |
| 21 | +############################################################################### |
| 22 | + |
| 23 | +# The floating point quantization code was adapted. The original code was taken from |
| 24 | +# the Game Engine Architecture book by Jason Gregory |
| 25 | + |
| 26 | +extends Reference |
| 27 | +class_name Quantize |
| 28 | + |
| 29 | +const ROTATION_BOUNDS: float = 0.707107 |
| 30 | + |
| 31 | +# Define some masks to help pack/unpack quantized rotation quaternions into integers |
| 32 | +const MASK_A_9BIT: int = 511 # 511 = 111111111 |
| 33 | +const MASK_B_9BIT: int = 511 << 9 |
| 34 | +const MASK_C_9BIT: int = 511 << 18 |
| 35 | +const MASK_INDEX_9BIT: int = 3 << 27 |
| 36 | +const MASK_SIGNAL_9BIT: int = 1 << 30 |
| 37 | + |
| 38 | + |
| 39 | +const MASK_A_10BIT: int = 1023 # 1023 = 1111111111 |
| 40 | +const MASK_B_10BIT: int = 1023 << 10 |
| 41 | +const MASK_C_10BIT: int = 1023 << 20 |
| 42 | +const MASK_INDEX_10BIT: int = 3 << 30 |
| 43 | + |
| 44 | +const MASK_A_15BIT: int = 32767 |
| 45 | +const MASK_B_15BIT: int = 32767 << 15 |
| 46 | +# The C component is packed into a secondary integer but having dedicated |
| 47 | +# constant may help reduce confusion when reading the code |
| 48 | +const MASK_C_15BIT: int = 32767 |
| 49 | +const MASK_INDEX_15BIT: int = 3 << 30 |
| 50 | +# When packing compressed quaternion data using 15 bits per component, one |
| 51 | +# bit becomes "wasted". While not entirely necessary, the system here uses |
| 52 | +# that bit to restore the signals of the original quaternion in case those |
| 53 | +# got flipped because the largest component were negative. |
| 54 | +const MASK_SIGNAL_15BIT: int = 1 << 15 |
| 55 | + |
| 56 | + |
| 57 | +# Quantize a unit float (range [0..1]) into an integer of the specified number of bits) |
| 58 | +static func quantize_unit_float(val: float, numbits: int) -> int: |
| 59 | + # Number of bits cannot exceed 32 bits |
| 60 | + assert(numbits <= 32 && numbits > 0) |
| 61 | + |
| 62 | + var intervals: int = 1 << numbits |
| 63 | + var scaled: float = val * (intervals - 1) |
| 64 | + var rounded: int = int(floor(scaled + 0.5)) |
| 65 | + |
| 66 | + if (rounded > intervals - 1): |
| 67 | + rounded = intervals - 1 |
| 68 | + |
| 69 | + return rounded |
| 70 | + |
| 71 | +static func restore_unit_float(quantized: int, numbits: int) -> float: |
| 72 | + assert(numbits <= 32 && numbits > 0) |
| 73 | + |
| 74 | + var intervals: int = 1 << numbits |
| 75 | + var intervalsize: float = 1.0 / (intervals - 1) |
| 76 | + var approxfloat: float = float(quantized) * intervalsize |
| 77 | + |
| 78 | + |
| 79 | + return approxfloat |
| 80 | + |
| 81 | +# Quantize a float in the range [minval..maxval] |
| 82 | +static func quantize_float(val: float, minval: float, maxval: float, numbits: int) -> int: |
| 83 | + var unitfloat: float = (val - minval) / (maxval - minval) |
| 84 | + var quantized: int = quantize_unit_float(unitfloat, numbits) |
| 85 | + |
| 86 | + return quantized |
| 87 | + |
| 88 | +# Restore float in arbitrary range |
| 89 | +static func restore_float(quantized: int, minval: float, maxval: float, numbits: int) -> float: |
| 90 | + var unitfloat: float = restore_unit_float(quantized, numbits) |
| 91 | + var val: float = minval + (unitfloat * (maxval - minval)) |
| 92 | + |
| 93 | + return val |
| 94 | + |
| 95 | + |
| 96 | +# Compress the given rotation quaternion using the specified number of bits per component |
| 97 | +# using the smallest three method. The returned dictionary contains 5 fields: |
| 98 | +# a, b, c -> the smallest three quantized components |
| 99 | +# index -> the index [0..3] of the dropped (largest) component |
| 100 | +# sig -> The "signal" of the dropped component (1.0 if >= 0, -1.0 if negative) |
| 101 | +# Note: Signal is not exactly necessary, but is provided just so if there is any desire to encode it |
| 102 | +static func compress_rotation_quat(q: Quat, numbits: int) -> Dictionary: |
| 103 | + # Unfortunately it's not possible to directly iterate through the quaternion's components |
| 104 | + # using a loop, so create a temporary array to store them |
| 105 | + var aq: Array = [q.x, q.y, q.z, q.w] |
| 106 | + var mindex: int = 0 # Index of largest component |
| 107 | + var mval: float = -1.0 # Largest component value |
| 108 | + var sig: float = 1.0 # "Signal" of the dropped component |
| 109 | + |
| 110 | + # Locate the largest component, storing its absolute value as well as the index |
| 111 | + # (0 = x, 1 = y, 2 = z and 3 = w) |
| 112 | + for i in 4: |
| 113 | + var abval: float = abs(aq[i]) |
| 114 | + |
| 115 | + if (abval > mval): |
| 116 | + mval = abval |
| 117 | + mindex = i |
| 118 | + |
| 119 | + if (aq[mindex] < 0.0): |
| 120 | + sig = -1.0 |
| 121 | + |
| 122 | + # Drop the largest component |
| 123 | + aq.erase(aq[mindex]) |
| 124 | + |
| 125 | + # Now loop again through the components, quantizing them |
| 126 | + for i in 3: |
| 127 | + var fl: float = aq[i] * sig |
| 128 | + aq[i] = quantize_float(fl, -ROTATION_BOUNDS, ROTATION_BOUNDS, numbits) |
| 129 | + |
| 130 | + |
| 131 | + return { |
| 132 | + "a": aq[0], |
| 133 | + "b": aq[1], |
| 134 | + "c": aq[2], |
| 135 | + "index": mindex, |
| 136 | + "sig": 1 if (sig == 1.0) else 0, |
| 137 | + } |
| 138 | + |
| 139 | + |
| 140 | +# Restore the rotation quaternion. The quantized values must be given in a dictionary with |
| 141 | +# the same format of the one returned by the compress_rotation_quat() function. |
| 142 | +static func restore_rotation_quat(quant: Dictionary, numbits: int) -> Quat: |
| 143 | + # Take the signal (just a multiplier) |
| 144 | + var sig: float = 1.0 if quant.sig == 1 else -1.0 |
| 145 | + |
| 146 | + # Restore components a, b and c |
| 147 | + var ra: float = restore_float(quant.a, -ROTATION_BOUNDS, ROTATION_BOUNDS, numbits) * sig |
| 148 | + var rb: float = restore_float(quant.b, -ROTATION_BOUNDS, ROTATION_BOUNDS, numbits) * sig |
| 149 | + var rc: float = restore_float(quant.c, -ROTATION_BOUNDS, ROTATION_BOUNDS, numbits) * sig |
| 150 | + # Restore the dropped component |
| 151 | + var dropped: float = sqrt(1.0 - ra*ra - rb*rb - rc*rc) * sig |
| 152 | + |
| 153 | + var ret: Quat = Quat() |
| 154 | + |
| 155 | + match quant.index: |
| 156 | + 0: |
| 157 | + # X was dropped |
| 158 | + ret = Quat(dropped, ra, rb, rc) |
| 159 | + |
| 160 | + 1: |
| 161 | + # Y was dropped |
| 162 | + ret = Quat(ra, dropped, rb, rc) |
| 163 | + |
| 164 | + 2: |
| 165 | + # Z was dropped |
| 166 | + ret = Quat(ra, rb, dropped, rc) |
| 167 | + |
| 168 | + 3: |
| 169 | + # W was dropped |
| 170 | + ret = Quat(ra, rb, rc, dropped) |
| 171 | + |
| 172 | + return ret |
| 173 | + |
| 174 | + |
| 175 | + |
| 176 | +# Compress the given rotation quaternion using 9 bits per component. This is a "wrapper" |
| 177 | +# function that packs the quantized value into a single integer. Because there is still |
| 178 | +# some "room" (only 29 bits of the 32 are used), the original signal of the quaternion is |
| 179 | +# also stored, meaning that it can be restored. |
| 180 | +static func compress_rquat_9bits(q: Quat) -> int: |
| 181 | + # Compress the components using the generalized Quat compression |
| 182 | + var c: Dictionary = compress_rotation_quat(q, 9) |
| 183 | + return ( ((c.sig << 30) & MASK_SIGNAL_9BIT) | |
| 184 | + ((c.index << 27) & MASK_INDEX_9BIT) | |
| 185 | + ((c.c << 18) & MASK_C_9BIT) | |
| 186 | + ((c.b << 9) & MASK_B_9BIT) | |
| 187 | + (c.a & MASK_A_9BIT) ) |
| 188 | + |
| 189 | +# Restores a quaternion that was previously quantized into a single integer using 9 bits |
| 190 | +# per component. In this case the original signal of the quaternion will be restored. |
| 191 | +static func restore_rquat_9bits(compressed: int) -> Quat: |
| 192 | + var unpacked: Dictionary = { |
| 193 | + "a": compressed & MASK_A_9BIT, |
| 194 | + "b": (compressed & MASK_B_9BIT) >> 9, |
| 195 | + "c": (compressed & MASK_C_9BIT) >> 18, |
| 196 | + "index": (compressed & MASK_INDEX_9BIT) >> 27, |
| 197 | + "sig": (compressed & MASK_SIGNAL_9BIT) >> 30, |
| 198 | + } |
| 199 | + |
| 200 | + return restore_rotation_quat(unpacked, 9) |
| 201 | + |
| 202 | + |
| 203 | +# Compress the given rotation quaternion using 10 bits per component. This is a "wrapper" |
| 204 | +# function that packs the quantized values into a single integer. Note that in this case |
| 205 | +# the restored quaternion may be entirely "flipped" as the original signal cannot be |
| 206 | +# stored within the packed integer. |
| 207 | +static func compress_rquat_10bits(q: Quat) -> int: |
| 208 | + # Compress the components using the generalized function |
| 209 | + var c: Dictionary = compress_rotation_quat(q, 10) |
| 210 | + return ( ((c.index << 30) & MASK_INDEX_10BIT) | |
| 211 | + ((c.c << 20) & MASK_C_10BIT) | |
| 212 | + ((c.b << 10) & MASK_B_10BIT) | |
| 213 | + (c.a & MASK_A_10BIT) ) |
| 214 | + |
| 215 | +# Restores a quaternion that was previously quantized into a single integer using 10 bits |
| 216 | +# per component. In this case the original signal may not be restored. |
| 217 | +static func restore_rquat_10bits(c: int) -> Quat: |
| 218 | + # Unpack the components |
| 219 | + var unpacked: Dictionary = { |
| 220 | + "a": c & MASK_A_10BIT, |
| 221 | + "b": (c & MASK_B_10BIT) >> 10, |
| 222 | + "c": (c & MASK_C_10BIT) >> 20, |
| 223 | + "index": (c & MASK_INDEX_10BIT) >> 30, |
| 224 | + "sig": 1, # Use 1.0 as multiplier because the signal cannot be restored in this case |
| 225 | + } |
| 226 | + |
| 227 | + return restore_rotation_quat(unpacked, 10) |
| 228 | + |
| 229 | + |
| 230 | +# Compress the given rotation quaternion using 15 bits per component. This is a "wrapper" |
| 231 | +# function that packs the quantized values into two intergers (using PoolIntArray). In |
| 232 | +# memory thsi will still use the full range of the integer values, but the second entry in |
| 233 | +# the returned array can safely discard 16 bits, which is basically the desired usage when |
| 234 | +# sending data through network. Note that in this case, using a full 32 bit + 16 bit leaves |
| 235 | +# room for a single bit, which is used to encode the original quaternion signal. |
| 236 | +static func compress_rquat_15bits(q: Quat) -> PoolIntArray: |
| 237 | + # Obtain the compressed data |
| 238 | + var c: Dictionary = compress_rotation_quat(q, 15) |
| 239 | + |
| 240 | + # Pack the first element of the array - contains index, A and B elements |
| 241 | + var packed0: int = ( ((c.index << 30) & MASK_INDEX_15BIT) | |
| 242 | + ((c.b << 15) & MASK_B_15BIT) | |
| 243 | + (c.a & MASK_A_15BIT) ) |
| 244 | + |
| 245 | + # Pack the second element of the array - contains signal and C element |
| 246 | + var packed1: int = (((c.sig & MASK_SIGNAL_15BIT) << 15) | (c.c & MASK_C_15BIT)) |
| 247 | + |
| 248 | + return PoolIntArray([packed0, packed1]) |
| 249 | + |
| 250 | +# Restores a quaternion compressed using 15 bits per component. The input must be integers |
| 251 | +# within the PoolIntArray of the compression function, in the same order for the arguments. |
| 252 | +static func restore_rquat_15bits(pack0: int, pack1: int) -> Quat: |
| 253 | + # Unpack the elements |
| 254 | + var unpacked: Dictionary = { |
| 255 | + "a": pack0 & MASK_A_15BIT, |
| 256 | + "b": (pack0 & MASK_B_15BIT) >> 15, |
| 257 | + "c": pack1 & MASK_C_15BIT, |
| 258 | + "index": (pack0 & MASK_INDEX_15BIT) >> 30, |
| 259 | + "sig": (pack1 & MASK_SIGNAL_15BIT) >> 15 |
| 260 | + } |
| 261 | + |
| 262 | + return restore_rotation_quat(unpacked, 15) |
| 263 | + |
| 264 | + |
0 commit comments