Browse Source

*** empty log message ***

David Rose 25 years ago
parent
commit
a6505665ed
37 changed files with 1410 additions and 140 deletions
  1. 7 0
      dtool/Config.pp
  2. 3 0
      dtool/LocalSetup.pp
  3. 23 0
      dtool/pptempl/Global.pp
  4. 33 0
      panda/src/chan/animChannel.cxx
  5. 5 0
      panda/src/chan/animChannel.h
  6. 15 0
      panda/src/chan/animChannelMatrixXfmTable.I
  7. 61 44
      panda/src/chan/animChannelMatrixXfmTable.cxx
  8. 1 0
      panda/src/chan/animChannelMatrixXfmTable.h
  9. 143 17
      panda/src/chan/animChannelScalarTable.cxx
  10. 1 3
      panda/src/chan/animChannelScalarTable.h
  11. 4 4
      panda/src/chan/config_chan.cxx
  12. 2 1
      panda/src/chan/config_chan.h
  13. 12 0
      panda/src/chan/movingPart.I
  14. 1 0
      panda/src/chan/movingPart.h
  15. 21 0
      panda/src/chan/movingPartBase.cxx
  16. 2 0
      panda/src/chan/movingPartBase.h
  17. 29 0
      panda/src/chan/partGroup.cxx
  18. 2 0
      panda/src/chan/partGroup.h
  19. 24 0
      panda/src/char/character.I
  20. 0 1
      panda/src/char/character.cxx
  21. 3 0
      panda/src/char/character.h
  22. 26 21
      panda/src/framework/framework.cxx
  23. 12 3
      panda/src/linmath/Sources.pp
  24. 8 0
      panda/src/linmath/cmath.I
  25. 2 0
      panda/src/linmath/cmath.h
  26. 64 18
      panda/src/linmath/lquaternion.I
  27. 12 4
      panda/src/linmath/lquaternion.h
  28. 8 18
      panda/src/linmath/lrotation.I
  29. 5 2
      panda/src/linmath/lrotation.h
  30. 11 0
      panda/src/linmath/vector_LVecBase3f.cxx
  31. 32 0
      panda/src/linmath/vector_LVecBase3f.h
  32. 8 2
      panda/src/mathutil/Sources.pp
  33. 4 0
      panda/src/mathutil/config_mathutil.cxx
  34. 4 0
      panda/src/mathutil/config_mathutil.h
  35. 735 0
      panda/src/mathutil/fftCompressor.cxx
  36. 83 0
      panda/src/mathutil/fftCompressor.h
  37. 4 2
      panda/src/putil/Sources.pp

+ 7 - 0
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

+ 3 - 0
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]
 

+ 23 - 0
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

+ 33 - 0
panda/src/chan/animChannel.cxx

@@ -5,8 +5,41 @@
 
 #include "animChannel.h"
 
+#include <compose_matrix.h>
 
 // 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;
+  }
+}
+

+ 5 - 0
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<LMatrix4f>"; }
+  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<float>"; }
+  static void output_value(ostream &out, ValueType value) {
+    out << value;
+  }
   static void write_datagram(Datagram &dest, ValueType& me)
   {
     dest.add_float32(me); 

+ 15 - 0
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

+ 61 - 44
panda/src/chan/animChannelMatrixXfmTable.cxx

@@ -13,6 +13,7 @@
 #include <datagramIterator.h>
 #include <bamReader.h>
 #include <bamWriter.h>
+#include <fftCompressor.h>
 
 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;
     }
   }

+ 1 - 0
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;

+ 143 - 17
panda/src/chan/animChannelScalarTable.cxx

@@ -12,6 +12,7 @@
 #include <datagramIterator.h>
 #include <bamReader.h>
 #include <bamWriter.h>
+#include <fftCompressor.h>
 
 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<int, int> 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<int, int>::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<int, int>::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();
+
+  PTA_float temp_table(0);
 
