Pārlūkot izejas kodu

Merge branch 'master' into webgl-port

rdb 4 gadi atpakaļ
vecāks
revīzija
8b7a3d4c3a
100 mainītis faili ar 1502 papildinājumiem un 905 dzēšanām
  1. 15 15
      .github/workflows/ci.yml
  2. 0 7
      .travis.yml
  3. 2 0
      BACKERS.md
  4. 1 1
      CONTRIBUTING.md
  5. 4 11
      README.md
  6. 4 0
      contrib/src/ai/aiCharacter.cxx
  7. 4 0
      contrib/src/ai/aiCharacter.h
  8. 10 2
      contrib/src/rplight/pssmCameraRig.cxx
  9. 1 11
      contrib/src/speedtree/speedTreeNode.cxx
  10. 0 1
      contrib/src/speedtree/speedTreeNode.h
  11. 5 1
      direct/src/actor/Actor.py
  12. 4 0
      direct/src/dcparser/dcPacker_ext.cxx
  13. 5 5
      direct/src/directtools/DirectSession.py
  14. 8 6
      direct/src/dist/FreezeTool.py
  15. 24 6
      direct/src/dist/commands.py
  16. 5 5
      direct/src/distributed/ClientRepositoryBase.py
  17. 2 2
      direct/src/distributed/ClockDelta.py
  18. 10 7
      direct/src/distributed/DistributedCamera.py
  19. 10 11
      direct/src/distributed/DistributedSmoothNode.py
  20. 9 2
      direct/src/distributed/DistributedSmoothNodeBase.py
  21. 4 1
      direct/src/distributed/ServerRepository.py
  22. 7 3
      direct/src/extensions_native/CInterval_extensions.py
  23. 381 352
      direct/src/extensions_native/NodePath_extensions.py
  24. 3 1
      direct/src/extensions_native/VBase3_extensions.py
  25. 3 1
      direct/src/extensions_native/VBase4_extensions.py
  26. 2 0
      direct/src/filter/FilterManager.py
  27. 5 3
      direct/src/gui/DirectGuiBase.py
  28. 1 0
      direct/src/gui/DirectOptionMenu.py
  29. 1 2
      direct/src/gui/DirectSlider.py
  30. 39 4
      direct/src/gui/OnscreenText.py
  31. 8 0
      direct/src/interval/MetaInterval.py
  32. 1 1
      direct/src/leveleditor/ObjectMgrBase.py
  33. 7 2
      direct/src/motiontrail/MotionTrail.py
  34. 2 1
      direct/src/particles/ForceGroup.py
  35. 7 6
      direct/src/particles/SpriteParticleRendererExt.py
  36. 2 1
      direct/src/showbase/AppRunnerGlobal.py
  37. 1 1
      direct/src/showbase/BufferViewer.py
  38. 9 0
      direct/src/showbase/DConfig.py
  39. 11 5
      direct/src/showbase/Loader.py
  40. 15 11
      direct/src/showbase/PythonUtil.py
  41. 62 63
      direct/src/showbase/ShowBase.py
  42. 5 3
      direct/src/showbase/ShowBaseGlobal.py
  43. 4 4
      direct/src/showutil/BuildGeometry.py
  44. 1 0
      direct/src/task/Task.py
  45. 128 0
      doc/ReleaseNotes
  46. 1 1
      doc/man/bam-info.1
  47. 0 53
      dtool/src/dtoolbase/dtoolbase_cc.h
  48. 4 0
      dtool/src/dtoolutil/dSearchPath.cxx
  49. 1 1
      dtool/src/dtoolutil/filename.I
  50. 10 10
      dtool/src/dtoolutil/filename.cxx
  51. 1 1
      dtool/src/dtoolutil/filename.h
  52. 1 1
      dtool/src/dtoolutil/pandaFileStreamBuf.cxx
  53. 14 49
      dtool/src/interrogate/functionRemap.cxx
  54. 110 43
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  55. 32 0
      dtool/src/interrogate/interrogateBuilder.cxx
  56. 1 0
      dtool/src/interrogatedb/interrogateFunction.h
  57. 1 1
      dtool/src/interrogatedb/py_wrappers.cxx
  58. 8 0
      dtool/src/prc/configVariableDouble.I
  59. 2 0
      dtool/src/prc/configVariableDouble.h
  60. 8 0
      dtool/src/prc/configVariableFilename.I
  61. 2 0
      dtool/src/prc/configVariableFilename.h
  62. 8 0
      dtool/src/prc/configVariableInt.I
  63. 2 0
      dtool/src/prc/configVariableInt.h
  64. 8 0
      dtool/src/prc/configVariableInt64.I
  65. 2 0
      dtool/src/prc/configVariableInt64.h
  66. 8 0
      dtool/src/prc/configVariableString.I
  67. 2 0
      dtool/src/prc/configVariableString.h
  68. 12 0
      makepanda/installer.nsi
  69. 35 9
      makepanda/makepanda.py
  70. 6 6
      makepanda/makepandacore.py
  71. 4 6
      makepanda/makewheel.py
  72. 6 2
      makepanda/test_wheel.py
  73. 18 1
      models/cmr12.egg
  74. 18 1
      models/cmss12.egg
  75. 18 1
      models/cmtt12.egg
  76. 10 3
      panda/metalibs/pandagl/pandagl.cxx
  77. 4 0
      panda/metalibs/pandagl/pandagl.h
  78. 6 0
      panda/metalibs/pandagles/CMakeLists.txt
  79. 6 0
      panda/metalibs/pandagles2/CMakeLists.txt
  80. 6 5
      panda/src/bullet/bulletDebugNode.I
  81. 1 11
      panda/src/bullet/bulletDebugNode.cxx
  82. 0 1
      panda/src/bullet/bulletDebugNode.h
  83. 4 4
      panda/src/bullet/bulletHeightfieldShape.cxx
  84. 10 5
      panda/src/bullet/bulletPersistentManifold.cxx
  85. 1 1
      panda/src/bullet/bulletPersistentManifold.h
  86. 4 4
      panda/src/bullet/bulletWorld.cxx
  87. 1 1
      panda/src/bullet/bulletWorld.h
  88. 5 0
      panda/src/bullet/config_bullet.cxx
  89. 1 0
      panda/src/bullet/config_bullet.h
  90. 1 4
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  91. 98 72
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  92. 126 21
      panda/src/collide/collisionLevelState.I
  93. 3 1
      panda/src/collide/collisionLevelState.h
  94. 28 3
      panda/src/collide/collisionLevelStateBase.I
  95. 5 0
      panda/src/collide/collisionLevelStateBase.h
  96. 1 1
      panda/src/collide/collisionLine.cxx
  97. 4 21
      panda/src/collide/collisionNode.cxx
  98. 0 1
      panda/src/collide/collisionNode.h
  99. 1 1
      panda/src/collide/collisionParabola.cxx
  100. 1 1
      panda/src/collide/collisionRay.cxx

+ 15 - 15
.github/workflows/ci.yml

@@ -11,7 +11,7 @@ jobs:
 
       matrix:
         profile:
-        - ubuntu-xenial-standard-unity-makefile
+        - ubuntu-bionic-standard-unity-makefile
         - ubuntu-bionic-coverage-ninja
         - macos-eigen-coverage-unity-xcode
         - macos-nometa-standard-makefile
@@ -19,8 +19,8 @@ jobs:
         - windows-nopython-nometa-standard-msvc
 
         include:
-        - profile: ubuntu-xenial-standard-unity-makefile
-          os: ubuntu-16.04
+        - profile: ubuntu-bionic-standard-unity-makefile
+          os: ubuntu-18.04
           config: Standard
           unity: YES
           generator: Unix Makefiles
@@ -92,10 +92,10 @@ jobs:
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
-        tar -xf panda3d-1.10.8-tools-mac.tar.gz
-        mv panda3d-1.10.8/thirdparty thirdparty
-        rmdir panda3d-1.10.8
+        curl -O https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz
+        tar -xf panda3d-1.10.9-tools-mac.tar.gz
+        mv panda3d-1.10.9/thirdparty thirdparty
+        rmdir panda3d-1.10.9
 
         # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
         install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
@@ -333,12 +333,12 @@ jobs:
     if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
     strategy:
       matrix:
-        os: [ubuntu-16.04, windows-2016, macOS-latest]
+        os: [ubuntu-18.04, windows-2016, macOS-latest]
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v1
     - name: Install dependencies (Ubuntu)
-      if: matrix.os == 'ubuntu-16.04'
+      if: matrix.os == 'ubuntu-18.04'
       run: |
         sudo apt-get update
         sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
@@ -347,16 +347,16 @@ jobs:
       shell: powershell
       run: |
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.8/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.9/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz
-        tar -xf panda3d-1.10.8-tools-mac.tar.gz
-        mv panda3d-1.10.8/thirdparty thirdparty
-        rmdir panda3d-1.10.8
+        curl -O https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-mac.tar.gz
+        tar -xf panda3d-1.10.9-tools-mac.tar.gz
+        mv panda3d-1.10.9/thirdparty thirdparty
+        rmdir panda3d-1.10.9
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
     - name: Set up Python 3.9
       uses: actions/setup-python@v1

+ 0 - 7
.travis.yml

@@ -1,7 +0,0 @@
-language: cpp
-branches:
-  only:
-    - release/1.10.x
-    - release/1.9.x
-script:
-    - echo "Build disabled on master branch."

+ 2 - 0
BACKERS.md

@@ -24,6 +24,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Sam Edwards
 * Max Voss
 * Hawkheart
+* Dan Mlodecki
 
 ## Enthusiasts
 
@@ -33,6 +34,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Kyle Roach
 * Brian Lach
 * C0MPU73R
+* Maxwell Dreytser
 
 ## Backers
 

+ 1 - 1
CONTRIBUTING.md

@@ -18,7 +18,7 @@ It is important for acceptance that the change is implemented in a way that fits
 the general design principles of the Panda3D API, and fits well with the general
 priorities of the team.  Therefore, prior discussion with other developers is
 critical.  Issues can be used to facilitate this, but we also invite you to
-visit the #development channel on Discord (or #panda3d-devel on FreeNode IRC).
+visit the #development channel on Discord (or #panda3d-devel on Libera Chat).
 
 We also recommend that you familiarize yourself with the established coding
 style and design patterns of Panda3D, to reduce the amount of changes that have

+ 4 - 11
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-8/).
+[this page](https://www.panda3d.org/download/sdk-1-10-10/).
 If you are familiar with installing Python packages, you can use
 the following command:
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 
-- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.9/panda3d-1.10.9-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win32.zip
 
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
@@ -136,7 +136,7 @@ macOS
 -----
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.8/panda3d-1.10.8-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz).
 
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
@@ -249,10 +249,3 @@ to everyone who has donated!
 <a href="https://opencollective.com/panda3d" target="_blank">
   <img src="https://opencollective.com/panda3d/contribute/[email protected]?color=blue" width=300 />
 </a>
