diff --git a/dtool/Config.pp b/dtool/Config.pp index 46f69e223b..cd60a10e0a 100644 --- a/dtool/Config.pp +++ b/dtool/Config.pp @@ -175,6 +175,13 @@ #defer HAVE_TIFF $[libtest $[TIFF_LPATH],$[TIFF_LIBS]] +// Is libfftw installed, and where? +#define FFTW_IPATH /usr/local/include +#define FFTW_LPATH /usr/local/lib +#define FFTW_LIBS rfftw fftw +#defer HAVE_FFTW $[libtest $[FFTW_LPATH],$[FFTW_LIBS]] + + // Is VRPN installed, and where? #define VRPN_IPATH #define VRPN_LPATH diff --git a/dtool/LocalSetup.pp b/dtool/LocalSetup.pp index d0392b197b..49a44004b5 100644 --- a/dtool/LocalSetup.pp +++ b/dtool/LocalSetup.pp @@ -40,6 +40,9 @@ $[cdefine HAVE_JPEG] /* Define if we have libtiff installed. */ $[cdefine HAVE_TIFF] +/* Define if we have libfftw installed. */ +$[cdefine HAVE_FFTW] + /* Define if we have VRPN installed. */ $[cdefine HAVE_VRPN] diff --git a/dtool/pptempl/Global.pp b/dtool/pptempl/Global.pp index d543859ed8..6857316aca 100644 --- a/dtool/pptempl/Global.pp +++ b/dtool/pptempl/Global.pp @@ -145,6 +145,13 @@ #define tiff_libs $[TIFF_LIBS] #endif +#if $[HAVE_FFTW] + #define fftw_ipath $[wildcard $[FFTW_IPATH]] + #define fftw_lpath $[wildcard $[FFTW_LPATH]] + #define fftw_cflags $[FFTW_CFLAGS] + #define fftw_libs $[FFTW_LIBS] +#endif + #if $[HAVE_VRPN] #define vrpn_ipath $[wildcard $[VRPN_IPATH]] #define vrpn_lpath $[wildcard $[VRPN_LPATH]] @@ -211,6 +218,7 @@ $[or $[not $[DIRECTORY_IF_SGIGL]],$[HAVE_SGIGL]], \ $[or $[not $[DIRECTORY_IF_JPEG]],$[HAVE_JPEG]], \ $[or $[not $[DIRECTORY_IF_TIFF]],$[HAVE_TIFF]], \ + $[or $[not $[DIRECTORY_IF_FFTW]],$[HAVE_FFTW]], \ $[or $[not $[DIRECTORY_IF_VRPN]],$[HAVE_VRPN]], \ $[or $[not $[DIRECTORY_IF_GTKMM]],$[HAVE_GTKMM]], \ $[or $[not $[DIRECTORY_IF_MAYA]],$[HAVE_MAYA]], \ @@ -239,6 +247,7 @@ $[or $[not $[TARGET_IF_SGIGL]],$[HAVE_SGIGL]], \ $[or $[not $[TARGET_IF_JPEG]],$[HAVE_JPEG]], \ $[or $[not $[TARGET_IF_TIFF]],$[HAVE_TIFF]], \ + $[or $[not $[TARGET_IF_FFTW]],$[HAVE_FFTW]], \ $[or $[not $[TARGET_IF_VRPN]],$[HAVE_VRPN]], \ $[or $[not $[TARGET_IF_GTKMM]],$[HAVE_GTKMM]], \ $[or $[not $[TARGET_IF_MAYA]],$[HAVE_MAYA]], \ @@ -274,6 +283,7 @@ $[if $[HAVE_CRYPTO],$[IF_CRYPTO_SOURCES]] \ $[if $[HAVE_JPEG],$[IF_JPEG_SOURCES]] \ $[if $[HAVE_TIFF],$[IF_TIFF_SOURCES]] \ + $[if $[HAVE_FFTW],$[IF_FFTW_SOURCES]] \ $[if $[HAVE_ZLIB],$[IF_ZLIB_SOURCES]] \ $[if $[HAVE_IPC],$[IF_IPC_SOURCES]] \ $[if $[HAVE_NET],$[IF_NET_SOURCES]] \ @@ -284,6 +294,7 @@ $[IF_CRYPTO_SOURCES] \ $[IF_JPEG_SOURCES] \ $[IF_TIFF_SOURCES] \ + $[IF_FFTW_SOURCES] \ $[IF_ZLIB_SOURCES] \ $[IF_IPC_SOURCES] \ $[IF_NET_SOURCES] \ @@ -359,6 +370,9 @@ #if $[ne $[USE_TIFF] $[components $[USE_TIFF],$[active_component_libs]],] #set alt_cflags $[alt_cflags] $[tiff_cflags] #endif + #if $[ne $[USE_FFTW] $[components $[USE_FFTW],$[active_component_libs]],] + #set alt_cflags $[alt_cflags] $[fftw_cflags] + #endif #if $[ne $[USE_VRPN] $[components $[USE_VRPN],$[active_component_libs]],] #set alt_cflags $[alt_cflags] $[vrpn_cflags] #endif @@ -418,6 +432,9 @@ #if $[ne $[USE_TIFF] $[components $[USE_TIFF],$[active_component_libs]],] #set alt_ipath $[alt_ipath] $[tiff_ipath] #endif + #if $[ne $[USE_FFTW] $[components $[USE_FFTW],$[active_component_libs]],] + #set alt_ipath $[alt_ipath] $[fftw_ipath] + #endif #if $[ne $[USE_VRPN] $[components $[USE_VRPN],$[active_component_libs]],] #set alt_ipath $[alt_ipath] $[vrpn_ipath] #endif @@ -477,6 +494,9 @@ #if $[ne $[USE_TIFF] $[components $[USE_TIFF],$[active_component_libs]],] #set alt_lpath $[alt_lpath] $[tiff_lpath] #endif + #if $[ne $[USE_FFTW] $[components $[USE_FFTW],$[active_component_libs]],] + #set alt_lpath $[alt_lpath] $[fftw_lpath] + #endif #if $[ne $[USE_VRPN] $[components $[USE_VRPN],$[active_component_libs]],] #set alt_lpath $[alt_lpath] $[vrpn_lpath] #endif @@ -537,6 +557,9 @@ #if $[ne $[USE_TIFF] $[components $[USE_TIFF],$[active_component_libs]],] #set alt_libs $[alt_libs] $[tiff_libs] #endif + #if $[ne $[USE_FFTW] $[components $[USE_FFTW],$[active_component_libs]],] + #set alt_libs $[alt_libs] $[fftw_libs] + #endif #if $[ne $[USE_VRPN] $[components $[USE_VRPN],$[active_component_libs]],] #set alt_libs $[alt_libs] $[vrpn_libs] #endif diff --git a/panda/src/chan/animChannel.cxx b/panda/src/chan/animChannel.cxx index 7ab540de95..dd15bbe0a0 100644 --- a/panda/src/chan/animChannel.cxx +++ b/panda/src/chan/animChannel.cxx @@ -5,8 +5,41 @@ #include "animChannel.h" +#include // Tell GCC that we'll take care of the instantiation explicitly here. #ifdef __GNUC__ #pragma implementation #endif + +//////////////////////////////////////////////////////////////////// +// Function: ACMatrixSwitchType::output_value +// Access: Public, Static +// Description: Outputs a very brief description of a matrix. +//////////////////////////////////////////////////////////////////// +void ACMatrixSwitchType:: +output_value(ostream &out, const ACMatrixSwitchType::ValueType &value) { + LVecBase3f scale, hpr, translate; + if (decompose_matrix(value, scale, hpr, translate)) { + if (!scale.almost_equal(LVecBase3f(1.0, 1.0, 1.0))) { + if (IS_NEARLY_EQUAL(scale[0], scale[1]) && + IS_NEARLY_EQUAL(scale[1], scale[2])) { + out << " scale " << scale[0]; + } else { + out << " scale " << scale; + } + } + + if (!hpr.almost_equal(LVecBase3f(0.0, 0.0, 0.0))) { + out << " hpr " << hpr; + } + + if (!translate.almost_equal(LVecBase3f(0.0, 0.0, 0.0))) { + out << " trans " << translate; + } + + } else { + out << " mat " << value; + } +} + diff --git a/panda/src/chan/animChannel.h b/panda/src/chan/animChannel.h index 3cca05a6e9..a6bb223c6e 100644 --- a/panda/src/chan/animChannel.h +++ b/panda/src/chan/animChannel.h @@ -72,6 +72,8 @@ public: static const char *get_channel_type_name() { return "AnimChannelMatrix"; } static const char *get_fixed_channel_type_name() { return "AnimChannelMatrixFixed"; } static const char *get_part_type_name() { return "MovingPart"; } + static void output_value(ostream &out, const ValueType &value); + static void write_datagram(Datagram &dest, ValueType& me) { me.write_datagram(dest); @@ -92,6 +94,9 @@ public: static const char *get_channel_type_name() { return "AnimChannelScalar"; } static const char *get_fixed_channel_type_name() { return "AnimChannelScalarFixed"; } static const char *get_part_type_name() { return "MovingPart"; } + static void output_value(ostream &out, ValueType value) { + out << value; + } static void write_datagram(Datagram &dest, ValueType& me) { dest.add_float32(me); diff --git a/panda/src/chan/animChannelMatrixXfmTable.I b/panda/src/chan/animChannelMatrixXfmTable.I index 1fbd8fc78b..8facb2212b 100644 --- a/panda/src/chan/animChannelMatrixXfmTable.I +++ b/panda/src/chan/animChannelMatrixXfmTable.I @@ -30,6 +30,21 @@ has_table(char table_id) const { return !(_tables[table_index] == NULL); } +//////////////////////////////////////////////////////////////////// +// Function: AnimChannelMatrixXfmTable::get_table +// Access: Public +// Description: Returns a pointer to the indicated subtable's data, +// if it exists, or NULL if it does not. +//////////////////////////////////////////////////////////////////// +INLINE CPTA_float AnimChannelMatrixXfmTable:: +get_table(char table_id) const { + int table_index = get_table_index(table_id); + if (table_index < 0) { + return CPTA_float(); + } + return _tables[table_index]; +} + //////////////////////////////////////////////////////////////////// // Function: AnimChannelMatrixXfmTable::clear_table // Access: Public diff --git a/panda/src/chan/animChannelMatrixXfmTable.cxx b/panda/src/chan/animChannelMatrixXfmTable.cxx index 5019998c47..4f38bf5126 100644 --- a/panda/src/chan/animChannelMatrixXfmTable.cxx +++ b/panda/src/chan/animChannelMatrixXfmTable.cxx @@ -13,6 +13,7 @@ #include #include #include +#include TypeHandle AnimChannelMatrixXfmTable::_type_handle; @@ -226,9 +227,15 @@ write_datagram(BamWriter *manager, Datagram &me) { AnimChannelMatrix::write_datagram(manager, me); - me.add_bool(quantize_bam_channels); - if (!quantize_bam_channels) { - // Write out everything the old way, as floats. + if (compress_channels && !FFTCompressor::is_compression_available()) { + chan_cat.error() + << "Compression is not available; writing uncompressed channels.\n"; + compress_channels = false; + } + + me.add_bool(compress_channels); + if (!compress_channels) { + // Write out everything uncompressed, as a stream of floats. for(int i = 0; i < num_tables; i++) { me.add_uint16(_tables[i].size()); for(int j = 0; j < (int)_tables[i].size(); j++) { @@ -237,32 +244,35 @@ write_datagram(BamWriter *manager, Datagram &me) } } else { - // Write out everything the compact way, as quantized integers. + // Write out everything using lossy compression. - // First, write out the scales. These will be in the range 1/256 .. 255. + FFTCompressor compressor; + compressor.set_quality(compress_chan_quality); + compressor.write_header(me); + + // First, write out the scales. int i; for(i = 0; i < 3; i++) { - me.add_uint16(_tables[i].size()); - for(int j = 0; j < (int)_tables[i].size(); j++) { - me.add_uint16((int)max(min(_tables[i][j]*256.0, 65535.0), 0.0)); - } + compressor.write_reals(me, _tables[i], _tables[i].size()); } - // Now, write out the joint angles. These are in the range 0 .. 360. - for(i = 3; i < 6; i++) { - me.add_uint16(_tables[i].size()); - for(int j = 0; j < (int)_tables[i].size(); j++) { - me.add_uint16((unsigned int)(_tables[i][j] * 65536.0 / 360.0)); - } + // Now, write out the joint angles. For these we need to build up + // a HPR array. + vector_LVecBase3f hprs; + int hprs_length = + max(max(_tables[3].size(), _tables[4].size()), _tables[5].size()); + hprs.reserve(hprs_length); + for (i = 0; i < hprs_length; i++) { + float h = (i < (int)_tables[3].size()) ? _tables[3][i] : 0.0f; + float p = (i < (int)_tables[4].size()) ? _tables[4][i] : 0.0f; + float r = (i < (int)_tables[5].size()) ? _tables[5][i] : 0.0f; + hprs.push_back(LVecBase3f(h, p, r)); } + compressor.write_hprs(me, &hprs[0], hprs_length); - // And now write out the translations. These are in the range - // -1000 .. 1000. + // And now the translations. for(i = 6; i < 9; i++) { - me.add_uint16(_tables[i].size()); - for(int j = 0; j < (int)_tables[i].size(); j++) { - me.add_int16((int)max(min(_tables[i][j] * 32767.0 / 1000.0, 32767.0), -32767.0)); - } + compressor.write_reals(me, _tables[i], _tables[i].size()); } } } @@ -280,9 +290,9 @@ fillin(DatagramIterator& scan, BamReader* manager) { AnimChannelMatrix::fillin(scan, manager); - bool wrote_quantized = scan.get_bool(); + bool wrote_compressed = scan.get_bool(); - if (!wrote_quantized) { + if (!wrote_compressed) { // Regular floats. for(int i = 0; i < num_tables; i++) { int size = scan.get_uint16(); @@ -294,36 +304,43 @@ fillin(DatagramIterator& scan, BamReader* manager) } } else { - // Quantized int16's and expand to floats. - int i; + // Compressed channels. + if (manager->get_file_minor_ver() < 1) { + chan_cat.error() + << "Cannot read old-style quantized channels.\n"; + return; + } + FFTCompressor compressor; + compressor.read_header(scan); + + int i; // First, read in the scales. for(i = 0; i < 3; i++) { - int size = scan.get_uint16(); - PTA_float ind_table; - for(int j = 0; j < size; j++) { - ind_table.push_back((double)scan.get_uint16() / 256.0); - } + PTA_float ind_table(0); + compressor.read_reals(scan, ind_table.v()); _tables[i] = ind_table; } - // Then, read in the joint angles. - for(i = 3; i < 6; i++) { - int size = scan.get_uint16(); - PTA_float ind_table; - for(int j = 0; j < size; j++) { - ind_table.push_back((double)scan.get_uint16() * 360.0 / 65536.0); - } - _tables[i] = ind_table; + // Read in the HPR array and store it back in the joint angles. + vector_LVecBase3f hprs; + compressor.read_hprs(scan, hprs); + PTA_float h_table(hprs.size()); + PTA_float p_table(hprs.size()); + PTA_float r_table(hprs.size()); + for (i = 0; i < (int)hprs.size(); i++) { + h_table[i] = hprs[i][0]; + p_table[i] = hprs[i][1]; + r_table[i] = hprs[i][2]; } + _tables[3] = h_table; + _tables[4] = p_table; + _tables[5] = r_table; // Now read in the translations. - for(i = 6; i < 9; i++) { - int size = scan.get_uint16(); - PTA_float ind_table; - for(int j = 0; j < size; j++) { - ind_table.push_back((double)scan.get_int16() * 1000.0 / 32767.0); - } + for (i = 6; i < 9; i++) { + PTA_float ind_table(0); + compressor.read_reals(scan, ind_table.v()); _tables[i] = ind_table; } } diff --git a/panda/src/chan/animChannelMatrixXfmTable.h b/panda/src/chan/animChannelMatrixXfmTable.h index 7b71517296..4a07d18ad2 100644 --- a/panda/src/chan/animChannelMatrixXfmTable.h +++ b/panda/src/chan/animChannelMatrixXfmTable.h @@ -35,6 +35,7 @@ public: void clear_all_tables(); void set_table(char table_id, const CPTA_float &table); INLINE bool has_table(char table_id) const; + INLINE CPTA_float get_table(char table_id) const; INLINE void clear_table(char table_id); virtual void write(ostream &out, int indent_level) const; diff --git a/panda/src/chan/animChannelScalarTable.cxx b/panda/src/chan/animChannelScalarTable.cxx index a929a272a4..dc15129a77 100644 --- a/panda/src/chan/animChannelScalarTable.cxx +++ b/panda/src/chan/animChannelScalarTable.cxx @@ -12,6 +12,7 @@ #include #include #include +#include TypeHandle AnimChannelScalarTable::_type_handle; @@ -118,8 +119,14 @@ write_datagram(BamWriter *manager, Datagram &me) { AnimChannelScalar::write_datagram(manager, me); - me.add_bool(quantize_bam_channels); - if (!quantize_bam_channels) { + if (compress_channels && !FFTCompressor::is_compression_available()) { + chan_cat.error() + << "Compression is not available; writing uncompressed channels.\n"; + compress_channels = false; + } + + me.add_bool(compress_channels); + if (!compress_channels) { // Write out everything the old way, as floats. me.add_uint16(_table.size()); for(int i = 0; i < (int)_table.size(); i++) { @@ -127,12 +134,86 @@ write_datagram(BamWriter *manager, Datagram &me) } } else { - // Write out everything the compact way, as quantized integers. + // Some channels, particularly blink channels, may involve only a + // small number of discrete values. If we come across one of + // those, write it out losslessly, since the lossy compression + // could damage it significantly (and we can achieve better + // compression directly anyway). We consider the channel value + // only to the nearest 1000th for this purpose, because floats + // aren't very good at being precisely equal to each other. + static const int max_values = 16; + static const float scale = 1000.0; - // We quantize morphs within the range -100 .. 100. - me.add_uint16(_table.size()); - for(int i = 0; i < (int)_table.size(); i++) { - me.add_int16((int)max(min(_table[i] * 32767.0 / 100.0, 32767.0), -32767.0)); + map index; + int i; + for (i = 0; + i < (int)_table.size() && (int)index.size() <= max_values; + i++) { + int value = (int)floor(_table[i] * scale + 0.5); + index.insert(map::value_type(value, index.size())); + } + int index_length = index.size(); + if (index_length <= max_values) { + // All right, here's a blink channel. Now we write out the + // index table, and then a table of all the index values, two + // per byte. + me.add_uint8(index_length); + + if (index_length > 0) { + // We need to write the index in order by its index number; for + // this, we need to invert the index. + vector_float reverse_index(index_length); + map::iterator mi; + for (mi = index.begin(); mi != index.end(); ++mi) { + float f = (float)(*mi).first / scale; + int i = (*mi).second; + nassertv(i >= 0 && i < (int)reverse_index.size()); + reverse_index[i] = f; + } + + for (i = 0; i < index_length; i++) { + me.add_float32(reverse_index[i]); + } + + // Now write out the actual channels. We write these two at a + // time, in the high and low nibbles of each byte. + int table_length = _table.size(); + me.add_uint16(table_length); + + if (index_length == 1) { + // In fact, we don't even need to write the channels at all, + // if there weren't at least two different values. + + } else { + for (i = 0; i < table_length - 1; i+= 2) { + int value1 = (int)floor(_table[i] * scale + 0.5); + int value2 = (int)floor(_table[i + 1] * scale + 0.5); + int i1 = index[value1]; + int i2 = index[value2]; + + me.add_uint8((i1 << 4) | i2); + } + + // There might be one odd value. + if (i < table_length) { + int value1 = (int)floor(_table[i] * scale + 0.5); + int i1 = index[value1]; + + me.add_uint8(i1 << 4); + } + } + } + + } else { + // No, we have continuous channels. Write them out using lossy + // compression. + me.add_uint8(0xff); + + FFTCompressor compressor; + compressor.set_quality(compress_chan_quality); + compressor.write_header(me); + + compressor.write_reals(me, _table, _table.size()); } } } @@ -150,26 +231,71 @@ fillin(DatagramIterator& scan, BamReader* manager) { AnimChannelScalar::fillin(scan, manager); - bool wrote_quantized = scan.get_bool(); + bool wrote_compressed = scan.get_bool(); - if (!wrote_quantized) { + PTA_float temp_table(0); + + if (!wrote_compressed) { // Regular floats. int size = scan.get_uint16(); - PTA_float temp_table; for(int i = 0; i < size; i++) { temp_table.push_back(scan.get_float32()); } - _table = temp_table; } else { - // Quantized int16's and expand to floats. - int size = scan.get_uint16(); - PTA_float temp_table; - for(int i = 0; i < size; i++) { - temp_table.push_back((double)scan.get_int16() * 100.0 / 32767.0); + // Compressed channels. + if (manager->get_file_minor_ver() < 1) { + chan_cat.error() + << "Cannot read old-style quantized channels.\n"; + return; + } + + // Did we write them as discrete or continuous channel values? + int index_length = scan.get_uint8(); + + if (index_length < 0xff) { + // Discrete. Read in the index. + if (index_length > 0) { + float index[index_length]; + + int i; + for (i = 0; i < index_length; i++) { + index[i] = scan.get_float32(); + } + + // Now read in the channel values. + int table_length = scan.get_uint16(); + if (index_length == 1) { + // With only one index value, we can infer the table. + for (i = 0; i < table_length; i++) { + temp_table.push_back(index[0]); + } + } else { + // Otherwise, we must read it. + for (i = 0; i < table_length - 1; i+= 2) { + int num = scan.get_uint8(); + int i1 = (num >> 4) & 0xf; + int i2 = num & 0xf; + temp_table.push_back(index[i1]); + temp_table.push_back(index[i2]); + } + // There might be one odd value. + if (i < table_length) { + int num = scan.get_uint8(); + int i1 = (num >> 4) & 0xf; + temp_table.push_back(index[i1]); + } + } + } + } else { + // Continuous channels. + FFTCompressor compressor; + compressor.read_header(scan); + compressor.read_reals(scan, temp_table.v()); } - _table = temp_table; } + + _table = temp_table; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/chan/animChannelScalarTable.h b/panda/src/chan/animChannelScalarTable.h index 17201af151..c7c6a3fa2a 100644 --- a/panda/src/chan/animChannelScalarTable.h +++ b/panda/src/chan/animChannelScalarTable.h @@ -17,9 +17,7 @@ // Class : AnimChannelScalarTable // Description : An animation channel that issues a scalar each frame, // read from a table such as might have been read from -// an egg file. The table actually consists of nine -// sub-tables, each representing one component of the -// transform: scale, rotate, translate. +// an egg file. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA AnimChannelScalarTable : public AnimChannelScalar { public: diff --git a/panda/src/chan/config_chan.cxx b/panda/src/chan/config_chan.cxx index 3c80956d47..2583645348 100644 --- a/panda/src/chan/config_chan.cxx +++ b/panda/src/chan/config_chan.cxx @@ -24,10 +24,10 @@ Configure(config_chan); NotifyCategoryDef(chan, ""); -// This is normally set true to quantize animation channels to 16-bit -// integer values when writing to a Bam file; a cheesy way to -// hopefully achieve greater compression ratios. -const bool quantize_bam_channels = config_chan.GetBool("quantize-bam-channels", true); +// Set this true to enable compress of animation channels when writing to +// the bam file. This is an experimental lossy compression. +bool compress_channels = config_chan.GetBool("compress-channels", false); +int compress_chan_quality = config_chan.GetInt("compress-chan-quality", 95); ConfigureFn(config_chan) { AnimBundle::init_type(); diff --git a/panda/src/chan/config_chan.h b/panda/src/chan/config_chan.h index 812f5322f1..0cbb1b6913 100644 --- a/panda/src/chan/config_chan.h +++ b/panda/src/chan/config_chan.h @@ -12,6 +12,7 @@ // Configure variables for chan package. NotifyCategoryDecl(chan, EXPCL_PANDA, EXPTP_PANDA); -EXPCL_PANDA extern const bool quantize_bam_channels; +EXPCL_PANDA extern bool compress_channels; +EXPCL_PANDA extern int compress_chan_quality; #endif diff --git a/panda/src/chan/movingPart.I b/panda/src/chan/movingPart.I index 556d168f55..1282e0a7bf 100644 --- a/panda/src/chan/movingPart.I +++ b/panda/src/chan/movingPart.I @@ -87,6 +87,18 @@ make_initial_channel() const { return new AnimChannelFixed(get_name(), _initial_value); } +//////////////////////////////////////////////////////////////////// +// Function: MovingPart::output_value +// Access: Public, Virtual +// Description: Outputs a very brief description of the channel's +// current value. +//////////////////////////////////////////////////////////////////// +template +void MovingPart:: +output_value(ostream &out) const { + SwitchType::output_value(out, _value); +} + //////////////////////////////////////////////////////////////////// // Function: MovingPart::write_datagram // Access: Public diff --git a/panda/src/chan/movingPart.h b/panda/src/chan/movingPart.h index 803932b4a6..3e5a436111 100644 --- a/panda/src/chan/movingPart.h +++ b/panda/src/chan/movingPart.h @@ -32,6 +32,7 @@ public: virtual TypeHandle get_value_type() const; virtual AnimChannelBase *make_initial_channel() const; + virtual void output_value(ostream &out) const; ValueType _value; ValueType _initial_value; diff --git a/panda/src/chan/movingPartBase.cxx b/panda/src/chan/movingPartBase.cxx index 2ae5beb617..e1b8fc2e7d 100644 --- a/panda/src/chan/movingPartBase.cxx +++ b/panda/src/chan/movingPartBase.cxx @@ -49,6 +49,27 @@ write(ostream &out, int indent_level) const { } } +//////////////////////////////////////////////////////////////////// +// Function: MovingPartBase::write_with_value +// Access: Public, Virtual +// Description: Writes a brief description of the channel and all of +// its descendants, along with their values. +//////////////////////////////////////////////////////////////////// +void MovingPartBase:: +write_with_value(ostream &out, int indent_level) const { + indent(out, indent_level) << get_value_type() << " " << get_name() << "\n"; + indent(out, indent_level); + output_value(out); + + if (_children.empty()) { + out << "\n"; + } else { + out << " {\n"; + write_descendants_with_value(out, indent_level + 2); + indent(out, indent_level) << "}\n"; + } +} + //////////////////////////////////////////////////////////////////// // Function: MovingPartBase::do_update // Access: Public, Virtual diff --git a/panda/src/chan/movingPartBase.h b/panda/src/chan/movingPartBase.h index aca2126083..90326ff1da 100644 --- a/panda/src/chan/movingPartBase.h +++ b/panda/src/chan/movingPartBase.h @@ -32,6 +32,8 @@ public: virtual TypeHandle get_value_type() const=0; virtual AnimChannelBase *make_initial_channel() const=0; virtual void write(ostream &out, int indent_level) const; + virtual void write_with_value(ostream &out, int indent_level) const; + virtual void output_value(ostream &out) const=0; virtual void do_update(PartBundle *root, PartGroup *parent, bool parent_changed, bool anim_changed); diff --git a/panda/src/chan/partGroup.cxx b/panda/src/chan/partGroup.cxx index 5d560bd3e9..1e9b198107 100644 --- a/panda/src/chan/partGroup.cxx +++ b/panda/src/chan/partGroup.cxx @@ -296,6 +296,20 @@ write(ostream &out, int indent_level) const { indent(out, indent_level) << "}\n"; } +//////////////////////////////////////////////////////////////////// +// Function: PartGroup::write_with_value +// Access: Public, Virtual +// Description: Writes a brief description of the group, showing its +// current value, and that of all of its descendants. +//////////////////////////////////////////////////////////////////// +void PartGroup:: +write_with_value(ostream &out, int indent_level) const { + indent(out, indent_level) + << get_type() << " " << get_name() << " {\n"; + write_descendants_with_value(out, indent_level + 2); + indent(out, indent_level) << "}\n"; +} + //////////////////////////////////////////////////////////////////// // Function: PartGroup::do_update @@ -327,6 +341,21 @@ write_descendants(ostream &out, int indent_level) const { } } +//////////////////////////////////////////////////////////////////// +// Function: PartGroup::write_descendants_with_value +// Access: Protected +// Description: Writes a brief description of all of the group's +// descendants and their values. +//////////////////////////////////////////////////////////////////// +void PartGroup:: +write_descendants_with_value(ostream &out, int indent_level) const { + Children::const_iterator ci; + + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write_with_value(out, indent_level); + } +} + diff --git a/panda/src/chan/partGroup.h b/panda/src/chan/partGroup.h index d5fd3fc48e..16c40a12be 100644 --- a/panda/src/chan/partGroup.h +++ b/panda/src/chan/partGroup.h @@ -64,12 +64,14 @@ public: int hierarchy_match_flags = 0) const; virtual void write(ostream &out, int indent_level) const; + virtual void write_with_value(ostream &out, int indent_level) const; virtual void do_update(PartBundle *root, PartGroup *parent, bool parent_changed, bool anim_changed); protected: void write_descendants(ostream &out, int indent_level) const; + void write_descendants_with_value(ostream &out, int indent_level) const; virtual void pick_channel_index(list &holes, int &next) const; virtual void bind_hierarchy(AnimGroup *anim, int channel_index); diff --git a/panda/src/char/character.I b/panda/src/char/character.I index b6d7c61105..910172ab89 100644 --- a/panda/src/char/character.I +++ b/panda/src/char/character.I @@ -48,3 +48,27 @@ get_part(int n) const { nassertr(n >= 0 && n < (int)_parts.size(), NULL); return _parts[n]; } + +//////////////////////////////////////////////////////////////////// +// Function: Character::write_parts +// Access: Public +// Description: Writes a list of the character's joints and sliders, +// in their hierchical structure, to the indicated +// output stream. +//////////////////////////////////////////////////////////////////// +INLINE void Character:: +write_parts(ostream &out) const { + get_bundle()->write(out, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: Character::write_part_values +// Access: Public +// Description: Writes a list of the character's joints and sliders, +// along with each current position, in their hierchical +// structure, to the indicated output stream. +//////////////////////////////////////////////////////////////////// +INLINE void Character:: +write_part_values(ostream &out) const { + get_bundle()->write_with_value(out, 0); +} diff --git a/panda/src/char/character.cxx b/panda/src/char/character.cxx index e7f8ba68e5..e1f8294224 100644 --- a/panda/src/char/character.cxx +++ b/panda/src/char/character.cxx @@ -93,7 +93,6 @@ safe_to_transform() const { return false; } - //////////////////////////////////////////////////////////////////// // Function: Character::app_traverse // Access: Public, Virtual diff --git a/panda/src/char/character.h b/panda/src/char/character.h index 4f625356ef..d1ed8141b0 100644 --- a/panda/src/char/character.h +++ b/panda/src/char/character.h @@ -43,6 +43,9 @@ PUBLISHED: INLINE int get_num_parts() const; INLINE PartGroup *get_part(int n) const; + INLINE void write_parts(ostream &out) const; + INLINE void write_part_values(ostream &out) const; + public: virtual void app_traverse(); diff --git a/panda/src/framework/framework.cxx b/panda/src/framework/framework.cxx index d5999de0f6..324ee9ba99 100644 --- a/panda/src/framework/framework.cxx +++ b/panda/src/framework/framework.cxx @@ -411,7 +411,7 @@ void event_esc(CPT_Event) { #ifdef HAVE_NET if (PStatClient::get_global_pstats()->is_connected()) { - nout << "Disconnecting from stats host" << endl; + framework_cat.info() << "Disconnecting from stats host" << endl; PStatClient::get_global_pstats()->disconnect(); } #endif @@ -438,23 +438,23 @@ void event_f(CPT_Event) { void event_S(CPT_Event) { #ifdef HAVE_NET - nout << "Connecting to stats host" << endl; + framework_cat.info() << "Connecting to stats host" << endl; PStatClient::get_global_pstats()->connect(); #else - nout << "Stats host not supported." << endl; + framework_cat.error() << "Stats host not supported." << endl; #endif } void event_A(CPT_Event) { #ifdef HAVE_NET if (PStatClient::get_global_pstats()->is_connected()) { - nout << "Disconnecting from stats host" << endl; + framework_cat.info() << "Disconnecting from stats host" << endl; PStatClient::get_global_pstats()->disconnect(); } else { - nout << "Stats host is already disconnected." << endl; + framework_cat.error() << "Stats host is already disconnected." << endl; } #else - nout << "Stats host not supported." << endl; + framework_cat.error() << "Stats host not supported." << endl; #endif } @@ -842,7 +842,7 @@ void pause_draw(void) { return; run_render.lock(); render_running = false; - nout << "draw thread paused" << endl; + framework_cat.info() << "draw thread paused" << endl; } void unpause_draw(void) { @@ -850,7 +850,7 @@ void unpause_draw(void) { return; run_render.unlock(); render_running = true; - nout << "draw thread continuing" << endl; + framework_cat.info() << "draw thread continuing" << endl; } void draw_loop(void*) { @@ -859,7 +859,7 @@ void draw_loop(void*) { main_win->update(); handle_framerate(); } - nout << "draw thread exiting" << endl; + framework_cat.info() << "draw thread exiting" << endl; } void event_x(CPT_Event) { @@ -925,30 +925,34 @@ int framework_main(int argc, char *argv[]) { // load display modules GraphicsPipe::resolve_modules(); - nout << "Known pipe types:" << endl; - GraphicsPipe::_factory.write_types(nout, 2); + framework_cat.info() << "Known pipe types:" << endl; + GraphicsPipe::_factory.write_types(framework_cat.info(false), 2); // Create a window main_pipe = GraphicsPipe::_factory. make_instance(InteractiveGraphicsPipe::get_class_type()); if (main_pipe == (GraphicsPipe*)0L) { - nout << "No interactive pipe is available! Check your Configrc!\n"; + framework_cat.error() + << "No interactive pipe is available! Check your Configrc!\n"; exit(1); } - cout << "Opened a '" << main_pipe->get_type().get_name() - << "' interactive graphics pipe." << endl; + framework_cat.info() + << "Opened a '" << main_pipe->get_type().get_name() + << "' interactive graphics pipe." << endl; rib_pipe = GraphicsPipe::_factory. make_instance(NoninteractiveGraphicsPipe::get_class_type()); if (rib_pipe == (GraphicsPipe*)0L) - cout << "Did not open a non-interactive graphics pipe, features related" - << " to that will\nbe disabled." << endl; + framework_cat.info() + << "Did not open a non-interactive graphics pipe, features related" + << " to that will\nbe disabled." << endl; else - cout << "Opened a '" << rib_pipe->get_type().get_name() - << "' non-interactive graphics pipe." << endl; + framework_cat.info() + << "Opened a '" << rib_pipe->get_type().get_name() + << "' non-interactive graphics pipe." << endl; ChanCfgOverrides override; @@ -1105,7 +1109,8 @@ int framework_main(int argc, char *argv[]) { PT_Node node = loader.load_sync(filename); if (node == (Node *)NULL) { - nout << "Unable to load file " << filename << "\n"; + framework_cat.error() + << "Unable to load file " << filename << "\n"; } else { new RenderRelation(root, node); } @@ -1168,7 +1173,7 @@ int framework_main(int argc, char *argv[]) { } if (!main_win->supports_update()) { - nout + framework_cat.info() << "Window type " << main_win->get_type() << " supports only the glut-style main loop interface.\n"; @@ -1184,7 +1189,7 @@ int framework_main(int argc, char *argv[]) { #ifdef USE_IPC if (forked_draw) { - nout << "forking draw thread" << endl; + framework_cat.info() << "forking draw thread" << endl; draw_thread = thread::create(draw_loop); for (;;) icb.idle(); diff --git a/panda/src/linmath/Sources.pp b/panda/src/linmath/Sources.pp index 6a8d8c426e..3cbd8a27c7 100644 --- a/panda/src/linmath/Sources.pp +++ b/panda/src/linmath/Sources.pp @@ -11,10 +11,18 @@ coordinateSystem.h deg_2_rad.h \ ioPtaDatagramLinMath.I ioPtaDatagramLinMath.cxx \ ioPtaDatagramLinMath.h lmatrix.cxx lmatrix.h luse.I luse.N luse.cxx \ - luse.h mathNumbers.cxx mathNumbers.h pta_Colorf.cxx pta_Colorf.h \ + luse.h lquaternion.I lquaternion.h lrotation.I lrotation.h \ + lvec2_ops.I lvec2_ops.h lvec3_ops.I lvec3_ops.h lvec4_ops.I \ + lvec4_ops.h lvecBase2.I lvecBase2.h lvecBase3.I lvecBase3.h \ + lvecBase4.I lvecBase4.h lvector2.I lvector2.h lvector3.I lvector3.h \ + lvector4.I lvector4.h \ + mathNumbers.cxx mathNumbers.h nearly_zero.h \ + pta_Colorf.cxx pta_Colorf.h \ pta_Normalf.cxx pta_Normalf.h pta_TexCoordf.cxx pta_TexCoordf.h \ pta_Vertexf.cxx pta_Vertexf.h vector_Colorf.cxx vector_Colorf.h \ - vector_LPoint2f.cxx vector_LPoint2f.h vector_Normalf.cxx \ + vector_LPoint2f.cxx vector_LPoint2f.h \ + vector_LVecBase3f.cxx vector_LVecBase3f.h \ + vector_Normalf.cxx \ vector_Normalf.h vector_Vertexf.cxx vector_Vertexf.h #define INSTALL_HEADERS \ @@ -29,7 +37,8 @@ lvecBase4.I lvecBase4.h lvector2.I lvector2.h lvector3.I lvector3.h \ lvector4.I lvector4.h mathNumbers.h nearly_zero.h pta_Colorf.h \ pta_Normalf.h pta_TexCoordf.h pta_Vertexf.h vector_Colorf.h \ - vector_LPoint2f.h vector_Normalf.h vector_TexCoordf.h \ + vector_LPoint2f.h vector_LVecBase3f.h \ + vector_Normalf.h vector_TexCoordf.h \ vector_Vertexf.h #define IGATESCAN all diff --git a/panda/src/linmath/cmath.I b/panda/src/linmath/cmath.I index 1f4c679943..0ce5c2a28e 100644 --- a/panda/src/linmath/cmath.I +++ b/panda/src/linmath/cmath.I @@ -25,6 +25,10 @@ INLINE float cabs(float v) { return fabs(v); } +INLINE float catan2(float y, float x) { + return atan2f(y, x); +} + INLINE double csqrt(double v) { return sqrt(v); } @@ -41,6 +45,10 @@ INLINE double cabs(double v) { return fabs(v); } +INLINE double catan2(double y, double x) { + return atan2(y, x); +} + INLINE bool cnan(double v) { #ifndef _WIN32 return (isnan(v) != 0); diff --git a/panda/src/linmath/cmath.h b/panda/src/linmath/cmath.h index 4172f8c223..d7121b14a3 100644 --- a/panda/src/linmath/cmath.h +++ b/panda/src/linmath/cmath.h @@ -18,11 +18,13 @@ INLINE float csqrt(float v); INLINE float csin(float v); INLINE float ccos(float v); INLINE float cabs(float v); +INLINE float catan2(float y, float x); INLINE double csqrt(double v); INLINE double csin(double v); INLINE double ccos(double v); INLINE double cabs(double v); +INLINE double catan2(double y, double x); // Returns true if the number is nan, false if it's a genuine number // or infinity. diff --git a/panda/src/linmath/lquaternion.I b/panda/src/linmath/lquaternion.I index c083f3eb2b..3cb4e12c76 100644 --- a/panda/src/linmath/lquaternion.I +++ b/panda/src/linmath/lquaternion.I @@ -3,10 +3,6 @@ // //////////////////////////////////////////////////////////////////// -#include "lquaternion.h" -#include "nearly_zero.h" -#include - template TypeHandle LQuaternionBase::_type_handle; @@ -341,19 +337,18 @@ INLINE void LQuaternionBase:: normalize(void) { NumType l = csqrt((_r*_r)+(_i*_i)+(_j*_j)+(_k*_k)); - if (IS_NEARLY_ZERO(l)) { + if (l == 0.0) { _r = 0.; _i = 0.; _j = 0.; _k = 0.; - return; + } else { + l = 1. / l; + _r *= l; + _i *= l; + _j *= l; + _k *= l; } - - l = 1. / l; - _r *= l; - _i *= l; - _j *= l; - _k *= l; } //////////////////////////////////////////////////////////////////// @@ -362,25 +357,25 @@ normalize(void) { // Description: Do-While Jones. //////////////////////////////////////////////////////////////////// template -INLINE void LQuaternionBase:: +void LQuaternionBase:: set(const LMatrix3 &m) { NumType m00 = m.get_cell(0, 0); NumType m11 = m.get_cell(1, 1); NumType m22 = m.get_cell(2, 2); - if (m00 >= 0.0 && ((m11 + m22) >= 0.0)) { + if (m00 != 0.0 && ((m11 + m22) != 0.0)) { _r = 1.0 + m00 + m11 + m22; _i = m.get_cell(2, 1) - m.get_cell(1, 2); _j = m.get_cell(0, 2) - m.get_cell(2, 0); _k = m.get_cell(1, 0) - m.get_cell(0, 1); } - else if (m00 >= 0.0 && ((m11 + m22) == 0.0)) { + else if (m00 != 0.0 && ((m11 + m22) == 0.0)) { _r = m.get_cell(2, 1) - m.get_cell(1, 2); _i = 1.0 + m00 - m11 - m22; _j = m.get_cell(1, 0) + m.get_cell(0, 1); _k = m.get_cell(0, 2) + m.get_cell(2, 0); } - else if (m00 == 0.0 && ((m11 - m22) >= 0.0)) { + else if (m00 == 0.0 && ((m11 - m22) != 0.0)) { _r = m.get_cell(0, 2) - m.get_cell(2, 0); _i = m.get_cell(1, 0) + m.get_cell(0, 1); _j = 1.0 - m00 + m11 - m22; @@ -411,7 +406,7 @@ set(const LMatrix4 &m) { // Description: Do-While Jones paper from cary. //////////////////////////////////////////////////////////////////// template -INLINE void LQuaternionBase:: +void LQuaternionBase:: extract_to_matrix(LMatrix3 &m) const { NumType tx, ty, tz, tq, tk, tk_denom; @@ -451,7 +446,7 @@ extract_to_matrix(LMatrix3 &m) const { // Description: //////////////////////////////////////////////////////////////////// template -INLINE void LQuaternionBase:: +void LQuaternionBase:: extract_to_matrix(LMatrix4 &m) const { NumType tx, ty, tz, tq, tk, tk_denom; @@ -485,6 +480,57 @@ extract_to_matrix(LMatrix4 &m) const { m.set_cell(2, 1, tk + tq); } +//////////////////////////////////////////////////////////////////// +// Function: set_hpr +// Access: public +// Description: Sets the quaternion as the unit quaternion that +// is equivalent to these Euler angles. +// (from Real-time Rendering, p.49) +//////////////////////////////////////////////////////////////////// +template +INLINE void LQuaternionBase:: +set_hpr(const LVecBase3 &hpr) { + LQuaternionBase quat_h, quat_p, quat_r; + + quat_h.set(ccos(hpr[0]), 0, csin(hpr[0]), 0); + quat_p.set(ccos(hpr[1]), csin(hpr[1]), 0, 0); + quat_r.set(ccos(hpr[2]), 0, 0, csin(hpr[2])); + + (*this) = quat_h * quat_p * quat_r; +} + +//////////////////////////////////////////////////////////////////// +// Function: get_hpr +// Access: public +// Description: Extracts the equivalent Euler angles from the unit +// quaternion. +//////////////////////////////////////////////////////////////////// +template +INLINE LVecBase3 LQuaternionBase:: +get_hpr() const { + NumType sint = (2.0 * _r * _j) - (2.0 * _i * _k); + NumType cost = csqrt(1 - sint * sint); + + NumType sinv, cosv, sinf, cosf; + + if (cost != 0.0) { + sinv = ((2.0 * _j * _k) + (2.0 * _r * _i)) / cost; + cosv = (1.0 - (2.0 * _i * _i) - (2.0 * _j * _j)) / cost; + sinf = (1.0 - (2.0 * _i * _i) - (2.0 * _j * _j)) / cost; + cosf = (1.0 - (2.0 * _j * _j) - (2.0 * _k * _k)) / cost; + + } else { + sinv = ((2.0 * _r * _i) - (2.0 * _j * _k)); + cosv = 1.0 - (2.0 * _i * _i) - (2.0 * _k * _k); + sinf = 0.0; + cosf = 1.0; + } + + return LVecBase3(rad_2_deg(atan2(sinv, cosv)), + rad_2_deg(atan2(sint, cost)), + rad_2_deg(atan2(sinf, cosf))); +} + //////////////////////////////////////////////////////////////////// // Function: operator *(Matrix3, Quat) // Access: public diff --git a/panda/src/linmath/lquaternion.h b/panda/src/linmath/lquaternion.h index 0532a1cdcd..86ecfc17b9 100644 --- a/panda/src/linmath/lquaternion.h +++ b/panda/src/linmath/lquaternion.h @@ -7,6 +7,11 @@ #define __LQUATERNION_H__ #include "lmatrix.h" +#include "nearly_zero.h" +#include "cmath.h" +#include "deg_2_rad.h" + +#include //////////////////////////////////////////////////////////////////// // Class : LQuaternionBase @@ -43,11 +48,14 @@ PUBLISHED: INLINE void set(NumType, NumType, NumType, NumType); - INLINE void set(const LMatrix3 &); - INLINE void set(const LMatrix4 &); + void set(const LMatrix3 &m); + INLINE void set(const LMatrix4 &m); - INLINE void extract_to_matrix(LMatrix3 &) const; - INLINE void extract_to_matrix(LMatrix4 &) const; + void extract_to_matrix(LMatrix3 &m) const; + void extract_to_matrix(LMatrix4 &m) const; + + INLINE void set_hpr(const LVecBase3 &hpr); + LVecBase3 get_hpr() const; INLINE NumType get_r(void) const; INLINE NumType get_i(void) const; diff --git a/panda/src/linmath/lrotation.I b/panda/src/linmath/lrotation.I index c7bfccc5da..b324feae17 100644 --- a/panda/src/linmath/lrotation.I +++ b/panda/src/linmath/lrotation.I @@ -3,10 +3,6 @@ // //////////////////////////////////////////////////////////////////// -#include "lrotation.h" -#include -#include - template TypeHandle LRotation::_type_handle; @@ -71,12 +67,12 @@ LRotation(const LMatrix4 &m) { //////////////////////////////////////////////////////////////////// template INLINE LRotation:: -LRotation(const LVector3 &axis, float angle) { - float radians = angle * ((float) MathNumbers::pi / 180.0f); - float theta_over_2 = radians / 2.0f; - float sin_to2 = sinf(theta_over_2); +LRotation(const LVector3 &axis, NumType angle) { + NumType radians = angle * ((NumType) MathNumbers::pi / (NumType)180.0); + NumType theta_over_2 = radians / (NumType)2.0; + NumType sin_to2 = csin(theta_over_2); - set_r(cosf(theta_over_2)); + set_r(ccos(theta_over_2)); set_i(axis[0] * sin_to2); set_j(axis[1] * sin_to2); set_k(axis[2] * sin_to2); @@ -85,18 +81,12 @@ LRotation(const LVector3 &axis, float angle) { //////////////////////////////////////////////////////////////////// // Function: LRotation::Constructor // Access: public -// Description: hpr (Real-time Rendering, p.49) +// Description: Sets the rotation from the given Euler angles. //////////////////////////////////////////////////////////////////// template INLINE LRotation:: -LRotation(float h, float p, float r) { - LQuaternionBase quat_h, quat_p, quat_r; - - quat_h.set(cosf(h), 0, sinf(h), 0); - quat_p.set(cosf(p), sinf(p), 0, 0); - quat_r.set(cosf(r), 0, 0, sinf(r)); - - (*this) = quat_h * quat_p * quat_r; +LRotation(NumType h, NumType p, NumType r) { + set_hpr(LVecBase3(h, p, r)); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/linmath/lrotation.h b/panda/src/linmath/lrotation.h index 99d0511bc5..766ac0f8a8 100644 --- a/panda/src/linmath/lrotation.h +++ b/panda/src/linmath/lrotation.h @@ -8,6 +8,9 @@ #include #include "lquaternion.h" +#include "cmath.h" + +#include //////////////////////////////////////////////////////////////////////// // Class : LRotation @@ -19,10 +22,10 @@ PUBLISHED: INLINE LRotation(); INLINE LRotation(const LQuaternionBase&); INLINE LRotation(NumType, NumType, NumType, NumType); - INLINE LRotation(const LVector3 &, float); + INLINE LRotation(const LVector3 &, NumType); INLINE LRotation(const LMatrix3 &); INLINE LRotation(const LMatrix4 &); - INLINE LRotation(float, float, float); + INLINE LRotation(NumType, NumType, NumType); virtual ~LRotation(); INLINE LRotation diff --git a/panda/src/linmath/vector_LVecBase3f.cxx b/panda/src/linmath/vector_LVecBase3f.cxx new file mode 100644 index 0000000000..7827bb89f9 --- /dev/null +++ b/panda/src/linmath/vector_LVecBase3f.cxx @@ -0,0 +1,11 @@ +// Filename: vector_LVecBase3f.cxx +// Created by: drose (11Dec00) +// +//////////////////////////////////////////////////////////////////// + +#include "vector_LVecBase3f.h" + +// Tell GCC that we'll take care of the instantiation explicitly here. +#ifdef __GNUC__ +#pragma implementation +#endif diff --git a/panda/src/linmath/vector_LVecBase3f.h b/panda/src/linmath/vector_LVecBase3f.h new file mode 100644 index 0000000000..5d152529f7 --- /dev/null +++ b/panda/src/linmath/vector_LVecBase3f.h @@ -0,0 +1,32 @@ +// Filename: vector_LVecBase3f.h +// Created by: drose (11Dec00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef VECTOR_LVECBASE3F_H +#define VECTOR_LVECBASE3F_H + +#include + +#include "luse.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : vector_LVecBase3f +// Description : A vector of LVecBase3fs. This class is defined once here, +// and exported to PANDA.DLL; other packages that want +// to use a vector of this type (whether they need to +// export it or not) should include this header file, +// rather than defining the vector again. +//////////////////////////////////////////////////////////////////// + +EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, std::vector) +typedef vector vector_LVecBase3f; + +// Tell GCC that we'll take care of the instantiation explicitly here. +#ifdef __GNUC__ +#pragma interface +#endif + +#endif diff --git a/panda/src/mathutil/Sources.pp b/panda/src/mathutil/Sources.pp index 09fb137438..c3f13a30c6 100644 --- a/panda/src/mathutil/Sources.pp +++ b/panda/src/mathutil/Sources.pp @@ -4,13 +4,17 @@ #define TARGET mathutil #define LOCAL_LIBS \ linmath putil + #define USE_FFTW yes + #define UNIX_SYS_LIBS m #define SOURCES \ boundingHexahedron.I boundingHexahedron.cxx boundingHexahedron.h \ boundingLine.I boundingLine.cxx boundingLine.h boundingSphere.I \ boundingSphere.cxx boundingSphere.h boundingVolume.I \ boundingVolume.cxx boundingVolume.h config_mathutil.cxx \ - config_mathutil.h finiteBoundingVolume.cxx finiteBoundingVolume.h \ + config_mathutil.h \ + fftCompressor.cxx fftCompressor.h \ + finiteBoundingVolume.cxx finiteBoundingVolume.h \ geometricBoundingVolume.I geometricBoundingVolume.cxx \ geometricBoundingVolume.h look_at.I look_at.cxx look_at.h \ omniBoundingVolume.I omniBoundingVolume.cxx omniBoundingVolume.h \ @@ -19,7 +23,9 @@ #define INSTALL_HEADERS \ boundingHexahedron.I boundingHexahedron.h boundingLine.I \ boundingLine.h boundingSphere.I boundingSphere.h boundingVolume.I \ - boundingVolume.h config_mathutil.h finiteBoundingVolume.h frustum.I \ + boundingVolume.h config_mathutil.h \ + fftCompressor.h \ + finiteBoundingVolume.h frustum.I \ frustum.h geometricBoundingVolume.I geometricBoundingVolume.h \ look_at.I look_at.h mathHelpers.I mathHelpers.h mathutil.h \ omniBoundingVolume.I omniBoundingVolume.h plane.I plane.h \ diff --git a/panda/src/mathutil/config_mathutil.cxx b/panda/src/mathutil/config_mathutil.cxx index 0c3e0612f0..407a92178b 100644 --- a/panda/src/mathutil/config_mathutil.cxx +++ b/panda/src/mathutil/config_mathutil.cxx @@ -16,6 +16,10 @@ Configure(config_mathutil); NotifyCategoryDef(mathutil, ""); +const double fft_offset = config_mathutil.GetDouble("fft-offset", 0.001); +const double fft_factor = config_mathutil.GetDouble("fft-factor", 0.1); +const double fft_exponent = config_mathutil.GetDouble("fft-exponent", 4); + ConfigureFn(config_mathutil) { BoundingHexahedron::init_type(); BoundingSphere::init_type(); diff --git a/panda/src/mathutil/config_mathutil.h b/panda/src/mathutil/config_mathutil.h index 83911f0ace..80b4b77ebf 100644 --- a/panda/src/mathutil/config_mathutil.h +++ b/panda/src/mathutil/config_mathutil.h @@ -11,6 +11,10 @@ NotifyCategoryDecl(mathutil, EXPCL_PANDA, EXPTP_PANDA); +extern const double fft_offset; +extern const double fft_factor; +extern const double fft_exponent; + #endif diff --git a/panda/src/mathutil/fftCompressor.cxx b/panda/src/mathutil/fftCompressor.cxx new file mode 100644 index 0000000000..0a779e8f1e --- /dev/null +++ b/panda/src/mathutil/fftCompressor.cxx @@ -0,0 +1,735 @@ +// Filename: fftCompressor.cxx +// Created by: drose (11Dec00) +// +//////////////////////////////////////////////////////////////////// + +#include "fftCompressor.h" +#include "config_mathutil.h" + +#include +#include +#include +#include + +#ifdef HAVE_FFTW + +#include + +// These FFTW support objects can only be defined if we actually have +// the FFTW library available. +static rfftw_plan get_real_compress_plan(int length); +static rfftw_plan get_real_decompress_plan(int length); + +typedef map RealPlans; +static RealPlans _real_compress_plans; +static RealPlans _real_decompress_plans; + +#endif + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::Constructor +// Access: Public +// Description: Constructs a new compressor object with default +// parameters. +//////////////////////////////////////////////////////////////////// +FFTCompressor:: +FFTCompressor() { + set_quality(-1); +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::is_compression_available +// Access: Public, Static +// Description: Returns true if the FFTW library is compiled in, so +// that this class is actually capable of doing useful +// compression/decompression work. Returns false +// otherwise, in which case any attempt to write a +// compressed stream will actually write an uncompressed +// stream, and any attempt to read a compressed stream +// will fail. +//////////////////////////////////////////////////////////////////// +bool FFTCompressor:: +is_compression_available() { +#ifndef HAVE_FFTW + return false; +#else + return true; +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::set_quality +// Access: Public +// Description: Sets the quality factor for the compression. This is +// an integer in the range 0 - 100 that roughly controls +// how aggressively the reals are compressed; lower +// numbers mean smaller output, and more data loss. +// +// As a special case, a negative quality indicates that +// the individual parameters should be separately +// controlled via config variables, and a quality +// greater than 100 indicates lossless output. +//////////////////////////////////////////////////////////////////// +void FFTCompressor:: +set_quality(int quality) { +#ifndef HAVE_FFTW + // If we don't actually have FFTW, we can't really compress anything. + if (_quality <= 100) { + mathutil_cat.warning() + << "FFTW library is not available; generating uncompressed output.\n"; + } + _quality = 101; + +#else + _quality = quality; + + if (_quality < 0) { + // A negative quality indicates to read the important parameters + // from config variables. + _fft_offset = fft_offset; + _fft_factor = fft_factor; + _fft_exponent = fft_exponent; + } else if (_quality < 40) { + // 0 - 40 : + // fft-offset 1.0 - 0.001 + // fft-factor 1.0 + // fft-exponent 4.0 + + double t = (double)_quality / 40.0; + _fft_offset = interpolate(t, 1.0, 0.001); + _fft_factor = 1.0; + _fft_exponent = 4.0; + + } else if (_quality < 95) { + // 40 - 95: + // fft-offset 0.001 + // fft-factor 1.0 - 0.1 + // fft-exponent 4.0 + + double t = (double)(_quality - 40) / 55.0; + _fft_offset = 0.001; + _fft_factor = interpolate(t, 1.0, 0.1); + _fft_exponent = 4.0; + + } else { + // 95 - 100: + // fft-offset 0.001 + // fft-factor 0.1 - 0.0 + // fft-exponent 4.0 + + double t = (double)(_quality - 95) / 5.0; + _fft_offset = 0.001; + _fft_factor = interpolate(t, 0.1, 0.0); + _fft_exponent = 4.0; + } +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::get_quality +// Access: Public +// Description: Returns the quality number that was previously set +// via set_quality(). +//////////////////////////////////////////////////////////////////// +int FFTCompressor:: +get_quality() const { + return _quality; +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::write_header +// Access: Public +// Description: Writes the compression parameters to the indicated +// datagram. It is necessary to call this before +// writing anything else to the datagram, since these +// parameters will be necessary to correctly decompress +// the data later. +//////////////////////////////////////////////////////////////////// +void FFTCompressor:: +write_header(Datagram &datagram) { + datagram.add_int8(_quality); + if (_quality < 0) { + datagram.add_float64(_fft_offset); + datagram.add_float64(_fft_factor); + datagram.add_float64(_fft_exponent); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::write_reals +// Access: Public +// Description: Writes an array of floating-point numbers to the +// indicated datagram. +//////////////////////////////////////////////////////////////////// +void FFTCompressor:: +write_reals(Datagram &datagram, const float *array, int length) { + datagram.add_int32(length); + + if (_quality > 100) { + // Special case: lossless output. + for (int i = 0; i < length; i++) { + datagram.add_float32(array[i]); + } + return; + } + +#ifndef HAVE_FFTW + // If we don't have FFTW, we shouldn't get here. + nassertv(false); + +#else + + if (length == 0) { + // Special case: do nothing. + return; + } + + if (length == 1) { + // Special case: just write out the one number. + datagram.add_float32(array[0]); + return; + } + + // Normal case: FFT the array, and write that out. + double data[length]; + int i; + for (i = 0; i < length; i++) { + data[i] = array[i]; + } + + double half_complex[length]; + + rfftw_plan plan = get_real_compress_plan(length); + rfftw_one(plan, data, half_complex); + + if (mathutil_cat.is_debug()) { + mathutil_cat.debug() + << "write_reals :"; + for (int i = 0; i < length; i++) { + double scale_factor = get_scale_factor(i, length); + mathutil_cat.debug(false) + // << " " << data[i]; + << " " << floor(half_complex[i] / scale_factor + 0.5); + } + mathutil_cat.debug(false) << "\n"; + } + + // Now encode the numbers, run-length encoded by size, so we only + // write out the number of bits we need for each number. + + vector_double run; + RunWidth run_width = RW_invalid; + int num_written = 0; + + for (i = 0; i < length; i++) { + static const double max_range_32 = 2147483647.0; + static const double max_range_16 = 32767.0; + static const double max_range_8 = 127.0; + + double scale_factor = get_scale_factor(i, length); + double num = floor(half_complex[i] / scale_factor + 0.5); + + // How many bits do we need to encode this integer? + double a = fabs(num); + RunWidth num_width; + + if (a == 0.0) { + num_width = RW_0; + + } else if (a <= max_range_8) { + num_width = RW_8; + + } else if (a <= max_range_16) { + num_width = RW_16; + + } else if (a <= max_range_32) { + num_width = RW_32; + + } else { + num_width = RW_double; + } + + // A special case: if we're writing a string of one-byters and we + // come across a single intervening zero, don't interrupt the run + // just for that. + if (run_width == RW_8 && num_width == RW_0) { + if (i + 1 >= length || half_complex[i + 1] != 0.0) { + num_width = RW_8; + } + } + + if (num_width != run_width) { + // Now we need to flush the last run. + num_written += write_run(datagram, run_width, run); + run.clear(); + run_width = num_width; + } + + run.push_back(num); + } + + num_written += write_run(datagram, run_width, run); + nassertv(num_written == length); +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::write_hprs +// Access: Public +// Description: Writes an array of HPR angles to the indicated +// datagram. +//////////////////////////////////////////////////////////////////// +void FFTCompressor:: +write_hprs(Datagram &datagram, const LVecBase3f *array, int length) { + // First, convert the HPR's to quats. We expect quats to have + // better FFT consistency, and therefore compress better, even + // though they have an extra component. + + // However, because the quaternion will be normalized, we don't even + // have to write out all three components; any three can be used to + // determine the fourth (provided we ensure consistency of sign). + + vector_float qi, qj, qk; + + for (int i = 0; i < length; i++) { + LMatrix3f mat; + compose_matrix(mat, LVecBase3f(1.0, 1.0, 1.0), array[i]); + LOrientationf rot; + rot.set(mat); + rot.normalize(); + + if (rot.get_r() < 0) { + // Since rot == -rot, we can flip the quarternion if need be to + // keep the r component positive. This has two advantages. + // One, it makes it possible to infer r completely given i, j, + // and k (since we know it must be >= 0), and two, it helps + // protect against poor continuity caused by inadvertent + // flipping of the quarternion's sign between frames. + + // The choice of leaving r implicit rather than any of the other + // three seems to work the best in terms of guaranteeing + // continuity. + rot.set(-rot.get_r(), -rot.get_i(), -rot.get_j(), -rot.get_k()); + } + + qi.push_back(rot.get_i()); + qj.push_back(rot.get_j()); + qk.push_back(rot.get_k()); + } + + write_reals(datagram, &qi[0], length); + write_reals(datagram, &qj[0], length); + write_reals(datagram, &qk[0], length); +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::read_header +// Access: Public +// Description: Reads the compression header that was written +// previously. This fills in the compression parameters +// necessary to correctly decompress the following data. +// +// Returns true if the header is read successfully, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool FFTCompressor:: +read_header(DatagramIterator &di) { + _quality = di.get_int8(); + +#ifndef HAVE_FFTW + if (_quality <= 100) { + mathutil_cat.error() + << "FFTW library is not available; cannot read compressed data.\n"; + return false; + } +#endif + + set_quality(_quality); + + if (_quality < 0) { + _fft_offset = di.get_float64(); + _fft_factor = di.get_float64(); + _fft_exponent = di.get_float64(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::read_reals +// Access: Public +// Description: Reads an array of floating-point numbers. The result +// is pushed onto the end of the indicated vector, which +// is not cleared first; it is the user's responsibility +// to ensure that the array is initially empty. Returns +// true if the data is read correctly, false if there is +// an error. +//////////////////////////////////////////////////////////////////// +bool FFTCompressor:: +read_reals(DatagramIterator &di, vector_float &array) { + int length = di.get_int32(); + + if (_quality > 100) { + // Special case: lossless output. + for (int i = 0; i < length; i++) { + array.push_back(di.get_float32()); + } + return true; + } + +#ifndef HAVE_FFTW + // If we don't have FFTW, we shouldn't get here. + return false; + +#else + + if (length == 0) { + // Special case: do nothing. + return true; + } + + if (length == 1) { + // Special case: just read in the one number. + array.push_back(di.get_float32()); + return true; + } + + // Normal case: read in the FFT array, and convert it back to + // (nearly) the original numbers. + vector_double half_complex; + half_complex.reserve(length); + int num_read = 0; + while (num_read < length) { + num_read += read_run(di, half_complex); + } + nassertr(num_read == length, false); + nassertr((int)half_complex.size() == length, false); + + int i; + for (i = 0; i < length; i++) { + half_complex[i] *= get_scale_factor(i, length); + } + + double data[length]; + rfftw_plan plan = get_real_decompress_plan(length); + rfftw_one(plan, &half_complex[0], data); + + double scale = 1.0 / (double)length; + array.reserve(array.size() + length); + for (i = 0; i < length; i++) { + array.push_back(data[i] * scale); + } + + if (mathutil_cat.is_debug()) { + mathutil_cat.debug() + << "read_reals :"; + for (int i = 0; i < length; i++) { + mathutil_cat.debug(false) + << " " << data[i] * scale; + } + mathutil_cat.debug(false) << "\n"; + } + + return true; +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::read_hprs +// Access: Public +// Description: Reads an array of HPR angles. The result is pushed +// onto the end of the indicated vector, which is not +// cleared first; it is the user's responsibility to +// ensure that the array is initially empty. +//////////////////////////////////////////////////////////////////// +bool FFTCompressor:: +read_hprs(DatagramIterator &di, vector_LVecBase3f &array) { + vector_float qi, qj, qk; + + bool okflag = true; + + okflag = + read_reals(di, qi) && + read_reals(di, qj) && + read_reals(di, qk); + + if (okflag) { + nassertr(qi.size() == qj.size() && qj.size() == qk.size(), false); + + array.reserve(array.size() + qi.size()); + for (int i = 0; i < (int)qi.size(); i++) { + float qr2 = 1.0 - (qi[i] * qi[i] + qj[i] * qj[i] + qk[i] * qk[i]); + float qr = qr2 < 0.0 ? 0.0 : sqrtf(qr2); + + LOrientationf rot(qr, qi[i], qj[i], qk[i]); + rot.normalize(); // Just for good measure. + + LMatrix3f mat; + rot.extract_to_matrix(mat); + LVecBase3f scale, hpr; + bool success = decompose_matrix(mat, scale, hpr); + nassertr(success, false); + array.push_back(hpr); + } + } + + return okflag; +} + + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::free_storage +// Access: Public, Static +// Description: Frees memory that has been allocated during past runs +// of the FFTCompressor. This is an optional call, but +// it may be made from time to time to empty the global +// cache that the compressor objects keep to facilitate +// fast compression/decompression. +//////////////////////////////////////////////////////////////////// +void FFTCompressor:: +free_storage() { + RealPlans::iterator pi; + for (pi = _real_compress_plans.begin(); + pi != _real_compress_plans.end(); + ++pi) { + rfftw_destroy_plan((*pi).second); + } + _real_compress_plans.clear(); + + for (pi = _real_decompress_plans.begin(); + pi != _real_decompress_plans.end(); + ++pi) { + rfftw_destroy_plan((*pi).second); + } + _real_decompress_plans.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::write_run +// Access: Private +// Description: Writes a sequence of integers that all require the +// same number of bits. Returns the number of integers +// written, i.e. run.size(). +//////////////////////////////////////////////////////////////////// +int FFTCompressor:: +write_run(Datagram &datagram, FFTCompressor::RunWidth run_width, + const vector_double &run) { + if (run.empty()) { + return 0; + } + nassertr(run_width != RW_invalid, 0); + + if (run_width != RW_double) { + // If the width is anything other than RW_double, we write a + // single byte indicating the width and length of the upcoming + // run. + + if (run.size() <= RW_length_mask && + ((int)run_width | run.size()) != RW_double) { + // If there are enough bits remaining in the byte, use them to + // indicate the length of the run. We have to be a little + // careful, however, not to accidentally write a byte that looks + // like an RW_double flag. + datagram.add_uint8((int)run_width | run.size()); + + } else { + // Otherwise, write zero as the length, to indicate that we'll + // write the actual length in the following 16-bit word. + datagram.add_uint8(run_width); + + // Assuming, of course, that the length fits within 16 bits. + nassertr(run.size() < 65536, 0); + nassertr(run.size() != 0, 0); + + datagram.add_uint16(run.size()); + } + } + + // Now write the data itself. + vector_double::const_iterator ri; + switch (run_width) { + case RW_0: + // If it's a string of zeroes, we're done! + break; + + case RW_8: + for (ri = run.begin(); ri != run.end(); ++ri) { + datagram.add_int8((int)*ri); + } + break; + + case RW_16: + for (ri = run.begin(); ri != run.end(); ++ri) { + datagram.add_int16((int)*ri); + } + break; + + case RW_32: + for (ri = run.begin(); ri != run.end(); ++ri) { + datagram.add_int32((int)*ri); + } + break; + + case RW_double: + for (ri = run.begin(); ri != run.end(); ++ri) { + // In the case of RW_double, we only write the numbers one at a + // time, each time preceded by the RW_double flag. Hopefully + // this will happen only rarely. + datagram.add_int8((int)RW_double); + datagram.add_float64(*ri); + } + break; + + default: + break; + } + + return run.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::read_run +// Access: Private +// Description: Reads a sequence of integers that all require the +// same number of bits. Returns the number of integers +// read. It is the responsibility of the user to clear +// the vector before calling this function, or the +// numbers read will be appended to the end. +//////////////////////////////////////////////////////////////////// +int FFTCompressor:: +read_run(DatagramIterator &di, vector_double &run) { + PN_uint8 start = di.get_uint8(); + RunWidth run_width; + int length; + + if ((start & 0xff) == RW_double) { + // RW_double is a special case, and requires the whole byte. In + // this case, we don't encode a length, but assume it's only one. + run_width = RW_double; + length = 1; + + } else { + run_width = (RunWidth)(start & RW_width_mask); + length = start & RW_length_mask; + } + + if (length == 0) { + // If the length was zero, it means the actual length follows as a + // 16-bit word. + length = di.get_uint16(); + } + nassertr(length != 0, 0); + + run.reserve(run.size() + length); + + int i; + switch (run_width) { + case RW_0: + for (i = 0; i < length; i++) { + run.push_back(0.0); + } + break; + + case RW_8: + for (i = 0; i < length; i++) { + run.push_back((double)(int)di.get_int8()); + } + break; + + case RW_16: + for (i = 0; i < length; i++) { + run.push_back((double)(int)di.get_int16()); + } + break; + + case RW_32: + for (i = 0; i < length; i++) { + run.push_back((double)(int)di.get_int32()); + } + break; + + case RW_double: + for (i = 0; i < length; i++) { + run.push_back(di.get_float64()); + } + break; + + default: + break; + } + + return length; +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::get_scale_factor +// Access: Private +// Description: Returns the appropriate scaling for the given +// position within the halfcomplex array. +//////////////////////////////////////////////////////////////////// +double FFTCompressor:: +get_scale_factor(int i, int length) const { + int m = (length / 2) + 1; + int k = (i < m) ? i : length - i; + nassertr(k >= 0 && k < m, 1.0); + + return _fft_offset + + _fft_factor * pow((double)(m-1 - k) / (double)(m-1), _fft_exponent); +} + +//////////////////////////////////////////////////////////////////// +// Function: FFTCompressor::interpolate +// Access: Private, Static +// Description: Returns a number between a and b, inclusive, +// according to the value of t between 0 and 1, +// inclusive. +//////////////////////////////////////////////////////////////////// +double FFTCompressor:: +interpolate(double t, double a, double b) { + return a + t * (b - a); +} + + +#ifdef HAVE_FFTW + +//////////////////////////////////////////////////////////////////// +// Function: get_real_compress_plan +// Description: Returns a FFTW plan suitable for compressing a float +// array of the indicated length. +//////////////////////////////////////////////////////////////////// +static rfftw_plan +get_real_compress_plan(int length) { + RealPlans::iterator pi; + pi = _real_compress_plans.find(length); + if (pi != _real_compress_plans.end()) { + return (*pi).second; + } + + rfftw_plan plan; + plan = rfftw_create_plan(length, FFTW_REAL_TO_COMPLEX, FFTW_MEASURE); + _real_compress_plans.insert(RealPlans::value_type(length, plan)); + + return plan; +} + +//////////////////////////////////////////////////////////////////// +// Function: get_real_decompress_plan +// Description: Returns a FFTW plan suitable for decompressing a float +// array of the indicated length. +//////////////////////////////////////////////////////////////////// +static rfftw_plan +get_real_decompress_plan(int length) { + RealPlans::iterator pi; + pi = _real_decompress_plans.find(length); + if (pi != _real_decompress_plans.end()) { + return (*pi).second; + } + + rfftw_plan plan; + plan = rfftw_create_plan(length, FFTW_COMPLEX_TO_REAL, FFTW_MEASURE); + _real_decompress_plans.insert(RealPlans::value_type(length, plan)); + + return plan; +} + +#endif diff --git a/panda/src/mathutil/fftCompressor.h b/panda/src/mathutil/fftCompressor.h new file mode 100644 index 0000000000..e5e51f46ca --- /dev/null +++ b/panda/src/mathutil/fftCompressor.h @@ -0,0 +1,83 @@ +// Filename: fftCompressor.h +// Created by: drose (11Dec00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef FFTCOMPRESSOR_H +#define FFTCOMPRESSOR_H + +#include + +#include +#include +#include +#include + +class Datagram; +class DatagramIterator; + +//////////////////////////////////////////////////////////////////// +// Class : FFTCompressor +// Description : This class manages a lossy compression and +// decompression of a stream of floating-point numbers +// to a datagram, based a fourier transform algorithm +// (similar in principle to JPEG compression). +// +// Actually, it doesn't do any real compression on its +// own; it just outputs a stream of integers that should +// compress much tighter via gzip than the original +// stream of floats would have. +// +// This class depends on the external FFTW library; +// without it, it will fall back on lossless output of +// the original data. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA FFTCompressor { +public: + FFTCompressor(); + + static bool is_compression_available(); + + void set_quality(int quality); + int get_quality() const; + + void write_header(Datagram &datagram); + void write_reals(Datagram &datagram, const float *array, int length); + void write_hprs(Datagram &datagram, const LVecBase3f *array, int length); + + bool read_header(DatagramIterator &di); + bool read_reals(DatagramIterator &di, vector_float &array); + bool read_hprs(DatagramIterator &di, vector_LVecBase3f &array); + + static void free_storage(); + +private: + enum RunWidth { + // We write a byte to the datagram at the beginning of each run to + // encode the width and length of the run. The width is indicated + // by the top two bits, while the length fits in the lower six, + // except RW_double, which is a special case. + RW_width_mask = 0xc0, + RW_length_mask = 0x3f, + RW_0 = 0x00, + RW_8 = 0x40, + RW_16 = 0x80, + RW_32 = 0xc0, + RW_double = 0xff, + RW_invalid = 0x01 + }; + + int write_run(Datagram &datagram, RunWidth run_width, + const vector_double &run); + int read_run(DatagramIterator &di, vector_double &run); + double get_scale_factor(int i, int length) const; + static double interpolate(double t, double a, double b); + + int _quality; + double _fft_offset; + double _fft_factor; + double _fft_exponent; +}; + +#endif + diff --git a/panda/src/putil/Sources.pp b/panda/src/putil/Sources.pp index 3780e28600..70a316950e 100644 --- a/panda/src/putil/Sources.pp +++ b/panda/src/putil/Sources.pp @@ -15,7 +15,8 @@ config_util.N config_util.cxx config_util.h configurable.cxx \ configurable.h factoryBase.I factoryBase.cxx factoryBase.h \ factoryParam.I factoryParam.cxx factoryParam.h factoryParams.I \ - factoryParams.cxx factoryParams.h globPattern.I globPattern.cxx \ + factoryParams.cxx factoryParams.h \ + globPattern.I globPattern.cxx \ globPattern.h globalPointerRegistry.I globalPointerRegistry.cxx \ globalPointerRegistry.h ioPtaDatagramFloat.cxx ioPtaDatagramFloat.h \ ioPtaDatagramInt.cxx ioPtaDatagramInt.h ioPtaDatagramShort.cxx \ @@ -46,7 +47,8 @@ buttonRegistry.h collideMask.h \ config_util.h configurable.h factory.I factory.h \ factoryBase.I factoryBase.h factoryParam.I factoryParam.h \ - factoryParams.I factoryParams.h globPattern.I globPattern.h \ + factoryParams.I factoryParams.h \ + globPattern.I globPattern.h \ globalPointerRegistry.I globalPointerRegistry.h indirectCompareTo.I \ indirectCompareTo.h ioPtaDatagramFloat.h ioPtaDatagramInt.h \ ioPtaDatagramShort.h iterator_types.h keyboardButton.h lineStream.I \