-  if (!wrote_quantized) {
+  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;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 3
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:

+ 4 - 4
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();

+ 2 - 1
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

+ 12 - 0
panda/src/chan/movingPart.I

@@ -87,6 +87,18 @@ make_initial_channel() const {
   return new AnimChannelFixed<SwitchType>(get_name(), _initial_value);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovingPart::output_value
+//       Access: Public, Virtual
+//  Description: Outputs a very brief description of the channel's
+//               current value.
+////////////////////////////////////////////////////////////////////
+template<class SwitchType>
+void MovingPart<SwitchType>::
+output_value(ostream &out) const {
+  SwitchType::output_value(out, _value);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MovingPart::write_datagram
 //       Access: Public

+ 1 - 0
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;

+ 21 - 0
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

+ 2 - 0
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);

+ 29 - 0
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);
+  }
+}
+
 
 
 

+ 2 - 0
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<int> &holes, int &next) const;
   virtual void bind_hierarchy(AnimGroup *anim, int channel_index);

+ 24 - 0
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);
+}

+ 0 - 1
panda/src/char/character.cxx

@@ -93,7 +93,6 @@ safe_to_transform() const {
   return false;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Character::app_traverse
 //       Access: Public, Virtual

+ 3 - 0
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();
 

+ 26 - 21
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();

+ 12 - 3
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

+ 8 - 0
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);

+ 2 - 0
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.

+ 64 - 18
panda/src/linmath/lquaternion.I

@@ -3,10 +3,6 @@
 // 
 ////////////////////////////////////////////////////////////////////
 
-#include "lquaternion.h"
-#include "nearly_zero.h"
-#include <notify.h>
-
 template<class NumType>
 TypeHandle LQuaternionBase<NumType>::_type_handle;
 
@@ -341,19 +337,18 @@ INLINE void LQuaternionBase<NumType>::
 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<class NumType>