-
-### Gold Sponsors
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/0/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/0/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/1/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/1/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/2/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/2/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/3/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/3/website)
-[![](https://opencollective.com/panda3d/tiers/gold-sponsor/4/avatar.svg?avatarHeight=128)](https://opencollective.com/panda3d/tiers/gold-sponsor/4/website)

+ 4 - 0
contrib/src/ai/aiCharacter.cxx

@@ -117,6 +117,10 @@ NodePath AICharacter::get_char_render() {
   return _window_render;
 }
 
+std::string AICharacter::get_name() {
+  return _name;
+}
+
 void AICharacter::set_pf_guide(bool pf_guide) {
   _pf_guide = pf_guide;
 }

+ 4 - 0
contrib/src/ai/aiCharacter.h

@@ -46,6 +46,8 @@ class EXPCL_PANDAAI AICharacter : public ReferenceCount {
   void set_char_render(NodePath render);
   NodePath get_char_render();
 
+  std::string get_name();
+
 PUBLISHED:
     double get_mass();
     void set_mass(double m);
@@ -65,6 +67,8 @@ PUBLISHED:
 
     explicit AICharacter(std::string model_name, NodePath model_np, double mass, double movt_force, double max_force);
     ~AICharacter();
+
+  MAKE_PROPERTY(name, get_name);
 };
 
 #endif

+ 10 - 2
contrib/src/rplight/pssmCameraRig.cxx

@@ -369,8 +369,16 @@ void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) {
   LMatrix4 transform = cam_node.get_transform()->get_mat();
 
   // Get Camera and Lens pointers
-  Camera* cam = DCAST(Camera, cam_node.get_child(0).node());
-  nassertv(cam != nullptr);
+  Camera *cam;
+  PandaNode *node = cam_node.node();
+  if (node->is_of_type(Camera::get_class_type())) {
+    cam = (Camera *)node;
+  }
+  else {
+    // Perhaps we passed in something like base.camera ?
+    cam = DCAST(Camera, cam_node.get_child(0).node());
+    nassertv(cam != nullptr);
+  }
   Lens* lens = cam->get_lens();
 
   // Extract near and far points:

+ 1 - 11
contrib/src/speedtree/speedTreeNode.cxx

@@ -1002,17 +1002,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   return true;
 }
 
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise.  This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool SpeedTreeNode::
-is_renderable() const {
-  return true;
-}
-
 /**
  * Adds the node's contents to the CullResult we are building up during the
  * cull traversal, so that it will be drawn at render time.  For most nodes
@@ -1162,6 +1151,7 @@ set_transparent_texture_mode(SpeedTree::ETextureAlphaRenderMode eMode) const {
 void SpeedTreeNode::
 init_node() {
   PandaNode::set_cull_callback();
+  PandaNode::set_renderable();
 
   _is_valid = false;
   _needs_repopulate = false;

+ 0 - 1
contrib/src/speedtree/speedTreeNode.h

@@ -157,7 +157,6 @@ public:
                                          GeomTransformer &transformer);
 
   virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
-  virtual bool is_renderable() const;
   virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
 
   void prepare_scene(GraphicsStateGuardianBase *gsgbase, const RenderState *net_state);

+ 5 - 1
direct/src/actor/Actor.py

@@ -11,7 +11,7 @@ from panda3d.core import Loader as PandaLoader
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.Loader import Loader
 from direct.directnotify import DirectNotifyGlobal
-
+import warnings
 
 class Actor(DirectObject, NodePath):
     """
@@ -1652,6 +1652,8 @@ class Actor(DirectObject, NodePath):
 
         This method is deprecated.  You should use setBlend() instead.
         """
+        if __debug__:
+            warnings.warn("This method is deprecated.  You should use setBlend() instead.", DeprecationWarning, stacklevel=2)
         self.setBlend(animBlend = True, blendType = blendType, partName = partName)
 
     def disableBlend(self, partName = None):
@@ -1661,6 +1663,8 @@ class Actor(DirectObject, NodePath):
 
         This method is deprecated.  You should use setBlend() instead.
         """
+        if __debug__:
+            warnings.warn("This method is deprecated.  You should use setBlend() instead.", DeprecationWarning, stacklevel=2)
         self.setBlend(animBlend = False, partName = partName)
 
     def setControlEffect(self, animName, effect,

+ 4 - 0
direct/src/dcparser/dcPacker_ext.cxx

@@ -226,6 +226,10 @@ unpack_object() {
       std::string str;
       _this->unpack_string(str);
       object = PyUnicode_FromStringAndSize(str.data(), str.size());
+      if (object == nullptr) {
+        nassert_raise("Unable to decode UTF-8 string; use blob type for binary data");
+        return nullptr;
+      }
     }
     break;
 

+ 5 - 5
direct/src/directtools/DirectSession.py

@@ -97,13 +97,13 @@ class DirectSession(DirectObject):
         self.joybox = None
         self.radamec = None
         self.fastrak = []
-        if base.config.GetBool('want-vrpn', 0):
+        if ConfigVariableBool('want-vrpn', False):
             from direct.directdevices import DirectDeviceManager
             self.deviceManager = DirectDeviceManager.DirectDeviceManager()
             # Automatically create any devices specified in config file
-            joybox = base.config.GetString('vrpn-joybox-device', '')
-            radamec = base.config.GetString('vrpn-radamec-device', '')
-            fastrak = base.config.GetString('vrpn-fastrak-device', '')
+            joybox = ConfigVariableString('vrpn-joybox-device', '').value
+            radamec = ConfigVariableString('vrpn-radamec-device', '').value
+            fastrak = ConfigVariableString('vrpn-fastrak-device', '').value
             if joybox:
                 from direct.directdevices import DirectJoybox
                 self.joybox = DirectJoybox.DirectJoybox(joybox)
@@ -300,7 +300,7 @@ class DirectSession(DirectObject):
             self.clusterMode = clusterMode
         except NameError:
             # Has the clusterMode been set via a config variable?
-            self.clusterMode = base.config.GetString("cluster-mode", '')
+            self.clusterMode = ConfigVariableString("cluster-mode", '').value
 
         if self.clusterMode == 'client':
             self.cluster = createClusterClient()

+ 8 - 6
direct/src/dist/FreezeTool.py

@@ -12,6 +12,7 @@ import io
 import distutils.sysconfig as sysconf
 import zipfile
 import importlib
+import warnings
 
 from . import pefile
 
@@ -89,6 +90,7 @@ ignoreImports = {
     'direct.showbase.PythonUtil': ['pstats', 'profile'],
 
     'toml.encoder': ['numpy'],
+    'py._builtin': ['__builtin__'],
 }
 
 if sys.version_info >= (3, 8):
@@ -105,7 +107,7 @@ overrideModules = {
     # Used by the warnings module, among others, to get line numbers.  Since
     # we set __file__, this would cause it to try and extract Python code
     # lines from the main executable, which we don't want.
-    'linecache': """__all__ = ["getline", "clearcache", "checkcache"]
+    'linecache': """__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
 
 cache = {}
 
@@ -661,7 +663,7 @@ okMissing = [
     'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
     'direct.extensions_native.extensions_darwin', '_manylinux',
     'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
-    'collections.Sequence', 'numpy_distutils',
+    'collections.Sequence', 'numpy_distutils', '_winapi',
     ]
 
 # Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -1985,7 +1987,7 @@ class Freezer:
 
         if append_offset:
             # This is for legacy deploy-stub.
-            print("WARNING: Could not find blob header. Is deploy-stub outdated?")
+            warnings.warn("Could not find blob header. Is deploy-stub outdated?")
             blob += struct.pack('<Q', blob_offset)
 
         with open(target, 'wb') as f:
@@ -2624,9 +2626,9 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         (or self.path if None).  Returns a tuple like (fp, path, stuff), where
         stuff is a tuple like (suffix, mode, type). """
 
-        if imp.is_frozen(name):
-            # Don't pick up modules that are frozen into p3dpython.
-            raise ImportError("'%s' is a frozen module" % (name))
+        #if imp.is_frozen(name):
+        #    # Don't pick up modules that are frozen into p3dpython.
+        #    raise ImportError("'%s' is a frozen module" % (name))
 
         if parent is not None:
             fullname = parent.__name__+'.'+name

+ 24 - 6
direct/src/dist/commands.py

@@ -95,14 +95,18 @@ PACKAGE_DATA_DIRS = {
         ('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
     ],
     'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
+    'certifi': [('certifi/cacert.pem', '', {})],
 }
 
 # Some dependencies have extra directories that need to be scanned for DLLs.
 # This dictionary maps wheel basenames (ie. the part of the .whl basename
-# before the first hyphen) to a list of directories inside the .whl.
+# before the first hyphen) to a list of tuples, the first value being the
+# directory inside the wheel, the second being which wheel to look in (or
+# None to look in its own wheel).
 
 PACKAGE_LIB_DIRS = {
-    'scipy':  ['scipy/extra-dll'],
+    'scipy':  [('scipy/extra-dll', None)],
+    'PyQt5':  [('PyQt5/Qt5/bin', 'PyQt5_Qt5')],
 }
 
 SITE_PY = u"""
@@ -177,7 +181,7 @@ class build_apps(setuptools.Command):
         self.exclude_modules = {}
         self.icons = {}
         self.platforms = [
-            'manylinux1_x86_64',
+            'manylinux2010_x86_64',
             'macosx_10_9_x86_64',
             'win_amd64',
         ]
@@ -213,7 +217,8 @@ class build_apps(setuptools.Command):
             'libpthread.so.*', 'libc.so.*', 'ld-linux-x86-64.so.*',
             'libgl.so.*', 'libx11.so.*', 'libncursesw.so.*', 'libz.so.*',
             'librt.so.*', 'libutil.so.*', 'libnsl.so.1', 'libXext.so.6',
-            'libXrender.so.1', 'libICE.so.6', 'libSM.so.6',
+            'libXrender.so.1', 'libICE.so.6', 'libSM.so.6', 'libEGL.so.1',
+            'libOpenGL.so.0', 'libGLdispatch.so.0', 'libGLX.so.0',
             'libgobject-2.0.so.0', 'libgthread-2.0.so.0', 'libglib-2.0.so.0',
 
             # macOS
@@ -610,6 +615,12 @@ class build_apps(setuptools.Command):
                     # by default.  Switch it up if FMOD is not included.
                     if value not in self.plugins and value == 'p3fmod_audio' and 'p3openal_audio' in self.plugins:
                         self.warn("Missing audio plugin p3fmod_audio referenced in PRC data, replacing with p3openal_audio")
+                        value = 'p3openal_audio'
+
+                if var == 'aux-display':
+                    # Silently remove aux-display lines for missing plugins.
+                    if value not in self.plugins:
+                        continue
 
                 for plugin in check_plugins:
                     if plugin in value and plugin not in self.plugins:
@@ -662,8 +673,13 @@ class build_apps(setuptools.Command):
                     # Also look for more specific per-package cases, defined in
                     # PACKAGE_LIB_DIRS at the top of this file.
                     extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, [])
-                    for extra_dir in extra_dirs:
-                        search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+                    for extra_dir, search_in in extra_dirs:
+                        if not search_in:
+                            search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+                        else:
+                            for whl2 in wheelpaths:
+                                if os.path.basename(whl2).startswith(search_in + '-'):
+                                    search_path.append(os.path.join(whl2, extra_dir.replace('/', os.path.sep)))
 
             return search_path
 
@@ -1315,6 +1331,8 @@ class bdist_apps(setuptools.Command):
     DEFAULT_INSTALLERS = {
         'manylinux1_x86_64': ['gztar'],
         'manylinux1_i686': ['gztar'],
+        'manylinux2010_x86_64': ['gztar'],
+        'manylinux2010_i686': ['gztar'],
         # Everything else defaults to ['zip']
     }
 

+ 5 - 5
direct/src/distributed/ClientRepositoryBase.py

@@ -33,7 +33,7 @@ class ClientRepositoryBase(ConnectionRepository):
         ConnectionRepository.__init__(self, connectMethod, base.config, hasOwnerView = True, threadedNet = threadedNet)
         self.dcSuffix = dcSuffix
         if hasattr(self, 'setVerbose'):
-            if self.config.GetBool('verbose-clientrepository'):
+            if ConfigVariableBool('verbose-clientrepository', False):
                 self.setVerbose(1)
 
         self.context=100000
@@ -42,7 +42,7 @@ class ClientRepositoryBase(ConnectionRepository):
         self.deferredGenerates = []
         self.deferredDoIds = {}
         self.lastGenerate = 0
-        self.setDeferInterval(base.config.GetDouble('deferred-generate-interval', 0.2))
+        self.setDeferInterval(ConfigVariableDouble('deferred-generate-interval', 0.2).value)
         self.noDefer = False  # Set this True to temporarily disable deferring.
 
         self.recorder = base.recorder
@@ -69,7 +69,7 @@ class ClientRepositoryBase(ConnectionRepository):
 
         # Keep track of how recently we last sent a heartbeat message.
         # We want to keep these coming at heartbeatInterval seconds.
-        self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
+        self.heartbeatInterval = ConfigVariableDouble('heartbeat-interval', 10).value
         self.heartbeatStarted = 0
         self.lastHeartbeat = 0
 
@@ -497,7 +497,7 @@ class ClientRepositoryBase(ConnectionRepository):
 
     def handleServerHeartbeat(self, di):
         # Got a heartbeat message from the server.
-        if base.config.GetBool('server-heartbeat-info', 1):
+        if ConfigVariableBool('server-heartbeat-info', True):
             self.notify.info("Server heartbeat.")
 
     def handleSystemMessage(self, di):
@@ -581,7 +581,7 @@ class ClientRepositoryBase(ConnectionRepository):
         return worldNP
 
     def isLive(self):
-        if base.config.GetBool('force-live', 0):
+        if ConfigVariableBool('force-live', False):
             return True
         return not (__dev__ or launcher.isTestServer())
 

+ 2 - 2
direct/src/distributed/ClockDelta.py

@@ -1,7 +1,7 @@
 # ClockDelta provides the ability to use clock synchronization for
 # distributed objects
 
-from panda3d.core import ClockObject
+from panda3d.core import ClockObject, ConfigVariableBool
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 import math
@@ -248,7 +248,7 @@ class ClockDelta(DirectObject.DirectObject):
         # set movie-network-time 1, then we'll circumvent this logic
         # and always return now.
         if self.globalClock.getMode() == ClockObject.MNonRealTime and \
-           base.config.GetBool('movie-network-time', False):
+           ConfigVariableBool('movie-network-time', False):
             return now
 
         # First, determine what network time we have for 'now'.

+ 10 - 7
direct/src/distributed/DistributedCamera.py

@@ -4,6 +4,11 @@ from direct.fsm.FSM import FSM
 from direct.interval.IntervalGlobal import *
 from direct.distributed.DistributedObject import DistributedObject
 
+
+_camera_id = ConfigVariableInt('camera-id', -1)
+_aware_of_cameras = ConfigVariableInt('aware-of-cameras', 0)
+
+
 class Fixture(NodePath, FSM):
     def __init__(self, id, parent, pos, hpr, fov):
         NodePath.__init__(self, 'cam-%s' % id)
@@ -60,15 +65,13 @@ class Fixture(NodePath, FSM):
 
     def setRecordingInProgress(self, inProgress):
         self.recordingInProgress = inProgress
-        if self.recordingInProgress and \
-           base.config.GetInt('camera-id', -1) >= 0:
+        if self.recordingInProgress and _camera_id.value >= 0:
             self.hide()
         else:
             self.show()
 
     def show(self):
-        if base.config.GetBool('aware-of-cameras',0) and \
-           not self.recordingInProgress:
+        if _aware_of_cameras and not self.recordingInProgress:
             NodePath.show(self)
 
     def getScaleIval(self):
@@ -99,7 +102,7 @@ class Fixture(NodePath, FSM):
 
     def enterStandby(self):
         self.show()
-        if self.id == base.config.GetInt('camera-id', -1):
+        if self.id == _camera_id.value:
             self.setColorScale(3,0,0,1)
             self.getScaleIval().loop()
         else:
@@ -116,7 +119,7 @@ class Fixture(NodePath, FSM):
             self.scaleIval.finish()
 
     def enterRecording(self):
-        if base.config.GetInt('camera-id', -1) == self.id:
+        if _camera_id.value == self.id:
             self.demand('Using')
         else:
             self.show()
@@ -177,7 +180,7 @@ class DistributedCamera(DistributedObject):
         DistributedObject.__init__(self, cr)
         self.parent = None
         self.fixtures = {}
-        self.cameraId = base.config.GetInt('camera-id',0)
+        self.cameraId = _camera_id.value
 
     def __getitem__(self, index):
         return self.fixtures.get(index)

+ 10 - 11
direct/src/distributed/DistributedSmoothNode.py

@@ -7,22 +7,21 @@ from . import DistributedNode
 from . import DistributedSmoothNodeBase
 from direct.task.Task import cont
 from direct.task.TaskManagerGlobal import taskMgr
-from direct.showbase import DConfig as config
 from direct.showbase.PythonUtil import report
 
 # This number defines our tolerance for out-of-sync telemetry packets.
 # If a packet appears to have originated from more than MaxFuture
 # seconds in the future, assume we're out of sync with the other
 # avatar and suggest a resync for both.
-MaxFuture = config.GetFloat("smooth-max-future", 0.2)
+MaxFuture = ConfigVariableDouble("smooth-max-future", 0.2)
 
 # How frequently can we suggest a resynchronize with another client?
-MinSuggestResync = config.GetFloat("smooth-min-suggest-resync", 15)
+MinSuggestResync = ConfigVariableDouble("smooth-min-suggest-resync", 15)
 
 # These flags indicate whether global smoothing and/or prediction is
 # allowed or disallowed.
-EnableSmoothing = config.GetBool("smooth-enable-smoothing", 1)
-EnablePrediction = config.GetBool("smooth-enable-prediction", 1)
+EnableSmoothing = ConfigVariableBool("smooth-enable-smoothing", True)
+EnablePrediction = ConfigVariableBool("smooth-enable-prediction", True)
 
 # These values represent the amount of time, in seconds, to delay the
 # apparent position of other avatars, when non-predictive and
@@ -30,8 +29,8 @@ EnablePrediction = config.GetBool("smooth-enable-prediction", 1)
 # addition to the automatic delay of the observed average latency from
 # each avatar, which is intended to compensate for relative clock
 # skew.
-Lag = config.GetDouble("smooth-lag", 0.2)
-PredictionLag = config.GetDouble("smooth-prediction-lag", 0.0)
+Lag = ConfigVariableDouble("smooth-lag", 0.2)
+PredictionLag = ConfigVariableDouble("smooth-prediction-lag", 0.0)
 
 
 GlobalSmoothing = 0
@@ -358,10 +357,10 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
             # be just slightly in the past, but it might be off by as much
             # as this frame's amount of time forward or back.
             howFarFuture = local - now
-            if howFarFuture - chug >= MaxFuture:
+            if howFarFuture - chug >= MaxFuture.value:
                 # Too far off; advise the other client of our clock information.
                 if globalClockDelta.getUncertainty() is not None and \
-                   realTime - self.lastSuggestResync >= MinSuggestResync and \
+                   realTime - self.lastSuggestResync >= MinSuggestResync.value and \
                    hasattr(self.cr, 'localAvatarDoId'):
                     self.lastSuggestResync = realTime
                     timestampB = globalClockDelta.localToNetworkTime(realTime)
@@ -527,12 +526,12 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
                 # Prediction and smoothing.
                 self.smoother.setSmoothMode(SmoothMover.SMOn)
                 self.smoother.setPredictionMode(SmoothMover.PMOn)
-                self.smoother.setDelay(PredictionLag)
+                self.smoother.setDelay(PredictionLag.value)
             else:
                 # Smoothing, but no prediction.
                 self.smoother.setSmoothMode(SmoothMover.SMOn)
                 self.smoother.setPredictionMode(SmoothMover.PMOff)
-                self.smoother.setDelay(Lag)
+                self.smoother.setDelay(Lag.value)
         else:
             # No smoothing, no prediction.
             self.smoother.setSmoothMode(SmoothMover.SMOff)

+ 9 - 2
direct/src/distributed/DistributedSmoothNodeBase.py

@@ -3,9 +3,12 @@
 from .ClockDelta import *
 from direct.task import Task
 from direct.task.TaskManagerGlobal import taskMgr
-from direct.showbase.PythonUtil import randFloat, Enum
+from direct.showbase.PythonUtil import randFloat
 from panda3d.direct import CDistributedSmoothNodeBase
 
+from enum import IntEnum
+
+
 class DummyTaskClass:
     def setDelay(self, blah):
         pass
@@ -15,7 +18,11 @@ DummyTask = DummyTaskClass()
 class DistributedSmoothNodeBase:
     """common base class for DistributedSmoothNode and DistributedSmoothNodeAI
     """
-    BroadcastTypes = Enum('FULL, XYH, XY')
+
+    class BroadcastTypes(IntEnum):
+        FULL = 0
+        XYH = 1
+        XY = 2
 
     def __init__(self):
         self.__broadcastPeriod = None

+ 4 - 1
direct/src/distributed/ServerRepository.py

@@ -11,6 +11,9 @@ from direct.distributed.PyDatagram import PyDatagram
 import inspect
 
 
+_server_doid_range = ConfigVariableInt('server-doid-range', 1000000)
+
+
 class ServerRepository:
 
     """ This maintains the server-side connection with a Panda server.
@@ -134,7 +137,7 @@ class ServerRepository:
 
         # The number of doId's to assign to each client.  Must remain
         # constant during server lifetime.
-        self.doIdRange = base.config.GetInt('server-doid-range', 1000000)
+        self.doIdRange = _server_doid_range.value
 
         # An allocator object that assigns the next doIdBase to each
         # client.

+ 7 - 3
direct/src/extensions_native/CInterval_extensions.py

@@ -1,6 +1,7 @@
 from panda3d.direct import CInterval
 from .extension_native_helpers import Dtool_funcToMethod
 from direct.directnotify.DirectNotifyGlobal import directNotify
+import warnings
 
 CInterval.DtoolClassDict["notify"] = directNotify.newCategory("Interval")
 
@@ -18,7 +19,8 @@ del setT
 #####################################################################
 
 def play(self, t0 = 0.0, duration = None, scale = 1.0):
-    self.notify.error("CInterval.play() is deprecated, use start() instead")
+    if __debug__:
+        warnings.warn("CInterval.play() is deprecated, use start() instead", DeprecationWarning, stacklevel=2)
     if duration:  # None or 0 implies full length
         self.start(t0, t0 + duration, scale)
     else:
@@ -29,7 +31,8 @@ del play
 #####################################################################
 
 def stop(self):
-    self.notify.error("CInterval.stop() is deprecated, use finish() instead")
+    if __debug__:
+        warnings.warn("CInterval.stop() is deprecated, use finish() instead", DeprecationWarning, stacklevel=2)
     self.finish()
 
 Dtool_funcToMethod(stop, CInterval)
@@ -37,7 +40,8 @@ del stop
 #####################################################################
 
 def setFinalT(self):
-    self.notify.error("CInterval.setFinalT() is deprecated, use finish() instead")
+    if __debug__:
+        warnings.warn("CInterval.setFinalT() is deprecated, use finish() instead", DeprecationWarning, stacklevel=2)
     self.finish()
 
 Dtool_funcToMethod(setFinalT, CInterval)

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 381 - 352
direct/src/extensions_native/NodePath_extensions.py


+ 3 - 1
direct/src/extensions_native/VBase3_extensions.py

@@ -4,6 +4,7 @@ Methods to extend functionality of the VBase3 class
 
 from panda3d.core import VBase3
 from .extension_native_helpers import Dtool_funcToMethod
+import warnings
 
 def pPrintValues(self):
     """
@@ -17,7 +18,8 @@ def asTuple(self):
     """
     Returns the vector as a tuple.
     """
-    print("Warning: VBase3.asTuple() is no longer needed and deprecated.  Use the vector directly instead.")
+    if __debug__:
+        warnings.warn("VBase3.asTuple() is no longer needed and deprecated.  Use the vector directly instead.", DeprecationWarning, stacklevel=2)
     return tuple(self)
 Dtool_funcToMethod(asTuple, VBase3)
 del asTuple

+ 3 - 1
direct/src/extensions_native/VBase4_extensions.py

@@ -4,6 +4,7 @@ Methods to extend functionality of the VBase4 class
 
 from panda3d.core import VBase4
 from .extension_native_helpers import Dtool_funcToMethod
+import warnings
 
 def pPrintValues(self):
     """
@@ -17,7 +18,8 @@ def asTuple(self):
     """
     Returns the vector as a tuple.
     """
-    print("Warning: VBase4.asTuple() is no longer needed and deprecated.  Use the vector directly instead.")
+    if __debug__:
+        warnings.warn("VBase4.asTuple() is no longer needed and deprecated.  Use the vector directly instead.", DeprecationWarning, stacklevel=2)
     return tuple(self)
 Dtool_funcToMethod(asTuple, VBase4)
 del asTuple

+ 2 - 0
direct/src/filter/FilterManager.py

@@ -367,6 +367,8 @@ class FilterManager(DirectObject):
         self.camstate = self.caminit
         self.camera.node().setInitialState(self.caminit)
         self.region.setCamera(self.camera)
+        if hasattr(self.region, 'clearCullResult'):
+            self.region.clearCullResult()
         self.nextsort = self.win.getSort() - 9
         self.basex = 0
         self.basey = 0

+ 5 - 3
direct/src/gui/DirectGuiBase.py

@@ -102,6 +102,8 @@ from direct.task.TaskManagerGlobal import taskMgr
 
 guiObjectCollector = PStatCollector("Client::GuiObjects")
 
+_track_gui_items = ConfigVariableBool('track-gui-items', False)
+
 
 class DirectGuiBase(DirectObject.DirectObject):
     """Base class of all DirectGUI widgets."""
@@ -638,7 +640,7 @@ class DirectGuiBase(DirectObject.DirectObject):
         """
         # Need to tack on gui item specific id
         gEvent = event + self.guiId
-        if ShowBaseGlobal.config.GetBool('debug-directgui-msgs', False):
+        if ConfigVariableBool('debug-directgui-msgs', False):
             from direct.showbase.PythonUtil import StackTrace
             print(gEvent)
             print(StackTrace())
@@ -667,7 +669,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     # Determine the default initial state for inactive (or
     # unclickable) components.  If we are in edit mode, these are
     # actually clickable by default.
-    guiEdit = ShowBaseGlobal.config.GetBool('direct-gui-edit', False)
+    guiEdit = ConfigVariableBool('direct-gui-edit', False)
     if guiEdit:
         inactiveInitState = DGG.NORMAL
     else:
@@ -733,7 +735,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             guiObjectCollector.addLevel(1)
             guiObjectCollector.flushLevel()
             # track gui items by guiId for tracking down leaks
-            if ShowBaseGlobal.config.GetBool('track-gui-items', False):
+            if _track_gui_items:
                 if not hasattr(ShowBase, 'guiItems'):
                     ShowBase.guiItems = {}
                 if self.guiId in ShowBase.guiItems:

+ 1 - 0
direct/src/gui/DirectOptionMenu.py

@@ -114,6 +114,7 @@ class DirectOptionMenu(DirectButton):
                                               )
         # Make sure it is on top of all the other gui widgets
         self.popupMenu.setBin('gui-popup', 0)
+        self.highlightedIndex = None
         if not self['items']:
             return
         # Create a new component for each item

+ 1 - 2
direct/src/gui/DirectSlider.py

@@ -58,8 +58,7 @@ class DirectSlider(DirectFrame):
         self.thumb = self.createcomponent("thumb", (), None,
                                           DirectButton, (self,),
                                           borderWidth = self['borderWidth'])
-        if self.thumb['frameSize'] is None and \
-           self.thumb.bounds == [0.0, 0.0, 0.0, 0.0]:
+        if self.thumb['frameSize'] is None:
             # Compute a default frameSize for the thumb.
             f = self['frameSize']
             if self['orientation'] == DGG.HORIZONTAL:

+ 39 - 4
direct/src/gui/OnscreenText.py

@@ -8,6 +8,7 @@ __all__ = ['OnscreenText', 'Plain', 'ScreenTitle', 'ScreenPrompt', 'NameConfirm'
 
 from panda3d.core import *
 from . import DirectGuiGlobals as DGG
+import warnings
 
 ## These are the styles of text we might commonly see.  They set the
 ## overall appearance of the text according to one of a number of
@@ -304,6 +305,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.setTextX()` method instead.
         """
+        if __debug__:
+            warnings.warn("Use `.setTextX()` method instead.", DeprecationWarning, stacklevel=2)
         self.setTextPos(x, self.__pos[1])
 
     def setTextY(self, y):
@@ -317,6 +320,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.setTextY()` method instead.
         """
+        if __debug__:
+            warnings.warn("Use `.setTextY()` method instead.", DeprecationWarning, stacklevel=2)
         self.setTextPos(self.__pos[0], y)
 
     def setTextPos(self, x, y=None):
@@ -346,6 +351,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.setTextPos()` method or `.text_pos` property instead.
         """
+        if __debug__:
+            warnings.warn("Use `.setTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
         self.__pos = (x, y)
         self.updateTransformMat()
 
@@ -354,6 +361,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.getTextPos()` method or `.text_pos` property instead.
         """
+        if __debug__:
+            warnings.warn("Use `.getTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
         return self.__pos
 
     pos = property(getPos)
@@ -379,6 +388,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use ``setTextR(-roll)`` instead (note the negated sign).
         """
+        if __debug__:
+            warnings.warn("Use ``setTextR(-roll)`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
         self.__roll = roll
         self.updateTransformMat()
 
@@ -387,6 +398,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use ``-getTextR()`` instead (note the negated sign).
         """
+        if __debug__:
+            warnings.warn("Use ``-getTextR()`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
         return self.__roll
 
     roll = property(getRoll, setRoll)
@@ -424,7 +437,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.setTextScale()` method or `.text_scale` property instead.
         """
-
+        if __debug__:
+            warnings.warn("Use `.setTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
         if sy is None:
             if isinstance(sx, tuple):
                 self.__scale = sx
@@ -439,6 +453,8 @@ class OnscreenText(NodePath):
         .. deprecated:: 1.11.0
            Use `.getTextScale()` method or `.text_scale` property instead.
         """
+        if __debug__:
+            warnings.warn("Use `.getTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
         return self.__scale
 
     scale = property(getScale, setScale)
@@ -526,10 +542,18 @@ class OnscreenText(NodePath):
         for option, value in kw.items():
             # Use option string to access setter function
             try:
-                setter = getattr(self, 'set' + option[0].upper() + option[1:])
-                if setter == self.setPos:
-                    setter(value[0], value[1])
+                if option == 'pos':
+                    self.setTextPos(value[0], value[1])
+                elif option == 'roll':
+                    self.setTextR(-value)
+                elif option == 'scale':
+                    self.setTextScale(value)
+                elif option == 'x':
+                    self.setTextX(value)
+                elif option == 'y':
+                    self.setTextY(value)
                 else:
+                    setter = getattr(self, 'set' + option[0].upper() + option[1:])
                     setter(value)
             except AttributeError:
                 print('OnscreenText.configure: invalid option: %s' % option)
@@ -541,6 +565,17 @@ class OnscreenText(NodePath):
     def cget(self, option):
         # Get current configuration setting.
         # This is for compatibility with DirectGui functions
+        if option == 'pos':
+            return self.__pos
+        elif option == 'roll':
+            return self.__roll
+        elif option == 'scale':
+            return self.__scale
+        elif option == 'x':
+            return self.__pos[0]
+        elif option == 'y':
+            return self.__pos[1]
+
         getter = getattr(self, 'get' + option[0].upper() + option[1:])
         return getter()
 

+ 8 - 0
direct/src/interval/MetaInterval.py

@@ -348,10 +348,14 @@ class MetaInterval(CMetaInterval):
     def getManager(self):
         return self.__manager
 
+    manager = property(getManager, setManager)
+
     def setT(self, t):
         self.__updateIvals()
         CMetaInterval.setT(self, t)
 
+    t = property(CMetaInterval.getT, setT)
+
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
         self.__updateIvals()
         self.setupPlay(startT, endT, playRate, 0)
@@ -475,6 +479,8 @@ class MetaInterval(CMetaInterval):
         else:
             CMetaInterval.setPlayRate(self, playRate)
 
+    play_rate = property(CMetaInterval.getPlayRate, setPlayRate)
+
     def __doPythonCallbacks(self):
         # This function invokes any Python-level Intervals that need
         # to be invoked at this point in time.  It must be called
@@ -549,6 +555,8 @@ class MetaInterval(CMetaInterval):
         self.__updateIvals()
         return CMetaInterval.getDuration(self)
 
+    duration = property(getDuration)
+
     def __repr__(self, *args, **kw):
         # This function overrides from the parent level to force it to
         # update the interval list first, if necessary.

+ 1 - 1
direct/src/leveleditor/ObjectMgrBase.py

@@ -64,7 +64,7 @@ class ObjectMgrBase:
         # [gjeon] to solve the problem of unproper $USERNAME
         userId = os.path.basename(os.path.expandvars('$USERNAME'))
         if userId == '':
-            userId = base.config.GetString("le-user-id")
+            userId = ConfigVariableString("le-user-id").value
         if userId == '':
             userId = 'unknown'
         newUid = str(time.time()) + userId

+ 7 - 2
direct/src/motiontrail/MotionTrail.py

@@ -4,6 +4,10 @@ from direct.task import Task
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
+import warnings
+
+
+_want_python_motion_trails = ConfigVariableBool('want-python-motion-trails', False)
 
 
 def remove_task():
@@ -11,7 +15,8 @@ def remove_task():
         total_motion_trails = len(MotionTrail.motion_trail_list)
 
         if total_motion_trails > 0:
-            print("warning: %d motion trails still exist when motion trail task is removed" %(total_motion_trails))
+            if __debug__:
+                warnings.warn("%d motion trails still exist when motion trail task is removed" % (total_motion_trails), RuntimeWarning, stacklevel=2)
 
         MotionTrail.motion_trail_list = []
 
@@ -132,7 +137,7 @@ class MotionTrail(NodePath, DirectObject):
         self.cmotion_trail.setGeomNode(self.geom_node)
 
         self.modified_vertices = True
-        if base.config.GetBool('want-python-motion-trails', 0):
+        if _want_python_motion_trails:
             self.use_python_version = True
         else:
             self.use_python_version = False

+ 2 - 1
direct/src/particles/ForceGroup.py

@@ -5,6 +5,7 @@ from direct.showbase.PhysicsManagerGlobal import *
 
 from direct.directnotify import DirectNotifyGlobal
 import sys
+import warnings
 
 
 class ForceGroup(DirectObject):
@@ -61,7 +62,7 @@ class ForceGroup(DirectObject):
 
     # Get/set
     def getName(self):
-        """Deprecated: access .name directly instead."""
+        warnings.warn("Deprecated: access .name directly instead.", DeprecationWarning, stacklevel=2)
         return self.name
 
     def getNode(self):

+ 7 - 6
direct/src/particles/SpriteParticleRendererExt.py

@@ -1,3 +1,4 @@
+from panda3d.core import ConfigVariableString
 from panda3d.physics import SpriteParticleRenderer
 
 
@@ -18,8 +19,8 @@ class SpriteParticleRendererExt(SpriteParticleRenderer):
 
     def getSourceTextureName(self):
         if self.sourceTextureName is None:
-            SpriteParticleRendererExt.sourceTextureName = base.config.GetString(
-                'particle-sprite-texture', 'maps/lightbulb.rgb')
+            SpriteParticleRendererExt.sourceTextureName = ConfigVariableString(
+                'particle-sprite-texture', 'maps/lightbulb.rgb').value
         # Return instance copy of class variable
         return self.sourceTextureName
 
@@ -57,8 +58,8 @@ class SpriteParticleRendererExt(SpriteParticleRenderer):
 
     def getSourceFileName(self):
         if self.sourceFileName is None:
-            SpriteParticleRendererExt.sourceFileName = base.config.GetString(
-                'particle-sprite-model', 'models/misc/smiley')
+            SpriteParticleRendererExt.sourceFileName = ConfigVariableString(
+                'particle-sprite-model', 'models/misc/smiley').value
         # Return instance copy of class variable
         return self.sourceFileName
 
@@ -68,8 +69,8 @@ class SpriteParticleRendererExt(SpriteParticleRenderer):
 
     def getSourceNodeName(self):
         if self.sourceNodeName is None:
-            SpriteParticleRendererExt.sourceNodeName = base.config.GetString(
-                'particle-sprite-node', '**/*')
+            SpriteParticleRendererExt.sourceNodeName = ConfigVariableString(
+                'particle-sprite-node', '**/*').value
         # Return instance copy of class variable
         return self.sourceNodeName
 

+ 2 - 1
direct/src/showbase/AppRunnerGlobal.py

@@ -12,7 +12,8 @@ the AppRunner at startup.
 """
 
 if __debug__:
-    print('AppRunner has been removed and AppRunnerGlobal has been deprecated')
+    import warnings
+    warnings.warn("AppRunner has been removed and AppRunnerGlobal has been deprecated.", DeprecationWarning, stacklevel=2)
 
 #: Contains the global :class:`~.AppRunner.AppRunner` instance, or None
 #: if this application was not run from the runtime environment.

+ 1 - 1
direct/src/showbase/BufferViewer.py

@@ -228,7 +228,7 @@ class BufferViewer(DirectObject):
         be precise so that the frame exactly aligns to pixel
         boundaries, and so that it doesn't overlap the card at all."""
 
-        format = GeomVertexFormat.getV3cp()
+        format = GeomVertexFormat.getV3c()
         vdata = GeomVertexData('card-frame', format, Geom.UHDynamic)
 
         vwriter = GeomVertexWriter(vdata, 'vertex')

+ 9 - 0
direct/src/showbase/DConfig.py

@@ -4,21 +4,30 @@ __all__ = []
 
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
                           ConfigVariableDouble, ConfigVariableString)
