diff --git a/common/src/main/scala/net/psforever/newcodecs/QuantizedDoubleCodec.scala b/common/src/main/scala/net/psforever/newcodecs/QuantizedDoubleCodec.scala new file mode 100644 index 00000000..8f054d63 --- /dev/null +++ b/common/src/main/scala/net/psforever/newcodecs/QuantizedDoubleCodec.scala @@ -0,0 +1,54 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.newcodecs + +import scodec.{ Attempt, Codec, DecodeResult, Err, SizeBound } +import scodec.bits.{ BitVector, ByteOrdering } + +final class QuantizedDoubleCodec(min: Double, max: Double, bits: Int) extends Codec[Double] { + + require(bits > 0 && bits <= 32, "bits must be in range [1, 32]") + + private val bitsL = bits.toLong + + private def description = s"$bits-bit q_double [$min, $max]" + + override def sizeBound = SizeBound.exact(bitsL) + + def QuantizeDouble(value : Double) : Int = { + val range : Double = max - min; + + if (range == 0.0) + return 0 + + val bit_max : Int = 1 << bits; + val rounded_quantized : Int = math.floor((value - min) * bit_max.toDouble / range + 0.5).toInt + + if (rounded_quantized < 0) + return 0 + + if (rounded_quantized > bit_max - 1) + return (bit_max - 1) + + return rounded_quantized + } + + def UnquantizeDouble(value : Int) : Double = { + return ((max - min) * value.toDouble / (1 << bitsL).toDouble + min) + } + + override def encode(value: Double) = { + if (value == 0.0) + Attempt.successful(BitVector.fromInt(0, bits)) + else + Attempt.successful(BitVector.fromInt(QuantizeDouble(value), bits, ByteOrdering.LittleEndian)) + } + + override def decode(buffer: BitVector) = { + if (buffer.sizeGreaterThanOrEqual(bitsL)) + Attempt.successful(DecodeResult(UnquantizeDouble(buffer.take(bitsL).toInt(false, ByteOrdering.LittleEndian)), buffer.drop(bitsL))) + else + Attempt.failure(Err.insufficientBits(bitsL, buffer.size)) + } + + override def toString = description +} diff --git a/common/src/main/scala/net/psforever/newcodecs/package.scala b/common/src/main/scala/net/psforever/newcodecs/package.scala new file mode 100644 index 00000000..8b090ab1 --- /dev/null +++ b/common/src/main/scala/net/psforever/newcodecs/package.scala @@ -0,0 +1,10 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.newcodecs + +import scodec.Codec + +package object newcodecs { + + def q_double(min: Double, max: Double, bits: Int): Codec[Double] = new QuantizedDoubleCodec(min, max, bits) + +} diff --git a/common/src/test/scala/CodecTest.scala b/common/src/test/scala/CodecTest.scala new file mode 100644 index 00000000..7d05ed21 --- /dev/null +++ b/common/src/test/scala/CodecTest.scala @@ -0,0 +1,34 @@ +// Copyright (c) 2016 PSForever.net to present +import org.specs2.mutable._ +import net.psforever.newcodecs._ +import scodec.bits._ + +class CodecTest extends Specification { + + "QuantizedDoubleCodec" should { + val string_1 = hex"6E2D70" + val string_2 = hex"000000" + val string_3 = hex"B616" + val string_4 = hex"857C" + val string_5 = hex"5380" + val string_6 = hex"FFFC" + + "decode" in { + newcodecs.q_double(0.0, 8192.0, 20).decode(string_1.bits).require.value mustEqual 3674.859375 + newcodecs.q_double(0.0, 8192.0, 20).decode(string_2.bits).require.value mustEqual 0.0 + newcodecs.q_double(0.0, 1024.0, 16).decode(string_3.bits).require.value mustEqual 90.84375 + newcodecs.q_double(-256.0, 256.0, 14).decode(string_4.bits).require.value mustEqual -3.84375 + newcodecs.q_double(-256.0, 256.0, 14).decode(string_5.bits).require.value mustEqual 2.59375 + newcodecs.q_double(-256.0, 256.0, 14).decode(string_6.bits).require.value mustEqual 255.96875 + } + + "encode" in { + newcodecs.q_double(0.0, 8192.0, 20).encode(3674.859375).require.bytes mustEqual string_1 + newcodecs.q_double(0.0, 8192.0, 20).encode(-1.23).require.bytes mustEqual string_2 + newcodecs.q_double(0.0, 1024.0, 16).encode(90.84375).require.bytes mustEqual string_3 + newcodecs.q_double(-256.0, 256.0, 14).encode(-3.84375).require.bytes mustEqual string_4 + newcodecs.q_double(-256.0, 256.0, 14).encode(2.59375).require.bytes mustEqual string_5 + newcodecs.q_double(-256.0, 256.0, 14).encode(257.0).require.bytes mustEqual string_6 + } + } +}