|
|
@@ -47,7 +47,9 @@ static RealPlans _real_decompress_plans;
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
FFTCompressor::
|
|
|
FFTCompressor() {
|
|
|
+ _bam_minor_version = 0;
|
|
|
set_quality(-1);
|
|
|
+ _use_error_threshold = false;
|
|
|
_transpose_quats = false;
|
|
|
}
|
|
|
|
|
|
@@ -161,6 +163,34 @@ get_quality() const {
|
|
|
return _quality;
|
|
|
}
|
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FFTCompressor::set_use_error_threshold
|
|
|
+// Access: Public
|
|
|
+// Description: Enables or disables the use of the error threshold
|
|
|
+// measurement to put a cap on the amount of damage done
|
|
|
+// by lossy compression. When this is enabled, the
|
|
|
+// potential results of the compression are analyzed
|
|
|
+// before the data is written; if it is determined that
|
|
|
+// the compression will damage a particular string of
|
|
|
+// reals too much, that particular string of reals is
|
|
|
+// written uncompressed.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+void FFTCompressor::
|
|
|
+set_use_error_threshold(bool use_error_threshold) {
|
|
|
+ _use_error_threshold = use_error_threshold;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FFTCompressor::get_use_error_threshold
|
|
|
+// Access: Public
|
|
|
+// Description: Returns whether the error threshold measurement is
|
|
|
+// enabled. See set_use_error_threshold().
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool FFTCompressor::
|
|
|
+get_use_error_threshold() const {
|
|
|
+ return _use_error_threshold;
|
|
|
+}
|
|
|
+
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
// Function: FFTCompressor::set_transpose_quats
|
|
|
// Access: Public
|
|
|
@@ -251,6 +281,31 @@ write_reals(Datagram &datagram, const float *array, int length) {
|
|
|
rfftw_plan plan = get_real_compress_plan(length);
|
|
|
rfftw_one(plan, data, half_complex);
|
|
|
|
|
|
+ bool reject_compression = false;
|
|
|
+
|
|
|
+ if (_use_error_threshold) {
|
|
|
+ // As a sanity check, decode the numbers again and see how far off
|
|
|
+ // we will be from the original string.
|
|
|
+ double error = get_error(data, half_complex, length);
|
|
|
+ if (error > fft_error_threshold) {
|
|
|
+ // No good: the compression is too damage. Just write out
|
|
|
+ // lossless data.
|
|
|
+ reject_compression = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ datagram.add_bool(reject_compression);
|
|
|
+ if (reject_compression) {
|
|
|
+ if (mathutil_cat.is_debug()) {
|
|
|
+ mathutil_cat.debug()
|
|
|
+ << "Writing stream of " << length << " numbers uncompressed.\n";
|
|
|
+ }
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ datagram.add_float32(array[i]);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// Now encode the numbers, run-length encoded by size, so we only
|
|
|
// write out the number of bits we need for each number.
|
|
|
|
|
|
@@ -463,7 +518,8 @@ write_hprs(Datagram &datagram, const LVecBase3f *array, int length) {
|
|
|
// false otherwise.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
bool FFTCompressor::
|
|
|
-read_header(DatagramIterator &di) {
|
|
|
+read_header(DatagramIterator &di, int bam_minor_version) {
|
|
|
+ _bam_minor_version = bam_minor_version;
|
|
|
_quality = di.get_int8();
|
|
|
|
|
|
if (mathutil_cat.is_debug()) {
|
|
|
@@ -533,6 +589,21 @@ read_reals(DatagramIterator &di, vector_float &array) {
|
|
|
|
|
|
// Normal case: read in the FFT array, and convert it back to
|
|
|
// (nearly) the original numbers.
|
|
|
+
|
|
|
+ // First, check the reject_compression flag. If it's set, we
|
|
|
+ // decided to just write out the stream uncompressed.
|
|
|
+ bool reject_compression = false;
|
|
|
+ if (_bam_minor_version >= 8) {
|
|
|
+ reject_compression = di.get_bool();
|
|
|
+ }
|
|
|
+ if (reject_compression) {
|
|
|
+ array.reserve(array.size() + length);
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ array.push_back(di.get_float32());
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
vector_double half_complex;
|
|
|
half_complex.reserve(length);
|
|
|
int num_read = 0;
|
|
|
@@ -898,6 +969,72 @@ interpolate(double t, double a, double b) {
|
|
|
return a + t * (b - a);
|
|
|
}
|
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FFTCompressor::get_error
|
|
|
+// Access: Private
|
|
|
+// Description: Measures the error that would be incurred from
|
|
|
+// compressing the string of reals.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+double FFTCompressor::
|
|
|
+get_error(const double *data, const double *half_complex, int length) const {
|
|
|
+ double *truncated_half_complex = (double *)alloca(length * sizeof(double));
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ double scale_factor = get_scale_factor(i, length);
|
|
|
+ double num = cfloor(half_complex[i] / scale_factor + 0.5);
|
|
|
+ truncated_half_complex[i] = num * scale_factor;
|
|
|
+ }
|
|
|
+
|
|
|
+ double *new_data = (double *)alloca(length * sizeof(double));
|
|
|
+ rfftw_plan plan = get_real_decompress_plan(length);
|
|
|
+ rfftw_one(plan, &truncated_half_complex[0], new_data);
|
|
|
+
|
|
|
+ double scale = 1.0 / (double)length;
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ new_data[i] *= scale;
|
|
|
+ }
|
|
|
+
|
|
|
+ double last_value = data[0];
|
|
|
+ double last_new_value = new_data[0];
|
|
|
+
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ // First, we get the delta from each frame to the next.
|
|
|
+ double next_value = data[i];
|
|
|
+ double data_delta = data[i] - last_value;
|
|
|
+ last_value = next_value;
|
|
|
+
|
|
|
+ double next_new_value = new_data[i];
|
|
|
+ double data_new_delta = new_data[i] - last_value;
|
|
|
+ last_new_value = next_new_value;
|
|
|
+
|
|
|
+ // And we store the relative change in delta between our original
|
|
|
+ // values and our compressed values.
|
|
|
+ new_data[i] = data_new_delta - data_delta;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Our error measurement is nothing more than the standard deviation
|
|
|
+ // of the relative change in delta, from above. If this is large,
|
|
|
+ // the compressed values are moving substantially more erratically
|
|
|
+ // than the original values.
|
|
|
+
|
|
|
+ double sum = 0.0;
|
|
|
+ double sum2 = 0.0;
|
|
|
+ for (i = 0; i < length; i++) {
|
|
|
+ sum += new_data[i];
|
|
|
+ sum2 += new_data[i] * new_data[i];
|
|
|
+ }
|
|
|
+ double variance = (sum2 - (sum * sum) / length) / (length - 1);
|
|
|
+ if (variance < 0.0) {
|
|
|
+ // This can only happen due to tiny roundoff error.
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ double std_deviation = sqrt(variance);
|
|
|
+
|
|
|
+ return std_deviation;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
|
|
|
#ifdef HAVE_FFTW
|
|
|
|