+import warnings
 
 
 def GetBool(sym, default=False):
+    if __debug__:
+        warnings.warn("This is deprecated. Use ConfigVariableBool instead", DeprecationWarning, stacklevel=2)
     return ConfigVariableBool(sym, default, "DConfig", ConfigFlags.F_dconfig).value
 
 
 def GetInt(sym, default=0):
+    if __debug__:
+        warnings.warn("This is deprecated. Use ConfigVariableInt instead", DeprecationWarning, stacklevel=2)
     return ConfigVariableInt(sym, default, "DConfig", ConfigFlags.F_dconfig).value
 
 
 def GetDouble(sym, default=0.0):
+    if __debug__:
+        warnings.warn("This is deprecated. Use ConfigVariableDouble instead", DeprecationWarning, stacklevel=2)
     return ConfigVariableDouble(sym, default, "DConfig", ConfigFlags.F_dconfig).value
 
 
 def GetString(sym, default=""):
+    if __debug__:
+        warnings.warn("This is deprecated. Use ConfigVariableString instead", DeprecationWarning, stacklevel=2)
     return ConfigVariableString(sym, default, "DConfig", ConfigFlags.F_dconfig).value
 
 

+ 11 - 5
direct/src/showbase/Loader.py

@@ -8,6 +8,7 @@ from panda3d.core import *
 from panda3d.core import Loader as PandaLoader
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.DirectObject import DirectObject
+import warnings
 
 # You can specify a phaseChecker callback to check
 # a modelPath to see if it is being loaded in the correct
@@ -295,7 +296,8 @@ class Loader(DirectObject):
         called after cancelRequest() has been performed.
 
         This is now deprecated: call cb.cancel() instead. """
-
+        if __debug__:
+            warnings.warn("This is now deprecated: call cb.cancel() instead.", DeprecationWarning, stacklevel=2)
         cb.cancel()
 
     def isRequestPending(self, cb):
@@ -304,7 +306,8 @@ class Loader(DirectObject):
         been cancelled.
 
         This is now deprecated: call cb.done() instead. """
-
+        if __debug__:
+            warnings.warn("This is now deprecated: call cb.done() instead.", DeprecationWarning, stacklevel=2)
         return bool(cb.requests)
 
     def loadModelOnce(self, modelPath):
@@ -315,7 +318,8 @@ class Loader(DirectObject):
         then attempt to load it from disk. Return a nodepath to
         the model if successful or None otherwise
         """
-        Loader.notify.info("loader.loadModelOnce() is deprecated; use loader.loadModel() instead.")
+        if __debug__:
+            warnings.warn("loader.loadModelOnce() is deprecated; use loader.loadModel() instead.", DeprecationWarning, stacklevel=2)
 
         return self.loadModel(modelPath, noCache = False)
 
@@ -326,7 +330,8 @@ class Loader(DirectObject):
         then attempt to load it from disk. Return a nodepath to
         a copy of the model if successful or None otherwise
         """
-        Loader.notify.info("loader.loadModelCopy() is deprecated; use loader.loadModel() instead.")
+        if __debug__:
+            warnings.warn("loader.loadModelCopy() is deprecated; use loader.loadModel() instead.", DeprecationWarning, stacklevel=2)
 
         return self.loadModel(modelPath, loaderOptions = loaderOptions, noCache = False)
 
@@ -344,7 +349,8 @@ class Loader(DirectObject):
 
         However, if you're loading a font, see loadFont(), below.
         """
-        Loader.notify.info("loader.loadModelNode() is deprecated; use loader.loadModel() instead.")
+        if __debug__:
+            warnings.warn("loader.loadModelNode() is deprecated; use loader.loadModel() instead.", DeprecationWarning, stacklevel=2)
 
         model = self.loadModel(modelPath, noCache = False)
         if model is not None:

+ 15 - 11
direct/src/showbase/PythonUtil.py

@@ -641,9 +641,7 @@ if __debug__:
             def _profiled(*args, **kArgs):
                 name = '(%s) %s from %s' % (category, f.__name__, f.__module__)
 
-                # showbase might not be loaded yet, so don't use
-                # base.config.  Instead, query the ConfigVariableBool.
-                if (category is None) or ConfigVariableBool('want-profile-%s' % category, 0).getValue():
+                if category is None or ConfigVariableBool('want-profile-%s' % category, False).value:
                     return profileFunc(Functor(f, *args, **kArgs), name, terse)
                 else:
                     return f(*args, **kArgs)
@@ -1129,8 +1127,6 @@ def normalDistrib(a, b, gauss=random.gauss):
     uniformly onto the curve inside [a, b]
 
     ------------------------------------------------------------------------
-    https://statweb.stanford.edu/~naras/jsm/NormalDensity/NormalDensity.html
-
     The 68-95-99.7% Rule
     ====================
     All normal density curves satisfy the following property which is often
@@ -1201,18 +1197,24 @@ class SerialNumGen:
         if start is None:
             start = 0
         self.__counter = start-1
+
     def next(self):
         self.__counter += 1
         return self.__counter
 
+    __next__ = next
+
 class SerialMaskedGen(SerialNumGen):
     def __init__(self, mask, start=None):
         self._mask = mask
         SerialNumGen.__init__(self, start)
+
     def next(self):
         v = SerialNumGen.next(self)
         return v & self._mask
 
+    __next__ = next
+
 _serialGen = SerialNumGen()
 def serialNum():
     global _serialGen
@@ -1223,7 +1225,7 @@ def uniqueName(name):
 
 class EnumIter:
     def __init__(self, enum):
-        self._values = list(enum._stringTable.keys())
+        self._values = tuple(enum._stringTable.keys())
         self._index = 0
     def __iter__(self):
         return self
@@ -1232,7 +1234,6 @@ class EnumIter:
             raise StopIteration
         self._index += 1
         return self._values[self._index-1]
-    next = __next__
 
 class Enum:
     """Pass in list of strings or string of comma-separated strings.
@@ -1990,7 +1991,7 @@ def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigPara
         return f
 
     try:
-        if not (__dev__ or config.GetBool('force-reports', 0)):
+        if not __dev__ and not ConfigVariableBool('force-reports', False):
             return decorator
 
         # determine whether we should use the decorator
@@ -2006,7 +2007,7 @@ def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigPara
                 dConfigParams = dConfigParam
 
             dConfigParamList = [param for param in dConfigParams \
-                                if config.GetBool('want-%s-report' % (param,), 0)]
+                                if ConfigVariableBool('want-%s-report' % (param,), False)]
 
             doPrint = bool(dConfigParamList)
 
@@ -2256,12 +2257,12 @@ if __debug__:
     def quickProfile(name="unnamed"):
         import pstats
         def profileDecorator(f):
-            if not config.GetBool("use-profiler", False):
+            if not ConfigVariableBool("use-profiler", False):
                 return f
             def _profiled(*args, **kArgs):
                 # must do this in here because we don't have base/simbase
                 # at the time that PythonUtil is loaded
-                if not config.GetBool("profile-debug", False):
+                if not ConfigVariableBool("profile-debug", False):
                     #dumb timings
                     st=globalClock.getRealTime()
                     f(*args,**kArgs)
@@ -2450,6 +2451,7 @@ class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
         self._curCounter = ['A']
+
     def next(self):
         result = ''.join([c for c in self._curCounter])
         index = -1
@@ -2473,6 +2475,8 @@ class AlphabetCounter:
                 break
         return result
 
+    __next__ = next
+
 if __debug__ and __name__ == '__main__':
     def testAlphabetCounter():
         tempList = []

+ 62 - 63
direct/src/showbase/ShowBase.py

@@ -72,6 +72,7 @@ if __debug__:
     from direct.showbase import GarbageReport
     from direct.directutil import DeltaProfiler
     from . import OnScreenDebug
+    import warnings
 
 @atexit.register
 def exitfunc():
