Browse Source

Merge branch 'master' into deploy-ng

rdb 7 years ago
parent
commit
01915a36db
100 changed files with 2834 additions and 1966 deletions
  1. 0 12
      direct/src/actor/Actor.py
  2. 72 0
      direct/src/dcparser/dcArrayParameter.cxx
  3. 4 0
      direct/src/dcparser/dcArrayParameter.h
  4. 5 5
      direct/src/dcparser/dcAtomicField.cxx
  5. 1 1
      direct/src/dcparser/dcAtomicField.h
  6. 9 2
      direct/src/dcparser/dcClass.cxx
  7. 1 1
      direct/src/dcparser/dcClass.h
  8. 3 3
      direct/src/dcparser/dcField.I
  9. 10 9
      direct/src/dcparser/dcField.cxx
  10. 6 6
      direct/src/dcparser/dcField.h
  11. 160 142
      direct/src/dcparser/dcLexer.cxx.prebuilt
  12. 31 31
      direct/src/dcparser/dcLexer.lxx
  13. 107 9
      direct/src/dcparser/dcPacker.I
  14. 9 9
      direct/src/dcparser/dcPacker.cxx
  15. 13 6
      direct/src/dcparser/dcPacker.h
  16. 16 0
      direct/src/dcparser/dcPackerInterface.cxx
  17. 7 2
      direct/src/dcparser/dcPackerInterface.h
  18. 434 547
      direct/src/dcparser/dcParser.cxx.prebuilt
  19. 66 63
      direct/src/dcparser/dcParser.h.prebuilt
  20. 28 27
      direct/src/dcparser/dcParser.yxx
  21. 2 0
      direct/src/dcparser/dcParserDefs.h
  22. 119 0
      direct/src/dcparser/dcSimpleParameter.cxx
  23. 4 0
      direct/src/dcparser/dcSimpleParameter.h
  24. 8 7
      direct/src/dcparser/dcSwitch.cxx
  25. 6 6
      direct/src/dcparser/dcSwitch.h
  26. 12 0
      direct/src/dcparser/hashGenerator.cxx
  27. 2 0
      direct/src/dcparser/hashGenerator.h
  28. 16 2
      direct/src/directscripts/Doxyfile.cxx
  29. 3 1
      direct/src/directscripts/Doxyfile.python
  30. 79 51
      direct/src/directscripts/extract_docs.py
  31. 1 1
      direct/src/distributed/cConnectionRepository.cxx
  32. 0 10
      direct/src/stdpy/thread.py
  33. 155 125
      dtool/metalibs/dtoolconfig/pydtool.cxx
  34. 58 2
      dtool/src/cppparser/cppExpression.cxx
  35. 1 0
      dtool/src/cppparser/cppExpression.h
  36. 161 161
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  37. 4 1
      dtool/src/interrogate/interrogate.cxx
  38. 1 0
      dtool/src/interrogate/interrogateBuilder.cxx
  39. 12 0
      dtool/src/interrogatedb/interrogate_interface.cxx
  40. 3 0
      dtool/src/interrogatedb/interrogate_interface.h
  41. 274 172
      makepanda/installer.nsi
  42. 25 24
      makepanda/installpanda.py
  43. 165 75
      makepanda/makepackage.py
  44. 25 10
      makepanda/makepanda.py
  45. 3 3
      makepanda/makepanda.vcproj
  46. 65 0
      makepanda/makepandacore.py
  47. 6 6
      panda/src/bullet/bulletBodyNode.cxx
  48. 5 5
      panda/src/bullet/bulletCapsuleShape.cxx
  49. 2 2
      panda/src/bullet/bulletCapsuleShape.h
  50. 8 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h
  51. 68 4
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm
  52. 3 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  53. 14 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  54. 10 10
      panda/src/collide/collisionBox.cxx
  55. 1 1
      panda/src/collide/collisionBox.h
  56. 19 19
      panda/src/collide/collisionCapsule.I
  57. 74 74
      panda/src/collide/collisionCapsule.cxx
  58. 161 0
      panda/src/collide/collisionCapsule.h
  59. 1 1
      panda/src/collide/collisionHandlerFluidPusher.cxx
  60. 11 11
      panda/src/collide/collisionPlane.cxx
  61. 1 1
      panda/src/collide/collisionPlane.h
  62. 4 4
      panda/src/collide/collisionSolid.cxx
  63. 2 2
      panda/src/collide/collisionSolid.h
  64. 10 10
      panda/src/collide/collisionSphere.cxx
  65. 1 1
      panda/src/collide/collisionSphere.h
  66. 2 2
      panda/src/collide/collisionTraverser.cxx
  67. 8 141
      panda/src/collide/collisionTube.h
  68. 7 3
      panda/src/collide/config_collide.cxx
  69. 1 2
      panda/src/collide/p3collide_composite1.cxx
  70. 2 1
      panda/src/collide/p3collide_composite2.cxx
  71. 4 4
      panda/src/display/graphicsEngine.cxx
  72. 2 2
      panda/src/display/graphicsEngine.h
  73. 1 1
      panda/src/doc/collisionFlags.txt
  74. 20 27
      panda/src/dxml/config_dxml.cxx
  75. 2 1
      panda/src/egg/eggGroup.cxx
  76. 10 10
      panda/src/egg2pg/eggLoader.cxx
  77. 2 2
      panda/src/egg2pg/eggLoader.h
  78. 15 15
      panda/src/egg2pg/eggSaver.cxx
  79. 2 2
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  80. 3 2
      panda/src/glstuff/glBufferContext_src.I
  81. 2 1
      panda/src/glstuff/glBufferContext_src.h
  82. 1 1
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  83. 16 2
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  84. 12 2
      panda/src/glstuff/glShaderContext_src.cxx
  85. 8 0
      panda/src/gobj/bufferContext.I
  86. 2 1
      panda/src/gobj/bufferContext.cxx
  87. 11 1
      panda/src/gobj/bufferContext.h
  88. 7 8
      panda/src/gobj/indexBufferContext.I
  89. 0 3
      panda/src/gobj/indexBufferContext.h
  90. 25 18
      panda/src/gobj/preparedGraphicsObjects.cxx
  91. 25 0
      panda/src/gobj/shaderBuffer.cxx
  92. 5 0
      panda/src/gobj/shaderBuffer.h
  93. 9 10
      panda/src/gobj/textureContext.I
  94. 0 3
      panda/src/gobj/textureContext.h
  95. 19 0
      panda/src/gobj/texturePeeker.cxx
  96. 1 0
      panda/src/gobj/texturePeeker.h
  97. 7 8
      panda/src/gobj/vertexBufferContext.I
  98. 0 4
      panda/src/gobj/vertexBufferContext.h
  99. 4 7
      panda/src/pgraph/camera.cxx
  100. 12 5
      panda/src/pgraph/pandaNode.cxx

+ 0 - 12
direct/src/actor/Actor.py

@@ -653,15 +653,11 @@ class Actor(DirectObject, NodePath):
         """
         # make sure we don't call this twice in a row
         # and pollute the the switches dictionary
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         child = self.__LODNode.find(str(lodName))
         index = self.__LODNode.node().findChild(child.node())
         self.__LODNode.node().forceSwitch(index)
 
     def printLOD(self):
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         sortedKeys = self.__sortedLODNames
         for eachLod in sortedKeys:
             print("python switches for %s: in: %d, out %d" % (eachLod,
@@ -679,12 +675,6 @@ class Actor(DirectObject, NodePath):
         """
         Restore all switch distance info (usually after a useLOD call)"""
         self.__LODNode.node().clearForceSwitch()
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
-##         for eachLod in sortedKeys:
-##             index = sortedKeys.index(eachLod)
-##             self.__LODNode.node().setSwitch(index, self.switches[eachLod][0],
-##                                      self.switches[eachLod][1])
 
     def addLOD(self, lodName, inDist=0, outDist=0, center=None):
         """addLOD(self, string)
@@ -706,8 +696,6 @@ class Actor(DirectObject, NodePath):
         # save the switch distance info
         self.switches[lodName] = [inDist, outDist]
         # add the switch distance info
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         self.__LODNode.node().setSwitch(self.getLODIndex(lodName), inDist, outDist)
 
     def getLODIndex(self, lodName):

+ 72 - 0
direct/src/dcparser/dcArrayParameter.cxx