-INLINE void LQuaternionBase<NumType>::
+void LQuaternionBase<NumType>::
 set(const LMatrix3<NumType> &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<NumType> &m) {
 //  Description: Do-While Jones paper from cary.
 ////////////////////////////////////////////////////////////////////
 template<class NumType>
-INLINE void LQuaternionBase<NumType>::
+void LQuaternionBase<NumType>::
 extract_to_matrix(LMatrix3<NumType> &m) const {
   NumType tx, ty, tz, tq, tk, tk_denom;
 
@@ -451,7 +446,7 @@ extract_to_matrix(LMatrix3<NumType> &m) const {
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 template<class NumType>
-INLINE void LQuaternionBase<NumType>::
+void LQuaternionBase<NumType>::
 extract_to_matrix(LMatrix4<NumType> &m) const {
   NumType tx, ty, tz, tq, tk, tk_denom;
 
@@ -485,6 +480,57 @@ extract_to_matrix(LMatrix4<NumType> &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<class NumType>
+INLINE void LQuaternionBase<NumType>::
+set_hpr(const LVecBase3<NumType> &hpr) {
+  LQuaternionBase<NumType> 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<class NumType>
+INLINE LVecBase3<NumType> LQuaternionBase<NumType>::
+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<NumType>(rad_2_deg(atan2(sinv, cosv)),
+			    rad_2_deg(atan2(sint, cost)),
+			    rad_2_deg(atan2(sinf, cosf)));
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: operator *(Matrix3, Quat)
 //       Access: public

+ 12 - 4
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 <notify.h>
 
 ////////////////////////////////////////////////////////////////////
 //       Class : LQuaternionBase
@@ -43,11 +48,14 @@ PUBLISHED:
 
   INLINE void set(NumType, NumType, NumType, NumType);
 
-  INLINE void set(const LMatrix3<NumType> &);
-  INLINE void set(const LMatrix4<NumType> &);
+  void set(const LMatrix3<NumType> &m);
+  INLINE void set(const LMatrix4<NumType> &m);
+
+  void extract_to_matrix(LMatrix3<NumType> &m) const;
+  void extract_to_matrix(LMatrix4<NumType> &m) const;
 
-  INLINE void extract_to_matrix(LMatrix3<NumType> &) const;
-  INLINE void extract_to_matrix(LMatrix4<NumType> &) const;
+  INLINE void set_hpr(const LVecBase3<NumType> &hpr);
+  LVecBase3<NumType> get_hpr() const;
 
   INLINE NumType get_r(void) const;
   INLINE NumType get_i(void) const;

+ 8 - 18
panda/src/linmath/lrotation.I

@@ -3,10 +3,6 @@
 // 
 ////////////////////////////////////////////////////////////////////
 
-#include "lrotation.h"
-#include <notify.h>
-#include <math.h>
-
 template<class NumType>
 TypeHandle LRotation<NumType>::_type_handle;
 
@@ -71,12 +67,12 @@ LRotation(const LMatrix4<NumType> &m) {
 ////////////////////////////////////////////////////////////////////
 template<class NumType>
 INLINE LRotation<NumType>::
-LRotation(const LVector3<NumType> &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<NumType> &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<NumType> &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<class NumType>
 INLINE LRotation<NumType>::
-LRotation(float h, float p, float r) {
-  LQuaternionBase<NumType> 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<NumType>(h, p, r));
 }
 
 ////////////////////////////////////////////////////////////////////

+ 5 - 2
panda/src/linmath/lrotation.h

@@ -8,6 +8,9 @@
 
 #include <pandabase.h>
 #include "lquaternion.h"
+#include "cmath.h"
+
+#include <notify.h>
 
 ////////////////////////////////////////////////////////////////////////
 //       Class : LRotation
@@ -19,10 +22,10 @@ PUBLISHED:
   INLINE LRotation();
   INLINE LRotation(const LQuaternionBase<NumType>&);
   INLINE LRotation(NumType, NumType, NumType, NumType);
-  INLINE LRotation(const LVector3<NumType> &, float);
+  INLINE LRotation(const LVector3<NumType> &, NumType);
   INLINE LRotation(const LMatrix3<NumType> &);
   INLINE LRotation(const LMatrix4<NumType> &);
-  INLINE LRotation(float, float, float);
+  INLINE LRotation(NumType, NumType, NumType);
   virtual ~LRotation();
 
   INLINE LRotation<NumType>

+ 11 - 0
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

+ 32 - 0
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 <pandabase.h>
+
+#include "luse.h"
+
+#include <vector>
+
+////////////////////////////////////////////////////////////////////
+//       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<LVecBase3f>)
+typedef vector<LVecBase3f> vector_LVecBase3f;
+
+// Tell GCC that we'll take care of the instantiation explicitly here.
+#ifdef __GNUC__
+#pragma interface
+#endif
+
+#endif

+ 8 - 2
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 \

+ 4 - 0
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();

+ 4 - 0
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
 
 

+ 735 - 0
panda/src/mathutil/fftCompressor.cxx

@@ -0,0 +1,735 @@
+// Filename: fftCompressor.cxx
+// Created by:  drose (11Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "fftCompressor.h"
+#include "config_mathutil.h"
+
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <compose_matrix.h>
+#include <math.h>
+
+#ifdef HAVE_FFTW
+
+#include <rfftw.h>
+
+// 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<int, rfftw_plan> 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

+ 83 - 0
panda/src/mathutil/fftCompressor.h

@@ -0,0 +1,83 @@
+// Filename: fftCompressor.h
+// Created by:  drose (11Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef FFTCOMPRESSOR_H
+#define FFTCOMPRESSOR_H
+
+#include <pandabase.h>
+
+#include <pointerToArray.h>
+#include <vector_float.h>
+#include <vector_double.h>
+#include <vector_LVecBase3f.h>
+
+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
+

+ 4 - 2
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 \