@@ -100,19 +101,21 @@ class ShowBase(DirectObject.DirectObject):
         including this instance itself (under the name ``base``).
         """
 
+        from . import ShowBaseGlobal
+
         #: Set if the want-dev Config.prc variable is enabled.  By default, it
         #: is set to True except when using Python with the -O flag.
-        self.__dev__ = self.config.GetBool('want-dev', __debug__)
+        self.__dev__ = ShowBaseGlobal.__dev__
         builtins.__dev__ = self.__dev__
 
-        logStackDump = (self.config.GetBool('log-stack-dump', False) or
-                        self.config.GetBool('client-log-stack-dump', False))
-        uploadStackDump = self.config.GetBool('upload-stack-dump', False)
+        logStackDump = (ConfigVariableBool('log-stack-dump', False).value or
+                        ConfigVariableBool('client-log-stack-dump', False).value)
+        uploadStackDump = ConfigVariableBool('upload-stack-dump', False).value
         if logStackDump or uploadStackDump:
             ExceptionVarDump.install(logStackDump, uploadStackDump)
 
         if __debug__:
-            self.__autoGarbageLogging = self.__dev__ and self.config.GetBool('auto-garbage-logging', False)
+            self.__autoGarbageLogging = self.__dev__ and ConfigVariableBool('auto-garbage-logging', False)
 
         #: The directory containing the main Python file of this application.
         self.mainDir = ExecutionEnvironment.getEnvironmentVariable("MAIN_DIR")
@@ -127,9 +130,12 @@ class ShowBase(DirectObject.DirectObject):
         self.debugRunningMultiplier = 4
 
         # [gjeon] to disable sticky keys
-        if self.config.GetBool('disable-sticky-keys', 0):
+        if ConfigVariableBool('disable-sticky-keys', False):
             storeAccessibilityShortcutKeys()
             allowAccessibilityShortcutKeys(False)
+            self.__disabledStickyKeys = True
+        else:
+            self.__disabledStickyKeys = False
 
         self.printEnvDebugInfo()
         vfs = VirtualFileSystem.getGlobalPtr()
@@ -139,18 +145,18 @@ class ShowBase(DirectObject.DirectObject):
         self.__deadInputs = 0
 
         # Store dconfig variables
-        self.sfxActive = self.config.GetBool('audio-sfx-active', 1)
-        self.musicActive = self.config.GetBool('audio-music-active', 1)
-        self.wantFog = self.config.GetBool('want-fog', 1)
-        self.wantRender2dp = self.config.GetBool('want-render2dp', 1)
+        self.sfxActive = ConfigVariableBool('audio-sfx-active', True).value
+        self.musicActive = ConfigVariableBool('audio-music-active', True).value
+        self.wantFog = ConfigVariableBool('want-fog', True).value
+        self.wantRender2dp = ConfigVariableBool('want-render2dp', True).value
 
-        self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
+        self.screenshotExtension = ConfigVariableString('screenshot-extension', 'jpg').value
         self.musicManager = None
         self.musicManagerIsValid = None
         self.sfxManagerList = []
         self.sfxManagerIsValidList = []
 
-        self.wantStats = self.config.GetBool('want-pstats', 0)
+        self.wantStats = ConfigVariableBool('want-pstats', False).value
         self.wantTk = False
         self.wantWx = False
         self.wantDirect = False
@@ -177,7 +183,7 @@ class ShowBase(DirectObject.DirectObject):
         # If the aspect ratio is 0 or None, it means to infer the
         # aspect ratio from the window size.
         # If you need to know the actual aspect ratio call base.getAspectRatio()
-        self.__configAspectRatio = ConfigVariableDouble('aspect-ratio', 0).getValue()
+        self.__configAspectRatio = ConfigVariableDouble('aspect-ratio', 0).value
         # This variable is used to see if the aspect ratio has changed when
         # we get a window-event.
         self.__oldAspectRatio = None
@@ -187,8 +193,8 @@ class ShowBase(DirectObject.DirectObject):
         #: be 'onscreen' (the default), 'offscreen' or 'none'.
         self.windowType = windowType
         if self.windowType is None:
-            self.windowType = self.config.GetString('window-type', 'onscreen')
-        self.requireWindow = self.config.GetBool('require-window', 1)
+            self.windowType = ConfigVariableString('window-type', 'onscreen').value
+        self.requireWindow = ConfigVariableBool('require-window', True).value
 
         #: This is the main, or only window; see `winList` for a list of *all* windows.
         self.win = None
@@ -261,11 +267,10 @@ class ShowBase(DirectObject.DirectObject):
             self.clusterSyncFlag = clusterSyncFlag
         except NameError:
             # Has the clusterSyncFlag been set via a config variable
-            self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
+            self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
 
         # We've already created aspect2d in ShowBaseGlobal, for the
         # benefit of creating DirectGui elements before ShowBase.
-        from . import ShowBaseGlobal
         self.hidden = ShowBaseGlobal.hidden
 
         #: The global :class:`~panda3d.core.GraphicsEngine`, as returned by
@@ -297,14 +302,14 @@ class ShowBase(DirectObject.DirectObject):
         # Maybe create a RecorderController to record and/or play back
         # the user session.
         self.recorder = None
-        playbackSession = self.config.GetString('playback-session', '')
-        recordSession = self.config.GetString('record-session', '')
-        if playbackSession:
+        playbackSession = ConfigVariableFilename('playback-session', '')
+        recordSession = ConfigVariableFilename('record-session', '')
+        if not playbackSession.empty():
             self.recorder = RecorderController()
-            self.recorder.beginPlayback(Filename.fromOsSpecific(playbackSession))
-        elif recordSession:
+            self.recorder.beginPlayback(playbackSession.value)
+        elif not recordSession.empty():
             self.recorder = RecorderController()
-            self.recorder.beginRecord(Filename.fromOsSpecific(recordSession))
+            self.recorder.beginRecord(recordSession.value)
 
         if self.recorder:
             # If we're either playing back or recording, pass the
@@ -318,19 +323,19 @@ class ShowBase(DirectObject.DirectObject):
 
         # For some reason, wx needs to be initialized before the graphics window
         if sys.platform == "darwin":
-            if self.config.GetBool("want-wx", 0):
+            if ConfigVariableBool("want-wx", False):
                 wx = importlib.import_module('wx')
                 self.wxApp = wx.App()
 
             # Same goes for Tk, which uses a conflicting NSApplication
-            if self.config.GetBool("want-tk", 0):
+            if ConfigVariableBool("want-tk", False):
                 Pmw = importlib.import_module('Pmw')
                 self.tkRoot = Pmw.initialise()
 
         # Open the default rendering window.
         if self.windowType != 'none':
             props = WindowProperties.getDefault()
-            if self.config.GetBool('read-raw-mice', 0):
+            if ConfigVariableBool('read-raw-mice', False):
                 props.setRawMice(1)
             self.openDefaultWindow(startDirect = False, props=props)
 
@@ -397,18 +402,18 @@ class ShowBase(DirectObject.DirectObject):
         # - pcalt-# (# is CPU number, 0-based)
         # - client-cpu-affinity config
         # - auto-single-cpu-affinity config
-        affinityMask = self.config.GetInt('client-cpu-affinity-mask', -1)
+        affinityMask = ConfigVariableInt('client-cpu-affinity-mask', -1).value
         if affinityMask != -1:
             TrueClock.getGlobalPtr().setCpuAffinity(affinityMask)
         else:
             # this is useful on machines that perform better with each process
             # assigned to a single CPU
-            autoAffinity = self.config.GetBool('auto-single-cpu-affinity', 0)
+            autoAffinity = ConfigVariableBool('auto-single-cpu-affinity', False).value
             affinity = None
             if autoAffinity and hasattr(builtins, 'clientIndex'):
                 affinity = abs(int(builtins.clientIndex))
             else:
-                affinity = self.config.GetInt('client-cpu-affinity', -1)
+                affinity = ConfigVariableInt('client-cpu-affinity', -1).value
             if (affinity in (None, -1)) and autoAffinity:
                 affinity = 0
             if affinity not in (None, -1):
@@ -466,13 +471,13 @@ class ShowBase(DirectObject.DirectObject):
 
         self.createBaseAudioManagers()
 
-        if self.__dev__ and self.config.GetBool('track-gui-items', False):
+        if self.__dev__ and ConfigVariableBool('track-gui-items', False):
             # dict of guiId to gui item, for tracking down leaks
             if not hasattr(ShowBase, 'guiItems'):
                 ShowBase.guiItems = {}
 
         # optionally restore the default gui sounds from 1.7.2 and earlier
-        if ConfigVariableBool('orig-gui-sounds', False).getValue():
+        if ConfigVariableBool('orig-gui-sounds', False).value:
             from direct.gui import DirectGuiGlobals as DGG
             DGG.setDefaultClickSound(self.loader.loadSfx("audio/sfx/GUI_click.wav"))
             DGG.setDefaultRolloverSound(self.loader.loadSfx("audio/sfx/GUI_rollover.wav"))
@@ -494,18 +499,15 @@ class ShowBase(DirectObject.DirectObject):
             self.setupWindowControls()
 
         # Client sleep
-        sleepTime = self.config.GetFloat('client-sleep', 0.0)
+        sleepTime = ConfigVariableDouble('client-sleep', 0.0)
         self.clientSleep = 0.0
-        self.setSleep(sleepTime)
+        self.setSleep(sleepTime.value)
 
         # Extra sleep for running 4+ clients on a single machine
         # adds a sleep right after the main render in igloop
         # tends to even out the frame rate and keeps it from going
         # to zero in the out of focus windows
-        if self.config.GetBool('multi-sleep', 0):
-            self.multiClientSleep = 1
-        else:
-            self.multiClientSleep = 0
+        self.multiClientSleep = ConfigVariableBool('multi-sleep', False)
 
         #: Utility for viewing offscreen buffers, see :mod:`.BufferViewer`.
         self.bufferViewer = BufferViewer(self.win, self.render2dp if self.wantRender2dp else self.render2d)
@@ -514,7 +516,7 @@ class ShowBase(DirectObject.DirectObject):
             if fStartDirect: # [gjeon] if this is False let them start direct manually
                 self.__doStartDirect()
 
-            if self.config.GetBool('show-tex-mem', False):
+            if ConfigVariableBool('show-tex-mem', False):
                 if not self.texmem or self.texmem.cleanedUp:
                     self.toggleTexMem()
 
@@ -542,10 +544,10 @@ class ShowBase(DirectObject.DirectObject):
         except ImportError:
             return
 
-        profile.Profile.bias = float(self.config.GetString("profile-bias","0"))
+        profile.Profile.bias = ConfigVariableDouble("profile-bias", 0.0).value
 
         def f8(x):
-            return ("%" + "8.%df" % self.config.GetInt("profile-decimals", 3)) % x
+            return ("%" + "8.%df" % ConfigVariableInt("profile-decimals", 3)) % x
         pstats.f8 = f8
 
     # temp; see ToonBase.py
@@ -557,7 +559,7 @@ class ShowBase(DirectObject.DirectObject):
         in.  Stuff like the model paths and other paths.  Feel free to
         add stuff to this.
         """
-        if self.config.GetBool('want-env-debug-info', 0):
+        if ConfigVariableBool('want-env-debug-info', False):
             print("\n\nEnvironment Debug Info {")
             print("* model path:")
             print(getModelPath())
@@ -592,8 +594,9 @@ class ShowBase(DirectObject.DirectObject):
         self.aspect2d.reparent_to(self.render2d)
 
         # [gjeon] restore sticky key settings
-        if self.config.GetBool('disable-sticky-keys', 0):
+        if self.__disabledStickyKeys:
             allowAccessibilityShortcutKeys(True)
+            self.__disabledStickyKeys = False
 
         self.ignoreAll()
         self.shutdown()
@@ -722,7 +725,7 @@ class ShowBase(DirectObject.DirectObject):
                               of :meth:`~panda3d.core.GraphicsWindow.setUnexposedDraw()`.
 
         :param callbackWindowDict: If not None, a
-                                   :class:`~panda3d.core.CallbackGraphicWindow`
+                                   :class:`~panda3d.core.CallbackGraphicsWindow`
                                    is created instead, which allows the caller
                                    to create the actual window with its own
                                    OpenGL context, and direct Panda's rendering
@@ -1075,16 +1078,10 @@ class ShowBase(DirectObject.DirectObject):
                 self.win.setClearStencilActive(oldClearStencilActive)
                 self.win.setClearStencil(oldClearStencil)
 
-            flag = self.config.GetBool('show-frame-rate-meter', False)
-            if self.appRunner is not None and self.appRunner.allowPythonDev:
-                # In an allow_python_dev p3d application, we always
-                # start up with the frame rate meter enabled, to
-                # provide a visual reminder that this flag has been
-                # set.
-                flag = True
-            self.setFrameRateMeter(flag)
-            flag = self.config.GetBool('show-scene-graph-analyzer-meter', False)
-            self.setSceneGraphAnalyzerMeter(flag)
+            flag = ConfigVariableBool('show-frame-rate-meter', False)
+            self.setFrameRateMeter(flag.value)
+            flag = ConfigVariableBool('show-scene-graph-analyzer-meter', False)
+            self.setSceneGraphAnalyzerMeter(flag.value)
         return success
 
     def setSleep(self, amount):
@@ -2034,7 +2031,8 @@ class ShowBase(DirectObject.DirectObject):
         """
         :deprecated: Use `.Loader.Loader.loadSfx()` instead.
         """
-        assert self.notify.warning("base.loadSfx is deprecated, use base.loader.loadSfx instead.")
+        if __debug__:
+            warnings.warn("base.loadSfx is deprecated, use base.loader.loadSfx instead.", DeprecationWarning, stacklevel=2)
         return self.loader.loadSfx(name)
 
     # This function should only be in the loader but is here for
@@ -2044,7 +2042,8 @@ class ShowBase(DirectObject.DirectObject):
         """
         :deprecated: Use `.Loader.Loader.loadMusic()` instead.
         """