@@ -262,6 +262,38 @@ pack_string(DCPackData &pack_data, const string &value,
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCArrayParameter::
+pack_blob(DCPackData &pack_data, const vector_uchar &value,
+          bool &pack_error, bool &range_error) const {
+  // We can only pack a string if the array element type is char or int8.
+  DCSimpleParameter *simple_type = _element_type->as_simple_parameter();
+  if (simple_type == nullptr) {
+    pack_error = true;
+    return;
+  }
+
+  size_t blob_size = value.size();
+
+  switch (simple_type->get_type()) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    _array_size_range.validate(blob_size, range_error);
+    if (_num_length_bytes != 0) {
+      nassertv(_num_length_bytes == 2);
+      do_pack_uint16(pack_data.get_write_pointer(2), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Packs the arrayParameter's specified default value (or a sensible default
  * if no value is specified) into the stream.  Returns true if the default
@@ -339,6 +371,46 @@ unpack_string(const char *data, size_t length, size_t &p, string &value,
   }
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCArrayParameter::
+unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value,
+            bool &pack_error, bool &range_error) const {
+  // We can only unpack a string if the array element type is char or int8.
+  DCSimpleParameter *simple_type = _element_type->as_simple_parameter();
+  if (simple_type == nullptr) {
+    pack_error = true;
+    return;
+  }
+
+  size_t blob_size;
+
+  switch (simple_type->get_type()) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    if (_num_length_bytes != 0) {
+      blob_size = do_unpack_uint16(data + p);
+      p += 2;
+    } else {
+      nassertv(_array_size >= 0);
+      blob_size = _array_size;
+    }
+    if (p + blob_size > length) {
+      pack_error = true;
+      return;
+    }
+    value = vector_uchar((const unsigned char *)data + p,
+                         (const unsigned char *)data + p + blob_size);
+    p += blob_size;
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Returns true if the other interface is bitwise the same as this one--that
  * is, a uint32 only matches a uint32, etc.  Names of components, and range

+ 4 - 0
direct/src/dcparser/dcArrayParameter.h

@@ -51,9 +51,13 @@ public:
   virtual void generate_hash(HashGenerator &hashgen) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
 
 protected:
   virtual bool do_check_match(const DCPackerInterface *other) const;

+ 5 - 5
direct/src/dcparser/dcAtomicField.cxx

@@ -88,11 +88,11 @@ get_element(int n) const {
  * If the element is an array-type element, the returned value will include
  * the two-byte length preceding the array data.
  *
- * This is deprecated; use get_element() instead.
+ * @deprecated use get_element() instead.
  */
-string DCAtomicField::
+vector_uchar DCAtomicField::
 get_element_default(int n) const {
-  nassertr(n >= 0 && n < (int)_elements.size(), string());
+  nassertr(n >= 0 && n < (int)_elements.size(), vector_uchar());
   return _elements[n]->get_default_value();
 }
 
@@ -100,7 +100,7 @@ get_element_default(int n) const {
  * Returns true if the nth element of the field has a default value specified,
  * false otherwise.
  *
- * This is deprecated; use get_element() instead.
+ * @deprecated use get_element() instead.
  */
 bool DCAtomicField::
 has_element_default(int n) const {
@@ -113,7 +113,7 @@ has_element_default(int n) const {
  * for documentary purposes; it does not generally affect operation.  If a
  * name is not specified, this will be the empty string.
  *
- * This method is deprecated; use get_element()->get_name() instead.
+ * @deprecated use get_element()->get_name() instead.
  */
 string DCAtomicField::
 get_element_name(int n) const {

+ 1 - 1
direct/src/dcparser/dcAtomicField.h

@@ -40,7 +40,7 @@ PUBLISHED:
   DCParameter *get_element(int n) const;
 
   // These five methods are deprecated and will be removed soon.
-  std::string get_element_default(int n) const;
+  vector_uchar get_element_default(int n) const;
   bool has_element_default(int n) const;
   std::string get_element_name(int n) const;
   DCSubatomicType get_element_type(int n) const;

+ 9 - 2
direct/src/dcparser/dcClass.cxx

@@ -587,7 +587,7 @@ receive_update_other(PyObject *distobj, DatagramIterator &di) const {
  */
 void DCClass::
 direct_update(PyObject *distobj, const string &field_name,
-              const string &value_blob) {
+              const vector_uchar &value_blob) {
   DCField *field = get_field_by_name(field_name);
   nassertv_always(field != nullptr);
 
@@ -606,7 +606,14 @@ direct_update(PyObject *distobj, const string &field_name,
 void DCClass::
 direct_update(PyObject *distobj, const string &field_name,
               const Datagram &datagram) {
-  direct_update(distobj, field_name, datagram.get_message());
+  DCField *field = get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
+  packer.begin_unpack(field);
+  field->receive_update(packer, distobj);
+  packer.end_unpack();
 }
 #endif  // HAVE_PYTHON
 

+ 1 - 1
direct/src/dcparser/dcClass.h

@@ -95,7 +95,7 @@ PUBLISHED:
   void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
 
   void direct_update(PyObject *distobj, const std::string &field_name,
-                     const std::string &value_blob);
+                     const vector_uchar &value_blob);
   void direct_update(PyObject *distobj, const std::string &field_name,
                      const Datagram &datagram);
   bool pack_required_field(Datagram &datagram, PyObject *distobj,

+ 3 - 3
direct/src/dcparser/dcField.I

@@ -42,7 +42,7 @@ has_default_value() const {
  * explicitly set (e.g.  has_default_value() returns true), returns that
  * value; otherwise, returns an implicit default for the field.
  */
-INLINE const std::string &DCField::
+INLINE const vector_uchar &DCField::
 get_default_value() const {
   if (_default_value_stale) {
     ((DCField *)this)->refresh_default_value();
@@ -172,8 +172,8 @@ set_class(DCClass *dclass) {
  * Establishes a default value for this field.
  */
 INLINE void DCField::
-set_default_value(const std::string &default_value) {
-  _default_value = default_value;
+set_default_value(vector_uchar default_value) {
+  _default_value = std::move(default_value);
   _has_default_value = true;
   _default_value_stale = false;
 }

+ 10 - 9
direct/src/dcparser/dcField.cxx

@@ -162,7 +162,7 @@ as_parameter() const {
  * is an error.
  */
 string DCField::
-format_data(const string &packed_data, bool show_field_names) {
+format_data(const vector_uchar &packed_data, bool show_field_names) {
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
@@ -178,20 +178,20 @@ format_data(const string &packed_data, bool show_field_names) {
  * above) that represents the value of this field, parse the string and return
  * the corresponding packed data.  Returns empty string if there is an error.
  */
-string DCField::
+vector_uchar DCField::
 parse_string(const string &formatted_string) {
   DCPacker packer;
   packer.begin_pack(this);
   if (!packer.parse_and_pack(formatted_string)) {
     // Parse error.
-    return string();
+    return vector_uchar();
   }
   if (!packer.end_pack()) {
     // Data type mismatch.
-    return string();
+    return vector_uchar();
   }
 
-  return packer.get_string();
+  return packer.get_bytes();
 }
 
 /**
@@ -200,7 +200,7 @@ parse_string(const string &formatted_string) {
  * record.  Returns true if all fields are valid, false otherwise.
  */
 bool DCField::
-validate_ranges(const string &packed_data) const {
+validate_ranges(const vector_uchar &packed_data) const {
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
@@ -209,7 +209,7 @@ validate_ranges(const string &packed_data) const {
     return false;
   }
 
-  return (packer.get_num_unpacked_bytes() == packed_data.length());
+  return (packer.get_num_unpacked_bytes() == packed_data.size());
 }
 
 #ifdef HAVE_PYTHON
@@ -488,7 +488,7 @@ pack_default_value(DCPackData &pack_data, bool &) const {
   // The default behavior is to pack the default value if we got it;
   // otherwise, to return false and let the packer visit our nested elements.
   if (!_default_value_stale) {
-    pack_data.append_data(_default_value.data(), _default_value.length());
+    pack_data.append_data((const char *)_default_value.data(), _default_value.size());
     return true;
   }
 
@@ -566,7 +566,8 @@ refresh_default_value() {
   if (!packer.end_pack()) {
     std::cerr << "Error while packing default value for " << get_name() << "\n";
   } else {
-    _default_value.assign(packer.get_data(), packer.get_length());
+    const unsigned char *data = (const unsigned char *)packer.get_data();
+    _default_value = vector_uchar(data, data + packer.get_length());
   }
   _default_value_stale = false;
 }

+ 6 - 6
direct/src/dcparser/dcField.h

@@ -53,13 +53,13 @@ PUBLISHED:
   virtual DCParameter *as_parameter();
   virtual const DCParameter *as_parameter() const;
 
-  std::string format_data(const std::string &packed_data, bool show_field_names = true);
-  std::string parse_string(const std::string &formatted_string);
+  std::string format_data(const vector_uchar &packed_data, bool show_field_names = true);
+  vector_uchar parse_string(const std::string &formatted_string);
 
-  bool validate_ranges(const std::string &packed_data) const;
+  bool validate_ranges(const vector_uchar &packed_data) const;
 
   INLINE bool has_default_value() const;
-  INLINE const std::string &get_default_value() const;
+  INLINE const vector_uchar &get_default_value() const;
 
   INLINE bool is_bogus_field() const;
 
@@ -98,7 +98,7 @@ public:
 
   INLINE void set_number(int number);
   INLINE void set_class(DCClass *dclass);
-  INLINE void set_default_value(const std::string &default_value);
+  INLINE void set_default_value(vector_uchar default_value);
 
 #ifdef HAVE_PYTHON
   static std::string get_pystr(PyObject *value);
@@ -115,7 +115,7 @@ protected:
   bool _bogus_field;
 
 private:
-  std::string _default_value;
+  vector_uchar _default_value;
 
 #ifdef WITHIN_PANDA
   PStatCollector _field_update_pcollector;

File diff suppressed because it is too large
+ 160 - 142
direct/src/dcparser/dcLexer.cxx.prebuilt


+ 31 - 31
direct/src/dcparser/dcLexer.lxx

@@ -1,7 +1,7 @@
 /*
 // Filename: dcLexer.lxx
 // Created by:  drose (05Oct00)
-// 
+//
 ////////////////////////////////////////////////////////////////////
 */
 
@@ -103,12 +103,12 @@ dcyyerror(const std::string &msg) {
   if (!dc_filename.empty()) {
     cerr << " in " << dc_filename;
   }
-  cerr 
+  cerr
     << " at line " << line_number << ", column " << col_number << ":\n"
     << current_line << "\n";
-  indent(cerr, col_number-1) 
+  indent(cerr, col_number-1)
     << "^\n" << msg << "\n\n";
-  
+
   error_count++;
 }
 
@@ -120,10 +120,10 @@ dcyywarning(const std::string &msg) {
   if (!dc_filename.empty()) {
     cerr << " in " << dc_filename;
   }
-  cerr 
+  cerr
     << " at line " << line_number << ", column " << col_number << ":\n"
     << current_line << "\n";
-  indent(cerr, col_number-1) 
+  indent(cerr, col_number-1)
     << "^\n" << msg << "\n\n";
 
   warning_count++;
@@ -313,9 +313,9 @@ scan_quoted_string(char quote_mark) {
 
 // scan_hex_string reads a string of hexadecimal digits delimited by
 // angle brackets and returns the representative string.
-static std::string
+static vector_uchar
 scan_hex_string() {
-  std::string result;
+  vector_uchar result;
 
   // We don't touch the current line number and column number during
   // scanning, so that if we detect an error while scanning the string
@@ -345,24 +345,24 @@ scan_hex_string() {
       line_number = line;
       col_number = col;
       dcyyerror("Invalid hex digit.");
-      return std::string();
+      return vector_uchar();
     }
 
     odd = !odd;
     if (odd) {
       last = value;
     } else {
-      result += (char)((last << 4) | value);
+      result.push_back((unsigned char)((last << 4) | value));
     }
     c = read_char(line, col);
   }
 
   if (c == EOF) {
     dcyyerror("This hex string is unterminated.");
-    return std::string();
+    return vector_uchar();
   } else if (odd) {
     dcyyerror("Odd number of hex digits.");
-    return std::string();
+    return vector_uchar();
   }
 
   line_number = line;
@@ -382,7 +382,7 @@ eat_c_comment() {
   int col = col_number;
 
   int c, last_c;
-  
+
   last_c = '\0';
   c = read_char(line, col);
   while (c != EOF && !(last_c == '*' && c == '/')) {
@@ -440,12 +440,12 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
   yyless(1);
 }
 
-[ \t\r] { 
+[ \t\r] {
   // Eat whitespace.
   accept();
 }
 
-"//".* { 
+"//".* {
   // Eat C++-style comments.
   accept();
 }
@@ -453,7 +453,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 "/*" {
   // Eat C-style comments.
   accept();
-  eat_c_comment(); 
+  eat_c_comment();
 }
 
 
@@ -607,7 +607,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
   return KW_CHAR;
 }
 
-{UNSIGNED_INTEGERNUM} { 
+{UNSIGNED_INTEGERNUM} {
   // An unsigned integer number.
   accept();
 
@@ -626,11 +626,11 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
     dcyylval.u.uint64 = next_value + (*p - '0');
     ++p;
   }
-  
+
   return UNSIGNED_INTEGER;
 }
 
-{SIGNED_INTEGERNUM} { 
+{SIGNED_INTEGERNUM} {
   // A signed integer number.
   accept();
 
@@ -671,14 +671,14 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
       dcyyerror("Number out of range.");
       dcyylval.u.int64 = 1;
     }
-  }    
-  
+  }
+
   return SIGNED_INTEGER;
 }
 
 {UNSIGNED_HEXNUM} {
   // A hexadecimal integer number.
-  accept(); 
+  accept();
 
   // As above, we'll decode the hex string by hand.
   dcyylval.str = dcyytext;
@@ -700,15 +700,15 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
     ++p;
   }
 
-  return UNSIGNED_INTEGER; 
+  return UNSIGNED_INTEGER;
 }
 
-{REALNUM} { 
+{REALNUM} {
   // A floating-point number.
-  accept(); 
-  dcyylval.u.real = patof(dcyytext); 
+  accept();
+  dcyylval.u.real = patof(dcyytext);
   dcyylval.str = dcyytext;
-  return REAL; 
+  return REAL;
 }
 
 ["] {
@@ -728,11 +728,11 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 [<] {
   // Long hex string.
   accept();
-  dcyylval.str = scan_hex_string();
+  dcyylval.bytes = scan_hex_string();
   return HEX_STRING;
 }
 
-[A-Za-z_][A-Za-z_0-9]* { 
+[A-Za-z_][A-Za-z_0-9]* {
   // Identifier or keyword.
   accept();
   dcyylval.str = dcyytext;
@@ -750,7 +750,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 
 . {
   // Send any other printable character as itself.
-  accept(); 
+  accept();
   return dcyytext[0];
 }
-  
+

+ 107 - 9
direct/src/dcparser/dcPacker.I

@@ -216,17 +216,31 @@ pack_string(const std::string &value) {
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+INLINE void DCPacker::
+pack_blob(const vector_uchar &value) {
+  nassertv(_mode == M_pack || _mode == M_repack);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+  } else {
+    _current_field->pack_blob(_pack_data, value, _pack_error, _range_error);
+    advance();
+  }
+}
+
 /**
  * Adds the indicated string value into the stream, representing a single pre-
  * packed field element, or a whole group of field elements at once.
  */
 INLINE void DCPacker::
-pack_literal_value(const std::string &value) {
+pack_literal_value(const vector_uchar &value) {
   nassertv(_mode == M_pack || _mode == M_repack);
   if (_current_field == nullptr) {
     _pack_error = true;
   } else {
-    _pack_data.append_data(value.data(), value.length());
+    _pack_data.append_data((const char *)value.data(), value.size());
     advance();
   }
 }
@@ -345,16 +359,36 @@ unpack_string() {
   return value;
 }
 
+/**
+ * Unpacks the current binary data value from the stream.
+ */
+INLINE vector_uchar DCPacker::
+unpack_blob() {
+  vector_uchar value;
+  nassertr(_mode == M_unpack, value);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+
+  } else {
+    _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p,
+                                 value, _pack_error, _range_error);
+    advance();
+  }
+
+  return value;
+}
+
 /**
  * Returns the literal string that represents the packed value of the current
  * field, and advances the field pointer.
  */
-INLINE std::string DCPacker::
+INLINE vector_uchar DCPacker::
 unpack_literal_value() {
   size_t start = _unpack_p;
   unpack_skip();
-  nassertr(_unpack_p >= start, std::string());
-  return std::string(_unpack_data + start, _unpack_p - start);
+  nassertr(_unpack_p >= start, vector_uchar());
+  return vector_uchar((const unsigned char *)_unpack_data + start,
+                      (const unsigned char *)_unpack_data + _unpack_p);
 }
 
 /**
@@ -453,16 +487,33 @@ unpack_string(std::string &value) {
   }
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+INLINE void DCPacker::
+unpack_blob(vector_uchar &value) {
+  nassertv(_mode == M_unpack);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+
+  } else {
+    _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p,
+                                value, _pack_error, _range_error);
+    advance();
+  }
+}
+
 /**
  * Returns the literal string that represents the packed value of the current
  * field, and advances the field pointer.
  */
 INLINE void DCPacker::
-unpack_literal_value(std::string &value) {
+unpack_literal_value(vector_uchar &value) {
   size_t start = _unpack_p;
   unpack_skip();
   nassertv(_unpack_p >= start);
-  value.assign(_unpack_data + start, _unpack_p - start);
+  value = vector_uchar((const unsigned char *)_unpack_data + start,
+                       (const unsigned char *)_unpack_data + _unpack_p);
 }
 
 /**
@@ -541,6 +592,15 @@ get_string() const {
   return _pack_data.get_string();
 }
 
+/**
+ * Returns the packed data buffer as a bytes object.  Also see get_data().
+ */
+INLINE vector_uchar DCPacker::
+get_bytes() const {
+  const unsigned char *p = (const unsigned char *)_pack_data.get_data();
+  return vector_uchar(p, p + _pack_data.get_length());
+}
+
 /**
  * Returns the total number of bytes in the unpack data buffer.  This is the
  * buffer used when unpacking; it is separate from the pack data returned by
@@ -601,9 +661,9 @@ take_data() {
  * between packing sessions.
  */
 INLINE void DCPacker::
-append_data(const char *buffer, size_t size) {
+append_data(const unsigned char *buffer, size_t size) {
   nassertv(_mode == M_idle);
-  _pack_data.append_data(buffer, size);
+  _pack_data.append_data((const char *)buffer, size);
 }
 
 /**
@@ -728,6 +788,16 @@ raw_pack_string(const std::string &value) {
   _pack_data.append_data(value.data(), value.length());
 }
 
+/**
+ * Packs the data into the buffer between packing sessions.
+ */
+INLINE void DCPacker::
+raw_pack_blob(const vector_uchar &value) {
+  nassertv(_mode == M_idle);
+  DCPackerInterface::do_pack_uint16(_pack_data.get_write_pointer(2), value.size());
+  _pack_data.append_data((const char *)value.data(), value.size());
+}
+
 /**
  * Unpacks the data from the buffer between unpacking sessions.
  */
@@ -870,6 +940,16 @@ raw_unpack_string() {
   return value;
 }
 
+/**
+ * Unpacks the data from the buffer between unpacking sessions.
+ */
+INLINE vector_uchar DCPacker::
+raw_unpack_blob() {
+  vector_uchar value;
+  raw_unpack_blob(value);
+  return value;
+}
+
 /**
  * Unpacks the data from the buffer between unpacking sessions.
  */
@@ -971,6 +1051,24 @@ raw_unpack_string(std::string &value) {
   _unpack_p += string_length;
 }
 
+/**
+ * Unpacks the data from the buffer between unpacking sessions.
+ */
+INLINE void DCPacker::
+raw_unpack_blob(vector_uchar &value) {
+  nassertv(_mode == M_idle && _unpack_data != nullptr);
+  unsigned int blob_size = raw_unpack_uint16();
+
+  if (_unpack_p + blob_size > _unpack_length) {
+    _pack_error = true;
+    return;
+  }
+
+  const unsigned char *p = (const unsigned char *)_unpack_data + _unpack_p;
+  value = vector_uchar(p, p + blob_size);
+  _unpack_p += blob_size;
+}
+
 /**
  * Advances to the next field after a call to pack_value() or pop().
  */

+ 9 - 9
direct/src/dcparser/dcPacker.cxx

@@ -114,12 +114,12 @@ end_pack() {
  * version of begin_unpack() that takes only one parameter.
  */
 void DCPacker::
-set_unpack_data(const string &data) {
+set_unpack_data(const vector_uchar &data) {
   nassertv(_mode == M_idle);
 
-  char *buffer = new char[data.length()];
-  memcpy(buffer, data.data(), data.length());
-  set_unpack_data(buffer, data.length(), true);
+  char *buffer = new char[data.size()];
+  memcpy(buffer, data.data(), data.size());
+  set_unpack_data(buffer, data.size(), true);
 }
 
 /**
@@ -721,11 +721,11 @@ pack_object(PyObject *object) {
       pack_string(string(buffer, length));
     }
   } else if (PyBytes_Check(object)) {
-    char *buffer;
+    const unsigned char *buffer;
     Py_ssize_t length;
-    PyBytes_AsStringAndSize(object, &buffer, &length);
+    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
     if (buffer) {
-      pack_string(string(buffer, length));
+      pack_blob(vector_uchar(buffer, buffer + length));
     }
 #else
   } else if (PyString_Check(object) || PyUnicode_Check(object)) {
@@ -1118,9 +1118,9 @@ enquote_string(ostream &out, char quote_mark, const string &str) {
  * Outputs the indicated string as a hex constant.
  */
 void DCPacker::
-output_hex_string(ostream &out, const string &str) {
+output_hex_string(ostream &out, const vector_uchar &str) {
   out << '<';
-  for (string::const_iterator pi = str.begin();
+  for (vector_uchar::const_iterator pi = str.begin();
        pi != str.end();
        ++pi) {
     char buffer[10];

+ 13 - 6
direct/src/dcparser/dcPacker.h

@@ -41,7 +41,7 @@ PUBLISHED:
   void begin_pack(const DCPackerInterface *root);
   bool end_pack();
 
-  void set_unpack_data(const std::string &data);
+  void set_unpack_data(const vector_uchar &data);
 public:
   void set_unpack_data(const char *unpack_data, size_t unpack_length,
                        bool owns_unpack_data);
@@ -75,7 +75,8 @@ PUBLISHED:
   INLINE void pack_int64(int64_t value);
   INLINE void pack_uint64(uint64_t value);
   INLINE void pack_string(const std::string &value);
-  INLINE void pack_literal_value(const std::string &value);
+  INLINE void pack_blob(const vector_uchar &value);
+  INLINE void pack_literal_value(const vector_uchar &value);
   void pack_default_value();
 
   INLINE double unpack_double();
@@ -84,7 +85,8 @@ PUBLISHED:
   INLINE int64_t unpack_int64();
   INLINE uint64_t unpack_uint64();
   INLINE std::string unpack_string();
-  INLINE std::string unpack_literal_value();
+  INLINE vector_uchar unpack_blob();
+  INLINE vector_uchar unpack_literal_value();
   void unpack_validate();
   void unpack_skip();
 
@@ -97,7 +99,8 @@ public:
   INLINE void unpack_int64(int64_t &value);
   INLINE void unpack_uint64(uint64_t &value);
   INLINE void unpack_string(std::string &value);
-  INLINE void unpack_literal_value(std::string &value);
+  INLINE void unpack_blob(vector_uchar &value);
+  INLINE void unpack_literal_value(vector_uchar &value);
 
 PUBLISHED:
 
@@ -119,6 +122,7 @@ PUBLISHED:
 
   INLINE size_t get_length() const;
   INLINE std::string get_string() const;
+  INLINE vector_uchar get_bytes() const;
   INLINE size_t get_unpack_length() const;
   INLINE std::string get_unpack_string() const;
 public:
@@ -126,7 +130,7 @@ public:
   INLINE const char *get_data() const;
   INLINE char *take_data();
 
-  INLINE void append_data(const char *buffer, size_t size);
+  INLINE void append_data(const unsigned char *buffer, size_t size);
   INLINE char *get_write_pointer(size_t size);
 
   INLINE const char *get_unpack_data() const;
@@ -148,6 +152,7 @@ PUBLISHED:
   INLINE void raw_pack_uint64(uint64_t value);
   INLINE void raw_pack_float64(double value);
   INLINE void raw_pack_string(const std::string &value);
+  INLINE void raw_pack_blob(const vector_uchar &value);
 
 // this is a hack to allw me to get in and out of 32bit Mode Faster need to
 // agree with channel_type in dcbase.h
@@ -165,6 +170,7 @@ PUBLISHED:
   INLINE uint64_t raw_unpack_uint64();
   INLINE double raw_unpack_float64();
   INLINE std::string raw_unpack_string();
+  INLINE vector_uchar raw_unpack_blob();
 
 public:
   INLINE void raw_unpack_int8(int &value);
@@ -177,10 +183,11 @@ public:
   INLINE void raw_unpack_uint64(uint64_t &value);
   INLINE void raw_unpack_float64(double &value);
   INLINE void raw_unpack_string(std::string &value);
+  INLINE void raw_unpack_blob(vector_uchar &value);
 
 public:
   static void enquote_string(std::ostream &out, char quote_mark, const std::string &str);
-  static void output_hex_string(std::ostream &out, const std::string &str);
+  static void output_hex_string(std::ostream &out, const vector_uchar &str);
 
 private:
   INLINE void advance();

+ 16 - 0
direct/src/dcparser/dcPackerInterface.cxx

@@ -248,6 +248,14 @@ pack_string(DCPackData &, const string &, bool &pack_error, bool &) const {
   pack_error = true;
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCPackerInterface::
+pack_blob(DCPackData &, const vector_uchar &, bool &pack_error, bool &) const {
+  pack_error = true;
+}
+
 /**
  * Packs the field's specified default value (or a sensible default if no
  * value is specified) into the stream.  Returns true if the default value is
@@ -306,6 +314,14 @@ unpack_string(const char *, size_t, size_t &, string &, bool &pack_error, bool &
   pack_error = true;
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCPackerInterface::
+unpack_blob(const char *, size_t, size_t &, vector_uchar &, bool &pack_error, bool &) const {
+  pack_error = true;
+}
+
 /**
  * Internally unpacks the current numeric or string value and validates it
  * against the type range limits, but does not return the value.  Returns true

+ 7 - 2
direct/src/dcparser/dcPackerInterface.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "dcSubatomicType.h"
+#include "vector_uchar.h"
 
 class DCFile;
 class DCField;
@@ -37,8 +38,8 @@ enum DCPackType {
 
   // These PackTypes are all fundamental types, and should be packed (or
   // unpacked) with the corresponding call to pack_double(), pack_int(), etc.
-  // PT_blob is the same as PT_string, but implies that the string contains
-  // binary data.
+  // PT_blob is similar to PT_string, except that it contains arbitrary binary
+  // data instead of just UTF-8 text.
   PT_double,
   PT_int,
   PT_uint,
@@ -113,6 +114,8 @@ public:
                            bool &pack_error, bool &range_error) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
 
   virtual void unpack_double(const char *data, size_t length, size_t &p,
@@ -127,6 +130,8 @@ public:
                              uint64_t &value, bool &pack_error, bool &range_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
   virtual bool unpack_validate(const char *data, size_t length, size_t &p,
                                bool &pack_error, bool &range_error) const;
   virtual bool unpack_skip(const char *data, size_t length, size_t &p,

File diff suppressed because it is too large
+ 434 - 547
direct/src/dcparser/dcParser.cxx.prebuilt


+ 66 - 63
direct/src/dcparser/dcParser.h.prebuilt

@@ -1,20 +1,19 @@
-/* A Bison parser, made by GNU Bison 2.4.2.  */
+/* A Bison parser, made by GNU Bison 3.1.  */
+
+/* Bison interface for Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
 
-/* Skeleton interface for Bison's Yacc-like parsers in C
-   
-      Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software
-   Foundation, Inc.
-   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -27,66 +26,74 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-   
+
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
+#ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
+# define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
+/* Debug traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int dcyydebug;
+#endif
 
-/* Tokens.  */
+/* Token type.  */
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
-   /* Put the tokens into the symbol table, so that GDB and other debuggers
-      know about them.  */
-   enum yytokentype {
-     UNSIGNED_INTEGER = 258,
-     SIGNED_INTEGER = 259,
-     REAL = 260,
-     STRING = 261,
-     HEX_STRING = 262,
-     IDENTIFIER = 263,
-     KEYWORD = 264,
-     KW_DCLASS = 265,
-     KW_STRUCT = 266,
-     KW_FROM = 267,
-     KW_IMPORT = 268,
-     KW_TYPEDEF = 269,
-     KW_KEYWORD = 270,
-     KW_SWITCH = 271,
-     KW_CASE = 272,
-     KW_DEFAULT = 273,
-     KW_BREAK = 274,
-     KW_INT8 = 275,
-     KW_INT16 = 276,
-     KW_INT32 = 277,
-     KW_INT64 = 278,
-     KW_UINT8 = 279,
-     KW_UINT16 = 280,
-     KW_UINT32 = 281,
-     KW_UINT64 = 282,
-     KW_FLOAT64 = 283,
-     KW_STRING = 284,
-     KW_BLOB = 285,
-     KW_BLOB32 = 286,
-     KW_INT8ARRAY = 287,
-     KW_INT16ARRAY = 288,
-     KW_INT32ARRAY = 289,
-     KW_UINT8ARRAY = 290,
-     KW_UINT16ARRAY = 291,
-     KW_UINT32ARRAY = 292,
-     KW_UINT32UINT8ARRAY = 293,
-     KW_CHAR = 294,
-     START_DC = 295,
-     START_PARAMETER_VALUE = 296,
-     START_PARAMETER_DESCRIPTION = 297
-   };
+  enum yytokentype
+  {
+    UNSIGNED_INTEGER = 258,
+    SIGNED_INTEGER = 259,
+    REAL = 260,
+    STRING = 261,
+    IDENTIFIER = 262,
+    HEX_STRING = 263,
+    KEYWORD = 264,
+    KW_DCLASS = 265,
+    KW_STRUCT = 266,
+    KW_FROM = 267,
+    KW_IMPORT = 268,
+    KW_TYPEDEF = 269,
+    KW_KEYWORD = 270,
+    KW_SWITCH = 271,
+    KW_CASE = 272,
+    KW_DEFAULT = 273,
+    KW_BREAK = 274,
+    KW_INT8 = 275,
+    KW_INT16 = 276,
+    KW_INT32 = 277,
+    KW_INT64 = 278,
+    KW_UINT8 = 279,
+    KW_UINT16 = 280,
+    KW_UINT32 = 281,
+    KW_UINT64 = 282,
+    KW_FLOAT64 = 283,
+    KW_STRING = 284,
+    KW_BLOB = 285,
+    KW_BLOB32 = 286,
+    KW_INT8ARRAY = 287,
+    KW_INT16ARRAY = 288,
+    KW_INT32ARRAY = 289,
+    KW_UINT8ARRAY = 290,
+    KW_UINT16ARRAY = 291,
+    KW_UINT32ARRAY = 292,
+    KW_UINT32UINT8ARRAY = 293,
+    KW_CHAR = 294,
+    START_DC = 295,
+    START_PARAMETER_VALUE = 296,
+    START_PARAMETER_DESCRIPTION = 297
+  };
 #endif
 /* Tokens.  */
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define REAL 260
 #define STRING 261
-#define HEX_STRING 262
-#define IDENTIFIER 263
+#define IDENTIFIER 262
+#define HEX_STRING 263
 #define KEYWORD 264
 #define KW_DCLASS 265
 #define KW_STRUCT 266
@@ -122,15 +129,11 @@
 #define START_PARAMETER_VALUE 296
 #define START_PARAMETER_DESCRIPTION 297
 
+/* Value type.  */
 
 
-
-#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
-
-# define yystype YYSTYPE /* obsolescent; will be withdrawn */
-# define YYSTYPE_IS_DECLARED 1
-#endif
-
 extern YYSTYPE dcyylval;
 
+int dcyyparse (void);
 
+#endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

+ 28 - 27
direct/src/dcparser/dcParser.yxx

@@ -57,7 +57,7 @@ dc_init_parser(istream &in, const string &filename, DCFile &file) {
 }
 
 void
-dc_init_parser_parameter_value(istream &in, const string &filename, 
+dc_init_parser_parameter_value(istream &in, const string &filename,
                                DCPacker &packer) {
   dc_file = nullptr;
   current_packer = &packer;
@@ -89,15 +89,16 @@ dc_cleanup_parser() {
 %token <u.uint64> UNSIGNED_INTEGER
 %token <u.int64> SIGNED_INTEGER
 %token <u.real> REAL
-%token <str> STRING HEX_STRING IDENTIFIER
+%token <str> STRING IDENTIFIER
+%token <bytes> HEX_STRING
 %token <u.keyword> KEYWORD
 
-%token KW_DCLASS 
-%token KW_STRUCT 
-%token KW_FROM 
-%token KW_IMPORT 
-%token KW_TYPEDEF 
-%token KW_KEYWORD 
+%token KW_DCLASS
+%token KW_STRUCT
+%token KW_FROM
+%token KW_IMPORT
+%token KW_TYPEDEF
+%token KW_KEYWORD
 %token KW_SWITCH
 %token KW_CASE
 %token KW_DEFAULT
@@ -224,7 +225,7 @@ import:
 {
   dc_file->add_import_module($2);
 }
-        | KW_FROM import_identifier KW_IMPORT 
+        | KW_FROM import_identifier KW_IMPORT
 {
   dc_file->add_import_module($2);
 }
@@ -255,7 +256,7 @@ typedef_decl:
 {
   if ($2 != nullptr) {
     DCTypedef *dtypedef = new DCTypedef($2);
-    
+
     if (!dc_file->add_typedef(dtypedef)) {
       DCTypedef *old_typedef = dc_file->get_typedef_by_name(dtypedef->get_name());
       if (old_typedef->is_bogus_typedef()) {
@@ -294,7 +295,7 @@ dclass_or_struct:
         ;
 
 dclass:
-        KW_DCLASS optional_name 
+        KW_DCLASS optional_name
 {
   current_class = new DCClass(dc_file, $2, false, false);
 }
@@ -322,7 +323,7 @@ dclass_name:
     if (dclass->is_struct()) {
       yyerror("struct name not allowed");
     }
-  
+
     $$ = dclass;
   }
 }
@@ -398,7 +399,7 @@ dclass_field:
         ;
 
 struct:
-        KW_STRUCT optional_name 
+        KW_STRUCT optional_name
 {
   current_class = new DCClass(dc_file, $2, true, false);
 }
@@ -426,7 +427,7 @@ struct_name:
     if (!dstruct->is_struct()) {
       yyerror("struct name required");
     }
-  
+
     $$ = dstruct;
   }
 }
@@ -538,7 +539,7 @@ unnamed_parameter:
 
 named_parameter_with_default:
         named_parameter
-        | named_parameter '=' 
+        | named_parameter '='
 {
   current_packer = &default_packer;
   current_packer->clear_data();
@@ -553,7 +554,7 @@ named_parameter_with_default:
     is_valid = $1->is_valid();
   }
   if (current_packer->end_pack()) {
-    $1->set_default_value(current_packer->get_string());
+    $1->set_default_value(current_packer->get_bytes());
 
   } else {
     if (is_valid) {
@@ -568,7 +569,7 @@ named_parameter_with_default:
 
 unnamed_parameter_with_default:
         unnamed_parameter
-        | unnamed_parameter '=' 
+        | unnamed_parameter '='
 {
   current_packer = &default_packer;
   current_packer->clear_data();
@@ -583,7 +584,7 @@ unnamed_parameter_with_default:
     is_valid = $1->is_valid();
   }
   if (current_packer->end_pack()) {
-    $1->set_default_value(current_packer->get_string());
+    $1->set_default_value(current_packer->get_bytes());
 
   } else {
     if (is_valid) {
@@ -663,7 +664,7 @@ simple_type_name:
   $$ = simple_param;
 }
         | simple_type_name '%' number
-{ 
+{
   DCSimpleParameter *simple_param = $1->as_simple_parameter();
   nassertr(simple_param != nullptr, 0);
   if (!simple_param->is_numeric_type()) {
@@ -703,10 +704,10 @@ type_name:
           dtypedef = new DCTypedef($1);
         }
       }
-      
+
       dc_file->add_typedef(dtypedef);
     }
-    
+
     $$ = dtypedef->make_new_parameter();
   }
 }
@@ -974,7 +975,7 @@ parameter_value:
 {
   if ($1 != current_packer->get_current_field_name()) {
     ostringstream strm;
-    strm << "Got '" << $1 << "', expected '" 
+    strm << "Got '" << $1 << "', expected '"
          << current_packer->get_current_field_name() << "'";
     yyerror(strm.str());
   }
@@ -1005,7 +1006,7 @@ parameter_actual_value:
 {
   current_packer->pack_literal_value($1);
 }
-        | '{' 
+        | '{'
 {
   current_packer->push();
 }
@@ -1013,7 +1014,7 @@ parameter_actual_value:
 {
   current_packer->pop();
 }
-        | '[' 
+        | '['
 {
   current_packer->push();
 }
@@ -1021,7 +1022,7 @@ parameter_actual_value:
 {
   current_packer->pop();
 }
-        | '(' 
+        | '('
 {
   current_packer->push();
 }
@@ -1223,7 +1224,7 @@ molecular_atom_list:
   if ($3 != nullptr) {
     current_molecular->add_atomic($3);
     if (!$3->is_bogus_field() && !current_molecular->compare_keywords(*$3)) {
-      yyerror("Mismatched keywords in molecule between " + 
+      yyerror("Mismatched keywords in molecule between " +
               current_molecular->get_atomic(0)->get_name() + " and " +
               $3->get_name());
     }
@@ -1283,7 +1284,7 @@ switch_case:
     yyerror("Invalid value for switch parameter");
     current_switch->add_invalid_case();
   } else {
-    int case_index = current_switch->add_case(current_packer->get_string());
+    int case_index = current_switch->add_case(current_packer->get_bytes());
     if (case_index == -1) {
       yyerror("Duplicate case value");
     }

+ 2 - 0
direct/src/dcparser/dcParserDefs.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "dcSubatomicType.h"
+#include "vector_uchar.h"
 
 class DCFile;
 class DCClass;
@@ -61,6 +62,7 @@ public:
     const DCKeyword *keyword;
   } u;
   std::string str;
+  vector_uchar bytes;
 };
 
 // The yacc-generated code expects to use the symbol 'YYSTYPE' to refer to the

+ 119 - 0
direct/src/dcparser/dcSimpleParameter.cxx

@@ -1069,6 +1069,52 @@ pack_string(DCPackData &pack_data, const string &value,
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCSimpleParameter::
+pack_blob(DCPackData &pack_data, const vector_uchar &value,
+          bool &pack_error, bool &range_error) const {
+  size_t blob_size = value.size();
+
+  switch (_type) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    if (blob_size == 0) {
+      pack_error = true;
+    } else {
+      if (blob_size != 1) {
+        range_error = true;
+      }
+      _uint_range.validate((unsigned int)value[0], range_error);
+      do_pack_uint8(pack_data.get_write_pointer(1), (unsigned int)value[0]);
+    }
+    break;
+
+  case ST_string:
+  case ST_blob:
+    _uint_range.validate(blob_size, range_error);
+    validate_uint_limits(blob_size, 16, range_error);
+    if (_num_length_bytes != 0) {
+      do_pack_uint16(pack_data.get_write_pointer(2), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  case ST_blob32:
+    _uint_range.validate(blob_size, range_error);
+    if (_num_length_bytes != 0) {
+      do_pack_uint32(pack_data.get_write_pointer(4), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Packs the simpleParameter's specified default value (or a sensible default
  * if no value is specified) into the stream.  Returns true if the default
@@ -1937,6 +1983,79 @@ unpack_string(const char *data, size_t length, size_t &p, string &value,
   return;
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCSimpleParameter::
+unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value,
+            bool &pack_error, bool &range_error) const {
+  // If the type is a single byte, unpack it into a string of length 1.
+  switch (_type) {
+  case ST_char:
+  case ST_int8:
+  case ST_uint8:
+    {
+      if (p + 1 > length) {
+        pack_error = true;
+        return;
+      }
+      unsigned int int_value = do_unpack_uint8(data + p);
+      _uint_range.validate(int_value, range_error);
+      value.resize(1);
+      value[0] = int_value;
+      p++;
+    }
+    return;
+
+  default:
+    break;
+  }
+
+  size_t blob_size;
+
+  if (_num_length_bytes == 0) {
+    blob_size = _fixed_byte_size;
+
+  } else {
+    switch (_type) {
+    case ST_string:
+    case ST_blob:
+      if (p + 2 > length) {
+        pack_error = true;
+        return;
+      }
+      blob_size = do_unpack_uint16(data + p);
+      p += 2;
+      break;
+
+    case ST_blob32:
+      if (p + 4 > length) {
+        pack_error = true;
+        return;
+      }
+      blob_size = do_unpack_uint32(data + p);
+      p += 4;
+      break;
+
+    default:
+      pack_error = true;
+      return;
+    }
+  }
+
+  _uint_range.validate(blob_size, range_error);
+
+  if (p + blob_size > length) {
+    pack_error = true;
+    return;
+  }
+  value = vector_uchar((const unsigned char *)data + p,
+                       (const unsigned char *)data + p + blob_size);
+  p += blob_size;
+
+  return;
+}
+
 /**
  * Internally unpacks the current numeric or string value and validates it
  * against the type range limits, but does not return the value.  Returns true

+ 4 - 0
direct/src/dcparser/dcSimpleParameter.h

@@ -62,6 +62,8 @@ public:
                            bool &pack_error, bool &range_error) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
 
   virtual void unpack_double(const char *data, size_t length, size_t &p,
@@ -76,6 +78,8 @@ public:
                              uint64_t &value, bool &pack_error, bool &range_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
   virtual bool unpack_validate(const char *data, size_t length, size_t &p,
                                bool &pack_error, bool &range_error) const;
   virtual bool unpack_skip(const char *data, size_t length, size_t &p,

+ 8 - 7
direct/src/dcparser/dcSwitch.cxx

@@ -109,7 +109,7 @@ get_num_cases() const {
  * if no case has this value.
  */
 int DCSwitch::
-get_case_by_value(const string &case_value) const {
+get_case_by_value(const vector_uchar &case_value) const {
   CasesByValue::const_iterator vi;
   vi = _cases_by_value.find(case_value);
   if (vi != _cases_by_value.end()) {
@@ -140,9 +140,9 @@ get_default_case() const {
 /**
  * Returns the packed value associated with the indicated case.
  */
-string DCSwitch::
+vector_uchar DCSwitch::
 get_value(int case_index) const {
-  nassertr(case_index >= 0 && case_index < (int)_cases.size(), string());
+  nassertr(case_index >= 0 && case_index < (int)_cases.size(), vector_uchar());
   return _cases[case_index]->_value;
 }
 
@@ -198,7 +198,7 @@ is_field_valid() const {
  * -1. This is normally called only by the parser.
  */
 int DCSwitch::
-add_case(const string &value) {
+add_case(const vector_uchar &value) {
   int case_index = (int)_cases.size();
   if (!_cases_by_value.insert(CasesByValue::value_type(value, case_index)).second) {
     add_invalid_case();
@@ -283,7 +283,8 @@ add_break() {
 const DCPackerInterface *DCSwitch::
 apply_switch(const char *value_data, size_t length) const {
   CasesByValue::const_iterator vi;
-  vi = _cases_by_value.find(string(value_data, length));
+  vi = _cases_by_value.find(vector_uchar((const unsigned char *)value_data,
+                                         (const unsigned char *)value_data + length));
   if (vi != _cases_by_value.end()) {
     return _cases[(*vi).second]->_fields;
   }
@@ -421,7 +422,7 @@ generate_hash(HashGenerator &hashgen) const {
   Cases::const_iterator ci;
   for (ci = _cases.begin(); ci != _cases.end(); ++ci) {
     const SwitchCase *dcase = (*ci);
-    hashgen.add_string(dcase->_value);
+    hashgen.add_blob(dcase->_value);
 
     const SwitchFields *fields = dcase->_fields;
     hashgen.add_int(fields->_fields.size());
@@ -702,7 +703,7 @@ do_check_match(const DCPackerInterface *) const {
  *
  */
 DCSwitch::SwitchCase::
-SwitchCase(const string &value, DCSwitch::SwitchFields *fields) :
+SwitchCase(const vector_uchar &value, DCSwitch::SwitchFields *fields) :
   _value(value),
   _fields(fields)
 {

+ 6 - 6
direct/src/dcparser/dcSwitch.h

@@ -40,18 +40,18 @@ PUBLISHED:
   DCField *get_key_parameter() const;
 
   int get_num_cases() const;
-  int get_case_by_value(const std::string &case_value) const;
+  int get_case_by_value(const vector_uchar &case_value) const;
   DCPackerInterface *get_case(int n) const;
   DCPackerInterface *get_default_case() const;
 
-  std::string get_value(int case_index) const;
+  vector_uchar get_value(int case_index) const;
   int get_num_fields(int case_index) const;
   DCField *get_field(int case_index, int n) const;
   DCField *get_field_by_name(int case_index, const std::string &name) const;
 
 public:
   bool is_field_valid() const;
-  int add_case(const std::string &value);
+  int add_case(const vector_uchar &value);
   void add_invalid_case();
   bool add_default();
   bool add_field(DCField *field);
@@ -98,13 +98,13 @@ public:
 
   class SwitchCase {
   public:
-    SwitchCase(const std::string &value, SwitchFields *fields);
+    SwitchCase(const vector_uchar &value, SwitchFields *fields);
     ~SwitchCase();
 
     bool do_check_match_switch_case(const SwitchCase *other) const;
 
   public:
-    std::string _value;
+    vector_uchar _value;
     SwitchFields *_fields;
   };
 
@@ -137,7 +137,7 @@ private:
   bool _fields_added;
 
   // This map indexes into the _cases vector, above.
-  typedef pmap<std::string, int> CasesByValue;
+  typedef pmap<vector_uchar, int> CasesByValue;
   CasesByValue _cases_by_value;
 };
 

+ 12 - 0
direct/src/dcparser/hashGenerator.cxx

@@ -56,6 +56,18 @@ add_string(const std::string &str) {
   }
 }
 
+/**
+ * Adds a blob to the hash, by breaking it down into a sequence of integers.
+ */
+void HashGenerator::
+add_blob(const vector_uchar &bytes) {
+  add_int(bytes.size());
+  vector_uchar::const_iterator bi;
+  for (bi = bytes.begin(); bi != bytes.end(); ++bi) {
+    add_int(*bi);
+  }
+}
+
 /**
  * Returns the hash number generated.
  */

+ 2 - 0
direct/src/dcparser/hashGenerator.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "primeNumberGenerator.h"
+#include "vector_uchar.h"
 
 /**
  * This class generates an arbitrary hash number from a sequence of ints.
@@ -26,6 +27,7 @@ public:
 
   void add_int(int num);
   void add_string(const std::string &str);
+  void add_blob(const vector_uchar &bytes);
 
   unsigned long get_hash() const;
 

+ 16 - 2
direct/src/directscripts/Doxyfile.cxx

@@ -797,7 +797,15 @@ EXCLUDE                = dtool/src/parser-inc \
                          panda/src/linmath/fltnames.h \
                          panda/src/linmath/dblnames.h \
                          panda/src/linmath/dbl2fltnames.h \
-                         panda/src/linmath/flt2dblnames.h
+                         panda/src/linmath/flt2dblnames.h \
+                         panda/src/android \
+                         panda/src/iphone \
+                         panda/src/tinydisplay \
+                         panda/src/movies/dr_flac.h \
+                         dtool/src/dtoolbase/pdtoa.cxx \
+                         dtool/src/dtoolutil/panda_getopt_long.h \
+                         dtool/src/dtoolutil/panda_getopt_impl.h \
+                         dtool/src/dtoolutil/panda_getopt_impl.cxx
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
@@ -1972,7 +1980,13 @@ PREDEFINED             = TVOLATILE= \
                          EXTEND= \
                          ALLOC_DELETED_CHAIN(x)= \
                          BLOCKING= \
-                         TYPENAME=typename
+                         TYPENAME=typename \
+                         MAKE_PROPERTY(x)= \
+                         MAKE_PROPERTY2(x)= \
+                         MAKE_SEQ(x)= \
+                         MAKE_SEQ_PROPERTY(x)= \
+                         MAKE_MAP_PROPERTY(x)= \
+                         MAKE_MAP_KEYS_SEQ(x)=
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The

+ 3 - 1
direct/src/directscripts/Doxyfile.python

@@ -665,7 +665,9 @@ EXCLUDE_SYMLINKS       = NO
 # for example use the pattern */test/*
 
 EXCLUDE_PATTERNS       = */Opt*-*/* \
-                         */CVS/*
+                         */CVS/* \
+                         */.git/* \
+                         */__pycache__/*
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
 # (namespaces, classes, functions, etc.) that should be excluded from the 

+ 79 - 51
direct/src/directscripts/extract_docs.py

@@ -9,9 +9,20 @@ from __future__ import print_function
 
 __all__ = []
 
-import os
+import os, sys
+from distutils import sysconfig
 import panda3d, pandac
-from panda3d.dtoolconfig import *
+from panda3d.interrogatedb import *
+
+
+if 'interrogate_element_is_sequence' not in globals():
+    def interrogate_element_is_sequence(element):
+        return False
+
+if 'interrogate_element_is_mapping' not in globals():
+    def interrogate_element_is_mapping(element):
+        return False
+
 
 LICENSE = """PANDA 3D SOFTWARE
 Copyright (c) Carnegie Mellon University.  All rights reserved.
@@ -19,6 +30,16 @@ All use of this software is subject to the terms of the revised BSD
 license.  You should have received a copy of this license along
 with this source code in a file named \"LICENSE.\"""".split("\n")
 
+MAINPAGE = """@mainpage Panda3D Python API Reference
+Welcome to the Panda3D API reference.
+
+Use the links at the top of this page to browse through the list of modules or
+the list of classes.
+
+This reference is automatically generated from comments in the source code.
+"""
+
+
 def comment(code):
     if not code:
         return ""
@@ -45,50 +66,17 @@ def comment(code):
         return ''
 
 def block_comment(code):
-    if not code:
-        return ""
+    code = code.strip()
 
-    lines = code.split("\n")
-    newlines = []
-    indent = 0
-    reading_desc = False
-
-    for line in lines:
-        if line.startswith("////"):
-            continue
-
-        line = line.rstrip()
-        strline = line.lstrip('/ \t')
-
-        if ':' in strline:
-            pre, post = strline.split(':', 1)
-            pre = pre.rstrip()
-            if pre == "Description":
-                strline = post.lstrip()
-            elif pre in ("Class", "Access", "Function", "Created by", "Enum"):
-                continue
-
-        if strline or len(newlines) > 0:
-            newlines.append('/// ' + strline)
-
-        #if reading_desc:
-        #    newlines.append('/// ' + line[min(indent, len(line) - len(strline)):])
-        #else:
-        #    # A "Description:" text starts the description.
-        #    if strline.startswith("Description"):
-        #        strline = strline[11:].lstrip(': \t')
-        #        indent = len(line) - len(strline)
-        #        reading_desc = True
-        #        newlines.append('/// ' + strline)
-        #    else:
-        #        print line
-
-    newcode = '\n'.join(newlines)
-    if len(newcode) > 0:
-        return newcode
-    else:
+    if not code.startswith('///<') and '@verbatim' not in code:
+        code = code.replace('<', '\\<').replace('>', '\\>')
+
+    if not code or code[0] != '/':
+        # Not really a comment; get rid of it.
         return ""
 
+    return code
+
 def translateFunctionName(name):
     if name.startswith("__"):
         return name
@@ -139,11 +127,17 @@ def translated_type_name(type, scoped=True):
         return "object"
     elif typename == "PN_stdfloat":
         return "float"
+    elif typename == "size_t":
+        return "int"
 
     if interrogate_type_is_atomic(type):
         token = interrogate_type_atomic_token(type)
         if token == 7:
             return 'str'
+        elif token == 8:
+            return 'long'
+        elif token == 9:
+            return 'NoneType'
         else:
             return typename
 
@@ -156,12 +150,25 @@ def translated_type_name(type, scoped=True):
     else:
         return typename
 
+
 def processElement(handle, element):
     if interrogate_element_has_comment(element):
         print(comment(interrogate_element_comment(element)), file=handle)
+    elif interrogate_element_has_getter(element):
+        # If the property has no comment, use the comment of the getter.
+        getter = interrogate_element_getter(element)
+        if interrogate_function_has_comment(getter):
+            print(block_comment(interrogate_function_comment(getter)), file=handle)
+
+    if interrogate_element_is_mapping(element) or \
+       interrogate_element_is_sequence(element):
+        suffix = "[]"
+    else:
+        suffix = ""
 
     print(translated_type_name(interrogate_element_type(element)), end=' ', file=handle)
-    print(interrogate_element_name(element) + ';', file=handle)
+    print(interrogate_element_name(element) + suffix + ';', file=handle)
+
 
 def processFunction(handle, function, isConstructor = False):
     for i_wrapper in range(interrogate_function_number_of_python_wrappers(function)):
@@ -195,6 +202,7 @@ def processFunction(handle, function, isConstructor = False):
 
         print(");", file=handle)
 
+
 def processType(handle, type):
     typename = translated_type_name(type, scoped=False)
     derivations = [ translated_type_name(interrogate_type_get_derivation(type, n)) for n in range(interrogate_type_number_of_derivations(type)) ]
@@ -211,8 +219,10 @@ def processType(handle, type):
             print(interrogate_type_enum_value_name(type, i_value), "=", interrogate_type_enum_value(type, i_value), ",", file=handle)
 
     elif interrogate_type_is_typedef(type):
-        wrapped_type = translated_type_name(interrogate_type_wrapped_type(type))
-        print("typedef %s %s;" % (wrapped_type, typename), file=handle)
+        wrapped_type = interrogate_type_wrapped_type(type)
+        if interrogate_type_is_global(wrapped_type):
+            wrapped_type_name = translated_type_name(wrapped_type)
+            print("typedef %s %s;" % (wrapped_type_name, typename), file=handle)
         return
     else:
         if interrogate_type_is_struct(type):
@@ -249,6 +259,7 @@ def processType(handle, type):
     print("};", file=handle)
 
 def processModule(handle, package):
+    print("Processing module %s" % (package))
     print("namespace %s {" % package, file=handle)
 
     if package != "core":
@@ -280,22 +291,39 @@ def processModule(handle, package):
 if __name__ == "__main__":
     handle = open("pandadoc.hpp", "w")
 
+    mainpage = MAINPAGE.strip()
+    if mainpage:
+        print("/**\n * " + mainpage.replace('\n', '\n * ') + '\n */', file=handle)
+
     print(comment("Panda3D modules that are implemented in C++."), file=handle)
     print("namespace panda3d {", file=handle)
 
     # Determine the path to the interrogatedb files
-    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "..", "..", "etc"))
-    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "input"))
+    pandac = os.path.dirname(pandac.__file__)
+    interrogate_add_search_directory(os.path.join(pandac, "..", "..", "etc"))
+    interrogate_add_search_directory(os.path.join(pandac, "input"))
 
     import panda3d.core
     processModule(handle, "core")
 
+    # Determine the suffix for the extension modules.
+    if sys.version_info >= (3, 0):
+        import _imp
+        ext_suffix = _imp.extension_suffixes()[0]
+    elif sys.platform == "win32":
+        ext_suffix = ".pyd"
+    else:
+        ext_suffix = ".so"
+
     for lib in os.listdir(os.path.dirname(panda3d.__file__)):
-        if lib.endswith(('.pyd', '.so')) and not lib.startswith('core.'):
-            module_name = os.path.splitext(lib)[0]
+        if lib.endswith(ext_suffix) and not lib.startswith('core.'):
+            module_name = lib[:-len(ext_suffix)]
             __import__("panda3d." + module_name)
             processModule(handle, module_name)
 
-
     print("}", file=handle)
     handle.close()
+
+    print("Wrote output to pandadoc.hpp.  You can now run:")
+    print()
+    print("  doxygen built/direct/directscripts/Doxyfile.python")

+ 1 - 1
direct/src/distributed/cConnectionRepository.cxx

@@ -891,7 +891,7 @@ describe_message(std::ostream &out, const string &prefix,
                  const Datagram &dg) const {
   DCPacker packer;
 
-  packer.set_unpack_data(dg.get_message());
+  packer.set_unpack_data((const char *)dg.get_data(), dg.get_length(), false);
   CHANNEL_TYPE do_id;
   int msg_type;
   bool is_update = false;

+ 0 - 10
direct/src/stdpy/thread.py

@@ -277,13 +277,6 @@ class _local(object):
         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
         d[key] = value
 
-##     def __getattr__(self, key):
-##         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
-##         try:
-##             return d[key]
-##         except KeyError:
-##             raise AttributeError
-
     def __getattribute__(self, key):
         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
         if key == '__dict__':
@@ -292,6 +285,3 @@ class _local(object):
             return d[key]
         except KeyError:
             return object.__getattribute__(self, key)
-
-
-

File diff suppressed because it is too large
+ 155 - 125
dtool/metalibs/dtoolconfig/pydtool.cxx


+ 58 - 2
dtool/src/cppparser/cppExpression.cxx

@@ -25,6 +25,7 @@
 #include "cppFunctionGroup.h"
 #include "cppFunctionType.h"
 #include "cppClosureType.h"
+#include "cppReferenceType.h"
 #include "cppStructType.h"
 #include "cppBison.h"
 #include "pdtoa.h"
@@ -260,12 +261,12 @@ CPPExpression(CPPIdentifier *ident, CPPScope *current_scope,
       _u._variable = inst;
       return;
     }
-    CPPFunctionGroup *fgroup = decl->as_function_group();
+    /*CPPFunctionGroup *fgroup = decl->as_function_group();
     if (fgroup != nullptr) {
       _type = T_function;
       _u._fgroup = fgroup;
       return;
-    }
+    }*/
   }
 
   _type = T_unknown_ident;
@@ -1232,6 +1233,61 @@ determine_type() const {
   return nullptr;  // Compiler kludge; can't get here.
 }
 
+/**
+ * Returns true if this is an lvalue expression.
+ */
+bool CPPExpression::
+is_lvalue() const {
+  switch (_type) {
+  case T_variable:
+  case T_function:
+  case T_unknown_ident:
+    return true;
+
+  case T_typecast:
+  case T_static_cast:
+  case T_dynamic_cast:
+  case T_const_cast:
+  case T_reinterpret_cast:
+    {
+      CPPReferenceType *ref_type = _u._typecast._to->as_reference_type();
+      return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+    }
+
+  case T_unary_operation:
+    if (_u._op._operator == 'f') {
+      // A function returning an lvalue reference.
+      CPPType *return_type = determine_type();
+      if (return_type != nullptr) {
+        CPPReferenceType *ref_type = return_type->as_reference_type();
+        return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+      }
+    }
+    return _u._op._operator == PLUSPLUS
+        || _u._op._operator == MINUSMINUS
+        || _u._op._operator == '*';
+
+  case T_binary_operation:
+    if (_u._op._operator == ',') {
+      CPPReferenceType *ref_type = _u._op._op2->as_reference_type();
+      return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+    }
+    return (_u._op._operator == POINTSAT || _u._op._operator == ',');
+
+  case T_trinary_operation:
+    return _u._op._op2->is_lvalue() && _u._op._op3->is_lvalue();
+
+  case T_literal:
+  case T_raw_literal:
+    return true;
+
+  default:
+    break;
+  }
+
+  return false;
+}
+
 /**
  * Returns true if this declaration is an actual, factual declaration, or
  * false if some part of the declaration depends on a template parameter which

+ 1 - 0
dtool/src/cppparser/cppExpression.h

@@ -133,6 +133,7 @@ public:
 
   Result evaluate() const;
   CPPType *determine_type() const;
+  bool is_lvalue() const;
   bool is_tbd() const;
 
   virtual bool is_fully_specified() const;

+ 161 - 161
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -4078,13 +4078,9 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
 
 /**
  * Special case optimization: if the last map is a subset of the map before
- * it, and the last parameter is only a simple parameter type (that we have
- * special default argument handling for), we can merge the cases.  When this
- * happens, we can make use of a special feature of PyArg_ParseTuple for
- * handling of these last few default arguments.  This doesn't work well for
- * all types of default expressions, though, hence the need for this elaborate
- * checking mechanism down here, which goes in parallel with the actual
- * optional arg handling logic in write_function_instance.
+ * it, we can merge the cases.  When this happens, we can make use of a
+ * special feature of PyArg_ParseTuple for handling of these last few default
+ * arguments.
  *
  * This isn't just to help reduce the amount of generated code; it also
  * enables arbitrary selection of keyword arguments for many functions, ie.
@@ -4095,14 +4091,6 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
  * Thanks to this mechanism, we can call it like so:
  *
  * func(c=True, d=".")
- *
- * The return value is the minimum of the number of maximum arguments.
- *
- * Sorry, let me try that again: it returns the largest number of arguments
- * for which the overloads will be separated out rather than handled via the
- * special default handling mechanism.  Or something.
- *
- * Please don't hate me.
  */
 int InterfaceMakerPythonNative::
 collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
@@ -4118,70 +4106,8 @@ collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
     if (std::includes(rmi_next->second.begin(), rmi_next->second.end(),
                       rmi->second.begin(), rmi->second.end())) {
 
-      // Check if the nth argument is something we can easily create a default
-      // for.
-      std::set<FunctionRemap *>::iterator sii;
-      for (sii = rmi->second.begin(); sii != rmi->second.end(); ++sii) {
-        FunctionRemap *remap = (*sii);
-        size_t pn = (size_t)rmi->first;
-        if (!remap->_has_this || remap->_type == FunctionRemap::T_constructor) {
-          --pn;
-        }
-        nassertd(pn < remap->_parameters.size()) goto abort_iteration;
-
-        ParameterRemap *param = remap->_parameters[pn]._remap;
-        CPPType *type = param->get_new_type();
-
-        if (param->new_type_is_atomic_string()) {
-          CPPType *orig_type = param->get_orig_type();
-          if (TypeManager::is_char_pointer(orig_type)) {
-          } else if (TypeManager::is_wchar_pointer(orig_type)) {
-            goto abort_iteration;
-          } else if (TypeManager::is_wstring(orig_type)) {
-            goto abort_iteration;
-          } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
-            goto abort_iteration;
-          } else {
-            // Regular strings are OK if the default argument is a string
-            // literal or the default string constructor, since those are
-            // trivial to handle.  This actually covers almost all of the
-            // cases of default string args.
-            CPPExpression::Type expr_type = param->get_default_value()->_type;
-            if (expr_type != CPPExpression::T_default_construct &&
-                expr_type != CPPExpression::T_string) {
-              goto abort_iteration;
-            }
-          }
-        } else if (TypeManager::is_integer(type)) {
-        } else if (TypeManager::is_float(type)) {
-        } else if (TypeManager::is_const_char_pointer(type)) {
-        } else if (TypeManager::is_pointer_to_PyTypeObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyStringObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyUnicodeObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyObject(type)) {
-        } else if (TypeManager::is_pointer_to_Py_buffer(type)) {
-          goto abort_iteration;
-        } else if (TypeManager::is_pointer_to_simple(type)) {
-          goto abort_iteration;
-        } else if (TypeManager::is_pointer(type)) {
-          // I'm allowing other pointer types, but only if the expression
-          // happens to evaluate to a numeric constant (which will likely only
-          // be NULL). There are too many issues to resolve right now with
-          // allowing more complex default expressions, including issues in
-          // the C++ parser (but the reader is welcome to give it a try!)
-          CPPExpression::Result res = param->get_default_value()->evaluate();
-          if (res._type != CPPExpression::RT_integer &&
-              res._type != CPPExpression::RT_pointer) {
-            goto abort_iteration;
-          }
-        } else {
-          goto abort_iteration;
-        }
-      }
-
       // rmi_next has a superset of the remaps in rmi, and we are going to
-      // erase rmi_next, so put all the remaps in rmi.  rmi->second =
-      // rmi_next->second;
+      // erase rmi_next, so put all the remaps in rmi.
 
       max_required_args = rmi_next->first;
       rmi = rmi_next;
@@ -4191,7 +4117,6 @@ collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
     }
   }
 
-abort_iteration:
   // Now erase the other remap sets.  Reverse iterators are weird, we first
   // need to get forward iterators and decrement them by one.
   std::map<int, std::set<FunctionRemap *> >::iterator erase_begin, erase_end;
@@ -4738,6 +4663,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       "(" + orig_type->get_local_name(&parser) + ")" + param_name;
 
     string default_expr;
+    const char *null_assign = "";
 
     if (is_optional) {
       // If this is an optional argument, PyArg_ParseTuple will leave the
@@ -4747,6 +4673,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       default_expr_str << " = ";
       default_value->output(default_expr_str, 0, &parser, false);
       default_expr = default_expr_str.str();
+      null_assign = " = nullptr";
 
       // We should only ever have to consider optional arguments for functions
       // taking a variable number of arguments.
@@ -4776,23 +4703,40 @@ write_function_instance(ostream &out, FunctionRemap *remap,
         expected_params += "str";
 
       } else if (TypeManager::is_wchar_pointer(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
+        out << "#if PY_VERSION_HEX >= 0x03020000\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
+        out << "#else\n";
+        indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
+        out << "#endif\n";
         format_specifiers += "U";
         parameter_list += ", &" + param_name;
 
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
-          << "#else"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << param_name << "_str[" << param_name << "_len] = 0;\n"
-          << "#endif\n";
+        if (is_optional) {
+          extra_convert
+            << "wchar_t *" << param_name << "_str;\n"
+            << "if (" << param_name << " != nullptr) {\n"
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "  " << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
+            << "#else"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "  " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << param_name << "_str[" << param_name << "_len] = 0;\n"
+            << "#endif\n"
+            << "} else {\n"
+            << "  " << param_name << "_str" << default_expr << ";\n"
+            << "}\n";
+        } else {
+          extra_convert
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
+            << "#else"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << param_name << "_str[" << param_name << "_len] = 0;\n"
+            << "#endif\n";
+        }
 
         pexpr_string = param_name + "_str";
 
@@ -4803,56 +4747,48 @@ write_function_instance(ostream &out, FunctionRemap *remap,
 
         expected_params += "unicode";
 
-      } else if (TypeManager::is_wstring(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
-        format_specifiers += "U";
-        parameter_list += ", &" + param_name;
-
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "Py_ssize_t " << param_name << "_len;\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
-          << param_name << ", &" << param_name << "_len);\n"
-          << "#else\n"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << "#endif\n";
-
-        pexpr_string = param_name + "_str, " + param_name + "_len";
-
-        extra_cleanup
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "PyMem_Free(" << param_name << "_str);\n"
-          << "#endif\n";
-
-        expected_params += "unicode";
-
-      } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
+      } else if (TypeManager::is_wstring(orig_type) ||
+                 TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
+        out << "#if PY_VERSION_HEX >= 0x03020000\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
+        out << "#else\n";
+        indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
+        out << "#endif\n";
         format_specifiers += "U";
         parameter_list += ", &" + param_name;
 
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "Py_ssize_t " << param_name << "_len;\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
-          << param_name << ", &" << param_name << "_len);\n"
-          << "#else\n"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << "#endif\n";
-
-        pexpr_string = param_name + "_str, " + param_name + "_len";
+        if (is_optional) {
+          extra_convert
+            << "Py_ssize_t " << param_name << "_len;\n"
+            << "wchar_t *" << param_name << "_str;\n"
+            << "std::wstring " << param_name << "_wstr;\n"
+            << "if (" << param_name << " != nullptr) {\n"
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "  " << param_name << "_str = PyUnicode_AsWideCharString("
+            << param_name << ", &" << param_name << "_len);\n"
+            << "#else\n"
+            << "  " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "  " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << "#endif\n"
+            << "  " << param_name << "_wstr.assign(" << param_name << "_str, " << param_name << "_len);\n"
+            << "} else {\n"
+            << "  " << param_name << "_wstr" << default_expr << ";\n"
+            << "}\n";
+          pexpr_string = "std::move(" + param_name + "_wstr)";
+        } else {
+          extra_convert
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "Py_ssize_t " << param_name << "_len;\n"
+            << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
+            << param_name << ", &" << param_name << "_len);\n"
+            << "#else\n"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << "#endif\n";
+          pexpr_string = param_name + "_str, " + param_name + "_len";
+        }
 
         extra_cleanup
           << "#if PY_VERSION_HEX >= 0x03030000\n"
@@ -5019,11 +4955,11 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       only_pyobjects = false;
 
     } else if (TypeManager::is_wchar(type)) {
-      indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
+      out << "#if PY_VERSION_HEX >= 0x03020000\n";
       indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-      indent(out, indent_level) << "#else\n";
+      out << "#else\n";
       indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-      indent(out, indent_level) << "#endif\n";
+      out << "#endif\n";
       format_specifiers += "U";
       parameter_list += ", &" + param_name;
 
@@ -5348,17 +5284,36 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
       indent(out, indent_level) << "Py_buffer " << param_name << "_view;\n";
 
-      extra_param_check << " && PyObject_GetBuffer("
-                        << param_name << ", &"
-                        << param_name << "_view, PyBUF_FULL) == 0";
-      pexpr_string = "&" + param_name + "_view";
-      extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
+      if (is_optional) {
+        indent(out, indent_level) << "Py_buffer *" << param_name << "_viewp;\n";
+
+        extra_convert
+          << "bool " << param_name << "_success;\n"
+          << "if (" << param_name << " != nullptr) {\n"
+          << "  " << param_name << "_success = (PyObject_GetBuffer("
+          << param_name << ", &" << param_name << "_view, PyBUF_FULL) == 0);\n"
+          << "  " << param_name << "_viewp = &" << param_name << "_view;\n"
+          << "} else {\n"
+          << "  " << param_name << "_viewp" << default_expr << ";\n"
+          << "  " << param_name << "_success = true;\n"
+          << "}\n";
+
+        extra_param_check << " && " << param_name << "_success";
+        pexpr_string = param_name + "_viewp";
+        extra_cleanup << "if (" << param_name << " != nullptr) PyBuffer_Release(&" << param_name << "_view);\n";
+      } else {
+        extra_param_check << " && PyObject_GetBuffer("
+                          << param_name << ", &"
+                          << param_name << "_view, PyBUF_FULL) == 0";
+        pexpr_string = "&" + param_name + "_view";
+        extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
+      }
       expected_params += "buffer";
       may_raise_typeerror = true;
       clear_error = true;
@@ -5367,7 +5322,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
@@ -5496,18 +5451,15 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name;
-        if (is_optional) {
-          out << " = nullptr";
-        }
-        out << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
 
       // If the default value is NULL, we also accept a None value.
       bool maybe_none = false;
-      if (default_value != nullptr && (return_flags & RF_coerced) == 0) {
+      if (default_value != nullptr && (return_flags & RF_coerced) == 0 &&
+          TypeManager::is_pointer(orig_type)) {
         CPPExpression::Result res = param->get_default_value()->evaluate();
         if (res._type == CPPExpression::RT_integer ||
             res._type == CPPExpression::RT_pointer) {
@@ -5576,8 +5528,11 @@ write_function_instance(ostream &out, FunctionRemap *remap,
               << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
               << "  " << param_name << "_this";
           } else if (is_optional) {
+            if (TypeManager::is_pointer(orig_type)) {
+              extra_convert << default_expr;
+            }
             extra_convert
-              << default_expr << ";\n"
+              << ";\n"
               << "if (" << param_name << " != nullptr) {\n"
               << "  " << param_name << "_this";
           } else if (maybe_none) {
@@ -5590,7 +5545,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
           extra_convert << " = Dtool_Coerce_" + make_safe_name(class_name) +
             "(" + param_name + ", " + param_name + "_local);\n";
 
-          if (is_optional || maybe_none) {
+          if (is_optional && !TypeManager::is_pointer(orig_type)) {
+            extra_convert
+              << "} else {\n"
+              << "  " << param_name << "_local" << default_expr << ";\n"
+              << "  " << param_name << "_this = &" << param_name << "_local;\n"
+              << "}\n";
+          } else if (is_optional || maybe_none) {
             extra_convert << "}\n";
           }
 
@@ -5645,21 +5606,60 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       } else { // The regular, non-coercion case.
         type->output_instance(extra_convert, param_name + "_this", &parser);
         if (is_optional && maybe_none) {
+          // This parameter has a default value of nullptr, so we need to also
+          // allow passing in None.
           extra_convert
             << default_expr << ";\n"
             << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
             << "  " << param_name << "_this";
-        } else if (is_optional) {
+        }
+        else if (is_optional && !TypeManager::is_pointer(orig_type) && !default_value->is_lvalue()) {
+          // Most annoying case, where we have to use an rvalue reference to
+          // extend the lifetime of the default argument.  In this case, the
+          // default expression is invoked even if not used.
+          extra_convert << ";\n";
+          if (TypeManager::is_const_pointer_to_anything(type)) {
+            extra_convert << "const ";
+            obj_type->output_instance(extra_convert, "&" + param_name + "_ref", &parser);
+          } else {
+            obj_type->output_instance(extra_convert, "&&" + param_name + "_ref", &parser);
+          }
           extra_convert
             << default_expr << ";\n"
-            << "if (" << param_name << " != nullptr) {\n"
+            << "if (" << param_name << " == nullptr) {\n"
+            << "  " << param_name << "_this = &" << param_name << "_ref;\n"
+            << "} else {\n"
             << "  " << param_name << "_this";
-        } else if (maybe_none) {
+        }
+        else if (is_optional) {
+          // General case where the default argument is either an lvalue or a
+          // pointer.
+          extra_convert
+            << ";\n"
+            << "if (" << param_name << " == nullptr) {\n"
+            << "  " << param_name << "_this = ";
+          if (TypeManager::is_pointer(orig_type)) {
+            default_value->output(extra_convert, 0, &parser, false);
+            extra_convert << ";\n";
+          } else {
+            // The rvalue case was handled above, so this is an lvalue, which
+            // means we can safely take a reference to it.
+            extra_convert << "&(";
+            default_value->output(extra_convert, 0, &parser, false);
+            extra_convert << ");\n";
+          }
+          extra_convert
+            << "} else {\n"
+            << "  " << param_name << "_this";
+        }
+        else if (maybe_none) {
+          // No default argument, but we still need to check for None.
           extra_convert
             << " = nullptr;\n"
             << "if (" << param_name << " != Py_None) {\n"
             << "  " << param_name << "_this";
         }
+
         if (const_ok && !report_errors) {
           // This function does the same thing in this case and is slightly
           // simpler.  But maybe we should just reorganize these functions

+ 4 - 1
dtool/src/interrogate/interrogate.cxx

@@ -312,7 +312,10 @@ main(int argc, char **argv) {
   string command_line;
   int i;
   for (i = 0; i < argc; i++) {
-    command_line += string(argv[i]) + " ";
+    if (i > 0) {
+      command_line += ' ';
+    }
+    command_line += string(argv[i]);
   }
 
   Filename fn;

+ 1 - 0
dtool/src/interrogate/interrogateBuilder.cxx

@@ -2024,6 +2024,7 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     if (iproperty._type != 0 && iproperty._type != return_index) {
       cerr << "Property " << property_name << " has inconsistent element type!\n";
     }
+    iproperty._type = return_index;
   } else {
     iproperty._type = 0;
   }

+ 12 - 0
dtool/src/interrogatedb/interrogate_interface.cxx

@@ -176,6 +176,18 @@ interrogate_element_setter(ElementIndex element) {
   return InterrogateDatabase::get_ptr()->get_element(element).get_setter();
 }
 
+bool
+interrogate_element_is_sequence(ElementIndex element) {
+  // cerr << "interrogate_element_is_sequence(" << element << ")\n";
+  return InterrogateDatabase::get_ptr()->get_element(element).is_sequence();
+}
+
+bool
+interrogate_element_is_mapping(ElementIndex element) {
+  // cerr << "interrogate_element_is_mapping(" << element << ")\n";
+  return InterrogateDatabase::get_ptr()->get_element(element).is_mapping();
+}
+
 int
 interrogate_number_of_globals() {
   // cerr << "interrogate_number_of_globals()\n";

+ 3 - 0
dtool/src/interrogatedb/interrogate_interface.h

@@ -153,6 +153,9 @@ EXPCL_INTERROGATEDB FunctionIndex interrogate_element_getter(ElementIndex elemen
 EXPCL_INTERROGATEDB bool interrogate_element_has_setter(ElementIndex element);
 EXPCL_INTERROGATEDB FunctionIndex interrogate_element_setter(ElementIndex element);
 
+EXPCL_INTERROGATEDB bool interrogate_element_is_sequence(ElementIndex element);
+EXPCL_INTERROGATEDB bool interrogate_element_is_mapping(ElementIndex element);
+
 // Global Data
 
 // This is the list of global data elements.

+ 274 - 172
makepanda/installer.nsi

@@ -14,7 +14,7 @@
 ;
 ;   BUILT         - location of panda install tree.
 ;   SOURCE        - location of the panda source-tree if available, OR location of panda install tree.
-;   PYVER         - version of Python that Panda was built with (ie, "2.7")
+;   INCLUDE_PYVER - version of Python that Panda was built with (eg. "2.7", "3.5-32")
 ;   REGVIEW       - either 32 or 64, depending on the build architecture.
 ;
 
@@ -46,7 +46,9 @@ SetCompressor ${COMPRESSOR}
 !insertmacro MUI_PAGE_LICENSE "${SOURCE}/doc/LICENSE"
 !insertmacro MUI_PAGE_DIRECTORY
 
+!ifdef INCLUDE_PYVER
 !define MUI_PAGE_CUSTOMFUNCTION_LEAVE ConfirmPythonSelection
+!endif
 !insertmacro MUI_PAGE_COMPONENTS
 
 !insertmacro MUI_PAGE_INSTFILES
@@ -64,8 +66,9 @@ ShowUninstDetails hide
 
 LicenseData "${SOURCE}/doc/LICENSE"
 
-InstType "Full (Recommended)"
-InstType "Minimal"
+InstType "Auto (Recommended)"
+InstType "Full"
+InstType "Light"
 
 LangString DESC_SecCore ${LANG_ENGLISH} "The Panda3D core libraries, configuration files and models/textures that are needed to use Panda3D."
 LangString DESC_SecOpenGL ${LANG_ENGLISH} "The OpenGL graphics back-end is the most well-supported renderer."
@@ -78,8 +81,9 @@ LangString DESC_SecODE ${LANG_ENGLISH} "Support for the Open Dynamics Engine to
 LangString DESC_SecPhysX ${LANG_ENGLISH} "Support for NVIDIA PhysX to implement physics."
 LangString DESC_SecRocket ${LANG_ENGLISH} "Support for the libRocket GUI library.  This is an optional library that offers an HTML/CSS-like approach to creating user interfaces."
 LangString DESC_SecTools ${LANG_ENGLISH} "Useful tools and model converters to help with Panda3D development.  Recommended."
-LangString DESC_SecPyBindings ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using Python.  These will only work with a ${REGVIEW}-bit version of Python ${PYVER}."
-LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${PYVER} preconfigured to make use of Panda3D."
+LangString DESC_SecGroupPython ${LANG_ENGLISH} "Contains modules that provide Python support for Panda3D."
+LangString DESC_SecPyShared ${LANG_ENGLISH} "Contains the common Python code used by the Panda3D Python bindings."
+LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${INCLUDE_PYVER} preconfigured to make use of Panda3D."
 LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D."
 LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D."
 LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D."
@@ -112,26 +116,109 @@ var MANPAGE
 !insertmacro !defineifexist HAVE_ODE "${BUILT}\bin\libpandaode.dll"
 !insertmacro !defineifexist HAVE_PHYSX "${BUILT}\bin\libpandaphysx.dll"
 !insertmacro !defineifexist HAVE_ROCKET "${BUILT}\bin\libp3rocket.dll"
-!insertmacro !defineifexist HAVE_PYTHON "${BUILT}\python"
 !insertmacro !defineifexist HAVE_SAMPLES "${SOURCE}\samples"
 !insertmacro !defineifexist HAVE_MAX_PLUGINS "${BUILT}\plugins\*.dlo"
 !insertmacro !defineifexist HAVE_MAYA_PLUGINS "${BUILT}\plugins\*.mll"
 
+!macro RemovePythonPath PYVER
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" ""
+    StrCmp $0 "$INSTDIR" 0 +2
+    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D"
+!macroend
+
+!macro PyBindingSection PYVER EXT_SUFFIX
+    LangString DESC_SecPyBindings${PYVER} ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using a ${REGVIEW}-bit version of Python ${PYVER}."
+
+    !insertmacro !defineifexist _present "${BUILT}\panda3d\core${EXT_SUFFIX}"
+    !ifdef _present
+    Section "${PYVER} bindings" SecPyBindings${PYVER}
+        !if "${PYVER}" == "${INCLUDE_PYVER}"
+            SectionIn 1 2 3
+        !else
+            !if "${PYVER}" == "2.7"
+                SectionIn 1 2
+            !else
+                ; See .onInit function where this is dynamically enabled.
+                SectionIn 2
+            !endif
+        !endif
+
+        SetDetailsPrint both
+        DetailPrint "Installing Panda3D bindings for Python ${PYVER}..."
+        SetDetailsPrint listonly
+
+        SetOutPath $INSTDIR\panda3d
+
+        File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\direct${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\egg${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\fx${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\interrogatedb${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\physics${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\_rplight${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\skel${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\vision${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\vrpn${EXT_SUFFIX}"
+
+        !ifdef HAVE_BULLET
+            SectionGetFlags ${SecBullet} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipBulletPyd
+            File /nonfatal /r "${BUILT}\panda3d\bullet${EXT_SUFFIX}"
+            SkipBulletPyd:
+        !endif
+
+        !ifdef HAVE_ODE
+            SectionGetFlags ${SecODE} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipODEPyd
+            File /nonfatal /r "${BUILT}\panda3d\ode${EXT_SUFFIX}"
+            SkipODEPyd:
+        !endif
+
+        !ifdef HAVE_PHYSX
+            SectionGetFlags ${SecPhysX} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipPhysXPyd
+            File /nonfatal /r "${BUILT}\panda3d\physx${EXT_SUFFIX}"
+            SkipPhysXPyd:
+        !endif
+
+        !ifdef HAVE_ROCKET
+            SectionGetFlags ${SecRocket} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipRocketPyd
+            File /nonfatal /r "${BUILT}\panda3d\rocket${EXT_SUFFIX}"
+            SkipRocketPyd:
+        !endif
+
+        SetOutPath $INSTDIR\pandac\input
+        File /r "${BUILT}\pandac\input\*"
+        SetOutPath $INSTDIR\Pmw
+        File /nonfatal /r /x CVS "${BUILT}\Pmw\*"
+        SetOutPath $INSTDIR\panda3d.dist-info
+        File /nonfatal /r "${BUILT}\panda3d.dist-info\*"
+
+        !ifdef REGVIEW
+        SetRegView ${REGVIEW}
+        !endif
+
+        ; Install a Panda3D path into the global PythonPath for this version
+        ; of Python.
+        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" "$INSTDIR"
+    SectionEnd
+    !undef _present
+    !endif
+!macroend
+
 Function runFunction
     ExecShell "open" "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk"
 FunctionEnd
 
-Function .onInit
-    ${If} ${REGVIEW} = 64
-    ${AndIfNot} ${RunningX64}
-        MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows.  Please download and install the 32-bit version of Panda3D instead."
-        Abort
-    ${EndIf}
-FunctionEnd
-
 SectionGroup "Panda3D Libraries"
     Section "Core Libraries" SecCore
-        SectionIn 1 2 RO
+        SectionIn 1 2 3 RO
 
         SetShellVarContext current
         SetOverwrite try
@@ -177,7 +264,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_GL
     Section "OpenGL" SecOpenGL
-        SectionIn 1 2 RO
+        SectionIn 1 2 3 RO
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandagl.dll"
@@ -186,7 +273,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_DX9
     Section "Direct3D 9" SecDirect3D9
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandadx9.dll"
@@ -196,7 +283,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_OPENAL
     Section "OpenAL Audio" SecOpenAL
-        SectionIn 1 2
+        SectionIn 1 2 3
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3openal_audio.dll"
@@ -207,7 +294,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_FMOD
     Section "FMOD Audio" SecFMOD
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3fmod_audio.dll"
@@ -217,7 +304,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_FFMPEG
     Section "FFMpeg" SecFFMpeg
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3ffmpeg.dll"
@@ -230,7 +317,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_BULLET
     Section "Bullet Physics" SecBullet
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandabullet.dll"
@@ -239,7 +326,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_ODE
     Section "ODE Physics" SecODE
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandaode.dll"
@@ -248,7 +335,8 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_PHYSX
     Section "NVIDIA PhysX" SecPhysX
-        SectionIn 1
+        ; Only enable in "Full"
+        SectionIn 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandaphysx.dll"
@@ -260,7 +348,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_ROCKET
     Section "libRocket GUI" SecRocket
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3rocket.dll"
@@ -272,7 +360,7 @@ SectionGroup "Panda3D Libraries"
 SectionGroupEnd
 
 Section "Tools and utilities" SecTools
-    SectionIn 1 2
+    SectionIn 1 2 3
 
     SetDetailsPrint both
     DetailPrint "Installing utilities..."
@@ -305,17 +393,14 @@ Section "Tools and utilities" SecTools
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"'
 SectionEnd
 
-SectionGroup "Python support"
-    Section "Python bindings" SecPyBindings
-        SectionIn 1 2
+SectionGroup "Python modules" SecGroupPython
+    Section "Shared code" SecPyShared
+        SectionIn 1 2 3
 
         SetDetailsPrint both
-        DetailPrint "Installing Panda3D Python modules..."
+        DetailPrint "Installing Panda3D shared Python modules..."
         SetDetailsPrint listonly
 
-        SetOutPath "$INSTDIR\bin"
-        File /nonfatal /r "${BUILT}\bin\*.pyd"
-
         SetOutPath $INSTDIR\direct\directscripts
         File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*"
         SetOutPath $INSTDIR\direct
@@ -328,143 +413,133 @@ SectionGroup "Python support"
         File /r "${BUILT}\pandac\*.py"
         SetOutPath $INSTDIR\panda3d
         File /r "${BUILT}\panda3d\*.py"
+    SectionEnd
 
-        File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\direct${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\egg${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\fx${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\interrogatedb${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\physics${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\_rplight${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\skel${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\vision${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\vrpn${EXT_SUFFIX}"
-
-        !ifdef HAVE_BULLET
-            SectionGetFlags ${SecBullet} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipBulletPyd
-            File /nonfatal /r "${BUILT}\panda3d\bullet${EXT_SUFFIX}"
-            SkipBulletPyd:
-        !endif
-
-        !ifdef HAVE_ODE
-            SectionGetFlags ${SecODE} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipODEPyd
-            File /nonfatal /r "${BUILT}\panda3d\ode${EXT_SUFFIX}"
-            SkipODEPyd:
-        !endif
-
-        !ifdef HAVE_PHYSX
-            SectionGetFlags ${SecPhysX} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipPhysXPyd
-            File /nonfatal /r "${BUILT}\panda3d\physx${EXT_SUFFIX}"
-            SkipPhysXPyd:
-        !endif
-
-        !ifdef HAVE_ROCKET
-            SectionGetFlags ${SecRocket} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipRocketPyd
-            File /nonfatal /r "${BUILT}\panda3d\rocket${EXT_SUFFIX}"
-            SkipRocketPyd:
-        !endif
+    !insertmacro PyBindingSection 2.7 .pyd
+    !if "${REGVIEW}" == "32"
+        !insertmacro PyBindingSection 3.5-32 .cp35-win32.pyd
+        !insertmacro PyBindingSection 3.6-32 .cp36-win32.pyd
+        !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd
+        !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
+        !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
+    !else
+        !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
+        !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
+        !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd
+        !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
+        !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
+    !endif
+SectionGroupEnd
 
-        SetOutPath $INSTDIR\pandac\input
-        File /r "${BUILT}\pandac\input\*"
-        SetOutPath $INSTDIR\Pmw
-        File /nonfatal /r /x CVS "${BUILT}\Pmw\*"
-        SetOutPath $INSTDIR\panda3d.dist-info
-        File /nonfatal /r "${BUILT}\panda3d.dist-info\*"
+!ifdef INCLUDE_PYVER
+Section "Python ${INCLUDE_PYVER}" SecPython
+    SectionIn 1 2 3
 
-        !ifdef REGVIEW
-        SetRegView ${REGVIEW}
-        !endif
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
 
-        ; Check for a non-Panda3D system-wide Python installation.
-        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp $0 "$INSTDIR\python" UserExternalPthCheck 0
-        StrCmp $0 "" UserExternalPthCheck 0
-        IfFileExists "$0\ppython.exe" UserExternalPthCheck 0
-        IfFileExists "$0\python.exe" AskExternalPth UserExternalPthCheck
-
-        ; Check for a non-Panda3D user installation of Python.
-        UserExternalPthCheck:
-        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp $0 "$INSTDIR\python" SkipExternalPth 0
-        StrCmp $0 "" SkipExternalPth 0
-        IfFileExists "$0\ppython.exe" SkipExternalPth 0
-        IfFileExists "$0\python.exe" AskExternalPth SkipExternalPth
-
-        ; We're pretty sure this Python build is of the right architecture.
-        AskExternalPth:
-        MessageBox MB_YESNO|MB_ICONQUESTION \
-            "Your system already has a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\nWould you like to configure it to be able to use the Panda3D libraries?$\r$\nIf you choose no, you will only be able to use Panda3D's own copy of Python." \
-            IDYES WriteExternalPth IDNO SkipExternalPth
-
-        WriteExternalPth:
-        ;FileOpen $1 "$0\Lib\site-packages\panda.pth" w
-        ;FileWrite $1 "$INSTDIR$\r$\n"
-        ;FileWrite $1 "$INSTDIR\bin$\r$\n"
-        ;FileClose $1
-
-        ; Actually, it looks like we can just do this instead:
-        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" "$INSTDIR"
+    SetDetailsPrint both
+    DetailPrint "Installing Python ${INCLUDE_PYVER} interpreter (${REGVIEW}-bit)..."
+    SetDetailsPrint listonly
 
-        SkipExternalPth:
-    SectionEnd
+    SetOutPath "$INSTDIR\bin"
+    File /nonfatal "${BUILT}\bin\python*.dll"
 
-    !ifdef HAVE_PYTHON
-    Section "Python ${PYVER}" SecPython
-        SectionIn 1 2
+    SetOutPath "$INSTDIR\python"
+    File /r /x *.pdb "${BUILT}\python\*"
 
-        !ifdef REGVIEW
-        SetRegView ${REGVIEW}
-        !endif
+    SetDetailsPrint both
+    DetailPrint "Adding registry keys for Python..."
+    SetDetailsPrint listonly
 
-        SetDetailsPrint both
-        DetailPrint "Installing Python ${PYVER} (${REGVIEW}-bit)..."
-        SetDetailsPrint listonly
+    ; Check if a copy of Python is installed for this user.
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+    StrCmp "$0" "$INSTDIR\python" RegPath 0
+    StrCmp "$0" "" SkipFileCheck 0
+    IfFileExists "$0\python.exe" AskRegPath 0
+    SkipFileCheck:
 
-        SetOutPath "$INSTDIR\bin"
-        File /nonfatal "${BUILT}\bin\python*.dll"
+    ; Check if a system-wide copy of Python is installed.
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+    StrCmp "$0" "$INSTDIR\python" RegPath 0
+    StrCmp "$0" "" RegPath 0
+    IfFileExists "$0\python.exe" AskRegPath RegPath
 
-        SetOutPath "$INSTDIR\python"
-        File /r /x *.pdb "${BUILT}\python\*"
+    AskRegPath:
+    MessageBox MB_YESNO|MB_ICONQUESTION \
+        "You already have a copy of Python ${INCLUDE_PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${INCLUDE_PYVER}, which will install alongside your existing copy.  Would you like to make Panda's copy the default Python for your user account?" \
+        IDNO SkipRegPath
 
-        SetDetailsPrint both
-        DetailPrint "Adding registry keys for Python..."
-        SetDetailsPrint listonly
+    RegPath:
+    WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" "$INSTDIR\python"
+    WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "ExecutablePath" "$INSTDIR\python\python.exe"
+    SkipRegPath:
 
-        ; Check if a copy of Python is installed for this user.
-        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp "$0" "$INSTDIR\python" RegPath 0
-        StrCmp "$0" "" SkipFileCheck 0
-        IfFileExists "$0\python.exe" AskRegPath 0
-        SkipFileCheck:
+SectionEnd
+!endif
 
-        ; Check if a system-wide copy of Python is installed.
-        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp "$0" "$INSTDIR\python" RegPath 0
-        StrCmp "$0" "" RegPath 0
-        IfFileExists "$0\python.exe" AskRegPath RegPath
+!macro MaybeEnablePyBindingSection PYVER
+    !if "${INCLUDE_PYVER}" != "${PYVER}"
+        !ifdef SecPyBindings${PYVER}
+            ; Check if a copy of Python is installed for this user.
+            Push $0
+            ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+            StrCmp "$0" "" +2 0
+            IfFileExists "$0\python.exe" Py${PYVER}Exists 0
+
+            ; Check if a system-wide copy of Python is installed.
+            ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+            StrCmp "$0" "" Py${PYVER}ExistsNot 0
+            IfFileExists "$0\python.exe" Py${PYVER}Exists Py${PYVER}ExistsNot
+
+            Py${PYVER}Exists:
+            SectionSetFlags ${SecPyBindings${PYVER}} ${SF_SELECTED}
+            SectionSetInstTypes ${SecPyBindings${PYVER}} 3
+
+            Py${PYVER}ExistsNot:
+            Pop $0
+        !endif
+    !endif
+!macroend
 
-        AskRegPath:
-        MessageBox MB_YESNO|MB_ICONQUESTION \
-            "You already have a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${PYVER}, which will install alongside your existing copy.  Would you like to make Panda's copy the default Python for your user account?" \
-            IDNO SkipRegPath
+Function .onInit
+    ${If} ${REGVIEW} = 64
+    ${AndIfNot} ${RunningX64}
+        MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows.  Please download and install the 32-bit version of Panda3D instead."
+        Abort
+    ${EndIf}
 
-        RegPath:
-        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" "$INSTDIR\python"
-        SkipRegPath:
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
 
-    SectionEnd
+    ; We never check for 2.7; it is always enabled in Auto mode
+    !if "${REGVIEW}" == "32"
+        !insertmacro MaybeEnablePyBindingSection 3.5-32
+        !insertmacro MaybeEnablePyBindingSection 3.6-32
+        !insertmacro MaybeEnablePyBindingSection 3.7-32
+        !insertmacro MaybeEnablePyBindingSection 3.8-32
+        !insertmacro MaybeEnablePyBindingSection 3.9-32
+    !else
+        !insertmacro MaybeEnablePyBindingSection 3.5
+        !insertmacro MaybeEnablePyBindingSection 3.6
+        !insertmacro MaybeEnablePyBindingSection 3.7
+        !insertmacro MaybeEnablePyBindingSection 3.8
+        !insertmacro MaybeEnablePyBindingSection 3.9
     !endif
-SectionGroupEnd
+FunctionEnd
+
+Function .onSelChange
+    ; If someone selects any Python version, the "shared modules" must be on.
+    ${If} ${SectionIsPartiallySelected} ${SecGroupPython}
+        SectionGetFlags ${SecPyShared} $R0
+        IntOp $R0 $R0 | ${SF_SELECTED}
+        SectionSetFlags ${SecPyShared} $R0
+    ${EndIf}
+FunctionEnd
 
+!ifdef INCLUDE_PYVER
 Function ConfirmPythonSelection
     ; Check the current state of the "Python" section selection.
     SectionGetFlags ${SecPython} $R0
@@ -474,16 +549,14 @@ Function ConfirmPythonSelection
     StrCmp $R1 ${SF_SELECTED} SkipCheck 0
 
     ; Maybe the user just doesn't want Python support at all?
-    SectionGetFlags ${SecPyBindings} $R1
-    IntOp $R1 $R1 & ${SF_SELECTED}
-    StrCmp $R1 ${SF_SELECTED} 0 SkipCheck
+    !insertmacro SectionFlagIsSet ${SecGroupPython} ${SF_PSELECTED} 0 SkipCheck
 
     !ifdef REGVIEW
     SetRegView ${REGVIEW}
     !endif
 
     ; Check for a user installation of Python.
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
     StrCmp $0 "$INSTDIR\python" CheckSystemWidePython 0
     StrCmp $0 "" CheckSystemWidePython 0
     IfFileExists "$0\ppython.exe" CheckSystemWidePython 0
@@ -491,7 +564,7 @@ Function ConfirmPythonSelection
 
     ; Check for a system-wide Python installation.
     CheckSystemWidePython:
-    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
     StrCmp $0 "$INSTDIR\python" AskConfirmation 0
     StrCmp $0 "" AskConfirmation 0
     IfFileExists "$0\ppython.exe" AskConfirmation 0
@@ -501,7 +574,7 @@ Function ConfirmPythonSelection
     ; of a different Panda3D build.)  Ask the user if he's sure about this.
     AskConfirmation:
     MessageBox MB_YESNO|MB_ICONQUESTION \
-        "You do not appear to have a ${REGVIEW}-bit version of Python ${PYVER} installed.  Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python ${PYVER} and manually configure it to be able to use Panda3D." \
+        "You do not appear to have a ${REGVIEW}-bit version of Python ${INCLUDE_PYVER} installed.  Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python and install the bindings for this version." \
         IDYES SkipCheck
 
     ; User clicked no, so re-enable the select box and abort.
@@ -511,9 +584,10 @@ Function ConfirmPythonSelection
 
     SkipCheck:
 FunctionEnd
+!endif
 
 Section "C++ support" SecHeadersLibs
-    SectionIn 1
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing header files..."
@@ -532,7 +606,7 @@ SectionEnd
 
 !ifdef HAVE_SAMPLES
 Section "Sample programs" SecSamples
-    SectionIn 1
+    SectionIn 1 2
 
     ; Necessary for proper start menu shortcut installation
     SetShellVarContext current
@@ -599,7 +673,7 @@ SectionEnd
 
 !ifdef HAVE_MAX_PLUGINS
 Section "3ds Max plug-ins" SecMaxPlugins
-    SectionIn 1 3
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing Autodesk 3ds Max plug-ins..."
@@ -614,7 +688,7 @@ SectionEnd
 
 !ifdef HAVE_MAYA_PLUGINS
 Section "Maya plug-ins" SecMayaPlugins
-    SectionIn 1 3
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing Autodesk Maya plug-ins..."
@@ -737,17 +811,30 @@ Section Uninstall
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon"
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\shell"
 
-    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-    StrCmp $0 "$INSTDIR\python" 0 +2
-    DeleteRegKey HKLM "Software\Python\PythonCore\${PYVER}"
+    !ifdef INCLUDE_PYVER
+        ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" 0 +2
+        DeleteRegKey HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}"
 
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-    StrCmp $0 "$INSTDIR\python" 0 +2
-    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}"
+        ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" 0 +2
+        DeleteRegKey HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}"
+    !endif
 
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" ""
-    StrCmp $0 "$INSTDIR" 0 +2
-    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D"
+    !insertmacro RemovePythonPath 2.7
+    !if "${REGVIEW}" == "32"
+        !insertmacro RemovePythonPath 3.5-32
+        !insertmacro RemovePythonPath 3.6-32
+        !insertmacro RemovePythonPath 3.7-32
+        !insertmacro RemovePythonPath 3.8-32
+        !insertmacro RemovePythonPath 3.9-32
+    !else
+        !insertmacro RemovePythonPath 3.5
+        !insertmacro RemovePythonPath 3.6
+        !insertmacro RemovePythonPath 3.7
+        !insertmacro RemovePythonPath 3.8
+        !insertmacro RemovePythonPath 3.9
+    !endif
 
     SetDetailsPrint both
     DetailPrint "Deleting files..."
@@ -812,8 +899,23 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecRocket} $(DESC_SecRocket)
   !endif
   !insertmacro MUI_DESCRIPTION_TEXT ${SecTools} $(DESC_SecTools)
-  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings} $(DESC_SecPyBindings)
-  !ifdef HAVE_PYTHON
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecGroupPython} $(DESC_SecGroupPython)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyShared} $(DESC_SecPyShared)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings2.7} $(DESC_SecPyBindings2.7)
+  !if "${REGVIEW}" == "32"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5-32} $(DESC_SecPyBindings3.5-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6-32} $(DESC_SecPyBindings3.6-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
+  !else
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
+  !endif
+  !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
   !endif
   !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs)

+ 25 - 24
makepanda/installpanda.py

@@ -13,10 +13,6 @@ from distutils.sysconfig import get_python_lib
 from optparse import OptionParser
 from makepandacore import *
 
-def python_sitepackages_path():
-    from distutils.sysconfig import get_python_lib
-    return get_python_lib(1)
-PYTHON_SITEPACKAGES=python_sitepackages_path()
 
 MIME_INFO = (
   ("egg", "model/x-egg", "EGG model file", "pview"),
@@ -153,18 +149,11 @@ def GetLibDir():
 
     return "lib"
 
-def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir()):
+def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(), python_versions=[]):
     if (not prefix.startswith("/")):
         prefix = "/" + prefix
     libdir = prefix + "/" + libdir
 
-    # Determine the location of the Python executable and site-packages dir.
-    PPATH = get_python_lib(1)
-    if os.path.islink(sys.executable):
-        PEXEC = os.path.join(os.path.dirname(sys.executable), os.readlink(sys.executable))
-    else:
-        PEXEC = sys.executable
-
     # Create the directory structure that we will be putting our files in.
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/bin")
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/include")
@@ -174,8 +163,10 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/application-registry")
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/applications")
     oscmd("mkdir -m 0755 -p "+destdir+libdir+"/panda3d")
-    oscmd("mkdir -m 0755 -p "+destdir+PPATH)
-    oscmd("mkdir -m 0755 -p "+destdir+PPATH+"/panda3d")
+
+    for python_version in python_versions:
+        oscmd("mkdir -m 0755 -p "+destdir+python_version["purelib"])
+        oscmd("mkdir -m 0755 -p "+destdir+python_version["platlib"]+"/panda3d")
 
     if (sys.platform.startswith("freebsd")):
         oscmd("mkdir -m 0755 -p "+destdir+prefix+"/etc")
@@ -201,10 +192,12 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
     if os.path.isdir(outputdir+"/Pmw"):      oscmd("cp -R "+outputdir+"/Pmw     "+destdir+prefix+"/share/panda3d/")
     if os.path.isdir(outputdir+"/plugins"):  oscmd("cp -R "+outputdir+"/plugins "+destdir+prefix+"/share/panda3d/")
 
-    suffix = GetExtensionSuffix()
-    for base in os.listdir(outputdir + "/panda3d"):
-        if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
-            oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+PPATH+"/panda3d/"+base)
+    for python_version in python_versions:
+        for base in os.listdir(outputdir + "/panda3d"):
+            suffix = python_version["ext_suffix"]
+            platlib = python_version["platlib"]
+            if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
+                oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+platlib+"/panda3d/"+base)
 
     WriteMimeFile(destdir+prefix+"/share/mime-info/panda3d.mime", MIME_INFO)
     WriteKeysFile(destdir+prefix+"/share/mime-info/panda3d.keys", MIME_INFO)
@@ -214,10 +207,14 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
         oscmd("cp makepanda/pview.desktop "+destdir+prefix+"/share/applications/pview.desktop")
 
     oscmd("cp doc/ReleaseNotes                  "+destdir+prefix+"/share/panda3d/ReleaseNotes")
-    oscmd("echo '"+prefix+"/share/panda3d' >    "+destdir+PPATH+"/panda3d.pth")
-    oscmd("echo '"+libdir+"/panda3d'>>   "+destdir+PPATH+"/panda3d.pth")
-    if os.path.isdir(outputdir+"/panda3d.dist-info"):
-        oscmd("cp -R "+outputdir+"/panda3d.dist-info "+destdir+PPATH)
+
+    for python_version in python_versions:
+        pth_file = python_version["purelib"] + "/panda3d.pth"
+        oscmd("echo '"+prefix+"/share/panda3d' > "+destdir+pth_file)
+
+        if os.path.isdir(outputdir+"/panda3d.dist-info"):
+            oscmd("cp -R "+outputdir+"/panda3d.dist-info "+destdir+python_version["platlib"])
+
     if (sys.platform.startswith("freebsd")):
         oscmd("echo '"+libdir+"/panda3d'>    "+destdir+"/usr/local/libdata/ldconfig/panda3d")
     else:
@@ -302,6 +299,8 @@ if (__name__ == "__main__"):
     if (destdir != "" and not os.path.isdir(destdir)):
         exit("Directory '%s' does not exist!" % destdir)
 
+    SetOutputDir(options.outputdir)
+
     if options.verbose:
         SetVerbose(True)
 
@@ -310,6 +309,8 @@ if (__name__ == "__main__"):
         InstallRuntime(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir)
     else:
         print("Installing Panda3D SDK into " + destdir + options.prefix)
-        InstallPanda(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir)
+        InstallPanda(destdir=destdir,
+                     prefix=options.prefix,
+                     outputdir=options.outputdir,
+                     python_versions=ReadPythonVersionInfoFile())
     print("Installation finished!")
-

+ 165 - 75
makepanda/makepackage.py

@@ -16,9 +16,9 @@ Architecture: ARCH
 Essential: no
 Depends: DEPENDS
 Recommends: RECOMMENDS
-Provides: panda3d, pythonPV-panda3d
-Conflicts: panda3d, pythonPV-panda3d
-Replaces: panda3d, pythonPV-panda3d
+Provides: PROVIDES
+Conflicts: PROVIDES
+Replaces: PROVIDES
 Maintainer: rdb <[email protected]>
 Installed-Size: INSTSIZE
 Description: Panda3D free 3D engine SDK
@@ -73,7 +73,6 @@ This package contains the SDK for development with Panda3D, install panda3d-runt
 /usr/share/panda3d
 /etc/ld.so.conf.d/panda3d.conf
 /usr/%_lib/panda3d
-""" + PYTHON_SITEPACKAGES + """
 /usr/include/panda3d
 """
 INSTALLER_SPEC_FILE_PVIEW = \
@@ -169,17 +168,10 @@ def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compresso
     elif os.path.isdir(file):
         shutil.rmtree(file)
 
-    if "PYTHONVERSION" in SDK:
-        pyver = SDK["PYTHONVERSION"][6:9]
-    else:
-        pyver = "%d.%d" % (sys.version_info[:2])
-
     if GetTargetArch() == 'x64':
         regview = '64'
     else:
         regview = '32'
-        if int(pyver[0]) == 3 and int(pyver[2]) >= 5:
-            pyver += '-32'
 
     if runtime:
         # Invoke the make_installer script.
@@ -212,11 +204,22 @@ def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compresso
         'OUTFILE'   : '..\\' + file,
         'BUILT'     : '..\\' + outputdir,
         'SOURCE'    : '..',
-        'PYVER'     : pyver,
         'REGVIEW'   : regview,
-        'EXT_SUFFIX': GetExtensionSuffix(),
     }
 
+    # Are we shipping a version of Python?
+    if os.path.isfile(os.path.join(outputdir, "python", "python.exe")):
+        py_dlls = glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9].dll")) \
+                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9]_d.dll"))
+        assert py_dlls
+        py_dll = os.path.basename(py_dlls[0])
+        pyver = py_dll[6] + "." + py_dll[7]
+
+        if GetTargetArch() != 'x64':
+            pyver += '-32'
+
+        nsis_defs['INCLUDE_PYVER'] = pyver
+
     if GetHost() == 'windows':
         cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2'
         for item in nsis_defs.items():
@@ -254,17 +257,28 @@ def MakeDebugSymbolArchive(zipname, dirname):
     zip.close()
 
 
-def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
+def MakeInstallerLinux(version, debversion=None, rpmrelease=1, runtime=False,
+                       python_versions=[], **kwargs):
     outputdir = GetOutputDir()
 
-    if not runtime and not PkgSkip("PYTHON"):
-        if "PYTHONVERSION" in SDK:
-            PYTHONV = SDK["PYTHONVERSION"].rstrip('dmu')
-        else:
-            PYTHONV = "python%d.%d" % (sys.version_info[:2])
-    else:
-        PYTHONV = "python"
-    PV = PYTHONV.replace("python", "")
+    # We pack Python 2 and Python 3, if we built with support for it.
+    python2_ver = None
+    python3_ver = None
+    install_python_versions = []
+
+    if not runtime:
+        # What's the system version of Python 3?
+        oscmd('python3 -V > "%s/tmp/python3_version.txt"' % (outputdir))
+        sys_python3_ver = '.'.join(ReadFile(outputdir + "/tmp/python3_version.txt").strip().split(' ')[1].split('.')[:2])
+
+        # Check that we built with support for these.
+        for version_info in python_versions:
+            if version_info["version"] == "2.7":
+                python2_ver = "2.7"
+                install_python_versions.append(version_info)
+            elif version_info["version"] == sys_python3_ver:
+                python3_ver = sys_python3_ver
+                install_python_versions.append(version_info)
 
     major_version = '.'.join(version.split('.')[:2])
     if not debversion:
@@ -272,7 +286,7 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
 
     # Clean and set up a directory to install Panda3D into
     oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec")
-    oscmd("mkdir --mode=0755 targetroot")
+    oscmd("mkdir -m 0755 targetroot")
 
     dpkg_present = False
     if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"):
@@ -288,9 +302,12 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
         # Invoke installpanda.py to install it into a temporary dir
         lib_dir = GetDebLibDir()
         if runtime:
-            InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir)
+            InstallRuntime(destdir="targetroot", prefix="/usr",
+                           outputdir=outputdir, libdir=lib_dir)
         else:
-            InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir)
+            InstallPanda(destdir="targetroot", prefix="/usr",
+                         outputdir=outputdir, libdir=lib_dir,
+                         python_versions=install_python_versions)
             oscmd("chmod -R 755 targetroot/usr/share/panda3d")
             oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1")
             oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/")
@@ -301,9 +318,10 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
             txt = RUNTIME_INSTALLER_DEB_FILE[1:]
         else:
             txt = INSTALLER_DEB_FILE[1:]
-        txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("PV", PV).replace("MAJOR", major_version)
-        txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024))
-        oscmd("mkdir --mode=0755 -p targetroot/DEBIAN")
+        txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("MAJOR", major_version)
+        txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024))
+
+        oscmd("mkdir -m 0755 -p targetroot/DEBIAN")
         oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums")
         if not runtime:
             oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums")
@@ -337,6 +355,7 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
             oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals())
             depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip()
             recommends = ""
+            provides = "panda3d-runtime"
         else:
             pkg_name = "panda3d" + major_version
             pkg_dir = "debian/panda3d" + major_version
@@ -352,15 +371,29 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
             # Parse the substvars files generated by dpkg-shlibdeps.
             depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip()
             recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
-            if PkgSkip("PYTHON")==0:
-                depends += ", " + PYTHONV
-                recommends += ", python-wxversion, python-profiler (>= " + PV + "), python-pmw, python-tk (>= " + PV + ")"
-            if PkgSkip("NVIDIACG")==0:
+            provides = "panda3d"
+
+            if python2_ver or python3_ver:
+                recommends += ", python-pmw"
+
+            if python2_ver:
+                depends += ", python%s" % (python2_ver)
+                recommends += ", python-wxversion"
+                recommends += ", python-tk (>= %s)" % (python2_ver)
+                provides += ", python2-panda3d"
+
+            if python3_ver:
+                depends += ", python%s" % (python3_ver)
+                recommends += ", python3-tk (>= %s)" % (python3_ver)
+                provides += ", python3-panda3d"
+
+            if not PkgSkip("NVIDIACG"):
                 depends += ", nvidia-cg-toolkit"
 
         # Write back the dependencies, and delete the dummy set-up.
         txt = txt.replace("DEPENDS", depends.strip(', '))
         txt = txt.replace("RECOMMENDS", recommends.strip(', '))
+        txt = txt.replace("PROVIDES", provides.strip(', '))
         WriteFile("targetroot/DEBIAN/control", txt)
         oscmd("rm -rf targetroot/debian")
 
@@ -376,7 +409,9 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
         if runtime:
             InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir())
         else:
-            InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir())
+            InstallPanda(destdir="targetroot", prefix="/usr",
+                         outputdir=outputdir, libdir=GetRPMLibDir(),
+                         python_versions=install_python_versions)
             oscmd("chmod -R 755 targetroot/usr/share/panda3d")
 
         oscmd("rpm -E '%_target_cpu' > "+outputdir+"/tmp/architecture.txt")
@@ -392,15 +427,23 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
             if not PkgSkip("PVIEW"):
                 txt += INSTALLER_SPEC_FILE_PVIEW
 
+            # Add the platform-specific Python directories.
+            dirs = set()
+            for version_info in install_python_versions:
+                dirs.add(version_info["platlib"])
+                dirs.add(version_info["purelib"])
+
+            for dir in dirs:
+                txt += dir + "\n"
+
             # Add the binaries in /usr/bin explicitly to the spec file
             for base in os.listdir(outputdir + "/bin"):
                 txt += "/usr/bin/%s\n" % (base)
 
         # Write out the spec file.
         txt = txt.replace("VERSION", version)
-        txt = txt.replace("RPMRELEASE", rpmrelease)
+        txt = txt.replace("RPMRELEASE", str(rpmrelease))
         txt = txt.replace("PANDASOURCE", pandasource)
-        txt = txt.replace("PV", PV)
         WriteFile("panda3d.spec", txt)
 
         oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec")
@@ -414,7 +457,7 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs):
         exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!")
 
 
-def MakeInstallerOSX(version, runtime=False, **kwargs):
+def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
     outputdir = GetOutputDir()
 
     if runtime:
@@ -428,17 +471,11 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
         oscmd(cmdstr)
         return
 
-    if "PYTHONVERSION" in SDK:
-        pyver = SDK["PYTHONVERSION"][6:9]
-    else:
-        pyver = "%d.%d" % (sys.version_info[:2])
-
     dmg_name = "Panda3D-" + version
-    if not pyver.startswith("2."):
+    if len(python_versions) == 1 and not python_versions[0]["version"].startswith("2."):
         dmg_name += "-py" + pyver
     dmg_name += ".dmg"
 
-    import compileall
     if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name)
     if (os.path.exists("dstroot")): oscmd("rm -rf dstroot")
     if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg')
@@ -479,33 +516,43 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
         # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird.
         oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname)
 
-    if not PkgSkip("PYTHON"):
-        pyexec = SDK.get("PYTHONEXEC", sys.executable)
-        oscmd("mkdir -p dstroot/pythoncode/usr/local/bin")
+    if python_versions:
+        # Let's only write a ppython link if there is only one Python version.
+        if len(python_versions) == 1:
+            oscmd("mkdir -p dstroot/pythoncode/usr/local/bin")
+            oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % (python_versions[0]["executable"]))
+
         oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d")
-        oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % pyver)
-        WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % pyver, "/Developer/Panda3D")
         oscmd("cp -R %s/pandac                dstroot/pythoncode/Developer/Panda3D/pandac" % outputdir)
         oscmd("cp -R %s/direct                dstroot/pythoncode/Developer/Panda3D/direct" % outputdir)
-        oscmd("ln -s %s                       dstroot/pythoncode/usr/local/bin/ppython" % pyexec)
         oscmd("cp -R %s/*.so                  dstroot/pythoncode/Developer/Panda3D/" % outputdir, True)
         oscmd("cp -R %s/*.py                  dstroot/pythoncode/Developer/Panda3D/" % outputdir, True)
         if os.path.isdir(outputdir+"/Pmw"):
             oscmd("cp -R %s/Pmw               dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir)
-            compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw")
-        WriteFile("dstroot/pythoncode/Developer/Panda3D/direct/__init__.py", "")
-        for base in os.listdir("dstroot/pythoncode/Developer/Panda3D/direct"):
-            if ((base != "extensions") and (base != "extensions_native")):
-                compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base)
 
-        suffix = GetExtensionSuffix()
         for base in os.listdir(outputdir+"/panda3d"):
-            if base.endswith('.py') or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
+            if base.endswith('.py'):
                 libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base
+                oscmd("cp -R " + outputdir + "/panda3d/" + base + " " + libname)
+
+    for version_info in python_versions:
+        pyver = version_info["version"]
+        oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
+        oscmd("mkdir -p dstroot/pybindings%s/Developer/Panda3D/panda3d" % (pyver))
+
+        # Copy over extension modules.
+        suffix = version_info["ext_suffix"]
+        for base in os.listdir(outputdir+"/panda3d"):
+            if base.endswith(suffix) and '.' not in base[:-len(suffix)]:
+                libname = "dstroot/pybindings%s/Developer/Panda3D/panda3d/%s" % (pyver, base)
                 # We really need to specify -R in order not to follow symlinks
                 # On OSX, just specifying -P is not enough to do that.
                 oscmd("cp -R -P " + outputdir + "/panda3d/" + base + " " + libname)
 
+        # Write a .pth file.
+        oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
+        WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D")
+
     if not PkgSkip("FFMPEG"):
         oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib")
         oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % outputdir)
@@ -528,9 +575,19 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
         oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D")
         oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/")
 
-    oscmd("chmod -R 0775 dstroot/*")
     DeleteVCS("dstroot")
     DeleteBuildFiles("dstroot")
+
+    # Compile Python files.  Do this *after* the DeleteVCS step, above, which
+    # deletes __pycache__ directories.
+    for version_info in python_versions:
+        if os.path.isdir("dstroot/pythoncode/Developer/Panda3D/Pmw"):
+            oscmd("%s -m compileall -q -f -d /Developer/Panda3D/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % (version_info["executable"]), True)
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/direct dstroot/pythoncode/Developer/Panda3D/direct" % (version_info["executable"]))
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % (version_info["executable"]))
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/panda3d dstroot/pythoncode/Developer/Panda3D/panda3d" % (version_info["executable"]))
+
+    oscmd("chmod -R 0775 dstroot/*")
     # We need to be root to perform a chown. Bleh.
     # Fortunately PackageMaker does it for us, on 10.5 and above.
     #oscmd("chown -R root:admin dstroot/*", True)
@@ -539,7 +596,10 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
     oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/")
 
     pkgs = ["base", "tools", "headers"]
-    if not PkgSkip("PYTHON"):    pkgs.append("pythoncode")
+    if python_versions:
+        pkgs.append("pythoncode")
+    for version_info in python_versions:
+        pkgs.append("pybindings" + version_info["version"])
     if not PkgSkip("FFMPEG"):    pkgs.append("ffmpeg")
     #if not PkgSkip("OPENAL"):    pkgs.append("openal")
     if not PkgSkip("FMODEX"):    pkgs.append("fmodex")
@@ -581,22 +641,55 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
     dist.write('    <title>Panda3D SDK %s</title>\n' % (version))
     dist.write('    <options customize="always" allow-external-scripts="no" rootVolumeOnly="false"/>\n')
     dist.write('    <license language="en" mime-type="text/plain">%s</license>\n' % ReadFile("doc/LICENSE"))
+    dist.write('    <script>\n')
+    dist.write('    function isPythonVersionInstalled(version) {\n')
+    dist.write('        return system.files.fileExistsAtPath("/usr/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/usr/local/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/opt/local/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/sw/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/System/Library/Frameworks/Python.framework/Versions/" + version + "/bin/python")\n')
+    dist.write('            || system.files.fileExistsAtPath("/Library/Frameworks/Python.framework/Versions/" + version + "/bin/python");\n')
+    dist.write('    }\n')
+    dist.write('    </script>\n')
     dist.write('    <choices-outline>\n')
-    for pkg in pkgs:
-        dist.write('        <line choice="%s"/>\n' % (pkg))
+    dist.write('        <line choice="base"/>\n')
+    if python_versions:
+        dist.write('        <line choice="pythoncode">\n')
+        for version_info in sorted(python_versions, key=lambda info:info["version"], reverse=True):
+            dist.write('            <line choice="pybindings%s"/>\n' % (version_info["version"]))
+        dist.write('        </line>\n')
+    dist.write('        <line choice="tools"/>\n')
+    if os.path.isdir("samples"):
+        dist.write('        <line choice="samples"/>\n')
+    if not PkgSkip("FFMPEG"):
+        dist.write('        <line choice="ffmpeg"/>\n')
+    if not PkgSkip("FMODEX"):
+        dist.write('        <line choice="fmodex"/>\n')
+    dist.write('        <line choice="headers"/>\n')
     dist.write('    </choices-outline>\n')
-    dist.write('    <choice id="base" title="Panda3D Base Installation" description="This package contains the Panda3D libraries, configuration files and models/textures that are needed to use Panda3D. Location: /Developer/Panda3D/" start_enabled="false">\n')
+    dist.write('    <choice id="base" title="Panda3D Base Installation" description="This package contains the Panda3D libraries, configuration files and models/textures that are needed to use Panda3D.&#10;&#10;Location: /Developer/Panda3D/" start_enabled="false">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.base.pkg"/>\n')
     dist.write('    </choice>\n')
-    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more. Location: /Developer/Panda3D/bin/">\n')
+    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more.&#10;&#10;Location: /Developer/Panda3D/bin/">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.tools.pkg"/>\n')
     dist.write('    </choice>\n')
 
-    if not PkgSkip("PYTHON"):
-        dist.write('    <choice id="pythoncode" title="Python Support" tooltip="Python bindings for the Panda3D libraries" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D. Location: /Developer/Panda3D/">\n')
+    if python_versions:
+        dist.write('    <choice id="pythoncode" title="Python Support" tooltip="Python bindings for the Panda3D libraries" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D.&#10;&#10;Location: /Developer/Panda3D/">\n')
         dist.write('        <pkg-ref id="org.panda3d.panda3d.pythoncode.pkg"/>\n')
         dist.write('    </choice>\n')
 
+    for version_info in python_versions:
+        pyver = version_info["version"]
+        if pyver == "2.7":
+            # Always install Python 2.7 by default; it's included on macOS.
+            cond = "true"
+        else:
+            cond = "isPythonVersionInstalled('%s')" % (pyver)
+        dist.write('    <choice id="pybindings%s" start_selected="%s" title="Python %s Bindings" tooltip="Python bindings for the Panda3D libraries" description="Support for Python %s.">\n' % (pyver, cond, pyver, pyver))
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.pybindings%s.pkg"/>\n' % (pyver))
+        dist.write('    </choice>\n')
+
     if not PkgSkip("FFMPEG"):
         dist.write('    <choice id="ffmpeg" title="FFMpeg Plug-In" tooltip="FFMpeg video and audio decoding plug-in" description="This package contains the FFMpeg plug-in, which is used for decoding video and audio files with OpenAL.')
         if PkgSkip("VORBIS") and PkgSkip("OPUS"):
@@ -621,11 +714,11 @@ def MakeInstallerOSX(version, runtime=False, **kwargs):
         dist.write('    </choice>\n')
 
     if os.path.isdir("samples"):
-        dist.write('    <choice id="samples" title="Sample Programs" tooltip="Python sample programs that use Panda3D" description="This package contains the Python sample programs that can help you with learning how to use Panda3D. Location: /Developer/Examples/Panda3D/">\n')
+        dist.write('    <choice id="samples" title="Sample Programs" tooltip="Python sample programs that use Panda3D" description="This package contains the Python sample programs that can help you with learning how to use Panda3D.&#10;&#10;Location: /Developer/Examples/Panda3D/">\n')
         dist.write('        <pkg-ref id="org.panda3d.panda3d.samples.pkg"/>\n')
         dist.write('    </choice>\n')
 
-    dist.write('    <choice id="headers" title="C++ Header Files" tooltip="Header files for C++ development with Panda3D" description="This package contains the C++ header files that are needed in order to do C++ development with Panda3D. You don\'t need this if you want to develop in Python. Location: /Developer/Panda3D/include/" start_selected="false">\n')
+    dist.write('    <choice id="headers" title="C++ Header Files" tooltip="Header files for C++ development with Panda3D" description="This package contains the C++ header files that are needed in order to do C++ development with Panda3D. You don\'t need this if you want to develop in Python.&#10;&#10;Location: /Developer/Panda3D/include/" start_selected="false">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.headers.pkg"/>\n')
     dist.write('    </choice>\n')
     for pkg in pkgs:
@@ -693,7 +786,7 @@ def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
     manifest_txt = manifest_txt.replace("ARCH", pkg_arch)
     manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not runtime else 'graphics/panda3d-runtime')
     manifest_txt = manifest_txt.replace("DEPENDS", dependencies)
-    manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024 / 1024))
+    manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024 // 1024))
 
     WriteFile("pkg-plist", plist_txt)
     WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not runtime else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:])
@@ -889,13 +982,9 @@ def MakeInstaller(version, **kwargs):
 
         fn += version
 
-        if "PYTHONVERSION" in SDK:
-            pyver = SDK["PYTHONVERSION"][6:9]
-        else:
-            pyver = "%d.%d" % (sys.version_info[:2])
-
-        if not runtime and pyver != "2.7":
-            fn += '-py' + pyver
+        python_versions = kwargs.get('python_versions', [])
+        if not runtime and len(python_versions) == 1 and python_versions[0]["version"] != "2.7":
+            fn += '-py' + python_versions[0]["version"]
 
         if GetOptimize() <= 2:
             fn += "-dbg"
@@ -961,4 +1050,5 @@ if __name__ == "__main__":
                   compressor=options.compressor,
                   debversion=options.debversion,
                   rpmrelease=options.rpmrelease,
-                  runtime=options.runtime)
+                  runtime=options.runtime,
+                  python_versions=ReadPythonVersionInfoFile())

+ 25 - 10
makepanda/makepanda.py

@@ -72,6 +72,7 @@ MSVC_VERSION = None
 BOOUSEINTELCOMPILER = False
 OPENCV_VER_23 = False
 PLATFORM = None
+COPY_PYTHON = True
 
 if "MACOSX_DEPLOYMENT_TARGET" in os.environ:
     OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"]
@@ -172,6 +173,7 @@ def parseopts(args):
     global COMPRESSOR,THREADCOUNT,OSXTARGET,OSX_ARCHS,HOST_URL
     global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT,P3DSUFFIX,RTDIST_VERSION
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
+    global COPY_PYTHON
 
     # Options for which to display a deprecation warning.
     removedopts = [
@@ -185,7 +187,7 @@ def parseopts(args):
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "static","host=","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
-        "universal", "target=", "arch=", "git-commit=",
+        "universal", "target=", "arch=", "git-commit=", "no-copy-python",
         ] + removedopts
 
     anything = 0
@@ -251,6 +253,7 @@ def parseopts(args):
                 MSVC_VERSION = value.strip().lower()
             elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True
             elif (option=="--clean"): clean_build = True
+            elif (option=="--no-copy-python"): COPY_PYTHON = False
             elif (option[2:] in removedopts):
                 Warn("Ignoring removed option %s" % (option))
             else:
@@ -3031,7 +3034,7 @@ else:
     configprc = configprc.replace("aux-display pandadx9", "")
 
 if (GetTarget() == 'darwin'):
-    configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "Library/Caches/Panda3D-%s" % MAJOR_VERSION)
+    configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$HOME/Library/Caches/Panda3D-%s" % MAJOR_VERSION)
 
     # OpenAL is not yet working well on OSX for us, so let's do this for now.
     configprc = configprc.replace("p3openal_audio", "p3fmod_audio")
@@ -3155,7 +3158,8 @@ if tp_dir is not None:
                 CopyFile(GetOutputDir() + "/bin/", fn)
 
             # Copy the whole Python directory.
-            CopyTree(GetOutputDir() + "/python", SDK["PYTHON"])
+            if COPY_PYTHON:
+                CopyTree(GetOutputDir() + "/python", SDK["PYTHON"])
 
             # NB: Python does not always ship with the correct manifest/dll.
             # Figure out the correct one to ship, and grab it from WinSxS dir.
@@ -3164,7 +3168,7 @@ if tp_dir is not None:
                 os.unlink(manifest)
             oscmd('mt -inputresource:"%s\\python.exe";#1 -out:"%s" -nologo' % (SDK["PYTHON"], manifest), True)
 
-            if os.path.isfile(manifest):
+            if COPY_PYTHON and os.path.isfile(manifest):
                 import xml.etree.ElementTree as ET
                 tree = ET.parse(manifest)
                 idents = tree.findall('./{urn:schemas-microsoft-com:asm.v1}dependency/{urn:schemas-microsoft-com:asm.v1}dependentAssembly/{urn:schemas-microsoft-com:asm.v1}assemblyIdentity')
@@ -3194,11 +3198,12 @@ if tp_dir is not None:
                     CopyFile(GetOutputDir() + "/python/", file)
 
             # Copy python.exe to ppython.exe.
-            if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"):
-                CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe")
-            if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"):
-                CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe")
-            ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n")
+            if COPY_PYTHON:
+                if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"):
+                    CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe")
+                if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"):
+                    CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe")
+                ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n")
 
 # Copy over the MSVC runtime.
 if GetTarget() == 'windows' and "VISUALSTUDIO" in SDK:
@@ -6869,6 +6874,10 @@ if RUNTESTS:
         cmdstr += " --verbose"
     oscmd(cmdstr)
 
+# Write out information about the Python versions in the built dir.
+python_version_info = GetCurrentPythonVersionInfo()
+UpdatePythonVersionInfoFile(python_version_info)
+
 ##########################################################################################
 #
 # The Installers
@@ -6882,10 +6891,16 @@ if RUNTESTS:
 if INSTALLER:
     ProgressOutput(100.0, "Building installer")
     from makepackage import MakeInstaller
+
+    # When using the --installer flag, only install for the current version.
+    python_versions = []
+    if python_version_info:
+        python_versions.append(python_version_info)
+
     MakeInstaller(version=VERSION, outputdir=GetOutputDir(),
                   optimize=GetOptimize(), compressor=COMPRESSOR,
                   debversion=DEBVERSION, rpmrelease=RPMRELEASE,
-                  runtime=RUNTIME)
+                  runtime=RUNTIME, python_versions=python_versions)
 
 if WHEEL:
     ProgressOutput(100.0, "Building wheel")

+ 3 - 3
makepanda/makepanda.vcproj

@@ -1043,7 +1043,7 @@
 				<File RelativePath="..\panda\src\collide\collisionLine.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerFluidPusher.h"></File>
 				<File RelativePath="..\panda\src\collide\config_collide.h"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.h"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionSegment.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionBox.h"></File>
 				<File RelativePath="..\panda\src\collide\collide_composite1.cxx"></File>
@@ -1054,7 +1054,7 @@
 				<File RelativePath="..\panda\src\collide\collisionNode.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionGeom.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionSegment.I"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.cxx"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerQueue.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionParabola.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionPlane.cxx"></File>
@@ -1089,7 +1089,7 @@
 				<File RelativePath="..\panda\src\collide\collisionInvSphere.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionLine.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionEntry.I"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.I"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionSolid.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionPlane.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerGravity.cxx"></File>

+ 65 - 0
makepanda/makepandacore.py

@@ -3383,6 +3383,71 @@ def FindLocation(fn, ipath, pyabi=None):
     ORIG_EXT[loc] = ext
     return loc
 
+
+########################################################################
+##
+## These files maintain a python_versions.json file in the built/tmp
+## directory that can be used by the other scripts in this directory.
+##
+########################################################################
+
+
+def GetCurrentPythonVersionInfo():
+    if PkgSkip("PYTHON"):
+        return
+
+    from distutils.sysconfig import get_python_lib
+    return {
+        "version": SDK["PYTHONVERSION"][6:9],
+        "soabi": GetPythonABI(),
+        "ext_suffix": GetExtensionSuffix(),
+        "executable": sys.executable,
+        "purelib": get_python_lib(False),
+        "platlib": get_python_lib(True),
+    }
+
+
+def UpdatePythonVersionInfoFile(new_info):
+    import json
+
+    json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
+    json_data = []
+    if os.path.isfile(json_file) and not PkgSkip("PYTHON"):
+        try:
+            json_data = json.load(open(json_file, 'r'))
+        except:
+            json_data = []
+
+        # Prune the list by removing the entries that conflict with our build,
+        # plus the entries that no longer exist
+        for version_info in json_data[:]:
+            core_pyd = os.path.join(GetOutputDir(), "panda3d", "core" + version_info["ext_suffix"])
+            if version_info["ext_suffix"] == new_info["ext_suffix"] or \
+               version_info["soabi"] == new_info["soabi"] or \
+               not os.path.isfile(core_pyd):
+                json_data.remove(version_info)
+
+    if not PkgSkip("PYTHON"):
+        json_data.append(new_info)
+
+    if VERBOSE:
+        print("Writing %s" % (json_file))
+    json.dump(json_data, open(json_file, 'w'), indent=4)
+
+
+def ReadPythonVersionInfoFile():
+    import json
+
+    json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
+    if os.path.isfile(json_file):
+        try:
+            return json.load(open(json_file, 'r'))
+        except:
+            pass
+
+    return []
+
+
 ########################################################################
 ##
 ## TargetAdd

+ 6 - 6
panda/src/bullet/bulletBodyNode.cxx

@@ -28,7 +28,7 @@
 #include "collisionPlane.h"
 #include "collisionSphere.h"
 #include "collisionPolygon.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 
 TypeHandle BulletBodyNode::_type_handle;
 
@@ -813,12 +813,12 @@ add_shapes_from_collision_solids(CollisionNode *cnode) {
       do_add_shape(BulletBoxShape::make_from_solid(box), ts);
     }
 
-    // CollisionTube
-    else if (CollisionTube::get_class_type() == type) {
-      CPT(CollisionTube) tube = DCAST(CollisionTube, solid);
-      CPT(TransformState) ts = TransformState::make_pos((tube->get_point_b() + tube->get_point_a()) / 2.0);
+    // CollisionCapsule
+    else if (CollisionCapsule::get_class_type() == type) {
+      CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, solid);
+      CPT(TransformState) ts = TransformState::make_pos((capsule->get_point_b() + capsule->get_point_a()) / 2.0);
 
-      do_add_shape(BulletCapsuleShape::make_from_solid(tube), ts);
+      do_add_shape(BulletCapsuleShape::make_from_solid(capsule), ts);
     }
 
     // CollisionPlane

+ 5 - 5
panda/src/bullet/bulletCapsuleShape.cxx

@@ -87,16 +87,16 @@ ptr() const {
 
 /**
  * Constructs a new BulletCapsuleShape using the information from a
- * CollisionTube from the builtin collision system.
+ * CollisionCapsule from the builtin collision system.
  */
 BulletCapsuleShape *BulletCapsuleShape::
-make_from_solid(const CollisionTube *solid) {
-  
+make_from_solid(const CollisionCapsule *solid) {
+
   PN_stdfloat radius = solid->get_radius();
-  // Get tube's cylinder height: length from point A to point B
+  // Get capsule's cylinder height: length from point A to point B
   PN_stdfloat height = (solid->get_point_b() - solid->get_point_a()).length();
 
-  // CollisionTubes are always Z-Up.
+  // CollisionCapsules are always Z-Up.
   return new BulletCapsuleShape(radius, height, Z_up);
 }
 

+ 2 - 2
panda/src/bullet/bulletCapsuleShape.h

@@ -20,7 +20,7 @@
 #include "bullet_utils.h"
 #include "bulletShape.h"
 
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 
 /**
  *
@@ -35,7 +35,7 @@ PUBLISHED:
   BulletCapsuleShape(const BulletCapsuleShape &copy);
   INLINE ~BulletCapsuleShape();
 
-  static BulletCapsuleShape *make_from_solid(const CollisionTube *solid);
+  static BulletCapsuleShape *make_from_solid(const CollisionCapsule *solid);
 
   INLINE PN_stdfloat get_radius() const;
   INLINE PN_stdfloat get_half_height() const;

+ 8 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -20,6 +20,7 @@
 
 #import <AppKit/NSOpenGL.h>
 #import <OpenGL/OpenGL.h>
+#import <CoreVideo/CoreVideo.h>
 
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -38,14 +39,21 @@ public:
                              CocoaGraphicsStateGuardian *share_with);
 
   virtual ~CocoaGraphicsStateGuardian();
+  bool setup_vsync();
 
   INLINE void lock_context();
   INLINE void unlock_context();
 
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_context;
+  NSOpenGLPixelFormat *_format = nullptr;
   FrameBufferProperties _fbprops;
 
+  CVDisplayLinkRef _display_link = nullptr;
+  TrueMutexImpl _swap_lock;
+  TrueConditionVarImpl _swap_condition;
+  AtomicAdjust::Integer _last_wait_frame = 0;
+
 protected:
   virtual void query_gl_version();
   virtual void *do_get_extension_func(const char *name);

+ 68 - 4
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm

@@ -28,6 +28,21 @@
 #define NSAppKitVersionNumber10_7 1138
 #endif
 
+/**
+ * Called whenever a display wants a frame.  The context argument contains the
+ * applicable CocoaGraphicsStateGuardian.
+ */
+static CVReturn
+display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
+                const CVTimeStamp* output_time, CVOptionFlags flags_in,
+                CVOptionFlags *flags_out, void *context) {
+  CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context;
+  gsg->_swap_lock.lock();
+  gsg->_swap_condition.notify();
+  gsg->_swap_lock.unlock();
+  return kCVReturnSuccess;
+}
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 /**
@@ -36,7 +51,8 @@ TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 CocoaGraphicsStateGuardian::
 CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
                            CocoaGraphicsStateGuardian *share_with) :
-  GLGraphicsStateGuardian(engine, pipe)
+  GLGraphicsStateGuardian(engine, pipe),
+  _swap_condition(_swap_lock)
 {
   _share_context = nil;
   _context = nil;
@@ -52,12 +68,60 @@ CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
  */
 CocoaGraphicsStateGuardian::
 ~CocoaGraphicsStateGuardian() {
+  if (_format != nil) {
+    [_format release];
+  }
+  if (_display_link != nil) {
+    CVDisplayLinkRelease(_display_link);
+    _display_link = nil;
+    _swap_lock.lock();
+    _swap_condition.notify();
+    _swap_lock.unlock();
+  }
   if (_context != nil) {
     [_context clearDrawable];
     [_context release];
   }
 }
 
+/**
+ * Creates a CVDisplayLink, which tells us when the display the window is on
+ * will want a frame.
+ */
+bool CocoaGraphicsStateGuardian::
+setup_vsync() {
+  if (_display_link != nil) {
+    // Already set up.
+    return true;
+  }
+
+  CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkStart(_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
+    return false;
+  }
+
+  return true;
+}
+
 /**
  * Gets the FrameBufferProperties to match the indicated config.
  */
@@ -262,15 +326,15 @@ choose_pixel_format(const FrameBufferProperties &properties,
   // TODO: print out renderer
 
   _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context];
-  [format release];
+  _format = format;
   if (_context == nil) {
     cocoadisplay_cat.error() <<
       "Failed to create OpenGL context!\n";
     return;
   }
 
-  // Set vsync setting on the context
-  GLint swap = sync_video ? 1 : 0;
+  // Disable vsync via the built-in mechanism, which doesn't work on Mojave
+  GLint swap = 0;
   [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
 
   cocoadisplay_cat.debug()

+ 3 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -23,6 +23,8 @@
 #import <AppKit/NSView.h>
 #import <AppKit/NSWindow.h>
 
+#import <CoreVideo/CoreVideo.h>
+
 /**
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * X.
@@ -92,6 +94,7 @@ private:
   PT(GraphicsWindowInputDevice) _input;
   bool _mouse_hidden;
   bool _context_needs_update;
+  bool _vsync_enabled = false;
 
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   CGDisplayModeRef _fullscreen_mode;

+ 14 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -274,6 +274,15 @@ end_flip() {
     CocoaGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
 
+    if (_vsync_enabled) {
+      AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count();
+      if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) {
+        cocoagsg->_swap_lock.lock();
+        cocoagsg->_swap_condition.wait();
+        cocoagsg->_swap_lock.unlock();
+      }
+    }
+
     cocoagsg->lock_context();
 
     // Swap the front and back buffer.
@@ -669,6 +678,8 @@ open_window() {
     mouse_mode_relative();
   }
 
+  _vsync_enabled = sync_video && cocoagsg->setup_vsync();
+
   return true;
 }
 
@@ -710,6 +721,8 @@ close_window() {
     _view = nil;
   }
 
+  _vsync_enabled = false;
+
   GraphicsWindow::close_window();
 }
 
@@ -1491,6 +1504,7 @@ handle_close_event() {
       cocoagsg->unlock_context();
     }
     _gsg.clear();
+    _vsync_enabled = false;
   }
 
   // Dump the view, too

+ 10 - 10
panda/src/collide/collisionBox.cxx

@@ -16,7 +16,7 @@
 #include "collisionRay.h"
 #include "collisionSphere.h"
 #include "collisionSegment.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionHandler.h"
 #include "collisionEntry.h"
 #include "config_collide.h"
@@ -547,19 +547,19 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
 }
 
 /**
- * Double dispatch point for tube as a FROM object
+ * Double dispatch point for capsule as a FROM object
  */
 PT(CollisionEntry) CollisionBox::
-test_intersection_from_tube(const CollisionEntry &entry) const {
-  const CollisionTube *tube;
-  DCAST_INTO_R(tube, entry.get_from(), nullptr);
+test_intersection_from_capsule(const CollisionEntry &entry) const {
+  const CollisionCapsule *capsule;
+  DCAST_INTO_R(capsule, entry.get_from(), nullptr);
 
   const LMatrix4 &wrt_mat = entry.get_wrt_mat();
 
-  LPoint3 from_a = tube->get_point_a() * wrt_mat;
-  LPoint3 from_b = tube->get_point_b() * wrt_mat;
+  LPoint3 from_a = capsule->get_point_a() * wrt_mat;
+  LPoint3 from_b = capsule->get_point_b() * wrt_mat;
   LVector3 from_direction = from_b - from_a;
-  PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, tube->get_radius())).length_squared();
+  PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, capsule->get_radius())).length_squared();
   PN_stdfloat radius = csqrt(radius_sq);
 
   LPoint3 box_min = get_min();
@@ -620,7 +620,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
       LVector3 delta(0);
       delta[edges[i].axis] = dimensions[edges[i].axis];
       double u1, u2;
-      CollisionTube::calc_closest_segment_points(u1, u2, from_a, from_direction, vertex, delta);
+      CollisionCapsule::calc_closest_segment_points(u1, u2, from_a, from_direction, vertex, delta);
       PN_stdfloat dist_sq = ((from_a + from_direction * u1) - (vertex + delta * u2)).length_squared();
       if (dist_sq < best_dist_sq) {
         best_dist_sq = dist_sq;
@@ -682,7 +682,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
   new_entry->set_interior_point(point - interior_vec * radius);
   new_entry->set_surface_point(surface_point);
 
-  if (has_effective_normal() && tube->get_respect_effective_normal()) {
+  if (has_effective_normal() && capsule->get_respect_effective_normal()) {
     new_entry->set_surface_normal(get_effective_normal());
   } else {
     new_entry->set_surface_normal(normal);

+ 1 - 1
panda/src/collide/collisionBox.h

@@ -82,7 +82,7 @@ protected:
   virtual PT(CollisionEntry)
     test_intersection_from_segment(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
-    test_intersection_from_tube(const CollisionEntry &entry) const;
+    test_intersection_from_capsule(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
     test_intersection_from_box(const CollisionEntry &entry) const;
 

+ 19 - 19
panda/src/collide/collisionTube.I → panda/src/collide/collisionCapsule.I

@@ -6,7 +6,7 @@
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  *
- * @file collisionTube.I
+ * @file collisionCapsule.I
  * @author drose
  * @date 2003-09-25
  */
@@ -14,8 +14,8 @@
 /**
  *
  */
-INLINE CollisionTube::
-CollisionTube(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) :
+INLINE CollisionCapsule::
+CollisionCapsule(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) :
   _a(a), _b(b), _radius(radius)
 {
   recalc_internals();
@@ -25,8 +25,8 @@ CollisionTube(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) :
 /**
  *
  */
-INLINE CollisionTube::
-CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az,
+INLINE CollisionCapsule::
+CollisionCapsule(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az,
               PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz,
               PN_stdfloat radius) :
   _a(ax, ay, az), _b(bx, by, bz), _radius(radius)
@@ -36,17 +36,17 @@ CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az,
 }
 
 /**
- * Creates an invalid tube.  Only used when reading from a bam file.
+ * Creates an invalid capsule.  Only used when reading from a bam file.
  */
-INLINE CollisionTube::
-CollisionTube() {
+INLINE CollisionCapsule::
+CollisionCapsule() {
 }
 
 /**
  *
  */
-INLINE CollisionTube::
-CollisionTube(const CollisionTube &copy) :
+INLINE CollisionCapsule::
+CollisionCapsule(const CollisionCapsule &copy) :
   CollisionSolid(copy),
   _a(copy._a),
   _b(copy._b),
@@ -58,7 +58,7 @@ CollisionTube(const CollisionTube &copy) :
 /**
  * Flushes the PStatCollectors used during traversal.
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 flush_level() {
   _volume_pcollector.flush_level();
   _test_pcollector.flush_level();
@@ -67,7 +67,7 @@ flush_level() {
 /**
  *
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 set_point_a(const LPoint3 &a) {
   _a = a;
   recalc_internals();
@@ -76,7 +76,7 @@ set_point_a(const LPoint3 &a) {
 /**
  *
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) {
   set_point_a(LPoint3(x, y, z));
 }
@@ -84,7 +84,7 @@ set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) {
 /**
  *
  */
-INLINE const LPoint3 &CollisionTube::
+INLINE const LPoint3 &CollisionCapsule::
 get_point_a() const {
   return _a;
 }
@@ -92,7 +92,7 @@ get_point_a() const {
 /**
  *
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 set_point_b(const LPoint3 &b) {
   _b = b;
   recalc_internals();
@@ -101,7 +101,7 @@ set_point_b(const LPoint3 &b) {
 /**
  *
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) {
   set_point_b(LPoint3(x, y, z));
 }
@@ -109,7 +109,7 @@ set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) {
 /**
  *
  */
-INLINE const LPoint3 &CollisionTube::
+INLINE const LPoint3 &CollisionCapsule::
 get_point_b() const {
   return _b;
 }
@@ -117,7 +117,7 @@ get_point_b() const {
 /**
  *
  */
-INLINE void CollisionTube::
+INLINE void CollisionCapsule::
 set_radius(PN_stdfloat radius) {
   nassertv(radius >= 0.0f);
   _radius = radius;
@@ -131,7 +131,7 @@ set_radius(PN_stdfloat radius) {
 /**
  *
  */
-INLINE PN_stdfloat CollisionTube::
+INLINE PN_stdfloat CollisionCapsule::
 get_radius() const {
   return _radius;
 }

+ 74 - 74
panda/src/collide/collisionTube.cxx → panda/src/collide/collisionCapsule.cxx

@@ -6,12 +6,12 @@
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  *
- * @file collisionTube.cxx
+ * @file collisionCapsule.cxx
  * @author drose
  * @date 2003-09-25
  */
 
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionSphere.h"
 #include "collisionLine.h"
 #include "collisionRay.h"
@@ -35,30 +35,30 @@
 #include "geomVertexWriter.h"
 #include "boundingSphere.h"
 
-PStatCollector CollisionTube::_volume_pcollector("Collision Volumes:CollisionTube");
-PStatCollector CollisionTube::_test_pcollector("Collision Tests:CollisionTube");
-TypeHandle CollisionTube::_type_handle;
+PStatCollector CollisionCapsule::_volume_pcollector("Collision Volumes:CollisionCapsule");
+PStatCollector CollisionCapsule::_test_pcollector("Collision Tests:CollisionCapsule");
+TypeHandle CollisionCapsule::_type_handle;
 
 /**
  *
  */
-CollisionSolid *CollisionTube::
+CollisionSolid *CollisionCapsule::
 make_copy() {
-  return new CollisionTube(*this);
+  return new CollisionCapsule(*this);
 }
 
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection(const CollisionEntry &entry) const {
-  return entry.get_into()->test_intersection_from_tube(entry);
+  return entry.get_into()->test_intersection_from_capsule(entry);
 }
 
 /**
  * Transforms the solid by the indicated matrix.
  */
-void CollisionTube::
+void CollisionCapsule::
 xform(const LMatrix4 &mat) {
   _a = _a * mat;
   _b = _b * mat;
@@ -77,7 +77,7 @@ xform(const LMatrix4 &mat) {
  * collision purposes.  The closest intersection point to this origin point is
  * considered to be the most significant.
  */
-LPoint3 CollisionTube::
+LPoint3 CollisionCapsule::
 get_collision_origin() const {
   return get_point_a();
 }
@@ -86,7 +86,7 @@ get_collision_origin() const {
  * Returns a PStatCollector that is used to count the number of bounding
  * volume tests made against a solid of this type in a given frame.
  */
-PStatCollector &CollisionTube::
+PStatCollector &CollisionCapsule::
 get_volume_pcollector() {
   return _volume_pcollector;
 }
@@ -95,7 +95,7 @@ get_volume_pcollector() {
  * Returns a PStatCollector that is used to count the number of intersection
  * tests made against a solid of this type in a given frame.
  */
-PStatCollector &CollisionTube::
+PStatCollector &CollisionCapsule::
 get_test_pcollector() {
   return _test_pcollector;
 }
@@ -103,15 +103,15 @@ get_test_pcollector() {
 /**
  *
  */
-void CollisionTube::
+void CollisionCapsule::
 output(std::ostream &out) const {
-  out << "tube, a (" << _a << "), b (" << _b << "), r " << _radius;
+  out << "capsule, a (" << _a << "), b (" << _b << "), r " << _radius;
 }
 
 /**
  *
  */
-PT(BoundingVolume) CollisionTube::
+PT(BoundingVolume) CollisionCapsule::
 compute_internal_bounds() const {
   PT(BoundingVolume) bound = CollisionSolid::compute_internal_bounds();
 
@@ -143,7 +143,7 @@ compute_internal_bounds() const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection_from_sphere(const CollisionEntry &entry) const {
   const CollisionSphere *sphere;
   DCAST_INTO_R(sphere, entry.get_from(), nullptr);
@@ -160,7 +160,7 @@ test_intersection_from_sphere(const CollisionEntry &entry) const {
   PN_stdfloat actual_t = 0.0f;
 
   if (wrt_prev_space != wrt_space) {
-    // If the sphere is moving relative to the tube, it becomes a tube itself.
+    // If the sphere is moving relative to the capsule, it becomes a capsule itself.
     from_a = sphere->get_center() * wrt_prev_space->get_mat();
   }
 
@@ -195,11 +195,11 @@ test_intersection_from_sphere(const CollisionEntry &entry) const {
 
   LPoint3 into_intersection_point;
   if (t2 > 1.0) {
-    // Point b is within the tube.  The first intersection point is point b
+    // Point b is within the capsule.  The first intersection point is point b
     // itself.
     into_intersection_point = from_b;
   } else {
-    // Point b is outside the tube, and point a is either inside the tube or
+    // Point b is outside the capsule, and point a is either inside the capsule or
     // beyond it.  The first intersection point is at t2.
     into_intersection_point = from_a + t2 * from_direction;
   }
@@ -221,7 +221,7 @@ test_intersection_from_sphere(const CollisionEntry &entry) const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection_from_line(const CollisionEntry &entry) const {
   const CollisionLine *line;
   DCAST_INTO_R(line, entry.get_from(), nullptr);
@@ -269,7 +269,7 @@ test_intersection_from_line(const CollisionEntry &entry) const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection_from_ray(const CollisionEntry &entry) const {
   const CollisionRay *ray;
   DCAST_INTO_R(ray, entry.get_from(), nullptr);
@@ -299,11 +299,11 @@ test_intersection_from_ray(const CollisionEntry &entry) const {
 
   LPoint3 into_intersection_point;
   if (t1 < 0.0) {
-    // Point a is within the tube.  The first intersection point is point a
+    // Point a is within the capsule.  The first intersection point is point a
     // itself.
     into_intersection_point = from_origin;
   } else {
-    // Point a is outside the tube.  The first intersection point is at t1.
+    // Point a is outside the capsule.  The first intersection point is at t1.
     into_intersection_point = from_origin + t1 * from_direction;
   }
   set_intersection_point(new_entry, into_intersection_point, 0.0);
@@ -330,7 +330,7 @@ test_intersection_from_ray(const CollisionEntry &entry) const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection_from_segment(const CollisionEntry &entry) const {
   const CollisionSegment *segment;
   DCAST_INTO_R(segment, entry.get_from(), nullptr);
@@ -362,11 +362,11 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
 
   LPoint3 into_intersection_point;
   if (t1 < 0.0) {
-    // Point a is within the tube.  The first intersection point is point a
+    // Point a is within the capsule.  The first intersection point is point a
     // itself.
     into_intersection_point = from_a;
   } else {
-    // Point a is outside the tube, and point b is either inside the tube or
+    // Point a is outside the capsule, and point b is either inside the capsule or
     // beyond it.  The first intersection point is at t1.
     into_intersection_point = from_a + t1 * from_direction;
   }
@@ -394,22 +394,22 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
-test_intersection_from_tube(const CollisionEntry &entry) const {
-  const CollisionTube *tube;
-  DCAST_INTO_R(tube, entry.get_from(), nullptr);
+PT(CollisionEntry) CollisionCapsule::
+test_intersection_from_capsule(const CollisionEntry &entry) const {
+  const CollisionCapsule *capsule;
+  DCAST_INTO_R(capsule, entry.get_from(), nullptr);
 
   LPoint3 into_a = _a;
   LVector3 into_direction = _b - into_a;
 
   const LMatrix4 &wrt_mat = entry.get_wrt_mat();
 
-  LPoint3 from_a = tube->get_point_a() * wrt_mat;
-  LPoint3 from_b = tube->get_point_b() * wrt_mat;
+  LPoint3 from_a = capsule->get_point_a() * wrt_mat;
+  LPoint3 from_b = capsule->get_point_b() * wrt_mat;
   LVector3 from_direction = from_b - from_a;
 
   LVector3 from_radius_v =
-    LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat;
+    LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
   PN_stdfloat from_radius = length(from_radius_v);
 
   // Determine the points on each segment with the smallest distance between.
@@ -420,7 +420,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
   LPoint3 into_closest = into_a + into_direction * into_t;
   LPoint3 from_closest = from_a + from_direction * from_t;
 
-  // If the distance is greater than the sum of tube radii, the test fails.
+  // If the distance is greater than the sum of capsule radii, the test fails.
   LVector3 closest_vec = from_closest - into_closest;
   PN_stdfloat distance = closest_vec.length();
   if (distance > _radius + from_radius) {
@@ -442,7 +442,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
     new_entry->set_surface_point(into_closest + surface_normal * _radius);
     new_entry->set_interior_point(from_closest - surface_normal * from_radius);
 
-    if (has_effective_normal() && tube->get_respect_effective_normal()) {
+    if (has_effective_normal() && capsule->get_respect_effective_normal()) {
       new_entry->set_surface_normal(get_effective_normal());
     } else if (distance != 0) {
       new_entry->set_surface_normal(surface_normal);
@@ -458,7 +458,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
 /**
  *
  */
-PT(CollisionEntry) CollisionTube::
+PT(CollisionEntry) CollisionCapsule::
 test_intersection_from_parabola(const CollisionEntry &entry) const {
   const CollisionParabola *parabola;
   DCAST_INTO_R(parabola, entry.get_from(), nullptr);
@@ -510,14 +510,14 @@ test_intersection_from_parabola(const CollisionEntry &entry) const {
  * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this
  * solid.
  */
-void CollisionTube::
+void CollisionCapsule::
 fill_viz_geom() {
   if (collide_cat.is_debug()) {
     collide_cat.debug()
       << "Recomputing viz for " << *this << "\n";
   }
 
-  // Generate the vertices such that we draw a tube with one endpoint at (0,
+  // Generate the vertices such that we draw a capsule with one endpoint at (0,
   // 0, 0), and another at (0, length, 0).  Then we'll rotate and translate it
   // into place with the appropriate look_at matrix.
   LVector3 direction = (_b - _a);
@@ -576,9 +576,9 @@ fill_viz_geom() {
 
 /**
  * Should be called internally to recompute the matrix and length when the
- * properties of the tube have changed.
+ * properties of the capsule have changed.
  */
-void CollisionTube::
+void CollisionCapsule::
 recalc_internals() {
   LVector3 direction = (_b - _a);
   _length = direction.length();
@@ -595,7 +595,7 @@ recalc_internals() {
  * Calculates a particular vertex on the surface of the first endcap
  * hemisphere, for use in generating the viz geometry.
  */
-LVertex CollisionTube::
+LVertex CollisionCapsule::
 calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices) {
   PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
   PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
@@ -620,7 +620,7 @@ calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices) {
  * Calculates a particular vertex on the surface of the second endcap
  * hemisphere, for use in generating the viz geometry.
  */
-LVertex CollisionTube::
+LVertex CollisionCapsule::
 calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices,
                     PN_stdfloat length) {
   PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
@@ -646,7 +646,7 @@ calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices,
  * Given line segments s1 and s2 defined by two points each, computes the
  * point on each segment with the closest distance between them.
  */
-void CollisionTube::
+void CollisionCapsule::
 calc_closest_segment_points(double &t1, double &t2,
                             const LPoint3 &from1, const LVector3 &delta1,
                             const LPoint3 &from2, const LVector3 &delta2) {
@@ -717,18 +717,18 @@ calc_closest_segment_points(double &t1, double &t2,
 }
 
 /**
- * Determine the point(s) of intersection of a parametric line with the tube.
+ * Determine the point(s) of intersection of a parametric line with the capsule.
  * The line is infinite in both directions, and passes through "from" and
- * from+delta.  If the line does not intersect the tube, the function returns
- * false, and t1 and t2 are undefined.  If it does intersect the tube, it
+ * from+delta.  If the line does not intersect the capsule, the function returns
+ * false, and t1 and t2 are undefined.  If it does intersect the capsule, it
  * returns true, and t1 and t2 are set to the points along the equation
  * from+t*delta that correspond to the two points of intersection.
  */
-bool CollisionTube::
+bool CollisionCapsule::
 intersects_line(double &t1, double &t2,
                 const LPoint3 &from0, const LVector3 &delta0,
                 PN_stdfloat inflate_radius) const {
-  // Convert the line into our canonical coordinate space: the tube is aligned
+  // Convert the line into our canonical coordinate space: the capsule is aligned
   // with the y axis.
   LPoint3 from = from0 * _inv_mat;
   LVector3 delta = delta0 * _inv_mat;
@@ -776,7 +776,7 @@ intersects_line(double &t1, double &t2,
         }
       }
 
-      // The point is within the tube!
+      // The point is within the capsule!
       t1 = t2 = 0.0;
       return true;
     }
@@ -819,15 +819,15 @@ intersects_line(double &t1, double &t2,
   PN_stdfloat t2_y = from[1] + t2 * delta[1];
 
   if (t1_y < -radius && t2_y < -radius) {
-    // Both points are way off the bottom of the tube; no intersection.
+    // Both points are way off the bottom of the capsule; no intersection.
     return false;
   } else if (t1_y > _length + radius && t2_y > _length + radius) {
-    // Both points are way off the top of the tube; no intersection.
+    // Both points are way off the top of the capsule; no intersection.
     return false;
   }
 
   if (t1_y < 0.0f) {
-    // The starting point is off the bottom of the tube.  Test the line
+    // The starting point is off the bottom of the capsule.  Test the line
     // against the first endcap.
     double t1a, t2a;
     if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
@@ -838,7 +838,7 @@ intersects_line(double &t1, double &t2,
     t1 = t1a;
 
   } else if (t1_y > _length) {
-    // The starting point is off the top of the tube.  Test the line against
+    // The starting point is off the top of the capsule.  Test the line against
     // the second endcap.
     double t1b, t2b;
     if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
@@ -850,7 +850,7 @@ intersects_line(double &t1, double &t2,
   }
 
   if (t2_y < 0.0f) {
-    // The ending point is off the bottom of the tube.  Test the line against
+    // The ending point is off the bottom of the capsule.  Test the line against
     // the first endcap.
     double t1a, t2a;
     if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
@@ -861,7 +861,7 @@ intersects_line(double &t1, double &t2,
     t2 = t2a;
 
   } else if (t2_y > _length) {
-    // The ending point is off the top of the tube.  Test the line against the
+    // The ending point is off the top of the capsule.  Test the line against the
     // second endcap.
     double t1b, t2b;
     if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
@@ -880,7 +880,7 @@ intersects_line(double &t1, double &t2,
  * whether it intersects one or the other endcaps.  The y parameter specifies
  * the center of the sphere (and hence the particular endcap).
  */
-bool CollisionTube::
+bool CollisionCapsule::
 sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y,
                        const LPoint3 &from, const LVector3 &delta,
                        PN_stdfloat radius) {
@@ -917,14 +917,14 @@ sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y,
 }
 
 /**
- * Determine a point of intersection of a parametric parabola with the tube.
+ * Determine a point of intersection of a parametric parabola with the capsule.
  *
  * We only consider the segment of the parabola between t1 and t2, which has
  * already been computed as corresponding to points p1 and p2.  If there is an
  * intersection, t is set to the parametric point of intersection, and true is
  * returned; otherwise, false is returned.
  */
-bool CollisionTube::
+bool CollisionCapsule::
 intersects_parabola(double &t, const LParabola &parabola,
                     double t1, double t2,
                     const LPoint3 &p1, const LPoint3 &p2) const {
@@ -965,11 +965,11 @@ intersects_parabola(double &t, const LParabola &parabola,
 }
 
 /**
- * Calculates a point that is exactly on the surface of the tube and its
+ * Calculates a point that is exactly on the surface of the capsule and its
  * corresponding normal, given a point that is supposedly on the surface of
- * the tube.
+ * the capsule.
  */
-void CollisionTube::
+void CollisionCapsule::
 calculate_surface_point_and_normal(const LPoint3 &surface_point,
                                    double extra_radius,
                                    LPoint3 &result_point,
@@ -1015,7 +1015,7 @@ calculate_surface_point_and_normal(const LPoint3 &surface_point,
  * point in the CollisionEntry, and also compute the relevant normal based on
  * that point.
  */
-void CollisionTube::
+void CollisionCapsule::
 set_intersection_point(CollisionEntry *new_entry,
                        const LPoint3 &into_intersection_point,
                        double extra_radius) const {
@@ -1033,16 +1033,16 @@ set_intersection_point(CollisionEntry *new_entry,
 
   new_entry->set_surface_normal(normal);
   new_entry->set_surface_point(point);
-  // Also adjust the original point into the tube by the amount of
-  // extra_radius, which should put it on the surface of the tube if our
+  // Also adjust the original point into the capsule by the amount of
+  // extra_radius, which should put it on the surface of the capsule if our
   // collision was tangential.
   new_entry->set_interior_point(into_intersection_point - normal * extra_radius);
 }
 
 /**
- * Tells the BamReader how to create objects of type CollisionTube.
+ * Tells the BamReader how to create objects of type CollisionCapsule.
  */
-void CollisionTube::
+void CollisionCapsule::
 register_with_read_factory() {
   BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
 }
@@ -1051,7 +1051,7 @@ register_with_read_factory() {
  * Writes the contents of this object to the datagram for shipping out to a
  * Bam file.
  */
-void CollisionTube::
+void CollisionCapsule::
 write_datagram(BamWriter *manager, Datagram &dg) {
   CollisionSolid::write_datagram(manager, dg);
   _a.write_datagram(dg);
@@ -1061,12 +1061,12 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 
 /**
  * This function is called by the BamReader's factory when a new object of
- * type CollisionTube is encountered in the Bam file.  It should create the
- * CollisionTube and extract its information from the file.
+ * type CollisionCapsule is encountered in the Bam file.  It should create the
+ * CollisionCapsule and extract its information from the file.
  */
-TypedWritable *CollisionTube::
+TypedWritable *CollisionCapsule::
 make_from_bam(const FactoryParams &params) {
-  CollisionTube *node = new CollisionTube();
+  CollisionCapsule *node = new CollisionCapsule();
   DatagramIterator scan;
   BamReader *manager;
 
@@ -1078,9 +1078,9 @@ make_from_bam(const FactoryParams &params) {
 
 /**
  * This internal function is called by make_from_bam to read in all of the
- * relevant data from the BamFile for the new CollisionTube.
+ * relevant data from the BamFile for the new CollisionCapsule.
  */
-void CollisionTube::
+void CollisionCapsule::
 fillin(DatagramIterator &scan, BamReader *manager) {
   CollisionSolid::fillin(scan, manager);
   _a.read_datagram(scan);

+ 161 - 0
panda/src/collide/collisionCapsule.h

@@ -0,0 +1,161 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file collisionCapsule.h
+ * @author drose
+ * @date 2003-09-25
+ */
+
+#ifndef COLLISIONCAPSULE_H
+#define COLLISIONCAPSULE_H
+
+#include "pandabase.h"
+#include "collisionSolid.h"
+#include "parabola.h"
+
+/**
+ * This implements a solid consisting of a cylinder with hemispherical endcaps,
+ * also known as a capsule or a spherocylinder.
+ *
+ * This shape was previously erroneously called CollisionTube.
+ */
+class EXPCL_PANDA_COLLIDE CollisionCapsule : public CollisionSolid {
+PUBLISHED:
+  INLINE explicit CollisionCapsule(const LPoint3 &a, const LPoint3 &db,
+                                   PN_stdfloat radius);
+  INLINE explicit CollisionCapsule(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az,
+                                   PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz,
+                                   PN_stdfloat radius);
+
+  virtual LPoint3 get_collision_origin() const;
+
+private:
+  INLINE CollisionCapsule();
+
+public:
+  INLINE CollisionCapsule(const CollisionCapsule &copy);
+  virtual CollisionSolid *make_copy();
+
+  virtual PT(CollisionEntry)
+  test_intersection(const CollisionEntry &entry) const;
+
+  virtual void xform(const LMatrix4 &mat);
+
+  virtual PStatCollector &get_volume_pcollector();
+  virtual PStatCollector &get_test_pcollector();
+
+  virtual void output(std::ostream &out) const;
+
+  INLINE static void flush_level();
+
+PUBLISHED:
+  INLINE void set_point_a(const LPoint3 &a);
+  INLINE void set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z);
+  INLINE const LPoint3 &get_point_a() const;
+
+  INLINE void set_point_b(const LPoint3 &b);
+  INLINE void set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z);
+  INLINE const LPoint3 &get_point_b() const;
+
+  INLINE void set_radius(PN_stdfloat radius);
+  INLINE PN_stdfloat get_radius() const;
+
+PUBLISHED:
+  MAKE_PROPERTY(point_a, get_point_a, set_point_a);
+  MAKE_PROPERTY(point_b, get_point_b, set_point_b);
+  MAKE_PROPERTY(radius, get_radius, set_radius);
+
+protected:
+  virtual PT(BoundingVolume) compute_internal_bounds() const;
+
+protected:
+  virtual PT(CollisionEntry)
+  test_intersection_from_sphere(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_line(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_ray(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_segment(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_capsule(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_parabola(const CollisionEntry &entry) const;
+
+  virtual void fill_viz_geom();
+
+private:
+  void recalc_internals();
+
+  LVertex calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices);
+  LVertex calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices,
+                              PN_stdfloat length);
+
+  static void calc_closest_segment_points(double &t1, double &t2,
+                                          const LPoint3 &from1, const LVector3 &delta1,
+                                          const LPoint3 &from2, const LVector3 &delta2);
+  bool intersects_line(double &t1, double &t2,
+                       const LPoint3 &from, const LVector3 &delta,
+                       PN_stdfloat inflate_radius) const;
+  static bool sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y,
+                                     const LPoint3 &from, const LVector3 &delta,
+                                     PN_stdfloat radius);
+  bool intersects_parabola(double &t, const LParabola &parabola,
+                           double t1, double t2,
+                           const LPoint3 &p1, const LPoint3 &p2) const;
+  void calculate_surface_point_and_normal(const LPoint3 &surface_point,
+                                          double extra_radius,
+                                          LPoint3 &result_point,
+                                          LVector3 &result_normal) const;
+  void set_intersection_point(CollisionEntry *new_entry,
+                              const LPoint3 &into_intersection_point,
+                              double extra_radius) const;
+
+private:
+  LPoint3 _a, _b;
+  PN_stdfloat _radius;
+
+  // These are derived from the above.
+  LMatrix4 _mat;
+  LMatrix4 _inv_mat;
+  PN_stdfloat _length;
+
+  static PStatCollector _volume_pcollector;
+  static PStatCollector _test_pcollector;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    CollisionSolid::init_type();
+    register_type(_type_handle, "CollisionCapsule",
+                  CollisionSolid::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class CollisionBox;
+};
+
+#include "collisionCapsule.I"
+
+#endif

+ 1 - 1
panda/src/collide/collisionHandlerFluidPusher.cxx

@@ -72,7 +72,7 @@ handle_entries() {
     PosB = collider's current position
     M = movement vector (PosB - PosA)
     BV = bounding sphere that includes collider at PosA and PosB
-    CS = 'collision set', all 'collidables' within BV (collision polys, tubes, etc)
+    CS = 'collision set', all 'collidables' within BV (collision polys, capsules, etc)
 
     VARIABLES
     N = movement vector since most recent collision (or start of frame)

+ 11 - 11
panda/src/collide/collisionPlane.cxx

@@ -18,7 +18,7 @@
 #include "collisionLine.h"
 #include "collisionRay.h"
 #include "collisionSegment.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionParabola.h"
 #include "config_collide.h"
 #include "pointerToArray.h"
@@ -298,16 +298,16 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
  *
  */
 PT(CollisionEntry) CollisionPlane::
-test_intersection_from_tube(const CollisionEntry &entry) const {
-  const CollisionTube *tube;
-  DCAST_INTO_R(tube, entry.get_from(), nullptr);
+test_intersection_from_capsule(const CollisionEntry &entry) const {
+  const CollisionCapsule *capsule;
+  DCAST_INTO_R(capsule, entry.get_from(), nullptr);
 
   const LMatrix4 &wrt_mat = entry.get_wrt_mat();
 
-  LPoint3 from_a = tube->get_point_a() * wrt_mat;
-  LPoint3 from_b = tube->get_point_b() * wrt_mat;
+  LPoint3 from_a = capsule->get_point_a() * wrt_mat;
+  LPoint3 from_b = capsule->get_point_b() * wrt_mat;
   LVector3 from_radius_v =
-    LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat;
+    LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
   PN_stdfloat from_radius = length(from_radius_v);
 
   PN_stdfloat dist_a = _plane.dist_to_plane(from_a);
@@ -325,7 +325,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
   }
   PT(CollisionEntry) new_entry = new CollisionEntry(entry);
 
-  LVector3 normal = (has_effective_normal() && tube->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
+  LVector3 normal = (has_effective_normal() && capsule->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
   new_entry->set_surface_normal(normal);
 
   PN_stdfloat t;
@@ -339,17 +339,17 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
       new_entry->set_surface_point(from_a - get_normal() * dist_a);
 
     } else {
-      // Within the tube!  Yay, that means we have a surface point.
+      // Within the capsule!  Yay, that means we have a surface point.
       new_entry->set_surface_point(from_a + t * from_direction);
     }
   } else {
     // If it's completely parallel, pretend it's colliding in the center of
-    // the tube.
+    // the capsule.
     new_entry->set_surface_point(from_a + 0.5f * from_direction - get_normal() * dist_a);
   }
 
   if (IS_NEARLY_EQUAL(dist_a, dist_b)) {
-    // Let's be fair and choose the center of the tube.
+    // Let's be fair and choose the center of the capsule.
     new_entry->set_interior_point(from_a + 0.5f * from_direction - get_normal() * from_radius);
 
   } else if (dist_a < dist_b) {

+ 1 - 1
panda/src/collide/collisionPlane.h

@@ -72,7 +72,7 @@ protected:
   virtual PT(CollisionEntry)
   test_intersection_from_segment(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
-  test_intersection_from_tube(const CollisionEntry &entry) const;
+  test_intersection_from_capsule(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
   test_intersection_from_parabola(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)

+ 4 - 4
panda/src/collide/collisionSolid.cxx

@@ -17,7 +17,7 @@
 #include "collisionLine.h"
 #include "collisionRay.h"
 #include "collisionSegment.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionParabola.h"
 #include "collisionBox.h"
 #include "collisionEntry.h"
@@ -240,11 +240,11 @@ test_intersection_from_segment(const CollisionEntry &) const {
 
 /**
  * This is part of the double-dispatch implementation of test_intersection().
- * It is called when the "from" object is a tube.
+ * It is called when the "from" object is a capsule.
  */
 PT(CollisionEntry) CollisionSolid::
-test_intersection_from_tube(const CollisionEntry &) const {
-  report_undefined_intersection_test(CollisionTube::get_class_type(),
+test_intersection_from_capsule(const CollisionEntry &) const {
+  report_undefined_intersection_test(CollisionCapsule::get_class_type(),
                                      get_type());
   return nullptr;
 }

+ 2 - 2
panda/src/collide/collisionSolid.h

@@ -108,7 +108,7 @@ protected:
   virtual PT(CollisionEntry)
   test_intersection_from_segment(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
-  test_intersection_from_tube(const CollisionEntry &entry) const;
+  test_intersection_from_capsule(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
   test_intersection_from_parabola(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
@@ -177,7 +177,7 @@ private:
   friend class CollisionLine;
   friend class CollisionRay;
   friend class CollisionSegment;
-  friend class CollisionTube;
+  friend class CollisionCapsule;
   friend class CollisionParabola;
   friend class CollisionHandlerFluidPusher;
   friend class CollisionBox;

+ 10 - 10
panda/src/collide/collisionSphere.cxx

@@ -17,7 +17,7 @@
 #include "collisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionSegment.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionParabola.h"
 #include "collisionBox.h"
 #include "config_collide.h"
@@ -444,18 +444,18 @@ test_intersection_from_segment(const CollisionEntry &entry) const {
  *
  */
 PT(CollisionEntry) CollisionSphere::
-test_intersection_from_tube(const CollisionEntry &entry) const {
-  const CollisionTube *tube;
-  DCAST_INTO_R(tube, entry.get_from(), nullptr);
+test_intersection_from_capsule(const CollisionEntry &entry) const {
+  const CollisionCapsule *capsule;
+  DCAST_INTO_R(capsule, entry.get_from(), nullptr);
 
   const LMatrix4 &wrt_mat = entry.get_wrt_mat();
 
-  LPoint3 from_a = tube->get_point_a() * wrt_mat;
-  LPoint3 from_b = tube->get_point_b() * wrt_mat;
+  LPoint3 from_a = capsule->get_point_a() * wrt_mat;
+  LPoint3 from_b = capsule->get_point_b() * wrt_mat;
   LVector3 from_direction = from_b - from_a;
 
   LVector3 from_radius_v =
-    LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat;
+    LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
   PN_stdfloat from_radius = length(from_radius_v);
 
   double t1, t2;
@@ -465,8 +465,8 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
   }
 
   if (t2 < 0.0 || t1 > 1.0) {
-    // Both intersection points are before the start of the tube or after
-    // the end of the tube.
+    // Both intersection points are before the start of the capsule or after
+    // the end of the capsule.
     return nullptr;
   }
 
@@ -487,7 +487,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const {
   new_entry->set_surface_point(get_center() + normal * get_radius());
   new_entry->set_interior_point(inner_point - normal * from_radius);
 
-  if (has_effective_normal() && tube->get_respect_effective_normal()) {
+  if (has_effective_normal() && capsule->get_respect_effective_normal()) {
     new_entry->set_surface_normal(get_effective_normal());
   } else {
     new_entry->set_surface_normal(normal);

+ 1 - 1
panda/src/collide/collisionSphere.h

@@ -72,7 +72,7 @@ protected:
   virtual PT(CollisionEntry)
   test_intersection_from_segment(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
-  test_intersection_from_tube(const CollisionEntry &entry) const;
+  test_intersection_from_capsule(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)
   test_intersection_from_parabola(const CollisionEntry &entry) const;
   virtual PT(CollisionEntry)

+ 2 - 2
panda/src/collide/collisionTraverser.cxx

@@ -20,7 +20,7 @@
 #include "collisionVisualizer.h"
 #include "collisionSphere.h"
 #include "collisionBox.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionPolygon.h"
 #include "collisionPlane.h"
 #include "config_collide.h"
@@ -345,7 +345,7 @@ traverse(const NodePath &root) {
   _geom_volume_pcollector.flush_level();
 
   CollisionSphere::flush_level();
-  CollisionTube::flush_level();
+  CollisionCapsule::flush_level();
   CollisionPolygon::flush_level();
   CollisionPlane::flush_level();
   CollisionBox::flush_level();

+ 8 - 141
panda/src/collide/collisionTube.h

@@ -7,154 +7,21 @@
  * with this source code in a file named "LICENSE."
  *
  * @file collisionTube.h
- * @author drose
- * @date 2003-09-25
+ * @author rdb
+ * @date 2018-12-23
  */
 
 #ifndef COLLISIONTUBE_H
 #define COLLISIONTUBE_H
 
-#include "pandabase.h"
-#include "collisionSolid.h"
-#include "parabola.h"
+#include "collisionCapsule.h"
 
+BEGIN_PUBLISH
 /**
- * This implements a solid roughly in cylindrical shape.  It's not called a
- * CollisionCylinder because it's not a true cylinder; specifically, it has
- * rounded ends instead of flat ends.  It looks more like a Contac pill.
+ * Alias for backward compatibility.
+ * @deprecated use CollisionCapsule instead.
  */
-class EXPCL_PANDA_COLLIDE CollisionTube : public CollisionSolid {
-PUBLISHED:
-  INLINE explicit CollisionTube(const LPoint3 &a, const LPoint3 &db,
-                                PN_stdfloat radius);
-  INLINE explicit CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az,
-                                PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz,
-                                PN_stdfloat radius);
-
-  virtual LPoint3 get_collision_origin() const;
-
-private:
-  INLINE CollisionTube();
-
-public:
-  INLINE CollisionTube(const CollisionTube &copy);
-  virtual CollisionSolid *make_copy();
-
-  virtual PT(CollisionEntry)
-  test_intersection(const CollisionEntry &entry) const;
-
-  virtual void xform(const LMatrix4 &mat);
-
-  virtual PStatCollector &get_volume_pcollector();
-  virtual PStatCollector &get_test_pcollector();
-
-  virtual void output(std::ostream &out) const;
-
-  INLINE static void flush_level();
-
-PUBLISHED:
-  INLINE void set_point_a(const LPoint3 &a);
-  INLINE void set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z);
-  INLINE const LPoint3 &get_point_a() const;
-
-  INLINE void set_point_b(const LPoint3 &b);
-  INLINE void set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z);
-  INLINE const LPoint3 &get_point_b() const;
-
-  INLINE void set_radius(PN_stdfloat radius);
-  INLINE PN_stdfloat get_radius() const;
-
-PUBLISHED:
-  MAKE_PROPERTY(point_a, get_point_a, set_point_a);
-  MAKE_PROPERTY(point_b, get_point_b, set_point_b);
-  MAKE_PROPERTY(radius, get_radius, set_radius);
-
-protected:
-  virtual PT(BoundingVolume) compute_internal_bounds() const;
-
-protected:
-  virtual PT(CollisionEntry)
-  test_intersection_from_sphere(const CollisionEntry &entry) const;
-  virtual PT(CollisionEntry)
-  test_intersection_from_line(const CollisionEntry &entry) const;
-  virtual PT(CollisionEntry)
-  test_intersection_from_ray(const CollisionEntry &entry) const;
-  virtual PT(CollisionEntry)
-  test_intersection_from_segment(const CollisionEntry &entry) const;
-  virtual PT(CollisionEntry)
-  test_intersection_from_tube(const CollisionEntry &entry) const;
-  virtual PT(CollisionEntry)
-  test_intersection_from_parabola(const CollisionEntry &entry) const;
-
-  virtual void fill_viz_geom();
-
-private:
-  void recalc_internals();
-
-  LVertex calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices);
-  LVertex calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices,
-                              PN_stdfloat length);
-
-  static void calc_closest_segment_points(double &t1, double &t2,
-                                          const LPoint3 &from1, const LVector3 &delta1,
-                                          const LPoint3 &from2, const LVector3 &delta2);
-  bool intersects_line(double &t1, double &t2,
-                       const LPoint3 &from, const LVector3 &delta,
-                       PN_stdfloat inflate_radius) const;
-  static bool sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y,
-                                     const LPoint3 &from, const LVector3 &delta,
-                                     PN_stdfloat radius);
-  bool intersects_parabola(double &t, const LParabola &parabola,
-                           double t1, double t2,
-                           const LPoint3 &p1, const LPoint3 &p2) const;
-  void calculate_surface_point_and_normal(const LPoint3 &surface_point,
-                                          double extra_radius,
-                                          LPoint3 &result_point,
-                                          LVector3 &result_normal) const;
-  void set_intersection_point(CollisionEntry *new_entry,
-                              const LPoint3 &into_intersection_point,
-                              double extra_radius) const;
-
-private:
-  LPoint3 _a, _b;
-  PN_stdfloat _radius;
-
-  // These are derived from the above.
-  LMatrix4 _mat;
-  LMatrix4 _inv_mat;
-  PN_stdfloat _length;
-
-  static PStatCollector _volume_pcollector;
-  static PStatCollector _test_pcollector;
-
-public:
-  static void register_with_read_factory();
-  virtual void write_datagram(BamWriter *manager, Datagram &dg);
-
-protected:
-  static TypedWritable *make_from_bam(const FactoryParams &params);
-  void fillin(DatagramIterator &scan, BamReader *manager);
-
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    CollisionSolid::init_type();
-    register_type(_type_handle, "CollisionTube",
-                  CollisionSolid::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-
-  friend class CollisionBox;
-};
-
-#include "collisionTube.I"
+typedef CollisionCapsule CollisionTube;
+END_PUBLISH
 
 #endif

+ 7 - 3
panda/src/collide/config_collide.cxx

@@ -13,6 +13,7 @@
 
 #include "config_collide.h"
 #include "collisionBox.h"
+#include "collisionCapsule.h"
 #include "collisionEntry.h"
 #include "collisionHandler.h"
 #include "collisionHandlerEvent.h"
@@ -38,7 +39,6 @@
 #include "collisionSolid.h"
 #include "collisionSphere.h"
 #include "collisionTraverser.h"
-#include "collisionTube.h"
 #include "collisionVisualizer.h"
 #include "dconfig.h"
 
@@ -126,6 +126,7 @@ init_libcollide() {
   initialized = true;
 
   CollisionBox::init_type();
+  CollisionCapsule::init_type();
   CollisionEntry::init_type();
   CollisionHandler::init_type();
   CollisionHandlerEvent::init_type();
@@ -150,14 +151,18 @@ init_libcollide() {
   CollisionSolid::init_type();
   CollisionSphere::init_type();
   CollisionTraverser::init_type();
-  CollisionTube::init_type();
 
 #ifdef DO_COLLISION_RECORDING
   CollisionRecorder::init_type();
   CollisionVisualizer::init_type();
 #endif
 
+  // Record the old name for CollisionCapsule for backwards compatibility.
+  BamWriter::record_obsolete_type_name(CollisionCapsule::get_class_type(),
+                                       "CollisionTube", 6, 44);
+
   CollisionBox::register_with_read_factory();
+  CollisionCapsule::register_with_read_factory();
   CollisionInvSphere::register_with_read_factory();
   CollisionLine::register_with_read_factory();
   CollisionNode::register_with_read_factory();
@@ -168,5 +173,4 @@ init_libcollide() {
   CollisionRay::register_with_read_factory();
   CollisionSegment::register_with_read_factory();
   CollisionSphere::register_with_read_factory();
-  CollisionTube::register_with_read_factory();
 }

+ 1 - 2
panda/src/collide/p3collide_composite1.cxx

@@ -1,5 +1,6 @@
 #include "config_collide.cxx"
 #include "collisionBox.cxx"
+#include "collisionCapsule.cxx"
 #include "collisionEntry.cxx"
 #include "collisionGeom.cxx"
 #include "collisionHandler.cxx"
@@ -12,5 +13,3 @@
 #include "collisionHandlerFluidPusher.cxx"
 #include "collisionHandlerQueue.cxx"
 #include "collisionInvSphere.cxx"
-#include "collisionLevelStateBase.cxx"
-#include "collisionLevelState.cxx"

+ 2 - 1
panda/src/collide/p3collide_composite2.cxx

@@ -1,3 +1,5 @@
+#include "collisionLevelStateBase.cxx"
+#include "collisionLevelState.cxx"
 #include "collisionLine.cxx"
 #include "collisionNode.cxx"
 #include "collisionParabola.cxx"
@@ -10,5 +12,4 @@
 #include "collisionSolid.cxx"
 #include "collisionSphere.cxx"
 #include "collisionTraverser.cxx"
-#include "collisionTube.cxx"
 #include "collisionVisualizer.cxx"

+ 4 - 4
panda/src/display/graphicsEngine.cxx

@@ -110,8 +110,8 @@ PStatCollector GraphicsEngine::_volume_sphere_pcollector("Collision Volumes:Coll
 PStatCollector GraphicsEngine::_test_sphere_pcollector("Collision Tests:CollisionSphere");
 PStatCollector GraphicsEngine::_volume_box_pcollector("Collision Volumes:CollisionBox");
 PStatCollector GraphicsEngine::_test_box_pcollector("Collision Tests:CollisionBox");
-PStatCollector GraphicsEngine::_volume_tube_pcollector("Collision Volumes:CollisionTube");
-PStatCollector GraphicsEngine::_test_tube_pcollector("Collision Tests:CollisionTube");
+PStatCollector GraphicsEngine::_volume_capsule_pcollector("Collision Volumes:CollisionCapsule");
+PStatCollector GraphicsEngine::_test_capsule_pcollector("Collision Tests:CollisionCapsule");
 PStatCollector GraphicsEngine::_volume_inv_sphere_pcollector("Collision Volumes:CollisionInvSphere");
 PStatCollector GraphicsEngine::_test_inv_sphere_pcollector("Collision Tests:CollisionInvSphere");
 PStatCollector GraphicsEngine::_volume_geom_pcollector("Collision Volumes:CollisionGeom");
@@ -875,8 +875,8 @@ render_frame() {
     _test_sphere_pcollector.clear_level();
     _volume_box_pcollector.clear_level();
     _test_box_pcollector.clear_level();
-    _volume_tube_pcollector.clear_level();
-    _test_tube_pcollector.clear_level();
+    _volume_capsule_pcollector.clear_level();
+    _test_capsule_pcollector.clear_level();
     _volume_inv_sphere_pcollector.clear_level();
     _test_inv_sphere_pcollector.clear_level();
     _volume_geom_pcollector.clear_level();

+ 2 - 2
panda/src/display/graphicsEngine.h

@@ -401,8 +401,8 @@ private:
   static PStatCollector _test_sphere_pcollector;
   static PStatCollector _volume_box_pcollector;
   static PStatCollector _test_box_pcollector;
-  static PStatCollector _volume_tube_pcollector;
-  static PStatCollector _test_tube_pcollector;
+  static PStatCollector _volume_capsule_pcollector;
+  static PStatCollector _test_capsule_pcollector;
   static PStatCollector _volume_inv_sphere_pcollector;
   static PStatCollector _test_inv_sphere_pcollector;
   static PStatCollector _volume_geom_pcollector;

+ 1 - 1
panda/src/doc/collisionFlags.txt

@@ -6,7 +6,7 @@ camera-collide: for things that the camera should avoid
 trigger: for things (usually not barriers or floors) that should trigger an
          event when avatars intersect with them
 sphere: for things that should have a collision sphere around them
-tube: for things that should have a collision tube (cylinder) around them
+tube: for things that should have a collision capsule around them
 
 NOTES
 

+ 20 - 27
panda/src/dxml/config_dxml.cxx

@@ -30,14 +30,12 @@ ConfigureFn(config_dxml) {
   init_libdxml();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: init_libdxml
-//  Description: Initializes the library.  This must be called at
-//               least once before any of the functions or classes in
-//               this library can be used.  Normally it will be
-//               called by the static initializers and need not be
-//               called explicitly, but special cases exist.
-////////////////////////////////////////////////////////////////////
+/**
+ * Initializes the library.  This must be called at least once before any of
+ * the functions or classes in this library can be used.  Normally it will be
+ * called by the static initializers and need not be called explicitly, but
+ * special cases exist.
+ */
 void
 init_libdxml() {
   static bool initialized = false;
@@ -48,11 +46,10 @@ init_libdxml() {
 }
 
 BEGIN_PUBLISH
-////////////////////////////////////////////////////////////////////
-//     Function: read_xml_stream
-//  Description: Reads an XML document from the indicated stream.
-//               Returns the document, or NULL on error.
-////////////////////////////////////////////////////////////////////
+/**
+ * Reads an XML document from the indicated stream.
+ * @returns the document, or NULL on error.
+ */
 TiXmlDocument *
 read_xml_stream(std::istream &in) {
   TiXmlDocument *doc = new TiXmlDocument;
@@ -67,10 +64,9 @@ read_xml_stream(std::istream &in) {
 END_PUBLISH
 
 BEGIN_PUBLISH
-////////////////////////////////////////////////////////////////////
-//     Function: write_xml_stream
-//  Description: Writes an XML document to the indicated stream.
-////////////////////////////////////////////////////////////////////
+/**
+ * Writes an XML document to the indicated stream.
+ */
 void
 write_xml_stream(std::ostream &out, TiXmlDocument *doc) {
   out << *doc;
@@ -78,10 +74,9 @@ write_xml_stream(std::ostream &out, TiXmlDocument *doc) {
 END_PUBLISH
 
 BEGIN_PUBLISH
-////////////////////////////////////////////////////////////////////
-//     Function: print_xml
-//  Description: Writes an XML object to stdout, with formatting.
-////////////////////////////////////////////////////////////////////
+/**
+ * Writes an XML object to stdout, with formatting.
+ */
 void
 print_xml(TiXmlNode *xnode) {
   xnode->Print(stdout, 0);
@@ -89,12 +84,10 @@ print_xml(TiXmlNode *xnode) {
 END_PUBLISH
 
 BEGIN_PUBLISH
-////////////////////////////////////////////////////////////////////
-//     Function: print_xml_to_file
-//  Description: Writes an XML object to the indicated file, with
-//               formatting.  Unfortunately the VFS cannot be
-//               supported; the file must be a real filename on disk.
-////////////////////////////////////////////////////////////////////
+/**
+ * Writes an XML object to the indicated file, with formatting.  Unfortunately
+ * the VFS cannot be supported; the file must be a real filename on disk.
+ */
 void
 print_xml_to_file(const Filename &filename, TiXmlNode *xnode) {
   std::string os_name = filename.to_os_specific();

+ 2 - 1
panda/src/egg/eggGroup.cxx

@@ -897,7 +897,8 @@ string_cs_type(const string &strval) {
   } else if (cmp_nocase_uh(strval, "inv-sphere") == 0 ||
              cmp_nocase_uh(strval, "invsphere") == 0) {
     return CST_inv_sphere;
-  } else if (cmp_nocase_uh(strval, "tube") == 0) {
+  } else if (cmp_nocase_uh(strval, "tube") == 0 ||
+             cmp_nocase_uh(strval, "capsule") == 0) {
     return CST_tube;
   } else if (cmp_nocase_uh(strval, "floor-mesh") == 0 ||
              cmp_nocase_uh(strval, "floormesh") == 0) {

+ 10 - 10
panda/src/egg2pg/eggLoader.cxx

@@ -74,7 +74,7 @@
 #include "collisionNode.h"
 #include "collisionSphere.h"
 #include "collisionInvSphere.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "collisionPlane.h"
 #include "collisionPolygon.h"
 #include "collisionFloorMesh.h"
@@ -2865,7 +2865,7 @@ make_collision_solids(EggGroup *start_group, EggGroup *egg_group,
     break;
 
   case EggGroup::CST_tube:
-    make_collision_tube(egg_group, cnode, start_group->get_collide_flags());
+    make_collision_capsule(egg_group, cnode, start_group->get_collide_flags());
     break;
 
   case EggGroup::CST_floor_mesh:
@@ -3045,12 +3045,12 @@ make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode,
 }
 
 /**
- * Creates a single CollisionTube corresponding to the polygons associated
+ * Creates a single CollisionCapsule corresponding to the polygons associated
  * with this group.
  */
 void EggLoader::
-make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
-                    EggGroup::CollideFlags flags) {
+make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode,
+                       EggGroup::CollideFlags flags) {
   EggGroup *geom_group = find_collision_geometry(egg_group, flags);
   if (geom_group != nullptr) {
     // Collect all of the vertices.
@@ -3175,7 +3175,7 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
 
         // Transform all of the points so that the major axis is along the Y
         // axis, and the origin is the center.  This is very similar to the
-        // CollisionTube's idea of its canonical orientation (although not
+        // CollisionCapsule's idea of its canonical orientation (although not
         // exactly the same, since it is centered on the origin instead of
         // having point_a on the origin).  It makes it easier to determine the
         // length and radius of the cylinder.
@@ -3230,11 +3230,11 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
         LPoint3d point_a = center - half;
         LPoint3d point_b = center + half;
 
-        CollisionTube *cstube =
-          new CollisionTube(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b),
+        CollisionCapsule *cscapsule =
+          new CollisionCapsule(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b),
                             radius);
-        apply_collision_flags(cstube, flags);
-        cnode->add_solid(cstube);
+        apply_collision_flags(cscapsule, flags);
+        cnode->add_solid(cscapsule);
       }
     }
   }

+ 2 - 2
panda/src/egg2pg/eggLoader.h

@@ -180,8 +180,8 @@ private:
                           EggGroup::CollideFlags flags);
   void make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode,
                                  EggGroup::CollideFlags flags);
-  void make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
-                           EggGroup::CollideFlags flags);
+  void make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode,
+                              EggGroup::CollideFlags flags);
   void make_collision_floor_mesh(EggGroup *egg_group, CollisionNode *cnode,
                            EggGroup::CollideFlags flags);
   void apply_collision_flags(CollisionSolid *solid,

+ 15 - 15
panda/src/egg2pg/eggSaver.cxx

@@ -39,7 +39,7 @@
 #include "collisionSphere.h"
 #include "collisionBox.h"
 #include "collisionInvSphere.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 #include "textureStage.h"
 #include "geomNode.h"
 #include "geom.h"
@@ -620,13 +620,13 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
         egg_poly->add_vertex(cvpool->create_unique_vertex(ev0));
         egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));
 
-      } else if (child->is_of_type(CollisionTube::get_class_type())) {
-        CPT(CollisionTube) tube = DCAST(CollisionTube, child);
-        LPoint3 point_a = tube->get_point_a();
-        LPoint3 point_b = tube->get_point_b();
+      } else if (child->is_of_type(CollisionCapsule::get_class_type())) {
+        CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, child);
+        LPoint3 point_a = capsule->get_point_a();
+        LPoint3 point_b = capsule->get_point_b();
         LPoint3 centroid = (point_a + point_b) * 0.5f;
 
-        // Also get an arbitrary vector perpendicular to the tube.
+        // Also get an arbitrary vector perpendicular to the capsule.
         LVector3 axis = point_b - point_a;
         LVector3 sideways;
         if (std::fabs(axis[2]) > std::fabs(axis[1])) {
@@ -635,18 +635,18 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
           sideways = axis.cross(LVector3(0, 0, 1));
         }
         sideways.normalize();
-        sideways *= tube->get_radius();
-        LVector3 extend = axis.normalized() * tube->get_radius();
+        sideways *= capsule->get_radius();
+        LVector3 extend = axis.normalized() * capsule->get_radius();
 
-        EggGroup *egg_tube;
+        EggGroup *egg_capsule;
         if (num_solids == 1) {
-          egg_tube = egg_group;
+          egg_capsule = egg_group;
         } else {
-          egg_tube = new EggGroup;
-          egg_group->add_child(egg_tube);
+          egg_capsule = new EggGroup;
+          egg_group->add_child(egg_capsule);
         }
-        egg_tube->set_cs_type(EggGroup::CST_tube);
-        egg_tube->set_collide_flags(flags);
+        egg_capsule->set_cs_type(EggGroup::CST_tube);
+        egg_capsule->set_collide_flags(flags);
 
         // Add two points for the endcaps, and then two points around the
         // centroid to indicate the radius.
@@ -657,7 +657,7 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
         ev3.set_pos(LCAST(double, (centroid - sideways) * net_mat));
 
         EggPolygon *egg_poly = new EggPolygon;
-        egg_tube->add_child(egg_poly);
+        egg_capsule->add_child(egg_poly);
 
         egg_poly->add_vertex(cvpool->create_unique_vertex(ev0));
         egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));

+ 2 - 2
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -79,8 +79,6 @@ init_from(FfmpegVideo *source) {
     return;
   }
 
-  ReMutexHolder av_holder(_av_lock);
-
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
   _frame = av_frame_alloc();
   _frame_out = av_frame_alloc();
@@ -109,6 +107,8 @@ init_from(FfmpegVideo *source) {
   _eof_known = false;
   _eof_frame = 0;
 
+  ReMutexHolder av_holder(_av_lock);
+
   // Check if we got an alpha format.  Please note that some video codecs
   // (eg. libvpx) change the pix_fmt after decoding the first frame, which is
   // why we didn't do this earlier.

+ 3 - 2
panda/src/glstuff/glBufferContext_src.I

@@ -16,8 +16,9 @@
  */
 INLINE CLP(BufferContext)::
 CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg,
-                   PreparedGraphicsObjects *pgo) :
-  BufferContext(&pgo->_sbuffer_residency),
+                   PreparedGraphicsObjects *pgo,
+                   TypedWritableReferenceCount *object) :
+  BufferContext(&pgo->_sbuffer_residency, object),
   AdaptiveLruPage(0),
   _glgsg(glgsg)
 {

+ 2 - 1
panda/src/glstuff/glBufferContext_src.h

@@ -21,7 +21,8 @@
 class EXPCL_GL CLP(BufferContext) : public BufferContext, public AdaptiveLruPage {
 public:
   INLINE CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg,
-                            PreparedGraphicsObjects *pgo);
+                            PreparedGraphicsObjects *pgo,
+                            TypedWritableReferenceCount *object);
   ALLOC_DELETED_CHAIN(CLP(BufferContext));
 
   virtual void evict_lru();

+ 1 - 1
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -1413,7 +1413,7 @@ open_buffer() {
   }
 
   if (_rb_context == nullptr) {
-    _rb_context = new BufferContext(&(glgsg->_renderbuffer_residency));
+    _rb_context = new BufferContext(&(glgsg->_renderbuffer_residency), nullptr);
   }
 
 /*

+ 16 - 2
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -6519,7 +6519,7 @@ prepare_shader_buffer(ShaderBuffer *data) {
   if (_supports_shader_buffers) {
     PStatGPUTimer timer(this, _prepare_shader_buffer_pcollector);
 
-    CLP(BufferContext) *gbc = new CLP(BufferContext)(this, _prepared_objects);
+    CLP(BufferContext) *gbc = new CLP(BufferContext)(this, _prepared_objects, data);
     _glGenBuffers(1, &gbc->_index);
 
     if (GLCAT.is_debug() && gl_debug_buffers) {
@@ -7049,6 +7049,16 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
       } else {
         format = Texture::F_srgb;
       }
+    } else if (_current_properties->get_float_color()) {
+      if (_current_properties->get_alpha_bits()) {
+        format = Texture::F_rgba32;
+      } else if (_current_properties->get_blue_bits()) {
+        format = Texture::F_rgb32;
+      } else if (_current_properties->get_green_bits()) {
+        format = Texture::F_rg32;
+      } else {
+        format = Texture::F_r32;
+      }
     } else {
       if (_current_properties->get_alpha_bits()) {
         format = Texture::F_rgba;
@@ -7058,7 +7068,11 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
     }
     if (_current_properties->get_float_color()) {
       component_type = Texture::T_float;
-    } else if (_current_properties->get_color_bits() <= 24) {
+    } else if (_current_properties->get_color_bits() <= 24
+            && _current_properties->get_red_bits() <= 8
+            && _current_properties->get_green_bits() <= 8
+            && _current_properties->get_blue_bits() <= 8
+            && _current_properties->get_alpha_bits() <= 8) {
       component_type = Texture::T_unsigned_byte;
     } else {
       component_type = Texture::T_unsigned_short;

+ 12 - 2
panda/src/glstuff/glShaderContext_src.cxx

@@ -2797,14 +2797,24 @@ update_shader_texture_bindings(ShaderContext *prev) {
     }
 
     if (tex->get_texture_type() != spec._desired_type) {
-      if (id != nullptr) {
+      switch (spec._part) {
+      case Shader::STO_named_input:
         GLCAT.error()
           << "Sampler type of GLSL shader input '" << *id << "' does not "
              "match type of texture " << *tex << ".\n";
-      } else {
+        break;
+
+      case Shader::STO_stage_i:
         GLCAT.error()
           << "Sampler type of GLSL shader input p3d_Texture" << spec._stage
           << " does not match type of texture " << *tex << ".\n";
+        break;
+
+      case Shader::STO_light_i_shadow_map:
+        GLCAT.error()
+          << "Sampler type of GLSL shader input p3d_LightSource[" << spec._stage
+          << "].shadowMap does not match type of texture " << *tex << ".\n";
+        break;
       }
       // TODO: also check whether shadow sampler textures have shadow filter
       // enabled.

+ 8 - 0
panda/src/gobj/bufferContext.I

@@ -11,6 +11,14 @@
  * @date 2006-03-16
  */
 
+/**
+ * Returns the associated object.
+ */
+INLINE TypedWritableReferenceCount *BufferContext::
+get_object() const {
+  return _object;
+}
+
 /**
  * Returns the number of bytes previously reported for the data object.  This
  * is used to track changes in the data object's allocated size; if it changes

+ 2 - 1
panda/src/gobj/bufferContext.cxx

@@ -19,7 +19,8 @@ TypeHandle BufferContext::_type_handle;
  *
  */
 BufferContext::
-BufferContext(BufferResidencyTracker *residency) :
+BufferContext(BufferResidencyTracker *residency, TypedWritableReferenceCount *object) :
+  _object(object),
   _residency(residency),
   _residency_state(0),
   _data_size_bytes(0),

+ 11 - 1
panda/src/gobj/bufferContext.h

@@ -23,6 +23,7 @@
 #include "bufferResidencyTracker.h"
 
 class PreparedGraphicsObjects;
+class TypedWritableReferenceCount;
 
 /**
  * This is a base class for those kinds of SavedContexts that occupy an
@@ -36,15 +37,19 @@ class PreparedGraphicsObjects;
  */
 class EXPCL_PANDA_GOBJ BufferContext : public SavedContext, private LinkedListNode {
 public:
-  BufferContext(BufferResidencyTracker *residency);
+  BufferContext(BufferResidencyTracker *residency, TypedWritableReferenceCount *object);
   virtual ~BufferContext();
 
+  INLINE TypedWritableReferenceCount *get_object() const;
+
 PUBLISHED:
   INLINE size_t get_data_size_bytes() const;
   INLINE UpdateSeq get_modified() const;
   INLINE bool get_active() const;
   INLINE bool get_resident() const;
 
+  MAKE_PROPERTY(object, get_object);
+
   MAKE_PROPERTY(data_size_bytes, get_data_size_bytes);
   MAKE_PROPERTY(modified, get_modified);
   MAKE_PROPERTY(active, get_active);
@@ -62,6 +67,11 @@ public:
 private:
   void set_owning_chain(BufferContextChain *chain);
 
+protected:
+  // This cannot be a PT(), because the object and the GSG both own their
+  // BufferContexts!  That would create a circular reference count.
+  TypedWritableReferenceCount *_object;
+
 private:
   BufferResidencyTracker *_residency;
   int _residency_state;

+ 7 - 8
panda/src/gobj/indexBufferContext.I

@@ -16,9 +16,8 @@
  */
 INLINE IndexBufferContext::
 IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data) :
-  BufferContext(&pgo->_ibuffer_residency),
-  AdaptiveLruPage(0),
-  _data(data)
+  BufferContext(&pgo->_ibuffer_residency, data),
+  AdaptiveLruPage(0)
 {
 }
 
@@ -27,7 +26,7 @@ IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data) :
  */
 INLINE GeomPrimitive *IndexBufferContext::
 get_data() const {
-  return _data;
+  return (GeomPrimitive *)_object;
 }
 
 /**
@@ -36,7 +35,7 @@ get_data() const {
  */
 INLINE bool IndexBufferContext::
 changed_size(const GeomPrimitivePipelineReader *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return get_data_size_bytes() != (size_t)reader->get_data_size_bytes();
 }
 
@@ -46,7 +45,7 @@ changed_size(const GeomPrimitivePipelineReader *reader) const {
  */
 INLINE bool IndexBufferContext::
 changed_usage_hint(const GeomPrimitivePipelineReader *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return _usage_hint != reader->get_usage_hint();
 }
 
@@ -56,7 +55,7 @@ changed_usage_hint(const GeomPrimitivePipelineReader *reader) const {
  */
 INLINE bool IndexBufferContext::
 was_modified(const GeomPrimitivePipelineReader *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return get_modified() != reader->get_modified();
 }
 
@@ -76,7 +75,7 @@ update_data_size_bytes(size_t new_data_size_bytes) {
  */
 INLINE void IndexBufferContext::
 mark_loaded(const GeomPrimitivePipelineReader *reader) {
-  nassertv(reader->get_object() == _data);
+  nassertv(reader->get_object() == get_data());
   update_data_size_bytes(reader->get_data_size_bytes());
   update_modified(reader->get_modified());
   _usage_hint = reader->get_usage_hint();

+ 0 - 3
panda/src/gobj/indexBufferContext.h

@@ -50,9 +50,6 @@ public:
   virtual void write(std::ostream &out, int indent_level) const;
 
 private:
-  // This cannot be a PT(GeomPrimitive), because the data and the GSG both own
-  // their IndexBufferContexts!  That would create a circular reference count.
-  GeomPrimitive *_data;
   GeomEnums::UsageHint _usage_hint;
 
 public:

+ 25 - 18
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -270,11 +270,11 @@ void PreparedGraphicsObjects::
 release_texture(TextureContext *tc) {
   ReMutexHolder holder(_lock);
 
-  tc->_texture->clear_prepared(tc->get_view(), this);
+  tc->get_texture()->clear_prepared(tc->get_view(), this);
 
   // We have to set the Texture pointer to NULL at this point, since the
   // Texture itself might destruct at any time after it has been released.
-  tc->_texture = nullptr;
+  tc->_object = nullptr;
 
   bool removed = (_prepared_textures.erase(tc) != 0);
   nassertv(removed);
@@ -307,8 +307,8 @@ release_all_textures() {
        tci != _prepared_textures.end();
        ++tci) {
     TextureContext *tc = (*tci);
-    tc->_texture->clear_prepared(tc->get_view(), this);
-    tc->_texture = nullptr;
+    tc->get_texture()->clear_prepared(tc->get_view(), this);
+    tc->_object = nullptr;
 
     _released_textures.insert(tc);
   }
@@ -946,14 +946,14 @@ void PreparedGraphicsObjects::
 release_vertex_buffer(VertexBufferContext *vbc) {
   ReMutexHolder holder(_lock);
 
-  vbc->_data->clear_prepared(this);
+  vbc->get_data()->clear_prepared(this);
 
-  size_t data_size_bytes = vbc->_data->get_data_size_bytes();
-  GeomEnums::UsageHint usage_hint = vbc->_data->get_usage_hint();
+  size_t data_size_bytes = vbc->get_data()->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = vbc->get_data()->get_usage_hint();
 
   // We have to set the Data pointer to NULL at this point, since the Data
   // itself might destruct at any time after it has been released.
-  vbc->_data = nullptr;
+  vbc->_object = nullptr;
 
   bool removed = (_prepared_vertex_buffers.erase(vbc) != 0);
   nassertv(removed);
@@ -985,8 +985,8 @@ release_all_vertex_buffers() {
        vbci != _prepared_vertex_buffers.end();
        ++vbci) {
     VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
-    vbc->_data->clear_prepared(this);
-    vbc->_data = nullptr;
+    vbc->get_data()->clear_prepared(this);
+    vbc->_object = nullptr;
 
     _released_vertex_buffers.insert(vbc);
   }
@@ -1061,7 +1061,7 @@ prepare_vertex_buffer_now(GeomVertexArrayData *data, GraphicsStateGuardianBase *
                       _vertex_buffer_cache, _vertex_buffer_cache_lru,
                       _vertex_buffer_cache_size);
   if (vbc != nullptr) {
-    vbc->_data = data;
+    vbc->_object = data;
 
   } else {
     // Ask the GSG to create a brand new VertexBufferContext.  There might be
@@ -1144,14 +1144,14 @@ void PreparedGraphicsObjects::
 release_index_buffer(IndexBufferContext *ibc) {
   ReMutexHolder holder(_lock);
 
-  ibc->_data->clear_prepared(this);
+  ibc->get_data()->clear_prepared(this);
 
-  size_t data_size_bytes = ibc->_data->get_data_size_bytes();
-  GeomEnums::UsageHint usage_hint = ibc->_data->get_usage_hint();
+  size_t data_size_bytes = ibc->get_data()->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = ibc->get_data()->get_usage_hint();
 
   // We have to set the Data pointer to NULL at this point, since the Data
   // itself might destruct at any time after it has been released.
-  ibc->_data = nullptr;
+  ibc->_object = nullptr;
 
   bool removed = (_prepared_index_buffers.erase(ibc) != 0);
   nassertv(removed);
@@ -1183,8 +1183,8 @@ release_all_index_buffers() {
        ibci != _prepared_index_buffers.end();
        ++ibci) {
     IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
-    ibc->_data->clear_prepared(this);
-    ibc->_data = nullptr;
+    ibc->get_data()->clear_prepared(this);
+    ibc->_object = nullptr;
 
     _released_index_buffers.insert(ibc);
   }
@@ -1258,7 +1258,7 @@ prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) {
                       _index_buffer_cache, _index_buffer_cache_lru,
                       _index_buffer_cache_size);
   if (ibc != nullptr) {
-    ibc->_data = data;
+    ibc->_object = data;
 
   } else {
     // Ask the GSG to create a brand new IndexBufferContext.  There might be
@@ -1341,6 +1341,13 @@ void PreparedGraphicsObjects::
 release_shader_buffer(BufferContext *bc) {
   ReMutexHolder holder(_lock);
 
+  ShaderBuffer *buffer = (ShaderBuffer *)bc->_object;
+  buffer->clear_prepared(this);
+
+  // We have to set the ShaderBuffer pointer to NULL at this point, since the
+  // buffer itself might destruct at any time after it has been released.
+  bc->_object = nullptr;
+
   bool removed = (_prepared_shader_buffers.erase(bc) != 0);
   nassertv(removed);
 

+ 25 - 0
panda/src/gobj/shaderBuffer.cxx

@@ -143,6 +143,31 @@ release_all() {
   return num_freed;
 }
 
+/**
+ * Removes the indicated PreparedGraphicsObjects table from the buffer's
+ * table, without actually releasing the texture.  This is intended to be
+ * called only from PreparedGraphicsObjects::release_shader_buffer(); it
+ * should never be called by user code.
+ */
+void ShaderBuffer::
+clear_prepared(PreparedGraphicsObjects *prepared_objects) {
+  nassertv(_contexts != nullptr);
+
+  Contexts::iterator ci;
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
+    _contexts->erase(ci);
+    if (_contexts->empty()) {
+      delete _contexts;
+      _contexts = nullptr;
+    }
+  } else {
+    // If this assertion fails, clear_prepared() was given a prepared_objects
+    // which the data array didn't know about.
+    nassert_raise("unknown PreparedGraphicsObjects");
+  }
+}
+
 /**
  * Tells the BamReader how to create objects of type ParamValue.
  */

+ 5 - 0
panda/src/gobj/shaderBuffer.h

@@ -57,6 +57,9 @@ PUBLISHED:
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();
 
+private:
+  void clear_prepared(PreparedGraphicsObjects *prepared_objects);
+
 private:
   uint64_t _data_size_bytes;
   UsageHint _usage_hint;
@@ -91,6 +94,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class PreparedGraphicsObjects;
 };
 
 INLINE std::ostream &operator << (std::ostream &out, const ShaderBuffer &m) {

+ 9 - 10
panda/src/gobj/textureContext.I

@@ -16,9 +16,8 @@
  */
 INLINE TextureContext::
 TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
-  BufferContext(&pgo->_texture_residency),
+  BufferContext(&pgo->_texture_residency, tex),
   AdaptiveLruPage(0),
-  _texture(tex),
   _view(view)
 {
 }
@@ -28,7 +27,7 @@ TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
  */
 INLINE Texture *TextureContext::
 get_texture() const {
-  return _texture;
+  return (Texture *)_object;
 }
 
 /**
@@ -56,7 +55,7 @@ was_modified() const {
  */
 INLINE bool TextureContext::
 was_properties_modified() const {
-  return _properties_modified != _texture->get_properties_modified();
+  return _properties_modified != get_texture()->get_properties_modified();
 }
 
 /**
@@ -65,7 +64,7 @@ was_properties_modified() const {
  */
 INLINE bool TextureContext::
 was_image_modified() const {
-  return _image_modified != _texture->get_image_modified();
+  return _image_modified != get_texture()->get_image_modified();
 }
 
 /**
@@ -74,7 +73,7 @@ was_image_modified() const {
  */
 INLINE bool TextureContext::
 was_simple_image_modified() const {
-  return _simple_image_modified != _texture->get_simple_image_modified();
+  return _simple_image_modified != get_texture()->get_simple_image_modified();
 }
 
 /**
@@ -121,8 +120,8 @@ update_data_size_bytes(size_t new_data_size_bytes) {
 INLINE void TextureContext::
 mark_loaded() {
   // _data_size_bytes = _data->get_texture_size_bytes();
-  _properties_modified = _texture->get_properties_modified();
-  _image_modified = _texture->get_image_modified();
+  _properties_modified = get_texture()->get_properties_modified();
+  _image_modified = get_texture()->get_image_modified();
   update_modified(std::max(_properties_modified, _image_modified));
 
   // Assume the texture is now resident.
@@ -135,8 +134,8 @@ mark_loaded() {
  */
 INLINE void TextureContext::
 mark_simple_loaded() {
-  _properties_modified = _texture->get_properties_modified();
-  _simple_image_modified = _texture->get_simple_image_modified();
+  _properties_modified = get_texture()->get_properties_modified();
+  _simple_image_modified = get_texture()->get_simple_image_modified();
   update_modified(std::max(_properties_modified, _simple_image_modified));
 
   // The texture's not exactly resident now, but some part of it is.

+ 0 - 3
panda/src/gobj/textureContext.h

@@ -60,9 +60,6 @@ public:
   virtual void write(std::ostream &out, int indent_level) const;
 
 private:
-  // This cannot be a PT(Texture), because the texture and the GSG both own
-  // their TextureContexts!  That would create a circular reference count.
-  Texture *_texture;
   int _view;
   UpdateSeq _properties_modified;
   UpdateSeq _image_modified;

+ 19 - 0
panda/src/gobj/texturePeeker.cxx

@@ -140,6 +140,12 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     _get_texel = get_texel_la;
     break;
 
+  case Texture::F_rg16:
+  case Texture::F_rg32:
+  case Texture::F_rg:
+    _get_texel = get_texel_rg;
+    break;
+
   case Texture::F_rgb:
   case Texture::F_rgb5:
   case Texture::F_rgb8:
@@ -148,6 +154,7 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
   case Texture::F_rgb332:
   case Texture::F_r11_g11_b10:
   case Texture::F_rgb9_e5:
+  case Texture::F_rgb32:
     _get_texel = get_texel_rgb;
     break;
 
@@ -582,6 +589,18 @@ get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_compo
   color[3] = (*get_component)(p);
 }
 
+/**
+ * Gets the color of the texel at byte p, given that the texture is in format
+ * F_rg or similar.
+ */
+void TexturePeeker::
+get_texel_rg(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
+  color[0] = (*get_component)(p);
+  color[1] = (*get_component)(p);
+  color[2] = 0.0f;
+  color[3] = 1.0f;
+}
+
 /**
  * Gets the color of the texel at byte p, given that the texture is in format
  * F_rgb or similar.

+ 1 - 0
panda/src/gobj/texturePeeker.h

@@ -77,6 +77,7 @@ private:
   static void get_texel_a(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_l(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
+  static void get_texel_rg(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);
   static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component);

+ 7 - 8
panda/src/gobj/vertexBufferContext.I

@@ -16,9 +16,8 @@
  */
 INLINE VertexBufferContext::
 VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data) :
-  BufferContext(&pgo->_vbuffer_residency),
-  AdaptiveLruPage(0),
-  _data(data)
+  BufferContext(&pgo->_vbuffer_residency, data),
+  AdaptiveLruPage(0)
 {
 }
 
@@ -27,7 +26,7 @@ VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data) :
  */
 INLINE GeomVertexArrayData *VertexBufferContext::
 get_data() const {
-  return _data;
+  return (GeomVertexArrayData *)_object;
 }
 
 /**
@@ -36,7 +35,7 @@ get_data() const {
  */
 INLINE bool VertexBufferContext::
 changed_size(const GeomVertexArrayDataHandle *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return get_data_size_bytes() != (size_t)reader->get_data_size_bytes();
 }
 
@@ -46,7 +45,7 @@ changed_size(const GeomVertexArrayDataHandle *reader) const {
  */
 INLINE bool VertexBufferContext::
 changed_usage_hint(const GeomVertexArrayDataHandle *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return _usage_hint != reader->get_usage_hint();
 }
 
@@ -56,7 +55,7 @@ changed_usage_hint(const GeomVertexArrayDataHandle *reader) const {
  */
 INLINE bool VertexBufferContext::
 was_modified(const GeomVertexArrayDataHandle *reader) const {
-  nassertr(reader->get_object() == _data, false);
+  nassertr(reader->get_object() == get_data(), false);
   return get_modified() != reader->get_modified();
 }
 
@@ -77,7 +76,7 @@ update_data_size_bytes(size_t new_data_size_bytes) {
  */
 INLINE void VertexBufferContext::
 mark_loaded(const GeomVertexArrayDataHandle *reader) {
-  nassertv(reader->get_object() == _data);
+  nassertv(reader->get_object() == get_data());
   update_data_size_bytes(reader->get_data_size_bytes());
   update_modified(reader->get_modified());
   _usage_hint = reader->get_usage_hint();

+ 0 - 4
panda/src/gobj/vertexBufferContext.h

@@ -51,10 +51,6 @@ public:
   virtual void write(std::ostream &out, int indent_level) const;
 
 private:
-  // This cannot be a PT(GeomVertexArrayData), because the data and the GSG
-  // both own their VertexBufferContexts!  That would create a circular
-  // reference count.
-  GeomVertexArrayData *_data;
   GeomEnums::UsageHint _usage_hint;
 
 public:

+ 4 - 7
panda/src/pgraph/camera.cxx

@@ -280,13 +280,10 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Camera::complete_pointers
-//       Access: Public, Virtual
-//  Description: Receives an array of pointers, one for each time
-//               manager->read_pointer() was called in fillin().
-//               Returns the number of pointers processed.
-////////////////////////////////////////////////////////////////////
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
 int Camera::
 complete_pointers(TypedWritable **p_list, BamReader *manager) {
   int pi = LensNode::complete_pointers(p_list, manager);

+ 12 - 5
panda/src/pgraph/pandaNode.cxx

@@ -1084,6 +1084,8 @@ set_effects(const RenderEffects *effects, Thread *current_thread) {
  */
 void PandaNode::
 set_transform(const TransformState *transform, Thread *current_thread) {
+  nassertv(!transform->is_invalid());
+
   // Need to have this held before we grab any other locks.
   LightMutexHolder holder(_dirty_prev_transforms._lock);
 
@@ -1120,6 +1122,8 @@ set_transform(const TransformState *transform, Thread *current_thread) {
  */
 void PandaNode::
 set_prev_transform(const TransformState *transform, Thread *current_thread) {
+  nassertv(!transform->is_invalid());
+
   // Need to have this held before we grab any other locks.
   LightMutexHolder holder(_dirty_prev_transforms._lock);
 
@@ -3510,12 +3514,12 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
             const BoundingVolume **child_begin = &child_volumes[0];
             const BoundingVolume **child_end = child_begin + child_volumes_i;
             ((BoundingVolume *)gbv)->around(child_begin, child_end);
-          }
 
-          // If we have a transform, apply it to the bounding volume we just
-          // computed.
-          if (!transform->is_identity()) {
-            gbv->xform(transform->get_mat());
+            // If we have a transform, apply it to the bounding volume we just
+            // computed.
+            if (!transform->is_identity()) {
+              gbv->xform(transform->get_mat());
+            }
           }
 
           cdataw->_external_bounds = gbv;
@@ -3849,6 +3853,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
   // Mark the bounds stale.
   ++_next_update;
 
+  nassertr(!_transform->is_invalid(), pi);
+  nassertr(!_prev_transform->is_invalid(), pi);
+
   return pi;
 }
 

Some files were not shown because too many files changed in this diff