-        assert self.notify.warning("base.loadMusic is deprecated, use base.loader.loadMusic instead.")
+        if __debug__:
+            warnings.warn("base.loadMusic is deprecated, use base.loader.loadMusic instead.", DeprecationWarning, stacklevel=2)
         return self.loader.loadMusic(name)
 
     def playSfx(
@@ -2237,7 +2236,7 @@ class ShowBase(DirectObject.DirectObject):
         # between collisionLoop and igLoop
         self.taskMgr.add(self.__collisionLoop, 'collisionLoop', sort = 30)
 
-        if ConfigVariableBool('garbage-collect-states').getValue():
+        if ConfigVariableBool('garbage-collect-states').value:
             self.taskMgr.add(self.__garbageCollectStates, 'garbageCollectStates', sort = 46)
         # give the igLoop task a reasonably "late" sort,
         # so that it will get run after most tasks
@@ -2505,7 +2504,7 @@ class ShowBase(DirectObject.DirectObject):
         # Make the spots round, so there's less static in the display.
         # This forces software point generation on many drivers, so
         # it's not on by default.
-        if self.config.GetBool('round-show-vertices', False):
+        if ConfigVariableBool('round-show-vertices', False):
             spot = PNMImage(256, 256, 1)
             spot.renderSpot((1, 1, 1, 1), (0, 0, 0, 0), 0.8, 1)
             tex = Texture('spot')
@@ -3131,7 +3130,7 @@ class ShowBase(DirectObject.DirectObject):
             # Set a timer to run the Panda frame 60 times per second.
             wxFrameRate = ConfigVariableDouble('wx-frame-rate', 60.0)
             self.wxTimer = wx.Timer(self.wxApp)
-            self.wxTimer.Start(1000.0 / wxFrameRate.getValue())
+            self.wxTimer.Start(1000.0 / wxFrameRate.value)
             self.wxApp.Bind(wx.EVT_TIMER, self.__wxTimerCallback)
 
             # wx is now the main loop, not us any more.
@@ -3218,7 +3217,7 @@ class ShowBase(DirectObject.DirectObject):
 
             # Set a timer to run the Panda frame 60 times per second.
             tkFrameRate = ConfigVariableDouble('tk-frame-rate', 60.0)
-            self.tkDelay = int(1000.0 / tkFrameRate.getValue())
+            self.tkDelay = int(1000.0 / tkFrameRate.value)
             self.tkRoot.after(self.tkDelay, self.__tkTimerCallback)
 
             # wx is now the main loop, not us any more.
@@ -3298,11 +3297,11 @@ class ShowBase(DirectObject.DirectObject):
         self.__directStarted = False
 
         # Start Tk, Wx and DIRECT if specified by Config.prc
-        fTk = self.config.GetBool('want-tk', 0)
-        fWx = self.config.GetBool('want-wx', 0)
+        fTk = ConfigVariableBool('want-tk', False).value
+        fWx = ConfigVariableBool('want-wx', False).value
         # Start DIRECT if specified in Config.prc or in cluster mode
-        fDirect = (self.config.GetBool('want-directtools', 0) or
-                   (self.config.GetString("cluster-mode", '') != ''))
+        fDirect = (ConfigVariableBool('want-directtools', 0).value or
+                   (not ConfigVariableString("cluster-mode", '').empty()))
         # Set fWantTk to 0 to avoid starting Tk with this call
         self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
 

+ 5 - 3
direct/src/showbase/ShowBaseGlobal.py

@@ -16,11 +16,12 @@ __all__ = []
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify # pylint: disable=unused-import
 from panda3d.core import VirtualFileSystem, Notify, ClockObject, PandaSystem
-from panda3d.core import ConfigPageManager, ConfigVariableManager
+from panda3d.core import ConfigPageManager, ConfigVariableManager, ConfigVariableBool
 from panda3d.core import NodePath, PGTop
 from . import DConfig as config
+import warnings
 
-__dev__ = config.GetBool('want-dev', __debug__)
+__dev__ = ConfigVariableBool('want-dev', __debug__).value
 
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
@@ -63,7 +64,8 @@ directNotify.setDconfigLevels()
 
 def run():
     """Deprecated alias for :meth:`base.run() <.ShowBase.run>`."""
-    assert ShowBase.notify.warning("run() is deprecated, use base.run() instead")
+    if __debug__:
+        warnings.warn("run() is deprecated, use base.run() instead", DeprecationWarning, stacklevel=2)
     base.run()
 
 

+ 4 - 4
direct/src/showutil/BuildGeometry.py

@@ -34,7 +34,7 @@ def addCircle(attachNode, vertexCount, radius, color = Vec4(1.0, 1.0, 1.0, 1.0),
         centerColor = color
     zFloat = 0.025
     targetCircleShape = getCirclePoints(5 + (vertexCount), 0.0, 0.0, radius)
-    gFormat = GeomVertexFormat.getV3cp()
+    gFormat = GeomVertexFormat.getV3c()
     targetCircleVertexData = GeomVertexData("holds my vertices", gFormat, Geom.UHDynamic)
     targetCircleVertexWriter = GeomVertexWriter(targetCircleVertexData, "vertex")
     targetCircleColorWriter = GeomVertexWriter(targetCircleVertexData, "color")
@@ -79,7 +79,7 @@ def addSquare(attachNode, sizeX, sizeY, color = Vec4(1.0, 1.0, 1.0, 1.0), layer
     color2 = color
     color3 = color
 
-    gFormat = GeomVertexFormat.getV3n3cpt2()
+    gFormat = GeomVertexFormat.getV3n3ct2()
     boxVertexData = GeomVertexData("vertices", gFormat, Geom.UHDynamic)
 
     boxVertexWriter = GeomVertexWriter(boxVertexData, "vertex")
@@ -147,7 +147,7 @@ def addBox(attachNode, sizeX, sizeY, sizeZ, color = Vec4(1.0, 1.0, 1.0, 1.0), da
         color2 = color * 0.50 #Vec4(0.0, 0.0, 0.0, 1.0)
         color3 = color * 0.25 #Vec4(0.0, 0.0, 0.0, 1.0)
 
-    gFormat = GeomVertexFormat.getV3n3cp()
+    gFormat = GeomVertexFormat.getV3n3c()
     boxVertexData = GeomVertexData("vertices", gFormat, Geom.UHDynamic)
 
     boxVertexWriter = GeomVertexWriter(boxVertexData, "vertex")
@@ -328,7 +328,7 @@ def addArrow(attachNode, sizeX, sizeY, color = Vec4(1.0, 1.0, 1.0, 1.0), layer =
     color2 = color
     color3 = color
 
-    gFormat = GeomVertexFormat.getV3n3cp()
+    gFormat = GeomVertexFormat.getV3n3c()
     boxVertexData = GeomVertexData("vertices", gFormat, Geom.UHDynamic)
 
     boxVertexWriter = GeomVertexWriter(boxVertexData, "vertex")

+ 1 - 0
direct/src/task/Task.py

@@ -91,6 +91,7 @@ pause = AsyncTaskPause
 Task.DtoolClassDict['pause'] = staticmethod(pause)
 
 gather = Task.gather
+shield = Task.shield
 
 def sequence(*taskList):
     seq = AsyncTaskSequence('sequence')

+ 128 - 0
doc/ReleaseNotes

@@ -1,3 +1,131 @@
+-----------------------  RELEASE 1.10.10  -----------------------
+
+This release fixes assorted, mostly very minor bugs.
+
+* Round refresh rates when choosing display mode on macOS (#1144)
+* Vectors now support floor division
+* It is now possible to use round(), ceil() and floor() with vector types
+* Add RenderState::get_unused_states()
+* Fix assertion error in RenderState::get_num_unused_states()
+* Fix Assimp loader not importing normal vectors correctly (#1163)
+* Fix error when trying to render DirectGUI in offscreen mode (#1174)
+* Fix crash when resizing window in multi-window DirectX 9 application (#1167)
+* Fixes to enable compilation with recent OpenEXR and FFMpeg versions
+* Fix draw callback being called twice if cull callback calls upcall()
+* Improve error message when display module fails to load
+* Fix writing/reading BitArray to/from bam files on 64-bit systems (#1181)
+* evdev input devices (such as gamepads) are now supported in FreeBSD as well
+* Panda no longer tries to compress buffer textures when compression is enabled
+* Fix Geom::make_lines_in_place() (& points, patches) leaving invalid state
+* Fix memory leak when cleaning up FilterManager (#1166)
+* Fix memory leak deleting multisample OpenGL FBOs (#1166)
+* Fix auto-binding of SSBOs sometimes causing overlapping bindings (#1176)
+* PSSMCameraRig::update() now accepts a camera node directly
+* Support copying depth buffer for 32-bit depth with 8-bit stencil (#1142)
+* Prevent trying to copy depth from non-depth buffer in OpenGL renderer (#1142)
+* Fixes to format selection for OpenGL renderbuffers (#1137, #1141)
+* gl-depth-zero-to-one is now supported in OpenGL ES 2+ (if driver supports)
+* Maya models can contain more than three eggObjectTypes (#1134)
+* Fix black screen on Linux when switching fullscreen without a WM active
+* Fix Linux crash when trying to load a directory instead of a file (#1140)
+* Fix crash when loading an invalid font
+* Fix a very obscure unintended DirectGUI behavior change in 1.10.9
+
+------------------------  RELEASE 1.10.9  -----------------------
+
+This is a bugfix release which addresses some severe issues on macOS, as well as
+a broad range of issues on other platforms, and adds some minor features.
+
+Shaders
+* Some macOS builds were erroneously not built with Cg shader support (#1119)
+* Show error for unrecognized p3d_TextureXYZ GLSL shader inputs
+* Fix Cg shader bug when using multiple GraphicsStateGuardian objects (#1117)
+* New filled wireframe mode that respects vertex shaders (#1124)
+* Fix undefined behavior when using non-nearest filtering on isampler/usampler
+* Harmless shader linking warnings on macOS are now suppressed
+
+Windowing
+* Fix mouse confinement activating on Windows for non-foreground window (#1115)
+* Fix occasional failure with switching to fullscreen on macOS (#1039)
+* Fix parented window being offset if undecorated flag isn't set on Windows
+
+Rendering
+* Better support for headless OpenGL on Linux via EGL-based fallback (#1086)
+* EGL implementation can now fall back to alternative EGL devices
+* Fix ability to create multisample FBO when using EGL (#1089)
+* Fix copy-RAM and copy-texture modes for multisample-enabled buffers (#1129)
+* FilterManager now respects depth-bits specified in Config.prc
+* Auto-convert vertex data down from 64-bit float to 32-bit in OpenGL ES
+
+Texturing
+* Add missing 16-bpc and 32-bpc integer texture formats
+* Support `Texture::set_ram_image_as()` for 3D and multiview textures (#1095)
+* TexturePeeker now supports cube maps (#1098)
+* TexturePeeker now supports integer texture formats
+
+GUI / Text
+* Workarounds for disappearing text and GUI items in multithreaded pipeline
+* Fix generated accent marks for some fonts appearing flipped
+* Included static fonts now include dotless i glyph
+* Fix DirectOptionMenu issues when changing items from item callback (#1125)
+
+Python API
+* Fix some Python 3 compatibility issues
+* Improvements to `direct.stdpy.pickle` pickle, esp. for Python 3
+* Fix `direct.stdpy.pickle` sometimes duplicating Panda objects
+* Add pickle support to various Panda classes
+* AsyncFuture can now store an arbitrary Python object as result
+* AsyncFuture.gather() now schedules coroutines on current task chain
+* Coroutine exceptions are no longer suppressed in optimized builds
+* Add ability to `await` all interval objects inside a coroutine (#909)
+* Fix garbled data when doing `base.win.properties.size` et al.
+* Fix Filename division operator in Python 3
+* Add Pythonic property syntax to various Panda classes
+* Add optional `delay=` argument to `taskMgr.add()`
+* Fix CollisionHandler's again_patterns property
+
+Physics / Collisions
+* Fix missing import in nodePath.subdivideCollisions() (#1084)
+* Support pickling of CollisionTraverser and CollisionHandler objects (#1090)
+* Improve CollisionBox debug visualization
+* Fix broken GlobalForceGroup module
+* Fix writing representation of AngularVectorForce
+* Better handling for some edge cases in PhysicalNode
+* Support bam serialization of BulletGhostNode objects (#1099)
+
+Deployment
+* Fix app immediately exiting on Windows if log_filename is not set
+* Allow generating log files with date/time in filename (#1103)
+* Automatically removes aux-display Config.prc lines for excluded plug-ins
+* No longer defaults to FMOD audio on macOS unless FMOD is explicitly bundled
+* Fix every app pulling in pep517 and numpy when building on some Linux distros
+* Fix building with pytz and pandas requirements
+* Some deterministic build support with PYTHONHASHSEED=0 and SOURCE_DATE_EPOCH
+* Fix harmless warnings about missing dependencies
+* Fix libbz2, libreadline and liblzma libraries being erroneously excluded
+
+Build / C++
+* Some macOS builds were erroneously not built with Cg shader support (#1119)
+* Fix code signatures being missing from arm64 wheels on macOS (#1123)
+* Windows builds now include missing DirectX 9 DLL (#1034)
+* Windows installer no longer pops up message box in silent mode (#1088)
+* Fix compatibility of Panda headers with C++17 and C++20 compilers
+* Support reproducible builds when SOURCE_DATE_EPOCH is set
+* Interrogate supports `__truediv__` and `__floordiv__` special methods
+* makepanda now looks for linux-libs-arm64 thirdparty directory in aarch64 build
+
+Miscellaneous
+* Assorted memory leaks fixed
+* Disable `vorbis-seek-lap`, which caused popping in ogg sounds (#1108)
+* Fix crash when playing video using older FFMpeg versions (#1085)
+* Fix flattening/egg loader bug messing up line strip geometry (#1122)
+* Fix egg lexer state not being cleaned up after error
+* multify respects SOURCE_DATE_EPOCH (but -T0 should be preferred)
+* Fix egg loader hanging when encountering unterminated quote or C-style comment
+* VirtualFileSystem::read_file() is now implemented for HTTP mounts
+* Fix regression causing crash in DIRECT tools
+* Fix repeatedly clicking object in DIRECT tools cause explosion of scaling node
+
 ------------------------  RELEASE 1.10.8  -----------------------
 
 Recommended maintenance release.

+ 1 - 1
doc/man/bam-info.1

@@ -15,7 +15,7 @@ List the scene graph hierarchy in the bam file.
 List explicitly each transition in the hierarchy.
 .TP
 .B \-g
-Output verbose information about the each Geom in the Bam file.
+Output verbose information about each Geom in the Bam file.
 .TP
 .B \-h
 Display this help page.

+ 0 - 53
dtool/src/dtoolbase/dtoolbase_cc.h

@@ -112,61 +112,8 @@ typedef std::ios::seekdir ios_seekdir;
 #define INLINE inline
 #endif
 
-// Apple has an outdated libstdc++.  Not all is lost, though, as we can fill
-// in some important missing functions.
-#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
-#include <tr1/tuple>
-#include <tr1/cmath>
-
-namespace std {
-  using std::tr1::tuple;
-  using std::tr1::tie;
-  using std::tr1::copysign;
-
-  typedef decltype(nullptr) nullptr_t;
-
-  template<class T> struct remove_reference      {typedef T type;};
-  template<class T> struct remove_reference<T&>  {typedef T type;};
-  template<class T> struct remove_reference<T&& >{typedef T type;};
-
-  template<class T> typename remove_reference<T>::type &&move(T &&t) {
-    return static_cast<typename remove_reference<T>::type&&>(t);
-  }
-
-  template<class T> struct owner_less;
-
-  typedef enum memory_order {
-    memory_order_relaxed,
-    memory_order_consume,
-    memory_order_acquire,
-    memory_order_release,
-    memory_order_acq_rel,
-    memory_order_seq_cst,
-  } memory_order;
-
-  #define ATOMIC_FLAG_INIT { 0 }
-  class atomic_flag {
-    bool _flag;
-
-  public:
-    atomic_flag() noexcept = default;
-    ALWAYS_INLINE constexpr atomic_flag(bool flag) noexcept : _flag(flag) {}
-    atomic_flag(const atomic_flag &) = delete;
-    ~atomic_flag() noexcept = default;
-    atomic_flag &operator = (const atomic_flag&) = delete;
-
-    ALWAYS_INLINE bool test_and_set(memory_order order = memory_order_seq_cst) noexcept {
-      return __atomic_test_and_set(&_flag, order);
-    }
-    ALWAYS_INLINE void clear(memory_order order = memory_order_seq_cst) noexcept {
-      __atomic_clear(&_flag, order);
-    }
-  };
-};
-#else
 // Expect that we have access to the <atomic> header.
 #define PHAVE_ATOMIC 1
-#endif
 
 // Determine the availability of C++11 features.
 #if defined(_MSC_VER) && _MSC_VER < 1900 // Visual Studio 2015

+ 4 - 0
dtool/src/dtoolutil/dSearchPath.cxx

@@ -242,6 +242,10 @@ get_directory(size_t n) const {
  */
 Filename DSearchPath::
 find_file(const Filename &filename) const {
+  if (filename.empty()) {
+    return string();
+  }
+
   if (filename.is_local()) {
     if (_directories.empty()) {
       // Let's say an empty search path is the same as a search path

+ 1 - 1
dtool/src/dtoolutil/filename.I

@@ -608,7 +608,7 @@ compare_to(const Filename &other) const {
  * instead.
  */
 INLINE bool Filename::
-__nonzero__() const {
+__bool__() const {
   return !_filename.empty();
 }
 

+ 10 - 10
dtool/src/dtoolutil/filename.cxx

@@ -1876,13 +1876,13 @@ open_read(std::ifstream &stream) const {
 #endif
 
   stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
   wstring os_specific = to_os_specific_w();
   stream.open(os_specific.c_str(), open_mode);
 #else
   string os_specific = to_os_specific();
   stream.open(os_specific.c_str(), open_mode);
-#endif  // _WIN32
+#endif  // _MSC_VER
 
   return (!stream.fail());
 }
@@ -1926,11 +1926,11 @@ open_write(std::ofstream &stream, bool truncate) const {
 #endif
 
   stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
   wstring os_specific = to_os_specific_w();
 #else
   string os_specific = to_os_specific();
-#endif  // _WIN32
+#endif  // _MSC_VER
   stream.open(os_specific.c_str(), open_mode);
 
   return (!stream.fail());
@@ -1958,11 +1958,11 @@ open_append(std::ofstream &stream) const {
 #endif
 
   stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
   wstring os_specific = to_os_specific_w();
 #else
   string os_specific = to_os_specific();
-#endif  // _WIN32
+#endif  // _MSC_VER
   stream.open(os_specific.c_str(), open_mode);
 
   return (!stream.fail());
@@ -2000,11 +2000,11 @@ open_read_write(std::fstream &stream, bool truncate) const {
 #endif
 
   stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
   wstring os_specific = to_os_specific_w();
 #else
   string os_specific = to_os_specific();
-#endif  // _WIN32
+#endif  // _MSC_VER
   stream.open(os_specific.c_str(), open_mode);
 
   return (!stream.fail());
@@ -2032,11 +2032,11 @@ open_read_append(std::fstream &stream) const {
 #endif
 
   stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
   wstring os_specific = to_os_specific_w();
 #else
   string os_specific = to_os_specific();
-#endif  // _WIN32
+#endif  // _MSC_VER
   stream.open(os_specific.c_str(), open_mode);
 
   return (!stream.fail());

+ 1 - 1
dtool/src/dtoolutil/filename.h

@@ -232,7 +232,7 @@ PUBLISHED:
   INLINE bool operator != (const std::string &other) const;
   INLINE bool operator < (const std::string &other) const;
   INLINE int compare_to(const Filename &other) const;
-  INLINE bool __nonzero__() const;
+  INLINE bool __bool__() const;
   int get_hash() const;
 
   INLINE void output(std::ostream &out) const;

+ 1 - 1
dtool/src/dtoolutil/pandaFileStreamBuf.cxx

@@ -333,7 +333,7 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
       // Posix case.
       {
         off_t li = lseek(_fd, off, SEEK_END);
-        if (li == (off_t)-1) {
+        if (li == (off_t)-1 || (sizeof(off_t) == 8 && li == 0x7fffffffffffffff)) {
           return -1;
         }
         new_pos = (size_t)li;

+ 14 - 49
dtool/src/interrogate/functionRemap.cxx

@@ -419,6 +419,17 @@ get_call_str(const string &container, const vector_string &pexprs) const {
       call << ')';
     }
 
+  } else if (_type == T_item_assignment_operator) {
+    call << "(";
+    _parameters[0]._remap->pass_parameter(call, container);
+    call << ")[";
+
+    size_t pn = _first_true_parameter;
+    _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
+    call << "] = ";
+    ++pn;
+    _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
+
   } else {
     const char *separator = "";
 
@@ -468,11 +479,6 @@ get_call_str(const string &container, const vector_string &pexprs) const {
     size_t pn = _first_true_parameter;
     size_t num_parameters = pexprs.size();
 
-    if (_type == T_item_assignment_operator) {
-      // The last parameter is the value to set.
-      --num_parameters;
-    }
-
     for (pn = _first_true_parameter;
          pn < num_parameters; ++pn) {
       nassertd(pn < _parameters.size()) break;
@@ -481,11 +487,6 @@ get_call_str(const string &container, const vector_string &pexprs) const {
       separator = ", ";
     }
     call << ")";
-
-    if (_type == T_item_assignment_operator) {
-      call << " = ";
-      _parameters[pn]._remap->pass_parameter(call, get_parameter_expr(pn, pexprs));
-    }
   }
 
   return call.str();
@@ -568,6 +569,9 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
 
   } else if ((ifunc._flags & InterrogateFunction::F_setter) != 0) {
     _type = T_setter;
+
+  } else if ((ifunc._flags & InterrogateFunction::F_item_assignment) != 0) {
+    _type = T_item_assignment_operator;
   }
 
   if ((_cppfunc->_storage_class & CPPInstance::SC_blocking) != 0) {
@@ -623,14 +627,6 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
         fname == "operator <<=" ||
         fname == "operator >>=") {
       _type = T_assignment_method;
-
-    } else if (fname == "operator []" && !_const_method && rtype != nullptr) {
-       // Check if this is an item-assignment operator.
-      CPPReferenceType *reftype = rtype->as_reference_type();
-      if (reftype != nullptr && reftype->_pointing_at->as_const_type() == nullptr) {
-        // It returns a mutable reference.
-        _type = T_item_assignment_operator;
-      }
     }
   }
 
@@ -707,37 +703,6 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
       }
     }
 
-  } else if (_type == T_item_assignment_operator) {
-    // An item-assignment method isn't really a thing in C++, but it is in
-    // scripting languages, so we use this to denote item-access operators
-    // that return a non-const reference.
-
-    if (_cpptype == nullptr) {
-      nout << "Method " << *_cppfunc << " has no struct type\n";
-      return false;
-    } else {
-      // Synthesize a const reference parameter for the assignment.
-      CPPType *bare_type = TypeManager::unwrap_reference(rtype);
-      CPPType *const_type = CPPType::new_type(new CPPConstType(bare_type));
-      CPPType *ref_type = CPPType::new_type(new CPPReferenceType(const_type));
-
-      Parameter param;
-      param._has_name = true;
-      param._name = "assign_val";
-      param._remap = interface_maker->remap_parameter(_cpptype, ref_type);
-
-      if (param._remap == nullptr || !param._remap->is_valid()) {
-        nout << "Invalid remap for assignment type of method " << *_cppfunc << "\n";
-        return false;
-      }
-      _parameters.push_back(param);
-
-      // Pretend we don't return anything at all.
-      CPPType *void_type = TypeManager::get_void_type();
-      _return_type = interface_maker->remap_parameter(_cpptype, void_type);
-      _void_return = true;
-    }
-
   } else if (fname == "operator <=>") {
     // This returns an opaque object that we must leave unchanged.
     _return_type = new ParameterRemapUnchanged(rtype);

+ 110 - 43
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -74,6 +74,7 @@ RenameSet methodRenameDictionary[] = {
   { "operator ="    , "assign",                 0 },
   { "operator ()"   , "__call__",               0 },
   { "operator []"   , "__getitem__",            0 },
+  { "operator [] =" , "__setitem__",            0 },
   { "operator ++unary", "increment",            0 },
   { "operator ++"   , "increment",              0 },
   { "operator --unary", "decrement",            0 },
@@ -103,7 +104,8 @@ RenameSet methodRenameDictionary[] = {
   { "operator ->"   , "dereference",            0 },
   { "operator <<="  , "__ilshift__",            1 },
   { "operator >>="  , "__irshift__",            1 },
-  { "operator typecast bool", "__nonzero__",    0 },
+  { "operator typecast bool", "__bool__",       0 },
+  { "__bool__"      , "__bool__",               0 },
   { "__nonzero__"   , "__nonzero__",            0 },
   { "__int__"       , "__int__",                0 },
   { "__reduce__"    , "__reduce__",             0 },
@@ -305,7 +307,9 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
   string method_name = func->_ifunc.get_name();
   bool is_unary_op = func->_ifunc.is_unary_op();
 
-  if (method_name == "operator +") {
+  if (method_name == "operator +" ||
+      method_name == "__add__" ||
+      method_name == "__radd__") {
     def._answer_location = "nb_add";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -317,13 +321,17 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "operator -") {
+  if (method_name == "operator -" ||
+      method_name == "__sub__" ||
+      method_name == "__rsub__") {
     def._answer_location = "nb_subtract";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator *") {
+  if (method_name == "operator *" ||
+      method_name == "__mul__" ||
+      method_name == "__rmul__") {
     def._answer_location = "nb_multiply";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -335,37 +343,47 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "__truediv__") {
+  if (method_name == "__truediv__" ||
+      method_name == "__rtruediv__") {
     def._answer_location = "nb_true_divide";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "__floordiv__") {
+  if (method_name == "__floordiv__" ||
+      method_name == "__rfloordiv__") {
     def._answer_location = "nb_floor_divide";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator %") {
+  if (method_name == "operator %" ||
+      method_name == "__mod__" ||
+      method_name == "__rmod__") {
     def._answer_location = "nb_remainder";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator <<") {
+  if (method_name == "operator <<" ||
+      method_name == "__lshift__" ||
+      method_name == "__rlshift__") {
     def._answer_location = "nb_lshift";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator >>") {
+  if (method_name == "operator >>" ||
+      method_name == "__rshift__" ||
+      method_name == "__rrshift__") {
     def._answer_location = "nb_rshift";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator ^") {
+  if (method_name == "operator ^" ||
+      method_name == "__xor__" ||
+      method_name == "__rxor__") {
     def._answer_location = "nb_xor";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -377,13 +395,17 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
   }
 
-  if (method_name == "operator &") {
+  if (method_name == "operator &" ||
+      method_name == "__and__" ||
+      method_name == "__rand__") {
     def._answer_location = "nb_and";
     def._wrapper_type = WT_binary_operator;
     return true;
   }
 
-  if (method_name == "operator |") {
+  if (method_name == "operator |" ||
+      method_name == "__or__" ||
+      method_name == "__ror__") {
     def._answer_location = "nb_or";
     def._wrapper_type = WT_binary_operator;
     return true;
@@ -1894,16 +1916,8 @@ write_module_class(ostream &out, Object *obj) {
         break;
 
       case WT_one_param:
-      case WT_binary_operator:
-      case WT_inplace_binary_operator:
         // PyObject *func(PyObject *self, PyObject *one)
         {
-          int return_flags = RF_err_null;
-          if (rfi->second._wrapper_type == WT_inplace_binary_operator) {
-            return_flags |= RF_self;
-          } else {
-            return_flags |= RF_pyobject;
-          }
           bool all_nonconst = true;
           for (FunctionRemap *remap : def._remaps) {
             if (remap->_const_method) {
@@ -1916,20 +1930,7 @@ write_module_class(ostream &out, Object *obj) {
           out << "//////////////////\n";
           out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
           out << "  " << cClassName << " *local_this = nullptr;\n";
-          if (rfi->second._wrapper_type != WT_one_param) {
-            // WT_binary_operator means we must return NotImplemented, instead
-            // of raising an exception, if the this pointer doesn't match.
-            // This is for things like __sub__, which Python likes to call on
-            // the wrong-type objects.
-            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
-            if (all_nonconst) {
-              out << "  if (local_this == nullptr || DtoolInstance_IS_CONST(self)) {\n";
-            } else {
-              out << "  if (local_this == nullptr) {\n";
-            }
-            out << "    Py_INCREF(Py_NotImplemented);\n";
-            out << "    return Py_NotImplemented;\n";
-          } else if (all_nonconst) {
+          if (all_nonconst) {
             out << "  if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_"
                 << ClassName << ", (void **)&local_this, \"" << ClassName
                 << "." << methodNameFromCppName(fname, "", false) << "\")) {\n";
@@ -1942,19 +1943,85 @@ write_module_class(ostream &out, Object *obj) {
 
           string expected_params;
           write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
-                                AT_single_arg, return_flags, false, !all_nonconst);
+                                AT_single_arg, RF_err_null | RF_pyobject, false, !all_nonconst);
+
+          out << "  if (!_PyErr_OCCURRED()) {\n";
+          out << "    return Dtool_Raise_BadArgumentsError(\n";
+          output_quoted(out, 6, expected_params);
+          out << ");\n";
+          out << "  }\n";
+          out << "  return nullptr;\n";
+          out << "}\n\n";
+        }
+        break;
 
-          if (rfi->second._wrapper_type != WT_one_param) {
-            out << "  Py_INCREF(Py_NotImplemented);\n";
-            out << "  return Py_NotImplemented;\n";
+      case WT_binary_operator:
+      case WT_inplace_binary_operator:
+        // PyObject *func(PyObject *self, PyObject *one)
+        {
+          int return_flags = RF_err_null;
+          if (rfi->second._wrapper_type == WT_inplace_binary_operator) {
+            return_flags |= RF_self;
           } else {
-            out << "  if (!_PyErr_OCCURRED()) {\n";
-            out << "    return Dtool_Raise_BadArgumentsError(\n";
-            output_quoted(out, 6, expected_params);
-            out << ");\n";
+            return_flags |= RF_pyobject;
+          }
+          bool forward_all_nonconst = true;
+          bool reverse_all_nonconst = true;
+          set<FunctionRemap *> forward_remaps;
+          set<FunctionRemap *> reverse_remaps;
+          for (FunctionRemap *remap : def._remaps) {
+            std::string fname = remap->_cppfunc->get_simple_name();
+            if (fname.compare(0, 3, "__r") == 0 && fname != "__rshift__") {
+              reverse_remaps.insert(remap);
+              if (remap->_const_method) {
+                reverse_all_nonconst = false;
+              }
+            } else {
+              forward_remaps.insert(remap);
+              if (remap->_const_method) {
+                forward_all_nonconst = false;
+              }
+            }
+          }
+          out << "//////////////////\n";
+          out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
+          out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
+          out << "//////////////////\n";
+          out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
+          out << "  " << cClassName << " *local_this = nullptr;\n";
+          // WT_binary_operator means we must return NotImplemented, instead
+          // of raising an exception, if the this pointer doesn't match.
+          // This is for things like __sub__, which Python likes to call on
+          // the wrong-type objects.
+          if (!forward_remaps.empty()) {
+            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
+            if (forward_all_nonconst) {
+              out << "  if (local_this != nullptr && !DtoolInstance_IS_CONST(self)) {\n";
+            } else {
+              out << "  if (local_this != nullptr) {\n";
+            }
+            string expected_params;
+            write_function_forset(out, forward_remaps, 1, 1, expected_params, 4, true, true,
+                                  AT_single_arg, return_flags, false, !forward_all_nonconst);
             out << "  }\n";
-            out << "  return nullptr;\n";
           }
+
+          if (!reverse_remaps.empty()) {
+            out << "  std::swap(self, arg);\n";
+            out << "  DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
+            if (reverse_all_nonconst) {
+              out << "  if (local_this != nullptr && !DtoolInstance_IS_CONST(self)) {\n";
+            } else {
+              out << "  if (local_this != nullptr) {\n";
+            }
+            string expected_params;
+            write_function_forset(out, reverse_remaps, 1, 1, expected_params, 4, true, true,
+                                  AT_single_arg, return_flags, false, !reverse_all_nonconst);
+            out << "  }\n";
+          }
+
+          out << "  Py_INCREF(Py_NotImplemented);\n";
+          out << "  return Py_NotImplemented;\n";
           out << "}\n\n";
         }
         break;

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

@@ -3012,6 +3012,38 @@ define_method(CPPInstance *function, InterrogateType &itype,
                index) == itype._methods.end()) {
         itype._methods.push_back(index);
       }
+
+      // For an operator [] returning a non-const reference, we synthesize an
+      // "item-assignment" operator, which does not exist in C++ but does in
+      // scripting languages.  This allows `obj[n] = ...`
+      if (ftype->_return_type != nullptr &&
+          ftype->_return_type->is_reference() &&
+          !ftype->_return_type->remove_reference()->is_const() &&
+          (ftype->_flags & CPPFunctionType::F_const_method) == 0 &&
+          function->get_simple_name() == "operator []") {
+
+        // Make up a CPPFunctionType with extra parameter.
+        CPPType *assign_type = TypeManager::wrap_const_reference(ftype->_return_type->remove_reference());
+        CPPParameterList *params = new CPPParameterList(*(ftype->_parameters));
+        CPPInstance *param1 = new CPPInstance(assign_type, "assign_val");
+        params->_parameters.push_back(param1);
+        CPPType *void_type = TypeManager::get_void_type();
+        CPPFunctionType *ftype = new CPPFunctionType(void_type, params, 0);
+
+        // Now make up an instance for the function.
+        CPPInstance *function = new CPPInstance(ftype, "operator [] =");
+        function->_ident->_native_scope = scope;
+
+        FunctionIndex index = get_function(function, "",
+                                           struct_type, scope,
+                                           InterrogateFunction::F_item_assignment);
+        if (index != 0) {
+          if (find(itype._methods.begin(), itype._methods.end(),
+                   index) == itype._methods.end()) {
+            itype._methods.push_back(index);
+          }
+        }
+      }
     }
   }
 }

+ 1 - 0
dtool/src/interrogatedb/interrogateFunction.h

@@ -70,6 +70,7 @@ private:
     F_setter          = 0x0020,
     F_unary_op        = 0x0040,
     F_operator_typecast = 0x0080,
+    F_item_assignment = 0x0100,
   };
 
   int _flags;

+ 1 - 1
dtool/src/interrogatedb/py_wrappers.cxx

@@ -371,7 +371,7 @@ static PyObject *Dtool_MutableSequenceWrapper_insert(PyObject *self, PyObject *a
       return PyErr_Format(PyExc_TypeError, "%s.insert() does not support negative indices", wrap->_base._name);
     }
   }
-  return wrap->_insert_func(wrap->_base._self, (ssize_t)std::max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
+  return wrap->_insert_func(wrap->_base._self, (size_t)std::max(index, (Py_ssize_t)0), PyTuple_GET_ITEM(args, 1));
 }
 
 /**

+ 8 - 0
dtool/src/prc/configVariableDouble.I

@@ -138,3 +138,11 @@ INLINE void ConfigVariableDouble::
 set_word(size_t n, double value) {
   set_double_word(n, value);
 }
+
+/**
+ * Returns true if the variable is not 0.0.
+ */
+INLINE bool ConfigVariableDouble::
+__bool__() const {
+  return get_value() != 0.0;
+}

+ 2 - 0
dtool/src/prc/configVariableDouble.h

@@ -46,6 +46,8 @@ PUBLISHED:
   INLINE double get_word(size_t n) const;
   INLINE void set_word(size_t n, double value);
 
+  INLINE bool __bool__() const;
+
 private:
   void set_default_value(double default_value);
 

+ 8 - 0
dtool/src/prc/configVariableFilename.I

@@ -218,6 +218,14 @@ set_word(size_t n, const Filename &value) {
   set_string_word(n, value);
 }
 
+/**
+ * Returns true if the variable is not empty.
+ */
+INLINE bool ConfigVariableFilename::
+__bool__() const {
+  return !get_value().empty();
+}
+
 /**
  * Returns the variable's value, as a reference into the config variable
  * itself.  This is the internal method that implements get_value(), which

+ 2 - 0
dtool/src/prc/configVariableFilename.h

@@ -60,6 +60,8 @@ PUBLISHED:
   INLINE Filename get_word(size_t n) const;
   INLINE void set_word(size_t n, const Filename &value);
 
+  INLINE bool __bool__() const;
+
 private:
   void reload_cache();
   INLINE const Filename &get_ref_value() const;

+ 8 - 0
dtool/src/prc/configVariableInt.I

@@ -138,3 +138,11 @@ INLINE void ConfigVariableInt::
 set_word(size_t n, int value) {
   set_int_word(n, value);
 }
+
+/**
+ * Returns true if the variable is not 0.
+ */
+INLINE bool ConfigVariableInt::
+__bool__() const {
+  return get_value() != 0;
+}

+ 2 - 0
dtool/src/prc/configVariableInt.h

@@ -46,6 +46,8 @@ PUBLISHED:
   INLINE int get_word(size_t n) const;
   INLINE void set_word(size_t n, int value);
 
+  INLINE bool __bool__() const;
+
 private:
   void set_default_value(int default_value);
 

+ 8 - 0
dtool/src/prc/configVariableInt64.I

@@ -138,3 +138,11 @@ INLINE void ConfigVariableInt64::
 set_word(size_t n, int64_t value) {
   set_int64_word(n, value);
 }
+
+/**
+ * Returns true if the variable is not empty.
+ */
+INLINE bool ConfigVariableInt64::
+__bool__() const {
+  return get_value() != 0;
+}

+ 2 - 0
dtool/src/prc/configVariableInt64.h

@@ -47,6 +47,8 @@ PUBLISHED:
   INLINE int64_t get_word(size_t n) const;
   INLINE void set_word(size_t n, int64_t value);
 
+  INLINE bool __bool__() const;
+
 private:
   void set_default_value(int64_t default_value);
 

+ 8 - 0
dtool/src/prc/configVariableString.I

@@ -160,3 +160,11 @@ INLINE void ConfigVariableString::
 set_word(size_t n, const std::string &value) {
   set_string_word(n, value);
 }
+
+/**
+ * Returns true if the variable is not empty.
+ */
+INLINE bool ConfigVariableString::
+__bool__() const {
+  return !get_value().empty();
+}

+ 2 - 0
dtool/src/prc/configVariableString.h

@@ -49,6 +49,8 @@ PUBLISHED:
   INLINE std::string get_word(size_t n) const;
   INLINE void set_word(size_t n, const std::string &value);
 
+  INLINE bool __bool__() const;
+
 private:
   void reload_cache();
 

+ 12 - 0
makepanda/installer.nsi

@@ -374,6 +374,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
         !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
+        !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
@@ -381,6 +382,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
         !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
+        !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
     !endif
 SectionGroupEnd
 
@@ -489,6 +491,7 @@ Function .onInit
         ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9-32
         !insertmacro MaybeEnablePyBindingSection 3.10-32
+        !insertmacro MaybeEnablePyBindingSection 3.11-32
         ${EndIf}
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
@@ -498,6 +501,7 @@ Function .onInit
         ${If} ${AtLeastWin8}
         !insertmacro MaybeEnablePyBindingSection 3.9
         !insertmacro MaybeEnablePyBindingSection 3.10
+        !insertmacro MaybeEnablePyBindingSection 3.11
         ${EndIf}
     !endif
 
@@ -511,6 +515,10 @@ Function .onInit
         SectionSetFlags ${SecPyBindings3.10} ${SF_RO}
         SectionSetInstTypes ${SecPyBindings3.10} 0
     !endif
+    !ifdef SecPyBindings3.11
+        SectionSetFlags ${SecPyBindings3.11} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.11} 0
+    !endif
     ${EndUnless}
 FunctionEnd
 
@@ -822,6 +830,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.8-32
         !insertmacro RemovePythonPath 3.9-32
         !insertmacro RemovePythonPath 3.10-32
+        !insertmacro RemovePythonPath 3.11-32
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
@@ -829,6 +838,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.8
         !insertmacro RemovePythonPath 3.9
         !insertmacro RemovePythonPath 3.10
+        !insertmacro RemovePythonPath 3.11
     !endif
 
     SetDetailsPrint both
@@ -897,6 +907,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
@@ -904,6 +915,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
   !endif
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 35 - 9
makepanda/makepanda.py

@@ -646,6 +646,7 @@ if (COMPILER == "MSVC"):
         else:
             LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib")
         IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/OpenEXR")
+        IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/Imath")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
     if (PkgSkip("ZLIB")==0):     LibName("ZLIB",     GetThirdpartyDir() + "zlib/lib/zlibstatic.lib")
     if (PkgSkip("VRPN")==0):     LibName("VRPN",     GetThirdpartyDir() + "vrpn/lib/vrpn.lib")
@@ -789,6 +790,7 @@ if (COMPILER=="GCC"):
         if (os.path.isdir("/usr/PCBSD")):
             IncDirectory("ALWAYS", "/usr/PCBSD/local/include")
             LibDirectory("ALWAYS", "/usr/PCBSD/local/lib")
+        SmartPkgEnable("INOTIFY", "libinotify", ("inotify"), "sys/inotify.h")
 
     if GetTarget() != "windows":
         PkgDisable("DIRECTCAM")
@@ -812,7 +814,7 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("OPENAL",    "openal",    ("openal"), "AL/al.h", framework = "OpenAL")
     SmartPkgEnable("SQUISH",    "",          ("squish"), "squish.h")
     SmartPkgEnable("TIFF",      "libtiff-4", ("tiff"), "tiff.h")
-    SmartPkgEnable("OPENEXR",   "OpenEXR",   ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), ("OpenEXR", "OpenEXR/ImfOutputFile.h"))
+    SmartPkgEnable("OPENEXR",   "OpenEXR",   ("IlmImf", "Imath", "Half", "Iex", "IexMath", "IlmThread"), ("OpenEXR", "Imath", "OpenEXR/ImfOutputFile.h"))
     SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
     SmartPkgEnable("OPUS",      "opusfile",  ("opusfile", "opus", "ogg"), ("ogg/ogg.h", "opus/opusfile.h", "opus"))
     SmartPkgEnable("JPEG",      "",          ("jpeg"), "jpeglib.h")
@@ -967,7 +969,8 @@ if (COMPILER=="GCC"):
         # CgGL is covered by the Cg framework, and we don't need X11 components on OSX
         if not PkgSkip("NVIDIACG"):
             SmartPkgEnable("CGGL", "", ("CgGL"), "Cg/cgGL.h", thirdparty_dir = "nvidiacg")
-        SmartPkgEnable("X11", "x11", "X11", ("X11", "X11/Xlib.h", "X11/XKBlib.h"))
+        if GetTarget() != "android":
+            SmartPkgEnable("X11", "x11", "X11", ("X11", "X11/Xlib.h", "X11/XKBlib.h"))
 
     if GetHost() != "darwin":
         # Workaround for an issue where pkg-config does not include this path
@@ -1080,6 +1083,14 @@ if not PkgSkip("EIGEN"):
             # will turn them into runtime assertions.
             DefSymbol("ALWAYS", "EIGEN_NO_STATIC_ASSERT")
 
+if not PkgSkip("EGL"):
+    DefSymbol('EGL', 'HAVE_EGL', '')
+    if PkgSkip("X11"):
+        DefSymbol('EGL', 'EGL_NO_X11', '')
+
+if not PkgSkip("X11"):
+    DefSymbol('X11', 'USE_X11', '')
+
 ########################################################################
 ##
 ## Give a Status Report on Command-Line Options
@@ -2488,7 +2499,6 @@ def WriteConfigSettings():
         dtool_config["IS_FREEBSD"] = '1'
         dtool_config["PHAVE_ALLOCA_H"] = 'UNDEF'
         dtool_config["PHAVE_MALLOC_H"] = 'UNDEF'
-        dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF'
         dtool_config["HAVE_PROC_CURPROC_FILE"] = '1'
         dtool_config["HAVE_PROC_CURPROC_MAP"] = '1'
         dtool_config["HAVE_PROC_CURPROC_CMDLINE"] = '1'
@@ -2914,6 +2924,8 @@ configprc = configprc.replace('\r\n', '\n')
 
 if (GetTarget() == 'windows'):
     configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$USER_APPDATA/Panda3D-%s" % MAJOR_VERSION)
+elif not PkgSkip("X11") and not PkgSkip("GL") and not PkgSkip("EGL") and not GetLinkAllStatic():
+    configprc = configprc.replace("#load-display pandadx9", "aux-display p3headlessgl")
 else:
     configprc = configprc.replace("aux-display pandadx9", "")
 
@@ -3254,9 +3266,10 @@ elif GetTarget() == 'darwin':
 elif GetTarget() == 'android':
     CopyAllHeaders('panda/src/android')
     CopyAllHeaders('panda/src/androiddisplay')
-else:
+if not PkgSkip('X11'):
     CopyAllHeaders('panda/src/x11display')
-    CopyAllHeaders('panda/src/glxdisplay')
+    if not PkgSkip('GL'):
+        CopyAllHeaders('panda/src/glxdisplay')
 CopyAllHeaders('panda/src/egldisplay')
 CopyAllHeaders('panda/metalibs/pandagl')
 CopyAllHeaders('panda/metalibs/pandagles')
@@ -4070,7 +4083,7 @@ TargetAdd('libp3dxml.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dxml', 'SRCDIR:p
 OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
     'TIFF', 'OPENEXR', 'ZLIB', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
     'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
-    'SETUPAPI', 'IOKIT']
+    'SETUPAPI', 'INOTIFY', 'IOKIT']
 
 TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 
@@ -4679,7 +4692,6 @@ if GetTarget() == 'windows' and not PkgSkip("GL"):
 # If we're not compiling with any windowing system at all, but we do have EGL,
 # we can use that to create a headless libpandagl instead.
 if not PkgSkip("EGL") and not PkgSkip("GL") and PkgSkip("X11") and GetTarget() not in ('windows', 'darwin'):
-    DefSymbol('EGL', 'HAVE_EGL', '')
     OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGL', 'GL', 'EGL']
     TargetAdd('pandagl_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagl', 'BUILDING:PANDAGL', 'GL', 'EGL']
@@ -4691,13 +4703,27 @@ if not PkgSkip("EGL") and not PkgSkip("GL") and PkgSkip("X11") and GetTarget() n
     TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
     TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'EGL', 'CGGL'])
 
+elif not PkgSkip("EGL") and not PkgSkip("GL") and GetTarget() not in ('windows', 'darwin'):
+    # As a temporary solution for #1086, build this module, which we can use as a
+    # fallback to OpenGL for headless systems.
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGL', 'GL', 'EGL']
+    TargetAdd('p3headlessgl_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
+    OPTS=['DIR:panda/metalibs/pandagl', 'BUILDING:PANDAGL', 'GL', 'EGL']
+    TargetAdd('p3headlessgl_pandagl.obj', opts=OPTS, input='pandagl.cxx')
+    TargetAdd('libp3headlessgl.dll', input='p3headlessgl_pandagl.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3glgsg_config_glgsg.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3glgsg_glgsg.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3headlessgl_egldisplay_composite1.obj')
+    TargetAdd('libp3headlessgl.dll', input=COMMON_PANDA_LIBS)
+    TargetAdd('libp3headlessgl.dll', opts=['MODULE', 'GL', 'EGL', 'CGGL'])
+
 #
 # DIRECTORY: panda/src/egldisplay/
 #
 
 if not PkgSkip("EGL") and not PkgSkip("GLES"):
     DefSymbol('GLES', 'OPENGLES_1', '')
-    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES', 'GLES', 'EGL']
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES', 'GLES', 'EGL', 'X11']
     TargetAdd('pandagles_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagles', 'BUILDING:PANDAGLES', 'GLES', 'EGL']
     TargetAdd('pandagles_pandagles.obj', opts=OPTS, input='pandagles.cxx')
@@ -4716,7 +4742,7 @@ if not PkgSkip("EGL") and not PkgSkip("GLES"):
 
 if not PkgSkip("EGL") and not PkgSkip("GLES2"):
     DefSymbol('GLES2', 'OPENGLES_2', '')
-    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL']
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL', 'X11']
     TargetAdd('pandagles2_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagles2', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL']
     TargetAdd('pandagles2_pandagles2.obj', opts=OPTS, input='pandagles2.cxx')

+ 6 - 6
makepanda/makepandacore.py

@@ -1357,22 +1357,22 @@ def GetThirdpartyDir():
         THIRDPARTYDIR = base + "/darwin-libs-a/"
 
     elif (target == 'linux'):
-        if (target_arch.startswith("arm")):
+        if target_arch in ("aarch64", "arm64"):
+            THIRDPARTYDIR = base + "/linux-libs-arm64/"
+        elif target_arch.startswith("arm"):
             THIRDPARTYDIR = base + "/linux-libs-arm/"
         elif (target_arch in ("x86_64", "amd64")):
             THIRDPARTYDIR = base + "/linux-libs-x64/"
-        elif target_arch == "aarch64":
-            THIRDPARTYDIR = base + "/linux-libs-aarch64/"
         else:
             THIRDPARTYDIR = base + "/linux-libs-a/"
 
     elif (target == 'freebsd'):
-        if (target_arch.startswith("arm")):
+        if target_arch in ("aarch64", "arm64"):
+            THIRDPARTYDIR = base + "/freebsd-libs-arm64/"
+        elif target_arch.startswith("arm"):
             THIRDPARTYDIR = base + "/freebsd-libs-arm/"
         elif (target_arch in ("x86_64", "amd64")):
             THIRDPARTYDIR = base + "/freebsd-libs-x64/"
-        elif target_arch == "aarch64":
-            THIRDPARTYDIR = base + "/freebsd-libs-aarch64/"
         else:
             THIRDPARTYDIR = base + "/freebsd-libs-a/"
 

+ 4 - 6
makepanda/makewheel.py

@@ -82,7 +82,7 @@ ABI_TAG = get_abi_tag()
 EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", ".sln"]
 
 # Plug-ins to install.
-PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"]
+PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio", "p3headlessgl"]
 
 # Libraries included in manylinux ABI that should be ignored.  See PEP 513/571/599.
 MANYLINUX_LIBS = [
@@ -95,6 +95,7 @@ MANYLINUX_LIBS = [
     # These are not mentioned in manylinux1 spec but should nonetheless always
     # be excluded.
     "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", "libdrm.so.2",
+    "libEGL.so.1", "libOpenGL.so.0", "libGLX.so.0", "libGLdispatch.so.0",
 ]
 
 # Binaries to never scan for dependencies on non-Windows systems.
@@ -408,7 +409,6 @@ class WheelFile(object):
                     deps_path = '@executable_path/../Frameworks'
                 else:
                     deps_path = '@loader_path'
-                remove_signature = False
                 loader_path = [os.path.dirname(source_path)]
                 for dep in deps:
                     if dep.endswith('/Python'):
@@ -450,11 +450,9 @@ class WheelFile(object):
                         continue
 
                     subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name])
-                    remove_signature = True
 
-                # Remove the codesign signature if we modified the library.
-                if remove_signature:
-                    subprocess.call(["codesign", "--remove-signature", temp.name])
+                # Make sure it has an ad-hoc code signature.
+                subprocess.call(["codesign", "-f", "-s", "-", temp.name])
             else:
                 # On other unixes, we just add dependencies normally.
                 for dep in deps:

+ 6 - 2
makepanda/test_wheel.py

@@ -38,8 +38,12 @@ def test_wheel(wheel, verbose=False):
 
     # Install pytest into the environment, as well as our wheel.
     packages = ["pytest", wheel]
-    if sys.version_info[0:2] == (3, 4) and sys.platform == "win32":
-        packages += ["colorama==0.4.1"]
+    if sys.version_info[0:2] == (3, 4):
+        if sys.platform == "win32":
+            packages += ["colorama==0.4.1"]
+
+        # See https://github.com/python-attrs/attrs/pull/807
+        packages += ["attrs<21"]
 
     if subprocess.call([python, "-m", "pip", "install"] + packages) != 0:
         shutil.rmtree(envdir)

+ 18 - 1
models/cmr12.egg

@@ -1815,6 +1815,14 @@
       0.978659 0 0
     }
     <Vertex> 476 {
+      0.0080542 0.491814264 0
+      <UV> { 0.948538 0.34035628 }
+    }
+    <Vertex> 477 {
+      0.267978 0.491814264 0
+      <UV> { 0.98848 0.34035628 }
+    }
+    <Vertex> 478 {
       0 1 0
     }
   }
@@ -2673,9 +2681,18 @@
       <VertexRef> { 460 <Ref> { vpool } }
     }
   }
+  <Group> 305 {
+    <Polygon> {
+      <TRef> { chars }
+      <VertexRef> { 276 277 476 477 <Ref> { vpool } }
+    }
+    <PointLight> {
+      <VertexRef> { 280 <Ref> { vpool } }
+    }
+  }
   <Group> ds {
     <PointLight> {
-      <VertexRef> { 476 <Ref> { vpool } }
+      <VertexRef> { 478 <Ref> { vpool } }
     }
   }
 }

+ 18 - 1
models/cmss12.egg

@@ -1815,6 +1815,14 @@
       0.978659 0 0
     }
     <Vertex> 476 {
+      0.174627 0.506365 0
+      <UV> { 0.531719 0.544 }
+    }
+    <Vertex> 477 {
+      0.0428523 0.506365 0
+      <UV> { 0.510343 0.544 }
+    }
+    <Vertex> 478 {
       0 1 0
     }
   }
@@ -2673,9 +2681,18 @@
       <VertexRef> { 460 <Ref> { vpool } }
     }
   }
+  <Group> 305 {
+    <Polygon> {
+      <TRef> { chars }
+      <VertexRef> { 231 232 476 477 <Ref> { vpool } }
+    }
+    <PointLight> {
+      <VertexRef> { 235 <Ref> { vpool } }
+    }
+  }
   <Group> ds {
     <PointLight> {
-      <VertexRef> { 476 <Ref> { vpool } }
+      <VertexRef> { 478 <Ref> { vpool } }
     }
   }
 }

+ 18 - 1
models/cmtt12.egg

@@ -1815,6 +1815,14 @@
       0.518605 0 0
     }
     <Vertex> 476 {
+      0.0609865 0.4672 0
+      <UV> { 0.305895 0.43927125 }
+    }
+    <Vertex> 477 {
+      0.457619 0.4672 0
+      <UV> { 0.37422 0.43927125 }
+    }
+    <Vertex> 478 {
       0 1 0
     }
   }
@@ -2678,9 +2686,18 @@
       <VertexRef> { 465 <Ref> { vpool } }
     }
   }
+  <Group> 305 {
+    <Polygon> {
+      <TRef> { chars }
+      <VertexRef> { 221 222 476 477 <Ref> { vpool } }
+    }
+    <PointLight> {
+      <VertexRef> { 225 <Ref> { vpool } }
+    }
+  }
   <Group> ds {
     <PointLight> {
-      <VertexRef> { 476 <Ref> { vpool } }
+      <VertexRef> { 478 <Ref> { vpool } }
     }
   }
 }

+ 10 - 3
panda/metalibs/pandagl/pandagl.cxx

@@ -23,7 +23,7 @@
 #include "glxGraphicsPipe.h"
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
 #include "config_egldisplay.h"
 #include "eglGraphicsPipe.h"
 #endif
@@ -54,7 +54,7 @@ init_libpandagl() {
   init_libglxdisplay();
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
   init_libegldisplay();
 #endif
 }
@@ -77,9 +77,16 @@ get_pipe_type_pandagl() {
   return glxGraphicsPipe::get_class_type().get_index();
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
   return eglGraphicsPipe::get_class_type().get_index();
 #endif
 
   return 0;
 }
+
+#if defined(HAVE_EGL) && !defined(USE_X11)
+int
+get_pipe_type_p3headlessgl() {
+  return eglGraphicsPipe::get_class_type().get_index();
+}
+#endif

+ 4 - 0
panda/metalibs/pandagl/pandagl.h

@@ -12,4 +12,8 @@
 EXPCL_PANDAGL void init_libpandagl();
 extern "C" EXPCL_PANDAGL int get_pipe_type_pandagl();
 
+#if defined(HAVE_EGL) && !defined(USE_X11)
+extern "C" EXPCL_PANDAGL int get_pipe_type_p3headlessgl();
+#endif
+
 #endif

+ 6 - 0
panda/metalibs/pandagles/CMakeLists.txt

@@ -20,6 +20,12 @@ add_metalib(pandagles ${MODULE_TYPE}
   COMPONENTS p3egldisplay_gles1 p3glesgsg p3glstuff p3x11display)
 unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
 
+if(HAVE_X11)
+  target_compile_definitions(pandagles PUBLIC USE_X11)
+else()
+  target_compile_definitions(pandagles PRIVATE EGL_NO_X11)
+endif()
+
 install(TARGETS pandagles
   EXPORT OpenGLES1 COMPONENT OpenGLES1
   DESTINATION ${MODULE_DESTINATION}

+ 6 - 0
panda/metalibs/pandagles2/CMakeLists.txt

@@ -10,6 +10,12 @@ add_metalib(pandagles2 ${MODULE_TYPE}
   COMPONENTS p3egldisplay_gles2 p3gles2gsg p3glstuff p3x11display)
 unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
 
+if(HAVE_X11)
+  target_compile_definitions(pandagles2 PUBLIC USE_X11)
+else()
+  target_compile_definitions(pandagles2 PRIVATE EGL_NO_X11)
+endif()
+
 install(TARGETS pandagles2
   EXPORT OpenGLES2 COMPONENT OpenGLES2
   DESTINATION ${MODULE_DESTINATION}

+ 6 - 5
panda/src/bullet/bulletDebugNode.I

@@ -20,7 +20,7 @@ INLINE BulletDebugNode::
 }
 
 /**
- *
+ * If true, displays collision shapes in wireframe mode.
  */
 INLINE void BulletDebugNode::
 show_wireframe(bool show) {
@@ -39,7 +39,8 @@ get_show_wireframe() const {
 }
 
 /**
- *
+ * If true, display limits defined for constraints, e.g. a pivot axis or maximum
+ * amplitude.
  */
 INLINE void BulletDebugNode::
 show_constraints(bool show) {
@@ -58,7 +59,7 @@ get_show_constraints() const {
 }
 
 /**
- *
+ * If true, displays axis aligned bounding boxes for objects.
  */
 INLINE void BulletDebugNode::
 show_bounding_boxes(bool show) {
@@ -77,7 +78,7 @@ get_show_bounding_boxes() const {
 }
 
 /**
- *
+ * If true, displays normal vectors for triangle mesh and heightfield faces.
  */
 INLINE void BulletDebugNode::
 show_normals(bool show) {
@@ -92,4 +93,4 @@ INLINE bool BulletDebugNode::
 get_show_normals() const {
 
   return _drawer._normals;
-}
+}

+ 1 - 11
panda/src/bullet/bulletDebugNode.cxx

@@ -48,6 +48,7 @@ BulletDebugNode(const char *name) : PandaNode(name) {
   set_bounds(bounds);
   set_final(true);
   set_overall_hidden(true);
+  set_renderable();
 }
 
 /**
@@ -151,17 +152,6 @@ draw_mask_changed() {
   }
 }
 
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise.  This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool BulletDebugNode::
-is_renderable() const {
-  return true;
-}
-
 /**
  * Adds the node's contents to the CullResult we are building up during the
  * cull traversal, so that it will be drawn at render time.  For most nodes

+ 0 - 1
panda/src/bullet/bulletDebugNode.h

@@ -55,7 +55,6 @@ public:
   virtual bool safe_to_combine_children() const;
   virtual bool safe_to_flatten_below() const;
 
-  virtual bool is_renderable() const;
   virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
 
 private:

+ 4 - 4
panda/src/bullet/bulletHeightfieldShape.cxx

@@ -44,7 +44,7 @@ BulletHeightfieldShape(const PNMImage &image, PN_stdfloat max_height, BulletUpAx
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          max_height,
                                          up,
                                          true, false);
@@ -102,7 +102,7 @@ BulletHeightfieldShape(Texture *tex, PN_stdfloat max_height, BulletUpAxis up) :
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          max_height,
                                          up,
                                          true, false);
@@ -127,7 +127,7 @@ BulletHeightfieldShape(const BulletHeightfieldShape &copy) {
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          _max_height,
                                          _up,
                                          true, false);
@@ -208,7 +208,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   _shape = new btHeightfieldTerrainShape(_num_rows,
                                          _num_cols,
-                                         _data,
+                                         (const void *)_data,
                                          _max_height,
                                          _up,
                                          true, false);

+ 10 - 5
panda/src/bullet/bulletPersistentManifold.cxx

@@ -49,6 +49,8 @@ get_contact_processing_threshold() const {
  */
 void BulletPersistentManifold::
 clear_manifold() {
+  nassertv_always(_manifold != nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
   _manifold->clearManifold();
@@ -59,6 +61,8 @@ clear_manifold() {
  */
 PandaNode *BulletPersistentManifold::
 get_node0() {
+  nassertr_always(_manifold != nullptr, nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
 #if BT_BULLET_VERSION >= 281
@@ -75,6 +79,8 @@ get_node0() {
  */
 PandaNode *BulletPersistentManifold::
 get_node1() {
+  nassertr_always(_manifold != nullptr, nullptr);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
 #if BT_BULLET_VERSION >= 281
@@ -91,6 +97,8 @@ get_node1() {
  */
 int BulletPersistentManifold::
 get_num_manifold_points() const {
+  nassertr_always(_manifold != nullptr, 0);
+
   LightMutexHolder holder(BulletWorld::get_global_lock());
 
   return _manifold->getNumContacts();
@@ -99,11 +107,8 @@ get_num_manifold_points() const {
 /**
  *
  */
-BulletManifoldPoint *BulletPersistentManifold::
+BulletManifoldPoint BulletPersistentManifold::
 get_manifold_point(int idx) const {
   LightMutexHolder holder(BulletWorld::get_global_lock());
-
-  nassertr(idx < _manifold->getNumContacts(), nullptr)
-
-  return new BulletManifoldPoint(_manifold->getContactPoint(idx));
+  return BulletManifoldPoint(_manifold->getContactPoint(idx));
 }

+ 1 - 1
panda/src/bullet/bulletPersistentManifold.h

@@ -34,7 +34,7 @@ PUBLISHED:
   PandaNode *get_node1();
 
   int get_num_manifold_points() const;
-  BulletManifoldPoint *get_manifold_point(int idx) const;
+  BulletManifoldPoint get_manifold_point(int idx) const;
   MAKE_SEQ(get_manifold_points, get_num_manifold_points, get_manifold_point);
 
   PN_stdfloat get_contact_breaking_threshold() const;

+ 4 - 4
panda/src/bullet/bulletWorld.cxx

@@ -125,7 +125,7 @@ BulletWorld() {
   // Some prefered settings
   _world->getDispatchInfo().m_enableSPU = true;      // default: true
   _world->getDispatchInfo().m_useContinuous = true;  // default: true
-  _world->getSolverInfo().m_splitImpulse = false;    // default: false
+  _world->getSolverInfo().m_splitImpulse = bullet_split_impulse;
   _world->getSolverInfo().m_numIterations = bullet_solver_iterations;
 }
 
@@ -1064,14 +1064,14 @@ contact_test_pair(PandaNode *node0, PandaNode *node1) const {
 /**
  *
  */
-BulletPersistentManifold *BulletWorld::
+BulletPersistentManifold BulletWorld::
 get_manifold(int idx) const {
   LightMutexHolder holder(get_global_lock());
 
-  nassertr(idx < _dispatcher->getNumManifolds(), nullptr);
+  nassertr(idx < _dispatcher->getNumManifolds(), BulletPersistentManifold(nullptr));
 
   btPersistentManifold *ptr = _dispatcher->getManifoldByIndexInternal(idx);
-  return (ptr) ? new BulletPersistentManifold(ptr) : nullptr;
+  return BulletPersistentManifold(ptr);
 }
 
 /**

+ 1 - 1
panda/src/bullet/bulletWorld.h

@@ -127,7 +127,7 @@ PUBLISHED:
 
   // Manifolds
   int get_num_manifolds() const;
-  BulletPersistentManifold *get_manifold(int idx) const;
+  BulletPersistentManifold get_manifold(int idx) const;
   MAKE_SEQ(get_manifolds, get_num_manifolds, get_manifold);
 
   // Collision filtering

+ 5 - 0
panda/src/bullet/config_bullet.cxx

@@ -100,6 +100,11 @@ PRC_DESC("Specifies if events should be send when new contacts are "
          "contact events might create more load on the event queue "
          "then you might want! Default value is FALSE."));
 
+ConfigVariableBool bullet_split_impulse
+("bullet-split-impulse", false,
+PRC_DESC("Penetrating recovery won't add momentum. "
+         "btContactSolverInfo::m_splitImpulse. Default value is false."));
+
 ConfigVariableInt bullet_solver_iterations
 ("bullet-solver-iterations", 10,
 PRC_DESC("Specifies the number of iterations for the Bullet contact "

+ 1 - 0
panda/src/bullet/config_bullet.h

@@ -33,6 +33,7 @@ extern ConfigVariableEnum<BulletWorld::BroadphaseAlgorithm> bullet_broadphase_al
 extern ConfigVariableEnum<BulletWorld::FilterAlgorithm> bullet_filter_algorithm;
 extern ConfigVariableDouble bullet_sap_extents;
 extern ConfigVariableBool bullet_enable_contact_events;
+extern ConfigVariableBool bullet_split_impulse;
 extern ConfigVariableInt bullet_solver_iterations;
 extern ConfigVariableBool bullet_additional_damping;
 extern ConfigVariableDouble bullet_additional_damping_linear_factor;

+ 1 - 4
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -68,12 +68,9 @@ protected:
   virtual void close_window();
   virtual bool open_window();
 
-  CGDisplayModeRef find_display_mode(int width, int height);
+  CFMutableArrayRef find_display_modes(int width, int height);
   bool do_switch_fullscreen(CGDisplayModeRef mode);
 
-  virtual void mouse_mode_absolute();
-  virtual void mouse_mode_relative();
-
 private:
   NSData *load_image_data(const Filename &filename);
   NSImage *load_image(const Filename &filename);

+ 98 - 72
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -628,18 +628,32 @@ open_window() {
 
   if (_properties.get_fullscreen()) {
     // Change the display mode.
-    CGDisplayModeRef mode;
-    mode = find_display_mode(_properties.get_x_size(),
-                             _properties.get_y_size());
+    CFMutableArrayRef modes;
+
+    modes = find_display_modes(_properties.get_x_size(),
+                               _properties.get_y_size());
+
+    if (CFArrayGetCount(modes) > 0) {
+      bool switched = false;
+      for (CFIndex i = 0; i < CFArrayGetCount(modes); i++) {
+        CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
+        if (do_switch_fullscreen(mode)) {
+          switched = true;
+          break;
+        }
+      }
+      CFRelease(modes);
 
-    if (mode == NULL) {
-      cocoadisplay_cat.error()
-        << "Could not find a suitable display mode!\n";
-      return false;
+      if (!switched) {
+        cocoadisplay_cat.error()
+          << "Failed to change display mode.\n";
+        return false;
+      }
 
-    } else if (!do_switch_fullscreen(mode)) {
+    } else {
       cocoadisplay_cat.error()
-        << "Failed to change display mode.\n";
+        << "Could not find a suitable display mode!\n";
+      CFRelease(modes);
       return false;
     }
   }
@@ -679,7 +693,7 @@ open_window() {
   // Enable relative mouse mode, if this was requested.
   if (_properties.has_mouse_mode() &&
       _properties.get_mouse_mode() == WindowProperties::M_relative) {
-    mouse_mode_relative();
+    CGAssociateMouseAndMouseCursorPosition(NO);
   }
 
   _vsync_enabled = sync_video && cocoagsg->setup_vsync();
@@ -733,22 +747,6 @@ close_window() {
   GraphicsWindow::close_window();
 }
 
-/**
- * Overridden from GraphicsWindow.
- */
-void CocoaGraphicsWindow::
-mouse_mode_absolute() {
-  CGAssociateMouseAndMouseCursorPosition(YES);
-}
-
-/**
- * Overridden from GraphicsWindow.
- */
-void CocoaGraphicsWindow::
-mouse_mode_relative() {
-  CGAssociateMouseAndMouseCursorPosition(NO);
-}
-
 /**
  * Applies the requested set of properties to the window, if possible, for
  * instance to request a change in size or minimization status.
@@ -787,38 +785,48 @@ set_properties_now(WindowProperties &properties) {
           height = _properties.get_y_size();
         }
 
-        CGDisplayModeRef mode;
-        mode = find_display_mode(width, height);
-
-        if (mode == NULL) {
-          cocoadisplay_cat.error()
-            << "Could not find a suitable display mode with size " << width
-            << "x" << height << "!\n";
+        CFMutableArrayRef modes = find_display_modes(width, height);
 
-        } else if (do_switch_fullscreen(mode)) {
-          if (_window != nil) {
-            // For some reason, setting the style mask makes it give up its
-            // first-responder status.
-            if ([_window respondsToSelector:@selector(setStyleMask:)]) {
-              [_window setStyleMask:NSBorderlessWindowMask];
+        if (CFArrayGetCount(modes) > 0) {
+          bool switched = false;
+          for (CFIndex i = 0; i < CFArrayGetCount(modes); i++) {
+            CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
+            if (do_switch_fullscreen(mode)) {
+              switched = true;
+              break;
             }
-            [_window makeFirstResponder:_view];
-            [_window setLevel:NSMainMenuWindowLevel+1];
-            [_window makeKeyAndOrderFront:nil];
           }
 
-          // We've already set the size property this way; clear it.
-          properties.clear_size();
-          _properties.set_size(width, height);
-          properties.clear_origin();
-          _properties.set_origin(0, 0);
-          properties.clear_fullscreen();
-          _properties.set_fullscreen(true);
+          if (switched) {
+            if (_window != nil) {
+              // For some reason, setting the style mask makes it give up its
+              // first-responder status.
+              if ([_window respondsToSelector:@selector(setStyleMask:)]) {
+                [_window setStyleMask:NSBorderlessWindowMask];
+              }
+              [_window makeFirstResponder:_view];
+              [_window setLevel:NSMainMenuWindowLevel+1];
+              [_window makeKeyAndOrderFront:nil];
+            }
 
+            // We've already set the size property this way; clear it.
+            properties.clear_size();
+            _properties.set_size(width, height);
+            properties.clear_origin();
+            _properties.set_origin(0, 0);
+            properties.clear_fullscreen();
+            _properties.set_fullscreen(true);
+
+          } else {
+            cocoadisplay_cat.error()
+              << "Failed to change display mode.\n";
+          }
         } else {
           cocoadisplay_cat.error()
-            << "Failed to change display mode.\n";
+            << "Could not find a suitable display mode with size " << width
+            << "x" << height << "!\n";
         }
+        CFRelease(modes);
 
       } else {
         do_switch_fullscreen(NULL);
@@ -864,22 +872,33 @@ set_properties_now(WindowProperties &properties) {
       properties.clear_size();
 
     } else {
-      CGDisplayModeRef mode = find_display_mode(width, height);
-
-      if (mode == NULL) {
-        cocoadisplay_cat.error()
-          << "Could not find a suitable display mode with size " << width
-          << "x" << height << "!\n";
+      CFMutableArrayRef modes = find_display_modes(width, height);
+
+      if (CFArrayGetCount(modes) > 0) {
+        bool switched = false;
+        for (CFIndex i = 0; i < CFArrayGetCount(modes); i++) {
+          CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
+          if (do_switch_fullscreen(mode)) {
+            switched = true;
+            break;
+          }
+        }
 
-      } else if (do_switch_fullscreen(mode)) {
-        // Yay!  Our resolution has changed.
-        _properties.set_size(width, height);
-        properties.clear_size();
+        if (switched) {
+          // Yay!  Our resolution has changed.
+          _properties.set_size(width, height);
+          properties.clear_size();
+        } else {
+          cocoadisplay_cat.error()
+            << "Failed to change display mode.\n";
+        }
 
       } else {
         cocoadisplay_cat.error()
-          << "Failed to change display mode.\n";
+          << "Could not find a suitable display mode with size " << width
+          << "x" << height << "!\n";
       }
+      CFRelease(modes);
     }
   }
 
@@ -1101,8 +1120,8 @@ set_properties_now(WindowProperties &properties) {
  * Returns an appropriate CGDisplayModeRef for the given width and height, or
  * NULL if none was found.
  */
-CGDisplayModeRef CocoaGraphicsWindow::
-find_display_mode(int width, int height) {
+CFMutableArrayRef CocoaGraphicsWindow::
+find_display_modes(int width, int height) {
   CFDictionaryRef options = NULL;
   // On macOS 10.15+ (Catalina), we want to select the display mode with the
   // samescaling factor as the current view to avoid cropping or scaling issues.
@@ -1122,6 +1141,9 @@ find_display_mode(int width, int height) {
                                  &kCFTypeDictionaryValueCallBacks);
   }
 #endif
+  CFMutableArrayRef valid_modes;
+  valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
   CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, options);
   if (options != NULL) {
     CFRelease(options);
@@ -1132,7 +1154,7 @@ find_display_mode(int width, int height) {
 
   // Get the current refresh rate and pixel encoding.
   CFStringRef current_pixel_encoding;
-  int refresh_rate;
+  double refresh_rate;
   mode = CGDisplayCopyDisplayMode(_display);
 
   // First check if the current mode is adequate.
@@ -1141,7 +1163,9 @@ find_display_mode(int width, int height) {
   if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_14 &&
       CGDisplayModeGetWidth(mode) == width &&
       CGDisplayModeGetHeight(mode) == height) {
-    return mode;
+    CFArrayAppendValue(valid_modes, mode);
+    CGDisplayModeRelease(mode);
+    return valid_modes;
   }
 
   current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
@@ -1164,7 +1188,7 @@ find_display_mode(int width, int height) {
     // the mode width and height but also actual pixel widh and height.
     if (CGDisplayModeGetWidth(mode) == width &&
         CGDisplayModeGetHeight(mode) == height &&
-        CGDisplayModeGetRefreshRate(mode) == refresh_rate &&
+        (int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
         (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_14 ||
         (CGDisplayModeGetPixelWidth(mode) == expected_pixel_width &&
@@ -1172,17 +1196,19 @@ find_display_mode(int width, int height) {
 #endif
         CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
 
-      CFRetain(mode);
-      CFRelease(pixel_encoding);
-      CFRelease(current_pixel_encoding);
-      CFRelease(modes);
-      return mode;
+      if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
+        // Exact match for refresh rate, prioritize this.
+        CFArrayInsertValueAtIndex(valid_modes, 0, mode);
+      } else {
+        CFArrayAppendValue(valid_modes, mode);
+      }
     }
+    CFRelease(pixel_encoding);
   }
 
   CFRelease(current_pixel_encoding);
   CFRelease(modes);
-  return NULL;
+  return valid_modes;
 }
 
 /**

+ 126 - 21
panda/src/collide/collisionLevelState.I

@@ -30,9 +30,9 @@ CollisionLevelState(const NodePath &node_path) :
  */
 template<class MaskType>
 INLINE CollisionLevelState<MaskType>::
-CollisionLevelState(const CollisionLevelState<MaskType> &parent, PandaNode *child) :
+CollisionLevelState(const CollisionLevelState<MaskType> &parent, const PandaNode::DownConnection &child, MaskType mask) :
   CollisionLevelStateBase(parent, child),
-  _current(parent._current)
+  _current(mask)
 {
 }
 #endif  // CPPPARSER
@@ -101,21 +101,21 @@ prepare_collider(const ColliderDef &def, const NodePath &root) {
 template<class MaskType>
 bool CollisionLevelState<MaskType>::
 any_in_bounds() {
-#ifndef NDEBUG
+#ifdef NDEBUG
+  const bool is_spam = false;
+#else
+  const bool is_spam = collide_cat.is_spam();
+#endif
   int indent_level = 0;
-  if (collide_cat.is_spam()) {
+  if (is_spam) {
     indent_level = _node_path.get_num_nodes() * 2;
     collide_cat.spam();
     indent(collide_cat.spam(false), indent_level)
       << "Considering " << _node_path.get_node_path() << "\n";
   }
-#endif  // NDEBUG
 
-  PandaNode *pnode = node();
-
-  CPT(BoundingVolume) node_bv = pnode->get_bounds();
-  if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
-    const GeometricBoundingVolume *node_gbv = (const GeometricBoundingVolume *)node_bv.p();
+  if (_node_gbv != nullptr) {
+    PandaNode *pnode = node();
     CollideMask this_mask = pnode->get_net_collide_mask();
 
     int num_colliders = get_num_colliders();
@@ -131,13 +131,11 @@ any_in_bounds() {
           // Also don't test a node with itself, or with any of its
           // descendants.
           if (pnode == cnode) {
-#ifndef NDEBUG
-            if (collide_cat.is_spam()) {
+            if (is_spam) {
               indent(collide_cat.spam(false), indent_level)
                 << "Not comparing " << c << " to " << _node_path
                 << " (same node)\n";
             }
-#endif  // NDEBUG
 
           } else {
             // There are bits in common, and it's not the same instance, so go
@@ -148,16 +146,14 @@ any_in_bounds() {
             is_in = true;  // If there's no bounding volume, we're implicitly in.
 
             if (col_gbv != nullptr) {
-              is_in = (node_gbv->contains(col_gbv) != 0);
+              is_in = (_node_gbv->contains(col_gbv) != 0);
               _node_volume_pcollector.add_level(1);
 
-#ifndef NDEBUG
-              if (collide_cat.is_spam()) {
+              if (is_spam) {
                 indent(collide_cat.spam(false), indent_level)
                   << "Comparing " << c << ": " << *col_gbv
-                  << " to " << *node_gbv << ", is_in = " << is_in << "\n";
+                  << " to " << *_node_gbv << ", is_in = " << is_in << "\n";
               }
-#endif  // NDEBUG
             }
           }
         }
@@ -171,8 +167,7 @@ any_in_bounds() {
     }
   }
 
-#ifndef NDEBUG
-  if (collide_cat.is_spam()) {
+  if (is_spam) {
     int num_active_colliders = 0;
     int num_colliders = get_num_colliders();
     for (int c = 0; c < num_colliders; c++) {
@@ -201,11 +196,121 @@ any_in_bounds() {
     collide_cat.spam(false)
       << "\n";
   }
-#endif  // NDEBUG
   return has_any_collider();
 }
 #endif  // CPPPARSER
 
+#ifndef CPPPARSER
+/**
+ * Checks the bounding volume of the given child of the current node against
+ * each of our colliders.  Returns a mask indicating which colliders are inside
+ * of the bounding volume.
+ */
+template<class MaskType>
+MaskType CollisionLevelState<MaskType>::
+get_child_mask(const PandaNode::DownConnection &child) const {
+  PandaNode *pnode = child.get_child();
+#ifdef NDEBUG
+  const bool is_spam = false;
+#else
+  const bool is_spam = collide_cat.is_spam();
+#endif
+  int indent_level = 0;
+  if (is_spam) {
+    indent_level = (_node_path.get_num_nodes() + 1) * 2;
+    collide_cat.spam();
+    indent(collide_cat.spam(false), indent_level)
+      << "Considering " << _node_path << "/" << pnode->get_name() << "\n";
+  }
+
+  MaskType mask = _current;
+
+  const GeometricBoundingVolume *node_gbv = child.get_bounds();
+  if (node_gbv != nullptr) {
+    CollideMask node_mask = child.get_net_collide_mask();
+
+    int num_colliders = get_num_colliders();
+    for (int c = 0; c < num_colliders; c++) {
+      if (mask.get_bit(c)) {
+        CollisionNode *cnode = get_collider_node(c);
+        bool is_in = false;
+
+        // Don't even bother testing the bounding volume if there are no
+        // collide bits in common between our collider and this node.
+        CollideMask from_mask = cnode->get_from_collide_mask() & _include_mask;
+        if (!(from_mask & node_mask).is_zero()) {
+          // Also don't test a node with itself, or with any of its
+          // descendants.
+          if (pnode == cnode) {
+            if (is_spam) {
+              indent(collide_cat.spam(false), indent_level)
+                << "Not comparing " << c << " to " << _node_path << "/"
+                << pnode->get_name() << " (same node)\n";
+            }
+
+          } else {
+            // There are bits in common, and it's not the same instance, so go
+            // ahead and try the bounding volume.
+            const GeometricBoundingVolume *col_gbv =
+              get_local_bound(c);
+
+            is_in = true;  // If there's no bounding volume, we're implicitly in.
+
+            if (col_gbv != nullptr) {
+              is_in = (node_gbv->contains(col_gbv) != 0);
+              _node_volume_pcollector.add_level(1);
+
+              if (is_spam) {
+                indent(collide_cat.spam(false), indent_level)
+                  << "Comparing " << c << ": " << *col_gbv
+                  << " to " << *node_gbv << ", is_in = " << is_in << "\n";
+              }
+            }
+          }
+        }
+
+        if (!is_in) {
+          // This collider cannot intersect with any geometry at this node or
+          // below.
+          mask.clear_bit(c);
+        }
+      }
+    }
+  }
+
+  if (is_spam) {
+    int num_active_colliders = 0;
+    int num_colliders = get_num_colliders();
+    for (int c = 0; c < num_colliders; c++) {
+      if (mask.get_bit(c)) {
+        num_active_colliders++;
+      }
+    }
+
+    collide_cat.spam();
+    indent(collide_cat.spam(false), indent_level)
+      << _node_path.get_node_path() << "/" << pnode->get_name() << " has "
+      << num_active_colliders << " interested colliders";
+    if (num_colliders != 0) {
+      collide_cat.spam(false)
+        << " (";
+      for (int c = 0; c < num_colliders; c++) {
+        if (mask.get_bit(c)) {
+          CollisionNode *cnode = get_collider_node(c);
+          collide_cat.spam(false)
+            << " " << c << ". " << cnode->get_name();
+        }
+      }
+      collide_cat.spam(false)
+        << " )";
+    }
+    collide_cat.spam(false)
+      << "\n";
+  }
+  return mask;
+}
+#endif  // CPPPARSER
+
 #ifndef CPPPARSER
 /**
  * Applies the inverse transform from the current node, if any, onto all the

+ 3 - 1
panda/src/collide/collisionLevelState.h

@@ -37,7 +37,8 @@ public:
 #ifndef CPPPARSER
   INLINE CollisionLevelState(const NodePath &node_path);
   INLINE CollisionLevelState(const CollisionLevelState<MaskType> &parent,
-                             PandaNode *child);
+                             const PandaNode::DownConnection &child,
+                             MaskType mask);
   INLINE CollisionLevelState(const CollisionLevelState<MaskType> &copy);
   INLINE void operator = (const CollisionLevelState<MaskType> &copy);
 
@@ -45,6 +46,7 @@ public:
   INLINE void prepare_collider(const ColliderDef &def, const NodePath &root);
 
   bool any_in_bounds();
+  MaskType get_child_mask(const PandaNode::DownConnection &child) const;
   bool apply_transform();
 
   INLINE static bool has_max_colliders();

+ 28 - 3
panda/src/collide/collisionLevelStateBase.I

@@ -18,7 +18,8 @@ INLINE CollisionLevelStateBase::
 CollisionLevelStateBase(const NodePath &node_path) :
   _node_path(node_path),
   _colliders(get_class_type()),
-  _include_mask(CollideMask::all_on())
+  _include_mask(CollideMask::all_on()),
+  _node_gbv(node_path.node()->get_bounds()->as_geometric_bounding_volume())
 {
 }
 
@@ -30,7 +31,21 @@ CollisionLevelStateBase(const CollisionLevelStateBase &parent, PandaNode *child)
   _node_path(parent._node_path, child),
   _colliders(parent._colliders),
   _include_mask(parent._include_mask),
-  _local_bounds(parent._local_bounds)
+  _local_bounds(parent._local_bounds),
+  _node_gbv(child->get_bounds()->as_geometric_bounding_volume())
+{
+}
+
+/**
+ * This constructor goes to the next child node in the traversal.
+ */
+INLINE CollisionLevelStateBase::
+CollisionLevelStateBase(const CollisionLevelStateBase &parent, const PandaNode::DownConnection &child) :
+  _node_path(parent._node_path, child.get_child()),
+  _colliders(parent._colliders),
+  _include_mask(parent._include_mask),
+  _local_bounds(parent._local_bounds),
+  _node_gbv(child.get_bounds())
 {
 }
 
@@ -43,7 +58,8 @@ CollisionLevelStateBase(const CollisionLevelStateBase &copy) :
   _colliders(copy._colliders),
   _include_mask(copy._include_mask),
   _local_bounds(copy._local_bounds),
-  _parent_bounds(copy._parent_bounds)
+  _parent_bounds(copy._parent_bounds),
+  _node_gbv(copy._node_gbv)
 {
 }
 
@@ -57,6 +73,7 @@ operator = (const CollisionLevelStateBase &copy) {
   _include_mask = copy._include_mask;
   _local_bounds = copy._local_bounds;
   _parent_bounds = copy._parent_bounds;
+  _node_gbv = copy._node_gbv;
 }
 
 /**
@@ -113,6 +130,14 @@ get_collider_node_path(int n) const {
   return _colliders[n]._node_path;
 }
 
+/**
+ * Returns the bounding volume of the current node.
+ */
+INLINE const GeometricBoundingVolume *CollisionLevelStateBase::
+get_node_bound() const {
+  return _node_gbv;
+}
+
 /**
  * Returns the bounding volume of the indicated collider, transformed into the
  * current node's transform space.

+ 5 - 0
panda/src/collide/collisionLevelStateBase.h

@@ -52,6 +52,8 @@ public:
   INLINE CollisionLevelStateBase(const NodePath &node_path);
   INLINE CollisionLevelStateBase(const CollisionLevelStateBase &parent,
                                  PandaNode *child);
+  INLINE CollisionLevelStateBase(const CollisionLevelStateBase &parent,
+                                 const PandaNode::DownConnection &child);
   INLINE CollisionLevelStateBase(const CollisionLevelStateBase &copy);
   INLINE void operator = (const CollisionLevelStateBase &copy);
 
@@ -67,6 +69,7 @@ public:
   INLINE const CollisionSolid *get_collider(int n) const;
   INLINE CollisionNode *get_collider_node(int n) const;
   INLINE NodePath get_collider_node_path(int n) const;
+  INLINE const GeometricBoundingVolume *get_node_bound() const;
   INLINE const GeometricBoundingVolume *get_local_bound(int n) const;
   INLINE const GeometricBoundingVolume *get_parent_bound(int n) const;
 
@@ -80,6 +83,8 @@ protected:
   Colliders _colliders;
   CollideMask _include_mask;
 
+  const GeometricBoundingVolume *_node_gbv = nullptr;
+
   typedef PTA(CPT(GeometricBoundingVolume)) BoundingVolumes;
   BoundingVolumes _local_bounds;
   BoundingVolumes _parent_bounds;

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

@@ -70,7 +70,7 @@ fill_viz_geom() {
   static const double scale = 100.0;
 
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("collision", GeomVertexFormat::get_v3cp(),
+    ("collision", GeomVertexFormat::get_v3c(),
      Geom::UH_static);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());

+ 4 - 21
panda/src/collide/collisionNode.cxx

@@ -43,6 +43,7 @@ CollisionNode(const std::string &name) :
   _collider_sort(0)
 {
   set_cull_callback();
+  set_renderable();
 
   // CollisionNodes are hidden by default.
   set_overall_hidden(true);
@@ -186,11 +187,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     CPT(CollisionSolid) solid = (*si).get_read_pointer();
     PT(PandaNode) node = solid->get_viz(trav, data, false);
     if (node != nullptr) {
-      CullTraverserData next_data(data, node);
-
       // We don't want to inherit the render state from above for these guys.
-      next_data._state = RenderState::make_empty();
-      trav->traverse(next_data);
+      trav->traverse_down(data, node, data._net_transform, RenderState::make_empty());
     }
   }
 
@@ -208,12 +206,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
         CPT(CollisionSolid) solid = (*si).get_read_pointer();
         PT(PandaNode) node = solid->get_viz(trav, data, false);
         if (node != nullptr) {
-          CullTraverserData next_data(data, node);
-
-          next_data._net_transform =
-            next_data._net_transform->compose(transform);
-          next_data._state = get_last_pos_state();
-          trav->traverse(next_data);
+          trav->traverse_down(data, node,
+            data._net_transform->compose(transform), get_last_pos_state());
         }
       }
     }
@@ -223,17 +217,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   return true;
 }
 
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise.  This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool CollisionNode::
-is_renderable() const {
-  return true;
-}
-
 /**
  * A simple downcast check.  Returns true if this kind of node happens to
  * inherit from CollisionNode, false otherwise.

+ 0 - 1
panda/src/collide/collisionNode.h

@@ -43,7 +43,6 @@ public:
   virtual CollideMask get_legal_collide_mask() const;
 
   virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
-  virtual bool is_renderable() const;
   virtual bool is_collision_node() const;
 
   virtual void output(std::ostream &out) const;

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

@@ -174,7 +174,7 @@ fill_viz_geom() {
   static const int num_points = 100;
 
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("collision", GeomVertexFormat::get_v3cp(),
+    ("collision", GeomVertexFormat::get_v3c(),
      Geom::UH_static);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());

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

@@ -128,7 +128,7 @@ fill_viz_geom() {
   static const double scale = 100.0;
 
   PT(GeomVertexData) vdata = new GeomVertexData
-    ("collision", GeomVertexFormat::get_v3cp(),
+    ("collision", GeomVertexFormat::get_v3c(),
      Geom::UH_static);
   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
   GeomVertexWriter color(vdata, InternalName::get_color());

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels