Browse Source

Merge branch 'master' into cmake

Sam Edwards 6 years ago
parent
commit
671f15e052
68 changed files with 996 additions and 790 deletions
  1. 2 2
      README.md
  2. 11 0
      contrib/src/ai/aiBehaviors.cxx
  3. 0 1
      contrib/src/ai/aiGlobals.h
  4. 3 0
      contrib/src/ai/arrival.cxx
  5. 2 0
      contrib/src/ai/obstacleAvoidance.cxx
  6. 2 0
      contrib/src/ai/pathFind.cxx
  7. 14 0
      contrib/src/ai/pathFollow.cxx
  8. 1 1
      contrib/src/rplight/shadowAtlas.h
  9. 0 84
      direct/src/controls/GravityWalker.py
  10. 0 45
      direct/src/controls/PhysicsWalker.py
  11. 3 2
      direct/src/showbase/Messenger.py
  12. 3 5
      direct/src/showbase/PythonUtil.py
  13. 4 7
      direct/src/task/Task.py
  14. 2 2
      makepanda/makepackage.py
  15. 13 5
      makepanda/makepanda.py
  16. 0 6
      makepanda/makepanda.vcproj
  17. 8 2
      makepanda/makepandacore.py
  18. 3 3
      panda/src/audio/audio.h
  19. 3 3
      panda/src/audio/audioManager.h
  20. 3 3
      panda/src/audio/audioSound.h
  21. 3 3
      panda/src/audio/nullAudioManager.h
  22. 3 3
      panda/src/audio/nullAudioSound.h
  23. 1 0
      panda/src/collide/collisionBox.cxx
  24. 9 3
      panda/src/collide/collisionSphere.cxx
  25. 1 0
      panda/src/device/inputDeviceSet.I
  26. 2 10
      panda/src/display/graphicsStateGuardian.cxx
  27. 1 1
      panda/src/distort/oSphereLens.cxx
  28. 30 12
      panda/src/egg2pg/eggLoader.cxx
  29. 2 0
      panda/src/egg2pg/eggLoader.h
  30. 0 2
      panda/src/express/p3express_composite2.cxx
  31. 0 207
      panda/src/express/threadSafePointerTo.I
  32. 0 14
      panda/src/express/threadSafePointerTo.cxx
  33. 0 105
      panda/src/express/threadSafePointerTo.h
  34. 0 132
      panda/src/express/threadSafePointerToBase.I
  35. 0 14
      panda/src/express/threadSafePointerToBase.cxx
  36. 0 63
      panda/src/express/threadSafePointerToBase.h
  37. 4 1
      panda/src/ffmpeg/config_ffmpeg.cxx
  38. 4 0
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  39. 1 1
      panda/src/glstuff/glShaderContext_src.cxx
  40. 2 2
      panda/src/gobj/lens.cxx
  41. 59 2
      panda/src/grutil/shaderTerrainMesh.cxx
  42. 10 0
      panda/src/grutil/shaderTerrainMesh.h
  43. 1 1
      panda/src/mathutil/boundingBox.I
  44. 14 0
      panda/src/putil/uniqueIdAllocator.cxx
  45. 2 0
      panda/src/putil/uniqueIdAllocator.h
  46. 41 5
      pandatool/src/deploy-stub/deploy-stub.c
  47. 3 0
      pandatool/src/win-stats/winStats.cxx
  48. 3 0
      pandatool/src/win-stats/winStatsChartMenu.h
  49. 3 0
      pandatool/src/win-stats/winStatsGraph.h
  50. 3 0
      pandatool/src/win-stats/winStatsLabel.h
  51. 3 0
      pandatool/src/win-stats/winStatsLabelStack.h
  52. 3 0
      pandatool/src/win-stats/winStatsMonitor.h
  53. 3 0
      pandatool/src/win-stats/winStatsPianoRoll.h
  54. 3 0
      pandatool/src/win-stats/winStatsStripChart.h
  55. 1 1
      samples/fireflies/main.py
  56. 278 0
      samples/particles/advanced.py
  57. 70 37
      samples/roaming-ralph/main.py
  58. BIN
      samples/roaming-ralph/models/world.egg.pz
  59. 8 0
      samples/shader-terrain/main.py
  60. 30 0
      tests/collide/collisions.py
  61. 45 0
      tests/collide/test_into_box.py
  62. 24 0
      tests/collide/test_into_lines.py
  63. 48 0
      tests/collide/test_into_poly.py
  64. 89 0
      tests/collide/test_into_sphere.py
  65. 6 0
      tests/display/test_color_buffer.py
  66. 77 0
      tests/gobj/test_lenses.py
  67. 6 0
      tests/putil/conftest.py
  68. 23 0
      tests/putil/test_clockobject.py

+ 2 - 2
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-1/).
+[this page](https://www.panda3d.org/download/sdk-1-10-2/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
 the following comand:
 the following comand:
 
 
@@ -191,7 +191,7 @@ pkg install python-dev termux-tools ndk-stl ndk-sysroot clang libvorbis-dev libo
 Then, you can build and install the .apk right away using these commands:
 Then, you can build and install the .apk right away using these commands:
 
 
 ```bash
 ```bash
-python makepanda/makepanda.py --everything --target android-21 --installer
+python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
 xdg-open panda3d.apk
 xdg-open panda3d.apk
 ```
 ```
 
 

+ 11 - 0
contrib/src/ai/aiBehaviors.cxx

@@ -13,6 +13,17 @@
 
 
 #include "aiBehaviors.h"
 #include "aiBehaviors.h"
 
 
+#include "arrival.h"
+#include "evade.h"
+#include "flee.h"
+#include "flock.h"
+#include "obstacleAvoidance.h"
+#include "pathFind.h"
+#include "pathFollow.h"
+#include "pursue.h"
+#include "seek.h"
+#include "wander.h"
+
 using std::cout;
 using std::cout;
 using std::endl;
 using std::endl;
 using std::string;
 using std::string;

+ 0 - 1
contrib/src/ai/aiGlobals.h

@@ -15,7 +15,6 @@
 #define _AI_GLOBALS_H
 #define _AI_GLOBALS_H
 
 
 #include "config_ai.h"
 #include "config_ai.h"
-#include "pandaFramework.h"
 #include "textNode.h"
 #include "textNode.h"
 #include "pandaSystem.h"
 #include "pandaSystem.h"
 
 

+ 3 - 0
contrib/src/ai/arrival.cxx

@@ -13,6 +13,9 @@
 
 
 #include "arrival.h"
 #include "arrival.h"
 
 
+#include "pursue.h"
+#include "seek.h"
+
 Arrival::Arrival(AICharacter *ai_ch, double distance) {
 Arrival::Arrival(AICharacter *ai_ch, double distance) {
   _ai_char = ai_ch;
   _ai_char = ai_ch;
 
 

+ 2 - 0
contrib/src/ai/obstacleAvoidance.cxx

@@ -13,6 +13,8 @@
 
 
 #include "obstacleAvoidance.h"
 #include "obstacleAvoidance.h"
 
 
+#include "aiWorld.h"
+
 ObstacleAvoidance::
 ObstacleAvoidance::
 ObstacleAvoidance(AICharacter *ai_char, float feeler_length) {
 ObstacleAvoidance(AICharacter *ai_char, float feeler_length) {
   _ai_char = ai_char;
   _ai_char = ai_char;

+ 2 - 0
contrib/src/ai/pathFind.cxx

@@ -13,6 +13,8 @@
 
 
 #include "pathFind.h"
 #include "pathFind.h"
 
 
+#include "pathFollow.h"
+
 using std::cout;
 using std::cout;
 using std::endl;
 using std::endl;
 using std::string;
 using std::string;

+ 14 - 0
contrib/src/ai/pathFollow.cxx

@@ -1,6 +1,20 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pathFind.cxx
+ * @author Deepak, John, Navin
+ * @date 2009-10-24
+ */
 
 
 #include "pathFollow.h"
 #include "pathFollow.h"
 
 
+#include "pathFind.h"
+
 PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
 PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
     _follow_weight = follow_wt;
     _follow_weight = follow_wt;
   _curr_path_waypoint = -1;
   _curr_path_waypoint = -1;

+ 1 - 1
contrib/src/rplight/shadowAtlas.h

@@ -28,7 +28,7 @@
 #define SHADOWATLAS_H
 #define SHADOWATLAS_H
 
 
 #include "pandabase.h"
 #include "pandabase.h"
-#include "lvecBase4.h"
+#include "luse.h"
 
 
 NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
 NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
 
 

+ 0 - 84
direct/src/controls/GravityWalker.py

@@ -71,90 +71,6 @@ class GravityWalker(DirectObject.DirectObject):
         self.isAirborne = 0
         self.isAirborne = 0
         self.highMark = 0
         self.highMark = 0
 
 
-    """
-    def spawnTest(self):
-        assert self.notify.debugStateCall(self)
-        if not self.wantDebugIndicator:
-            return
-        from pandac.PandaModules import *
-        from direct.interval.IntervalGlobal import *
-        from toontown.coghq import MovingPlatform
-
-        if hasattr(self, "platform"):
-            # Remove the prior instantiation:
-            self.moveIval.pause()
-            del self.moveIval
-            self.platform.destroy()
-            del self.platform
-            self.platform2.destroy()
-            del self.platform2
-
-        model = loader.loadModel('phase_9/models/cogHQ/platform1')
-        fakeId = id(self)
-        self.platform = MovingPlatform.MovingPlatform()
-        self.platform.setupCopyModel(fakeId, model, 'platformcollision')
-        self.platformRoot = render.attachNewNode("GravityWalker-spawnTest-%s"%fakeId)
-        self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 0.0, 1.0))
-        self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
-        self.platform.reparentTo(self.platformRoot)
-
-        self.platform2 = MovingPlatform.MovingPlatform()
-        self.platform2.setupCopyModel(1+fakeId, model, 'platformcollision')
-        self.platform2Root = render.attachNewNode("GravityWalker-spawnTest2-%s"%fakeId)
-        self.platform2Root.setPos(base.localAvatar, Vec3(-16.0, 30.0, 1.0))
-        self.platform2Root.setHpr(base.localAvatar, Vec3.zero())
-        self.platform2.reparentTo(self.platform2Root)
-
-        duration = 5
-        self.moveIval = Parallel(
-                Sequence(
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 30.0, 0.0),
-                                    name='platformOut%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformBack%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 30.0),
-                                    name='platformUp%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformDown%s' % fakeId,
-                                    fluid = 1),
-                ),
-                Sequence(
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, -30.0, 0.0),
-                                    name='platform2Out%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, 30.0, 30.0),
-                                    name='platform2Back%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, -30.0, 0.0),
-                                    name='platform2Up%s' % fakeId,
-                                    fluid = 1),
-                    WaitInterval(0.3),
-                    LerpPosInterval(self.platform2, duration,
-                                    Vec3(0.0, 0.0, 0.0),
-                                    name='platformDown%s' % fakeId,
-                                    fluid = 1),
-                ),
-            name='platformIval%s' % fakeId,
-            )
-        self.moveIval.loop()
-    """
     def setWalkSpeed(self, forward, jump, reverse, rotate):
     def setWalkSpeed(self, forward, jump, reverse, rotate):
         assert self.notify.debugStateCall(self)
         assert self.notify.debugStateCall(self)
         self.avatarControlForwardSpeed=forward
         self.avatarControlForwardSpeed=forward

+ 0 - 45
direct/src/controls/PhysicsWalker.py

@@ -67,51 +67,6 @@ class PhysicsWalker(DirectObject.DirectObject):
         self.isAirborne = 0
         self.isAirborne = 0
         self.highMark = 0
         self.highMark = 0
 
 
-    """
-    def spawnTest(self):
-        assert self.debugPrint("\n\nspawnTest()\n")
-        if not self.wantDebugIndicator:
-            return
-        from pandac.PandaModules import *
-        from direct.interval.IntervalGlobal import *
-        from toontown.coghq import MovingPlatform
-
-        if hasattr(self, "platform"):
-            # Remove the prior instantiation:
-            self.moveIval.pause()
-            del self.moveIval
-            self.platform.destroy()
-            del self.platform
-
-        model = loader.loadModel('phase_9/models/cogHQ/platform1')
-        fakeId = id(self)
-        self.platform = MovingPlatform.MovingPlatform()
-        self.platform.setupCopyModel(fakeId, model, 'platformcollision')
-        self.platformRoot = render.attachNewNode("physicsWalker-spawnTest-%s"%fakeId)
-        self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 3.0, 1.0))
-        self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
-        self.platform.reparentTo(self.platformRoot)
-
-        startPos = Vec3(0.0, -15.0, 0.0)
-        endPos = Vec3(0.0, 15.0, 0.0)
-        distance = Vec3(startPos-endPos).length()
-        duration = distance/4
-        self.moveIval = Sequence(
-            WaitInterval(0.3),
-            LerpPosInterval(self.platform, duration,
-                            endPos, startPos=startPos,
-                            name='platformOut%s' % fakeId,
-                            fluid = 1),
-            WaitInterval(0.3),
-            LerpPosInterval(self.platform, duration,
-                            startPos, startPos=endPos,
-                            name='platformBack%s' % fakeId,
-                            fluid = 1),
-            name='platformIval%s' % fakeId,
-            )
-        self.moveIval.loop()
-    """
-
     def setWalkSpeed(self, forward, jump, reverse, rotate):
     def setWalkSpeed(self, forward, jump, reverse, rotate):
         assert self.debugPrint("setWalkSpeed()")
         assert self.debugPrint("setWalkSpeed()")
         self.avatarControlForwardSpeed=forward
         self.avatarControlForwardSpeed=forward

+ 3 - 2
direct/src/showbase/Messenger.py

@@ -624,8 +624,9 @@ class Messenger:
             for key in list(acceptorDict.keys()):
             for key in list(acceptorDict.keys()):
                 function, extraArgs, persistent = acceptorDict[key]
                 function, extraArgs, persistent = acceptorDict[key]
                 object = self._getObject(key)
                 object = self._getObject(key)
-                if (type(object) == types.InstanceType):
-                    className = object.__class__.__name__
+                objectClass = getattr(object, '__class__', None)
+                if objectClass:
+                    className = objectClass.__name__
                 else:
                 else:
                     className = "Not a class"
                     className = "Not a class"
                 functionName = function.__name__
                 functionName = function.__name__

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

@@ -1652,17 +1652,15 @@ def itype(obj):
     # version of type that gives more complete information about instance types
     # version of type that gives more complete information about instance types
     global dtoolSuperBase
     global dtoolSuperBase
     t = type(obj)
     t = type(obj)
-    if t is types.InstanceType:
-        return '%s of <class %s>>' % (repr(types.InstanceType)[:-1],
-                                      str(obj.__class__))
+    if sys.version_info < (3, 0) and t is types.InstanceType:
+        return "<type 'instance' of <class %s>>" % (obj.__class__)
     else:
     else:
         # C++ object instances appear to be types via type()
         # C++ object instances appear to be types via type()
         # check if this is a C++ object
         # check if this is a C++ object
         if dtoolSuperBase is None:
         if dtoolSuperBase is None:
             _getDtoolSuperBase()
             _getDtoolSuperBase()
         if isinstance(obj, dtoolSuperBase):
         if isinstance(obj, dtoolSuperBase):
-            return '%s of %s>' % (repr(types.InstanceType)[:-1],
-                                  str(obj.__class__))
+            return "<type 'instance' of %s>" % (obj.__class__)
         return t
         return t
 
 
 def deeptype(obj, maxLen=100, _visitedIds=None):
 def deeptype(obj, maxLen=100, _visitedIds=None):

+ 4 - 7
direct/src/task/Task.py

@@ -388,13 +388,10 @@ class TaskManager:
     def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
     def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
         if isinstance(funcOrTask, AsyncTask):
         if isinstance(funcOrTask, AsyncTask):
             task = funcOrTask
             task = funcOrTask
-        elif hasattr(funcOrTask, '__call__'):
-            task = PythonTask(funcOrTask)
-            if name is None:
-                name = getattr(funcOrTask, '__qualname__', None) or \
-                       getattr(funcOrTask, '__name__', None)
-        elif hasattr(funcOrTask, 'cr_await') or type(funcOrTask) == types.GeneratorType:
-            # It's a coroutine, or something emulating one.
+        elif hasattr(funcOrTask, '__call__') or \
+                hasattr(funcOrTask, 'cr_await') or \
+                type(funcOrTask) == types.GeneratorType:
+            # It's a function, coroutine, or something emulating a coroutine.
             task = PythonTask(funcOrTask)
             task = PythonTask(funcOrTask)
             if name is None:
             if name is None:
                 name = getattr(funcOrTask, '__qualname__', None) or \
                 name = getattr(funcOrTask, '__qualname__', None) or \

+ 2 - 2
makepanda/makepackage.py

@@ -748,7 +748,7 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
     oscmd('rm -f Panda3D-rw.dmg')
     oscmd('rm -f Panda3D-rw.dmg')
 
 
 
 
-def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
+def MakeInstallerFreeBSD(version, runtime=False, python_versions=[], **kwargs):
     outputdir = GetOutputDir()
     outputdir = GetOutputDir()
 
 
     oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST")
     oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST")
@@ -758,7 +758,7 @@ def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
     if runtime:
     if runtime:
         InstallRuntime(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
         InstallRuntime(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
     else:
     else:
-        InstallPanda(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
+        InstallPanda(destdir="targetroot", prefix="/usr/local", outputdir=outputdir, python_versions=python_versions)
 
 
     if not os.path.exists("/usr/sbin/pkg"):
     if not os.path.exists("/usr/sbin/pkg"):
         exit("Cannot create an installer without pkg")
         exit("Cannot create an installer without pkg")

+ 13 - 5
makepanda/makepanda.py

@@ -554,6 +554,17 @@ if RUNTIME and not HOST_URL:
     # Set this to a nice default.
     # Set this to a nice default.
     HOST_URL = "https://runtime.panda3d.org/"
     HOST_URL = "https://runtime.panda3d.org/"
 
 
+if not PkgSkip("PYTHON") and SDK["PYTHONVERSION"] == "python2.7":
+    warn_prefix = "%sWARNING:%s " % (GetColor("red"), GetColor())
+    print("=========================================================================")
+    print(warn_prefix + "Python 2.7 will reach EOL after December 31, 2019, and will not")
+    print(warn_prefix + "be supported after that date.  Please ensure you are prepared")
+    print(warn_prefix + "by planning your upgrade to Python 3 now.")
+    print("=========================================================================")
+    sys.stdout.flush()
+    # Give the user some time to contemplate their sins
+    time.sleep(6.0)
+
 ########################################################################
 ########################################################################
 ##
 ##
 ## Choose a Compiler.
 ## Choose a Compiler.
@@ -3003,9 +3014,6 @@ else:
 if (GetTarget() == 'darwin'):
 if (GetTarget() == 'darwin'):
     configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$HOME/Library/Caches/Panda3D-%s" % MAJOR_VERSION)
     configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$HOME/Library/Caches/Panda3D-%s" % MAJOR_VERSION)
 
 
-    # OpenAL is not yet working well on OSX for us, so let's do this for now.
-    configprc = configprc.replace("p3openal_audio", "p3fmod_audio")
-
 if GetTarget() == 'windows':
 if GetTarget() == 'windows':
     # Convert to Windows newlines.
     # Convert to Windows newlines.
     ConditionalWriteFile(GetOutputDir()+"/etc/Config.prc", configprc, newline='\r\n')
     ConditionalWriteFile(GetOutputDir()+"/etc/Config.prc", configprc, newline='\r\n')
@@ -4709,7 +4717,7 @@ if not RUNTIME and not PkgSkip("EGG"):
   TargetAdd('p3egg2pg_composite2.obj', opts=OPTS, input='p3egg2pg_composite2.cxx')
   TargetAdd('p3egg2pg_composite2.obj', opts=OPTS, input='p3egg2pg_composite2.cxx')
 
 
   OPTS=['DIR:panda/src/egg2pg']
   OPTS=['DIR:panda/src/egg2pg']
-  IGATEFILES=['load_egg_file.h']
+  IGATEFILES=['load_egg_file.h', 'save_egg_file.h']
   TargetAdd('libp3egg2pg.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3egg2pg.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3egg2pg.in', opts=['IMOD:panda3d.egg', 'ILIB:libp3egg2pg', 'SRCDIR:panda/src/egg2pg'])
   TargetAdd('libp3egg2pg.in', opts=['IMOD:panda3d.egg', 'ILIB:libp3egg2pg', 'SRCDIR:panda/src/egg2pg'])
 
 
@@ -5156,7 +5164,7 @@ if (not RUNTIME and GetTarget() == 'android'):
     TargetAdd('libppython.dll', input='libp3framework.dll')
     TargetAdd('libppython.dll', input='libp3framework.dll')
     TargetAdd('libppython.dll', input='libp3android.dll')
     TargetAdd('libppython.dll', input='libp3android.dll')
     TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS)
     TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS)
-    TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID'])
+    TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID', 'PYTHON'])
 
 
 #
 #
 # DIRECTORY: panda/src/androiddisplay/
 # DIRECTORY: panda/src/androiddisplay/

+ 0 - 6
makepanda/makepanda.vcproj

@@ -2147,7 +2147,6 @@
 				<File RelativePath="..\panda\src\express\test_types.cxx"></File>
 				<File RelativePath="..\panda\src\express\test_types.cxx"></File>
 				<File RelativePath="..\panda\src\express\weakPointerTo.I"></File>
 				<File RelativePath="..\panda\src\express\weakPointerTo.I"></File>
 				<File RelativePath="..\panda\src\express\pta_int.cxx"></File>
 				<File RelativePath="..\panda\src\express\pta_int.cxx"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerToBase.I"></File>
 				<File RelativePath="..\panda\src\express\namable.I"></File>
 				<File RelativePath="..\panda\src\express\namable.I"></File>
 				<File RelativePath="..\panda\src\express\vector_float.cxx"></File>
 				<File RelativePath="..\panda\src\express\vector_float.cxx"></File>
 				<File RelativePath="..\panda\src\express\pointerTo.h"></File>
 				<File RelativePath="..\panda\src\express\pointerTo.h"></File>
@@ -2174,7 +2173,6 @@
 				<File RelativePath="..\panda\src\express\datagramGenerator.h"></File>
 				<File RelativePath="..\panda\src\express\datagramGenerator.h"></File>
 				<File RelativePath="..\panda\src\express\weakPointerToVoid.h"></File>
 				<File RelativePath="..\panda\src\express\weakPointerToVoid.h"></File>
 				<File RelativePath="..\panda\src\express\datagram.I"></File>
 				<File RelativePath="..\panda\src\express\datagram.I"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerTo.h"></File>
 				<File RelativePath="..\panda\src\express\weakPointerCallback.h"></File>
 				<File RelativePath="..\panda\src\express\weakPointerCallback.h"></File>
 				<File RelativePath="..\panda\src\express\datagram.h"></File>
 				<File RelativePath="..\panda\src\express\datagram.h"></File>
 				<File RelativePath="..\panda\src\express\textEncoder.h"></File>
 				<File RelativePath="..\panda\src\express\textEncoder.h"></File>
@@ -2215,7 +2213,6 @@
 				<File RelativePath="..\panda\src\express\datagramIterator.I"></File>
 				<File RelativePath="..\panda\src\express\datagramIterator.I"></File>
 				<File RelativePath="..\panda\src\express\vector_uchar.h"></File>
 				<File RelativePath="..\panda\src\express\vector_uchar.h"></File>
 				<File RelativePath="..\panda\src\express\datagramIterator.h"></File>
 				<File RelativePath="..\panda\src\express\datagramIterator.h"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerTo.cxx"></File>
 				<File RelativePath="..\panda\src\express\datagramGenerator.cxx"></File>
 				<File RelativePath="..\panda\src\express\datagramGenerator.cxx"></File>
 				<File RelativePath="..\panda\src\express\hashGeneratorBase.h"></File>
 				<File RelativePath="..\panda\src\express\hashGeneratorBase.h"></File>
 				<File RelativePath="..\panda\src\express\patchfile.cxx"></File>
 				<File RelativePath="..\panda\src\express\patchfile.cxx"></File>
@@ -2259,7 +2256,6 @@
 				<File RelativePath="..\panda\src\express\namable.cxx"></File>
 				<File RelativePath="..\panda\src\express\namable.cxx"></File>
 				<File RelativePath="..\panda\src\express\trueClock.I"></File>
 				<File RelativePath="..\panda\src\express\trueClock.I"></File>
 				<File RelativePath="..\panda\src\express\config_express.cxx"></File>
 				<File RelativePath="..\panda\src\express\config_express.cxx"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerToBase.cxx"></File>
 				<File RelativePath="..\panda\src\express\pointerToVoid.I"></File>
 				<File RelativePath="..\panda\src\express\pointerToVoid.I"></File>
 				<File RelativePath="..\panda\src\express\vector_uchar.cxx"></File>
 				<File RelativePath="..\panda\src\express\vector_uchar.cxx"></File>
 				<File RelativePath="..\panda\src\express\vector_float.h"></File>
 				<File RelativePath="..\panda\src\express\vector_float.h"></File>
@@ -2286,7 +2282,6 @@
 				<File RelativePath="..\panda\src\express\datagramGenerator.I"></File>
 				<File RelativePath="..\panda\src\express\datagramGenerator.I"></File>
 				<File RelativePath="..\panda\src\express\compress_string.cxx"></File>
 				<File RelativePath="..\panda\src\express\compress_string.cxx"></File>
 				<File RelativePath="..\panda\src\express\subStreamBuf.cxx"></File>
 				<File RelativePath="..\panda\src\express\subStreamBuf.cxx"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerToBase.h"></File>
 				<File RelativePath="..\panda\src\express\pointerToArrayBase.cxx"></File>
 				<File RelativePath="..\panda\src\express\pointerToArrayBase.cxx"></File>
 				<File RelativePath="..\panda\src\express\express_composite.cxx"></File>
 				<File RelativePath="..\panda\src\express\express_composite.cxx"></File>
 				<File RelativePath="..\panda\src\express\virtualFileComposite.I"></File>
 				<File RelativePath="..\panda\src\express\virtualFileComposite.I"></File>
@@ -2294,7 +2289,6 @@
 				<File RelativePath="..\panda\src\express\multifile.I"></File>
 				<File RelativePath="..\panda\src\express\multifile.I"></File>
 				<File RelativePath="..\panda\src\express\virtualFile.I"></File>
 				<File RelativePath="..\panda\src\express\virtualFile.I"></File>
 				<File RelativePath="..\panda\src\express\patchfile.I"></File>
 				<File RelativePath="..\panda\src\express\patchfile.I"></File>
-				<File RelativePath="..\panda\src\express\threadSafePointerTo.I"></File>
 				<File RelativePath="..\panda\src\express\virtualFileList.h"></File>
 				<File RelativePath="..\panda\src\express\virtualFileList.h"></File>
 			</Filter>
 			</Filter>
 			<Filter Name="iphonedisplay">
 			<Filter Name="iphonedisplay">

+ 8 - 2
makepanda/makepandacore.py

@@ -2797,8 +2797,14 @@ def SetupVisualStudioEnviron():
         elif not win_kit.endswith('\\'):
         elif not win_kit.endswith('\\'):
             win_kit += '\\'
             win_kit += '\\'
 
 
-        AddToPathEnv("LIB", win_kit + "Lib\\10.0.10150.0\\ucrt\\" + arch)
-        AddToPathEnv("INCLUDE", win_kit + "Include\\10.0.10150.0\\ucrt")
+        for vnum in 10150, 10240, 10586, 14393, 15063, 16299, 17134, 17763:
+            version = "10.0.{0}.0".format(vnum)
+            if os.path.isfile(win_kit + "Include\\" + version + "\\ucrt\\assert.h"):
+                print("Using Universal CRT %s" % (version))
+                break
+
+        AddToPathEnv("LIB", "%s\\Lib\\%s\\ucrt\\%s" % (win_kit, version, arch))
+        AddToPathEnv("INCLUDE", "%s\\Include\\%s\\ucrt" % (win_kit, version))
 
 
         # Copy the DLLs to the bin directory.
         # Copy the DLLs to the bin directory.
         CopyAllFiles(GetOutputDir() + "/bin/", win_kit + "Redist\\ucrt\\DLLs\\" + arch + "\\")
         CopyAllFiles(GetOutputDir() + "/bin/", win_kit + "Redist\\ucrt\\DLLs\\" + arch + "\\")

+ 3 - 3
panda/src/audio/audio.h

@@ -11,12 +11,12 @@
  * @date 2000-07-06
  * @date 2000-07-06
  */
  */
 
 
-#ifndef __AUDIO_H__
-#define __AUDIO_H__
+#ifndef AUDIO_H
+#define AUDIO_H
 
 
 #include "filterProperties.h"
 #include "filterProperties.h"
 #include "audioLoadRequest.h"
 #include "audioLoadRequest.h"
 #include "audioSound.h"
 #include "audioSound.h"
 #include "audioManager.h"
 #include "audioManager.h"
 
 
-#endif /* __AUDIO_H__ */
+#endif /* AUDIO_H */

+ 3 - 3
panda/src/audio/audioManager.h

@@ -12,8 +12,8 @@
  * Prior system by: cary
  * Prior system by: cary
  */
  */
 
 
-#ifndef __AUDIO_MANAGER_H__
-#define __AUDIO_MANAGER_H__
+#ifndef AUDIOMANAGER_H
+#define AUDIOMANAGER_H
 
 
 #include "config_audio.h"
 #include "config_audio.h"
 #include "audioSound.h"
 #include "audioSound.h"
@@ -222,4 +222,4 @@ operator << (std::ostream &out, const AudioManager &mgr) {
 
 
 #include "audioManager.I"
 #include "audioManager.I"
 
 
-#endif /* __AUDIO_MANAGER_H__ */
+#endif /* AUDIOMANAGER_H */

+ 3 - 3
panda/src/audio/audioSound.h

@@ -12,8 +12,8 @@
  * Prior system by: cary
  * Prior system by: cary
  */
  */
 
 
-#ifndef __AUDIOSOUND_H__
-#define __AUDIOSOUND_H__
+#ifndef AUDIOSOUND_H
+#define AUDIOSOUND_H
 
 
 #include "config_audio.h"
 #include "config_audio.h"
 #include "typedReferenceCount.h"
 #include "typedReferenceCount.h"
@@ -160,4 +160,4 @@ operator << (std::ostream &out, const AudioSound &sound) {
 EXPCL_PANDA_AUDIO std::ostream &
 EXPCL_PANDA_AUDIO std::ostream &
 operator << (std::ostream &out, AudioSound::SoundStatus status);
 operator << (std::ostream &out, AudioSound::SoundStatus status);
 
 
-#endif /* __AUDIOSOUND_H__ */
+#endif /* AUDIOSOUND_H */

+ 3 - 3
panda/src/audio/nullAudioManager.h

@@ -12,8 +12,8 @@
  * Prior system by: cary
  * Prior system by: cary
  */
  */
 
 
-#ifndef __NULL_AUDIO_MANAGER_H__
-#define __NULL_AUDIO_MANAGER_H__
+#ifndef NULLAUDIOMANAGER_H
+#define NULLAUDIOMANAGER_H
 
 
 #include "audioManager.h"
 #include "audioManager.h"
 #include "nullAudioSound.h"
 #include "nullAudioSound.h"
@@ -89,4 +89,4 @@ private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 };
 };
 
 
-#endif /* __NULL_AUDIO_MANAGER_H__ */
+#endif /* NULLAUDIOMANAGER_H */

+ 3 - 3
panda/src/audio/nullAudioSound.h

@@ -12,8 +12,8 @@
  * Prior system by: cary
  * Prior system by: cary
  */
  */
 
 
-#ifndef __NULL_AUDIO_SOUND_H__
-#define __NULL_AUDIO_SOUND_H__
+#ifndef NULLAUDIOSOUND_H
+#define NULLAUDIOSOUND_H
 
 
 #include "audioSound.h"
 #include "audioSound.h"
 
 
@@ -91,4 +91,4 @@ private:
   friend class NullAudioManager;
   friend class NullAudioManager;
 };
 };
 
 
-#endif /* __NULL_AUDIO_SOUND_H__ */
+#endif /* NULLAUDIOSOUND_H */

+ 1 - 0
panda/src/collide/collisionBox.cxx

@@ -186,6 +186,7 @@ get_test_pcollector() {
  */
  */
 void CollisionBox::
 void CollisionBox::
 output(std::ostream &out) const {
 output(std::ostream &out) const {
+  out << "box, (" << get_min() << ") to (" << get_max() << ")";
 }
 }
 
 
 /**
 /**

+ 9 - 3
panda/src/collide/collisionSphere.cxx

@@ -620,14 +620,20 @@ intersects_line(double &t1, double &t2,
 
 
   double A = dot(delta, delta);
   double A = dot(delta, delta);
 
 
-  nassertr(A != 0.0, false);
-
   LVector3 fc = from - get_center();
   LVector3 fc = from - get_center();
-  double B = 2.0f* dot(delta, fc);
   double fc_d2 = dot(fc, fc);
   double fc_d2 = dot(fc, fc);
   double radius = get_radius() + inflate_radius;
   double radius = get_radius() + inflate_radius;
   double C = fc_d2 - radius * radius;
   double C = fc_d2 - radius * radius;
 
 
+  if (A == 0.0) {
+    // Degenerate case where delta is zero.  This is effectively a test
+    // against a point (or sphere, for nonzero inflate_radius).
+    t1 = 0.0;
+    t2 = 0.0;
+    return C < 0.0;
+  }
+
+  double B = 2.0f * dot(delta, fc);
   double radical = B*B - 4.0*A*C;
   double radical = B*B - 4.0*A*C;
 
 
   if (IS_NEARLY_ZERO(radical)) {
   if (IS_NEARLY_ZERO(radical)) {

+ 1 - 0
panda/src/device/inputDeviceSet.I

@@ -15,6 +15,7 @@
 #include "gamepadButton.h"
 #include "gamepadButton.h"
 #include "mouseButton.h"
 #include "mouseButton.h"
 #include "buttonRegistry.h"
 #include "buttonRegistry.h"
+#include "winInputDeviceManager.h"
 
 
 #if defined(_WIN32) && !defined(CPPPARSER)
 #if defined(_WIN32) && !defined(CPPPARSER)
 
 

+ 2 - 10
panda/src/display/graphicsStateGuardian.cxx

@@ -1433,12 +1433,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       // Apply the default OpenGL lights otherwise.
       // Apply the default OpenGL lights otherwise.
       // Special exception for light 0, which defaults to white.
       // Special exception for light 0, which defaults to white.
       string basename = name->get_basename();
       string basename = name->get_basename();
-      if (basename == "color" || basename == "diffuse") {
-        t.set_row(3, _light_color_scale);
-        return &t;
-      } else if (basename == "specular") {
-        return &LMatrix4::ones_mat();
-      }
+      return &LMatrix4::ones_mat();
     }
     }
     return fetch_specified_member(NodePath(), name, t);
     return fetch_specified_member(NodePath(), name, t);
   }
   }
@@ -1494,7 +1489,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     } else if (index == 0) {
     } else if (index == 0) {
       // Apply the default OpenGL lights otherwise.
       // Apply the default OpenGL lights otherwise.
       // Special exception for light 0, which defaults to white.
       // Special exception for light 0, which defaults to white.
-      t.set_row(0, _light_color_scale);
+      t.set_row(0, LVecBase4(1, 1, 1, 1));
     }
     }
     return &t;
     return &t;
   }
   }
@@ -1539,7 +1534,6 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
     Light *light = node->as_light();
     Light *light = node->as_light();
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     LColor c = light->get_color();
     LColor c = light->get_color();
-    c.componentwise_mult(_light_color_scale);
     t.set_row(3, c);
     t.set_row(3, c);
     return &t;
     return &t;
 
 
@@ -1551,7 +1545,6 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     if (node->is_ambient_light()) {
     if (node->is_ambient_light()) {
       LColor c = light->get_color();
       LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
       t.set_row(3, c);
       t.set_row(3, c);
     } else {
     } else {
       // Non-ambient lights don't currently have an ambient color in Panda3D.
       // Non-ambient lights don't currently have an ambient color in Panda3D.
@@ -1570,7 +1563,6 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
       t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
       t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
     } else {
     } else {
       LColor c = light->get_color();
       LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
       t.set_row(3, c);
       t.set_row(3, c);
     }
     }
     return &t;
     return &t;

+ 1 - 1
panda/src/distort/oSphereLens.cxx

@@ -48,7 +48,7 @@ do_extrude(const Lens::CData *lens_cdata,
   LPoint3 f = point2d * do_get_film_mat_inv(lens_cdata);
   LPoint3 f = point2d * do_get_film_mat_inv(lens_cdata);
 
 
   PN_stdfloat focal_length = do_get_focal_length(lens_cdata);
   PN_stdfloat focal_length = do_get_focal_length(lens_cdata);
-  PN_stdfloat angle = f[0] * cylindrical_k / focal_length;
+  PN_stdfloat angle = f[0] * ospherical_k / focal_length;
   PN_stdfloat sinAngle, cosAngle;
   PN_stdfloat sinAngle, cosAngle;
   csincos(deg_2_rad(angle), &sinAngle, &cosAngle);
   csincos(deg_2_rad(angle), &sinAngle, &cosAngle);
 
 

+ 30 - 12
panda/src/egg2pg/eggLoader.cxx

@@ -1814,10 +1814,11 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
     // A collision group: create collision geometry.
     // A collision group: create collision geometry.
     node = new CollisionNode(egg_group->get_name());
     node = new CollisionNode(egg_group->get_name());
 
 
+    // Piggy-back the desired transform to apply onto the node, since we can't
+    // break the ABI in 1.10.
+    node->set_transform(TransformState::make_mat(LCAST(PN_stdfloat, egg_group->get_vertex_to_node())));
     make_collision_solids(egg_group, egg_group, (CollisionNode *)node.p());
     make_collision_solids(egg_group, egg_group, (CollisionNode *)node.p());
-
-    // Transform all of the collision solids into local space.
-    node->xform(LCAST(PN_stdfloat, egg_group->get_vertex_to_node()));
+    node->clear_transform();
 
 
     if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
     if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
       // If we also specified to keep the geometry, continue the traversal.
       // If we also specified to keep the geometry, continue the traversal.
@@ -2773,7 +2774,7 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
  */
  */
 bool EggLoader::
 bool EggLoader::
 make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
 make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
-         LPoint3 &min_p, LPoint3 &max_p, LColor &color) {
+         const LMatrix4 &xform, LPoint3 &min_p, LPoint3 &max_p) {
   EggGroup *geom_group = find_collision_geometry(egg_group, flags);
   EggGroup *geom_group = find_collision_geometry(egg_group, flags);
   if (geom_group != nullptr) {
   if (geom_group != nullptr) {
     // Collect all of the vertices.
     // Collect all of the vertices.
@@ -2802,13 +2803,12 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
     }
     }
 
 
     EggVertex *vertex = (*vi);
     EggVertex *vertex = (*vi);
-    LVertexd min_pd = vertex->get_pos3();
-    LVertexd max_pd = min_pd;
-    color = vertex->get_color();
+    LPoint3 min_pd = LCAST(PN_stdfloat, vertex->get_pos3()) * xform;
+    LPoint3 max_pd = min_pd;
 
 
     for (++vi; vi != vertices.end(); ++vi) {
     for (++vi; vi != vertices.end(); ++vi) {
       vertex = (*vi);
       vertex = (*vi);
-      const LVertexd &pos = vertex->get_pos3();
+      LPoint3 pos = LCAST(PN_stdfloat, vertex->get_pos3()) * xform;
       min_pd.set(min(min_pd[0], pos[0]),
       min_pd.set(min(min_pd[0], pos[0]),
                  min(min_pd[1], pos[1]),
                  min(min_pd[1], pos[1]),
                  min(min_pd[2], pos[2]));
                  min(min_pd[2], pos[2]));
@@ -2817,13 +2817,25 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
                  max(max_pd[2], pos[2]));
                  max(max_pd[2], pos[2]));
     }
     }
 
 
-    min_p = LCAST(PN_stdfloat, min_pd);
-    max_p = LCAST(PN_stdfloat, max_pd);
+    min_p = min_pd;
+    max_p = max_pd;
     return (min_pd != max_pd);
     return (min_pd != max_pd);
   }
   }
   return false;
   return false;
 }
 }
 
 
+/**
+ * Creates a single generic Box corresponding to the polygons associated with
+ * this group.  This box is used by make_collision_box.
+ */
+bool EggLoader::
+make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
+         LPoint3 &min_p, LPoint3 &max_p, LColor &color) {
+
+  color.set(1.0, 1.0, 1.0, 1.0);
+  return make_box(egg_group, flags, LMatrix4::ident_mat(), min_p, max_p);
+}
+
 /**
 /**
  * Creates CollisionSolids corresponding to the collision geometry indicated
  * Creates CollisionSolids corresponding to the collision geometry indicated
  * at the given node and below.
  * at the given node and below.
@@ -2904,6 +2916,7 @@ make_collision_plane(EggGroup *egg_group, CollisionNode *cnode,
           create_collision_plane(DCAST(EggPolygon, *ci), egg_group);
           create_collision_plane(DCAST(EggPolygon, *ci), egg_group);
         if (csplane != nullptr) {
         if (csplane != nullptr) {
           apply_collision_flags(csplane, flags);
           apply_collision_flags(csplane, flags);
+          csplane->xform(cnode->get_transform()->get_mat());
           cnode->add_solid(csplane);
           cnode->add_solid(csplane);
           return;
           return;
         }
         }
@@ -3004,6 +3017,7 @@ make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode,
     CollisionSphere *cssphere =
     CollisionSphere *cssphere =
       new CollisionSphere(center, radius);
       new CollisionSphere(center, radius);
     apply_collision_flags(cssphere, flags);
     apply_collision_flags(cssphere, flags);
+    cssphere->xform(cnode->get_transform()->get_mat());
     cnode->add_solid(cssphere);
     cnode->add_solid(cssphere);
   }
   }
 }
 }
@@ -3017,8 +3031,8 @@ make_collision_box(EggGroup *egg_group, CollisionNode *cnode,
                    EggGroup::CollideFlags flags) {
                    EggGroup::CollideFlags flags) {
   LPoint3 min_p;
   LPoint3 min_p;
   LPoint3 max_p;
   LPoint3 max_p;
-  LColor dummycolor;
-  if (make_box(egg_group, flags, min_p, max_p, dummycolor)) {
+  CPT(TransformState) transform = cnode->get_transform();
+  if (make_box(egg_group, flags, transform->get_mat(), min_p, max_p)) {
     CollisionBox *csbox =
     CollisionBox *csbox =
       new CollisionBox(min_p, max_p);
       new CollisionBox(min_p, max_p);
     apply_collision_flags(csbox, flags);
     apply_collision_flags(csbox, flags);
@@ -3040,6 +3054,7 @@ make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode,
     CollisionInvSphere *cssphere =
     CollisionInvSphere *cssphere =
       new CollisionInvSphere(center, radius);
       new CollisionInvSphere(center, radius);
     apply_collision_flags(cssphere, flags);
     apply_collision_flags(cssphere, flags);
+    cssphere->xform(cnode->get_transform()->get_mat());
     cnode->add_solid(cssphere);
     cnode->add_solid(cssphere);
   }
   }
 }
 }
@@ -3234,6 +3249,7 @@ make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode,
           new CollisionCapsule(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b),
           new CollisionCapsule(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b),
                             radius);
                             radius);
         apply_collision_flags(cscapsule, flags);
         apply_collision_flags(cscapsule, flags);
+        cscapsule->xform(cnode->get_transform()->get_mat());
         cnode->add_solid(cscapsule);
         cnode->add_solid(cscapsule);
       }
       }
     }
     }
@@ -3395,6 +3411,7 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
         new CollisionPolygon(vertices_begin, vertices_end);
         new CollisionPolygon(vertices_begin, vertices_end);
       if (cspoly->is_valid()) {
       if (cspoly->is_valid()) {
         apply_collision_flags(cspoly, flags);
         apply_collision_flags(cspoly, flags);
+        cspoly->xform(cnode->get_transform()->get_mat());
         cnode->add_solid(cspoly);
         cnode->add_solid(cspoly);
       }
       }
     }
     }
@@ -3485,6 +3502,7 @@ create_collision_floor_mesh(CollisionNode *cnode,
     CollisionFloorMesh::TriangleIndices triangle = *ti;
     CollisionFloorMesh::TriangleIndices triangle = *ti;
     csfloor->add_triangle(triangle.p1, triangle.p2, triangle.p3);
     csfloor->add_triangle(triangle.p1, triangle.p2, triangle.p3);
   }
   }
+  csfloor->xform(cnode->get_transform()->get_mat());
   cnode->add_solid(csfloor);
   cnode->add_solid(csfloor);
 }
 }
 
 

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

@@ -163,6 +163,8 @@ private:
   bool make_sphere(EggGroup *start_group, EggGroup::CollideFlags flags,
   bool make_sphere(EggGroup *start_group, EggGroup::CollideFlags flags,
                    LPoint3 &center, PN_stdfloat &radius, LColor &color);
                    LPoint3 &center, PN_stdfloat &radius, LColor &color);
 
 
+  bool make_box(EggGroup *start_group, EggGroup::CollideFlags flags,
+                const LMatrix4 &xform, LPoint3 &min_p, LPoint3 &max_p);
   bool make_box(EggGroup *start_group, EggGroup::CollideFlags flags,
   bool make_box(EggGroup *start_group, EggGroup::CollideFlags flags,
                 LPoint3 &min_p, LPoint3 &max_p, LColor &color);
                 LPoint3 &min_p, LPoint3 &max_p, LColor &color);
 
 

+ 0 - 2
panda/src/express/p3express_composite2.cxx

@@ -8,8 +8,6 @@
 #include "subStream.cxx"
 #include "subStream.cxx"
 #include "subStreamBuf.cxx"
 #include "subStreamBuf.cxx"
 #include "temporaryFile.cxx"
 #include "temporaryFile.cxx"
-#include "threadSafePointerTo.cxx"
-#include "threadSafePointerToBase.cxx"
 #include "trueClock.cxx"
 #include "trueClock.cxx"
 #include "typedReferenceCount.cxx"
 #include "typedReferenceCount.cxx"
 #include "virtualFile.cxx"
 #include "virtualFile.cxx"

+ 0 - 207
panda/src/express/threadSafePointerTo.I

@@ -1,207 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerTo.I
- * @author drose
- * @date 2006-04-28
- */
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerTo<T>::
-ThreadSafePointerTo(To *ptr) : ThreadSafePointerToBase<T>(ptr) {
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerTo<T>::
-ThreadSafePointerTo(const ThreadSafePointerTo<T> &copy) :
-  ThreadSafePointerToBase<T>((const ThreadSafePointerToBase<T> &)copy)
-{
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerTo<T>::
-~ThreadSafePointerTo() {
-}
-
-/**
- *
- */
-template<class T>
-INLINE typename ThreadSafePointerTo<T>::To &ThreadSafePointerTo<T>::
-operator *() const {
-  return *((To *)AtomicAdjust::get_ptr(this->_void_ptr));
-}
-
-/**
- *
- */
-template<class T>
-INLINE typename ThreadSafePointerTo<T>::To *ThreadSafePointerTo<T>::
-operator -> () const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- * We also have the typecast operator to automatically convert
- * ThreadSafePointerTo's to the required kind of actual pointer.  This
- * introduces ambiguities which the compiler will resolve one way or the
- * other, but we don't care which way it goes because either will be correct.
- */
-template<class T>
-INLINE ThreadSafePointerTo<T>::
-operator T * () const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- * Returns an ordinary pointer instead of a ThreadSafePointerTo.  Useful to
- * work around compiler problems, particularly for implicit upcasts.
- */
-template<class T>
-INLINE typename ThreadSafePointerTo<T>::To *ThreadSafePointerTo<T>::
-p() const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerTo<T> &ThreadSafePointerTo<T>::
-operator = (To *ptr) {
-  this->reassign(ptr);
-  return *this;
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerTo<T> &ThreadSafePointerTo<T>::
-operator = (const ThreadSafePointerTo<T> &copy) {
-  this->reassign((const ThreadSafePointerToBase<T> &)copy);
-  return *this;
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T>::
-ThreadSafeConstPointerTo(const typename ThreadSafeConstPointerTo<T>::To *ptr) :
-  ThreadSafePointerToBase<T>((typename ThreadSafeConstPointerTo<T>::To *)ptr)
-{
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T>::
-ThreadSafeConstPointerTo(const ThreadSafePointerTo<T> &copy) :
-  ThreadSafePointerToBase<T>((const ThreadSafePointerToBase<T> &)copy)
-{
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T>::
-~ThreadSafeConstPointerTo() {
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T>::
-ThreadSafeConstPointerTo(const ThreadSafeConstPointerTo<T> &copy) :
-  ThreadSafePointerToBase<T>((const ThreadSafePointerToBase<T> &)copy)
-{
-}
-
-/**
- *
- */
-template<class T>
-INLINE const typename ThreadSafeConstPointerTo<T>::To &ThreadSafeConstPointerTo<T>::
-operator *() const {
-  return *((To *)AtomicAdjust::get_ptr(this->_void_ptr));
-}
-
-/**
- *
- */
-template<class T>
-INLINE const typename ThreadSafeConstPointerTo<T>::To *ThreadSafeConstPointerTo<T>::
-operator -> () const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- * We also have the typecast operator to automatically convert
- * ThreadSafeConstPointerTo's to the required kind of actual pointer.  This
- * introduces ambiguities which the compiler will resolve one way or the
- * other, but we don't care which way it goes because either will be correct.
- */
-
-template<class T>
-INLINE ThreadSafeConstPointerTo<T>::
-operator const T * () const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- * Returns an ordinary pointer instead of a ThreadSafeConstPointerTo.  Useful
- * to work around compiler problems, particularly for implicit upcasts.
- */
-template<class T>
-INLINE const typename ThreadSafeConstPointerTo<T>::To *ThreadSafeConstPointerTo<T>::
-p() const {
-  return (To *)AtomicAdjust::get_ptr(this->_void_ptr);
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T> &ThreadSafeConstPointerTo<T>::
-operator = (const To *ptr) {
-  this->reassign((To *)ptr);
-  return *this;
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T> &ThreadSafeConstPointerTo<T>::
-operator = (const ThreadSafePointerTo<T> &copy) {
-  this->reassign((const ThreadSafePointerToBase<T> &)copy);
-  return *this;
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafeConstPointerTo<T> &ThreadSafeConstPointerTo<T>::
-operator = (const ThreadSafeConstPointerTo<T> &copy) {
-  this->reassign((const ThreadSafePointerToBase<T> &)copy);
-  return *this;
-}

+ 0 - 14
panda/src/express/threadSafePointerTo.cxx

@@ -1,14 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerTo.cxx
- * @author drose
- * @date 2006-04-28
- */
-
-#include "threadSafePointerTo.h"

+ 0 - 105
panda/src/express/threadSafePointerTo.h

@@ -1,105 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerTo.h
- * @author drose
- * @date 2006-04-28
- */
-
-#ifndef THREADSAFEPOINTERTO_H
-#define THREADSAFEPOINTERTO_H
-
-#include "pandabase.h"
-#include "threadSafePointerToBase.h"
-
-/**
- * This works exactly like PointerTo, except that the object is designed to be
- * thread-safe: it is generally safe to make unprotected assignments to this
- * pointer, in the sense that the last assignment will win and the reference
- * counts will be properly maintained.
- */
-template <class T>
-class ThreadSafePointerTo : public ThreadSafePointerToBase<T> {
-public:
-  typedef typename ThreadSafePointerToBase<T>::To To;
-PUBLISHED:
-  INLINE ThreadSafePointerTo(To *ptr = nullptr);
-  INLINE ThreadSafePointerTo(const ThreadSafePointerTo<T> &copy);
-  INLINE ~ThreadSafePointerTo();
-
-public:
-  INLINE To &operator *() const;
-  INLINE To *operator -> () const;
-  // MSVC.NET 2005 insists that we use T *, and not To *, here.
-  INLINE operator T *() const;
-
-PUBLISHED:
-  // When downcasting to a derived class from a
-  // ThreadSafePointerTo<BaseClass>, C++ would normally require you to cast
-  // twice: once to an actual BaseClass pointer, and then again to your
-  // desired pointer.  You can use the handy function p() to avoid this first
-  // cast and make your code look a bit cleaner.
-
-  // e.g.  instead of (MyType *)(BaseClass *)ptr, use (MyType *)ptr.p()
-
-  // If your base class is a derivative of TypedObject, you might want to use
-  // the DCAST macro defined in typedObject.h instead, e.g.  DCAST(MyType,
-  // ptr).  This provides a clean downcast that doesn't require .p() or any
-  // double-casting, and it can be run-time checked for correctness.
-  INLINE To *p() const;
-
-  INLINE ThreadSafePointerTo<T> &operator = (To *ptr);
-  INLINE ThreadSafePointerTo<T> &operator = (const ThreadSafePointerTo<T> &copy);
-
-  // These functions normally wouldn't need to be redefined here, but we do so
-  // anyway just to help out interrogate (which doesn't seem to want to
-  // automatically export the ThreadSafePointerToBase class).  When this works
-  // again in interrogate, we can remove these.
-  INLINE bool is_null() const { return ThreadSafePointerToBase<T>::is_null(); }
-  INLINE void clear() { ThreadSafePointerToBase<T>::clear(); }
-};
-
-
-/**
- *
- */
-template <class T>
-class ThreadSafeConstPointerTo : public ThreadSafePointerToBase<T> {
-public:
-  typedef typename ThreadSafePointerToBase<T>::To To;
-PUBLISHED:
-  INLINE ThreadSafeConstPointerTo(const To *ptr = nullptr);
-  INLINE ThreadSafeConstPointerTo(const ThreadSafePointerTo<T> &copy);
-  INLINE ThreadSafeConstPointerTo(const ThreadSafeConstPointerTo<T> &copy);
-  INLINE ~ThreadSafeConstPointerTo();
-
-public:
-  INLINE const To &operator *() const;
-  INLINE const To *operator -> () const;
-  INLINE operator const T *() const;
-
-PUBLISHED:
-  INLINE const To *p() const;
-
-  INLINE ThreadSafeConstPointerTo<T> &operator = (const To *ptr);
-  INLINE ThreadSafeConstPointerTo<T> &operator = (const ThreadSafePointerTo<T> &copy);
-  INLINE ThreadSafeConstPointerTo<T> &operator = (const ThreadSafeConstPointerTo<T> &copy);
-
-  // This functions normally wouldn't need to be redefined here, but we do so
-  // anyway just to help out interrogate (which doesn't seem to want to
-  // automatically export the ThreadSafePointerToBase class).  When this works
-  // again in interrogate, we can remove this.
-  INLINE void clear() { ThreadSafePointerToBase<T>::clear(); }
-};
-
-#define TSPT(type) ThreadSafePointerTo< type >
-#define TSCPT(type) ThreadSafeConstPointerTo< type >
-
-#include "threadSafePointerTo.I"
-
-#endif

+ 0 - 132
panda/src/express/threadSafePointerToBase.I

@@ -1,132 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerToBase.I
- * @author drose
- * @date 2006-04-28
- */
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerToBase<T>::
-ThreadSafePointerToBase(To *ptr) {
-  reassign(ptr);
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerToBase<T>::
-ThreadSafePointerToBase(const ThreadSafePointerToBase<T> &copy) {
-  reassign(copy);
-}
-
-/**
- *
- */
-template<class T>
-INLINE ThreadSafePointerToBase<T>::
-~ThreadSafePointerToBase() {
-  reassign(nullptr);
-}
-
-/**
- * This is the main work of the ThreadSafePointerTo family.  When the pointer
- * is reassigned, decrement the old reference count and increment the new one.
- */
-template<class T>
-INLINE void ThreadSafePointerToBase<T>::
-reassign(To *ptr) {
-  To *old_ptr = (To *)AtomicAdjust::get_ptr(_void_ptr);
-  if (ptr == old_ptr) {
-    return;
-  }
-
-#ifdef HAVE_THREADS
-  void *orig_ptr = AtomicAdjust::compare_and_exchange_ptr(_void_ptr, old_ptr, ptr);
-  while (orig_ptr != old_ptr) {
-    // Some other thread assigned it first.  Try again.
-    old_ptr = (To *)AtomicAdjust::get_ptr(_void_ptr);
-    if (ptr == old_ptr) {
-      return;
-    }
-
-    orig_ptr = AtomicAdjust::compare_and_exchange_ptr(_void_ptr, old_ptr, ptr);
-  }
-#else  // HAVE_THREADS
-  _void_ptr = ptr;
-#endif  // HAVE_THREADS
-
-  if (ptr != nullptr) {
-    ptr->ref();
-#ifdef DO_MEMORY_USAGE
-    if (MemoryUsage::get_track_memory_usage()) {
-      update_type(ptr);
-    }
-#endif
-  }
-
-  // Now delete the old pointer.
-  if (old_ptr != nullptr) {
-    unref_delete(old_ptr);
-  }
-}
-
-/**
- *
- */
-template<class T>
-INLINE void ThreadSafePointerToBase<T>::
-reassign(const ThreadSafePointerToBase<To> &copy) {
-  reassign((To *)copy._void_ptr);
-}
-
-/**
- * Ensures that the MemoryUsage record for the pointer has the right type of
- * object, if we know the type ourselves.
- */
-template<class T>
-void ThreadSafePointerToBase<T>::
-update_type(To *ptr) {
-#ifdef DO_MEMORY_USAGE
-  TypeHandle type = get_type_handle(To);
-  if (type == TypeHandle::none()) {
-    do_init_type(To);
-    type = get_type_handle(To);
-  }
-  if (type != TypeHandle::none()) {
-    MemoryUsage::update_type(ptr, type);
-  }
-#endif  // DO_MEMORY_USAGE
-}
-
-/**
- * A convenient way to set the ThreadSafePointerTo object to NULL. (Assignment
- * to a NULL pointer also works, of course.)
- */
-template<class T>
-INLINE void ThreadSafePointerToBase<T>::
-clear() {
-  reassign(nullptr);
-}
-
-/**
- * A handy function to output ThreadSafePointerTo's as a hex pointer followed
- * by a reference count.
- */
-template<class T>
-INLINE void ThreadSafePointerToBase<T>::
-output(std::ostream &out) const {
-  out << _void_ptr;
-  if (_void_ptr != nullptr) {
-    out << ":" << ((To *)_void_ptr)->get_ref_count();
-  }
-}

+ 0 - 14
panda/src/express/threadSafePointerToBase.cxx

@@ -1,14 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerToBase.cxx
- * @author drose
- * @date 2006-04-28
- */
-
-#include "threadSafePointerToBase.h"

+ 0 - 63
panda/src/express/threadSafePointerToBase.h

@@ -1,63 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file threadSafePointerToBase.h
- * @author drose
- * @date 2006-04-28
- */
-
-#ifndef THREADSAFEPOINTERTOBASE_H
-#define THREADSAFEPOINTERTOBASE_H
-
-#include "pandabase.h"
-#include "pointerToVoid.h"
-#include "referenceCount.h"
-#include "typedef.h"
-#include "memoryUsage.h"
-#include "config_express.h"
-#include "atomicAdjust.h"
-
-/**
- * This is the base class for ThreadSafePointerTo and
- * ThreadSafeConstPointerTo.  Don't try to use it directly; use either derived
- * class instead.
- */
-template <class T>
-class ThreadSafePointerToBase : public PointerToVoid {
-public:
-  typedef T To;
-
-protected:
-  INLINE ThreadSafePointerToBase(To *ptr);
-  INLINE ThreadSafePointerToBase(const ThreadSafePointerToBase<T> &copy);
-  INLINE ~ThreadSafePointerToBase();
-
-  INLINE void reassign(To *ptr);
-  INLINE void reassign(const ThreadSafePointerToBase<To> &copy);
-
-  void update_type(To *ptr);
-
-  // No assignment or retrieval functions are declared in
-  // ThreadSafePointerToBase, because we will have to specialize on const vs.
-  // non-const later.
-
-PUBLISHED:
-  INLINE void clear();
-
-  void output(std::ostream &out) const;
-};
-
-template<class T>
-INLINE std::ostream &operator <<(std::ostream &out, const ThreadSafePointerToBase<T> &pointer) {
-  pointer.output(out);
-  return out;
-}
-
-#include "threadSafePointerToBase.I"
-
-#endif

+ 4 - 1
panda/src/ffmpeg/config_ffmpeg.cxx

@@ -17,8 +17,8 @@
 #include "ffmpegVideoCursor.h"
 #include "ffmpegVideoCursor.h"
 #include "ffmpegAudio.h"
 #include "ffmpegAudio.h"
 #include "ffmpegAudioCursor.h"
 #include "ffmpegAudioCursor.h"
-
 #include "movieTypeRegistry.h"
 #include "movieTypeRegistry.h"
+#include "pandaSystem.h"
 
 
 extern "C" {
 extern "C" {
   #include <libavcodec/avcodec.h>
   #include <libavcodec/avcodec.h>
@@ -129,6 +129,9 @@ init_libffmpeg() {
   FfmpegVideo::register_with_read_factory();
   FfmpegVideo::register_with_read_factory();
   FfmpegVideoCursor::register_with_read_factory();
   FfmpegVideoCursor::register_with_read_factory();
 
 
+  PandaSystem *ps = PandaSystem::get_global_ptr();
+  ps->add_system("FFmpeg");
+
   // Register ffmpeg as catch-all audiovideo type.
   // Register ffmpeg as catch-all audiovideo type.
   MovieTypeRegistry *reg = MovieTypeRegistry::get_global_ptr();
   MovieTypeRegistry *reg = MovieTypeRegistry::get_global_ptr();
   reg->register_audio_type(&FfmpegAudio::make, "*");
   reg->register_audio_type(&FfmpegAudio::make, "*");

+ 4 - 0
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -659,6 +659,10 @@ rebuild_bitplanes() {
       glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
       glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
     }
     }
+
+    // Mark the GSG as supporting multisampling, so that it will respect an
+    // AntialiasAttrib with mode M_multisample.
+    glgsg->_supports_multisample = true;
   } else {
   } else {
     glDisable(GL_MULTISAMPLE);
     glDisable(GL_MULTISAMPLE);
   }
   }

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

@@ -2462,7 +2462,7 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) {
       }
       }
 
 
       GLint p = bind._id._seqno;
       GLint p = bind._id._seqno;
-      max_p = max(max_p, p + 1);
+      max_p = max(max_p, p + bind._elements);
 
 
       // Don't apply vertex colors if they are disabled with a ColorAttrib.
       // Don't apply vertex colors if they are disabled with a ColorAttrib.
       int num_elements, element_stride, divisor;
       int num_elements, element_stride, divisor;

+ 2 - 2
panda/src/gobj/lens.cxx

@@ -1221,8 +1221,8 @@ do_project(const CData *cdata, const LPoint3 &point3d, LPoint3 &point2d) const {
   point2d.set(full[0] * recip_full3, full[1] * recip_full3, full[2] * recip_full3);
   point2d.set(full[0] * recip_full3, full[1] * recip_full3, full[2] * recip_full3);
   return
   return
     (full[3] > 0.0f) &&
     (full[3] > 0.0f) &&
-    (point2d[0] >= -1.0f) && (point2d[0] <= 1.0f) &&
-    (point2d[1] >= -1.0f) && (point2d[1] <= 1.0f);
+    (point2d[0] >= -1.0f - NEARLY_ZERO(PN_stdfloat)) && (point2d[0] <= 1.0f + NEARLY_ZERO(PN_stdfloat)) &&
+    (point2d[1] >= -1.0f - NEARLY_ZERO(PN_stdfloat)) && (point2d[1] <= 1.0f + NEARLY_ZERO(PN_stdfloat));
 }
 }
 
 
 /**
 /**

+ 59 - 2
panda/src/grutil/shaderTerrainMesh.cxx

@@ -30,6 +30,7 @@
 #include "renderAttrib.h"
 #include "renderAttrib.h"
 #include "shaderInput.h"
 #include "shaderInput.h"
 #include "boundingBox.h"
 #include "boundingBox.h"
+#include "boundingSphere.h"
 #include "samplerState.h"
 #include "samplerState.h"
 #include "config_grutil.h"
 #include "config_grutil.h"
 #include "typeHandle.h"
 #include "typeHandle.h"
@@ -106,8 +107,6 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
   _update_enabled(true),
   _update_enabled(true),
   _heightfield_tex(nullptr)
   _heightfield_tex(nullptr)
 {
 {
-  set_final(true);
-  set_bounds(new OmniBoundingVolume());
 }
 }
 
 
 /**
 /**
@@ -563,6 +562,64 @@ void ShaderTerrainMesh::add_for_draw(CullTraverser *trav, CullTraverserData &dat
   _basic_collector.stop();
   _basic_collector.stop();
 }
 }
 
 
+/**
+ * This is used to support NodePath::calc_tight_bounds().  It is not intended
+ * to be called directly, and it has nothing to do with the normal Panda
+ * bounding-volume computation.
+ *
+ * If the node contains any geometry, this updates min_point and max_point to
+ * enclose its bounding box.  found_any is to be set true if the node has any
+ * geometry at all, or left alone if it has none.  This method may be called
+ * over several nodes, so it may enter with min_point, max_point, and
+ * found_any already set.
+ */
+CPT(TransformState) ShaderTerrainMesh::
+calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
+                  const TransformState *transform, Thread *current_thread) const {
+  CPT(TransformState) next_transform =
+    PandaNode::calc_tight_bounds(min_point, max_point, found_any, transform,
+                                 current_thread);
+
+  const LMatrix4 &mat = next_transform->get_mat();
+  LPoint3 terrain_min_point = LPoint3(0, 0, 0) * mat;
+  LPoint3 terrain_max_point = LPoint3(1, 1, 1) * mat;
+  if (!found_any) {
+    min_point = terrain_min_point;
+    max_point = terrain_max_point;
+    found_any = true;
+  } else {
+    min_point = min_point.fmin(terrain_min_point);
+    max_point = max_point.fmax(terrain_max_point);
+  }
+
+  return next_transform;
+}
+
+/**
+ * Returns a newly-allocated BoundingVolume that represents the internal
+ * contents of the node.  Should be overridden by PandaNode classes that
+ * contain something internally.
+ */
+void ShaderTerrainMesh::
+compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
+                        int &internal_vertices,
+                        int pipeline_stage,
+                        Thread *current_thread) const {
+
+  BoundingVolume::BoundsType btype = get_bounds_type();
+  if (btype == BoundingVolume::BT_default) {
+    btype = bounds_type;
+  }
+
+  if (btype == BoundingVolume::BT_sphere) {
+    internal_bounds = new BoundingSphere(LPoint3(0.5, 0.5, 0.5), csqrt(0.75));
+  } else {
+    internal_bounds = new BoundingBox(LPoint3(0, 0, 0), LPoint3(1, 1, 1));
+  }
+
+  internal_vertices = 0;
+}
+
 /**
 /**
  * @brief Traverses the quadtree
  * @brief Traverses the quadtree
  * @details This method traverses the given chunk, deciding whether it should
  * @details This method traverses the given chunk, deciding whether it should

+ 10 - 0
panda/src/grutil/shaderTerrainMesh.h

@@ -91,6 +91,16 @@ public:
   virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
   virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
 
 
 private:
 private:
+  virtual CPT(TransformState)
+    calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
+                      bool &found_any,
+                      const TransformState *transform,
+                      Thread *current_thread = Thread::get_current_thread()) const;
+
+  virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
+                                       int &internal_vertices,
+                                       int pipeline_stage,
+                                       Thread *current_thread) const;
 
 
   // Chunk data
   // Chunk data
   struct Chunk {
   struct Chunk {

+ 1 - 1
panda/src/mathutil/boundingBox.I

@@ -101,7 +101,7 @@ get_plane(int n) const {
 INLINE_MATHUTIL void BoundingBox::
 INLINE_MATHUTIL void BoundingBox::
 set_min_max(const LPoint3 &min, const LPoint3 &max) {
 set_min_max(const LPoint3 &min, const LPoint3 &max) {
   nassertv(!min.is_nan() && !max.is_nan());
   nassertv(!min.is_nan() && !max.is_nan());
-  nassertv(_min[0] <= _max[0] && _min[1] <= _max[1] && _min[2] <= _max[2]);
+  nassertv(min[0] <= max[0] && min[1] <= max[1] && min[2] <= max[2]);
   _min = min;
   _min = min;
   _max = max;
   _max = max;
   _flags = 0;
   _flags = 0;

+ 14 - 0
panda/src/putil/uniqueIdAllocator.cxx

@@ -173,6 +173,20 @@ initial_reserve_id(uint32_t id) {
   --_free;
   --_free;
 }
 }
 
 
+/**
+ * Checks the allocated state of an index. Returns true for
+ * indices that are currently allocated and in use.
+ */
+bool UniqueIdAllocator::
+is_allocated(uint32_t id) {
+  if (id < _min || id > _max) {
+    // This id is out of range, not allocated.
+    return false;
+  }
+
+  uint32_t index = id - _min; // Convert to _table index.
+  return _table[index] == IndexAllocated;
+}
 
 
 /**
 /**
  * Free an allocated index (index must be between _min and _max that were
  * Free an allocated index (index must be between _min and _max that were

+ 2 - 0
panda/src/putil/uniqueIdAllocator.h

@@ -43,6 +43,8 @@ PUBLISHED:
   uint32_t allocate();
   uint32_t allocate();
   void initial_reserve_id(uint32_t id);
   void initial_reserve_id(uint32_t id);
 
 
+  bool is_allocated(uint32_t index);
+
   void free(uint32_t index);
   void free(uint32_t index);
   PN_stdfloat fraction_used() const;
   PN_stdfloat fraction_used() const;
 
 

+ 41 - 5
pandatool/src/deploy-stub/deploy-stub.c

@@ -75,6 +75,10 @@ static struct _inittab extensions[] = {
 #endif
 #endif
 #endif
 #endif
 
 
+#ifdef _WIN32
+static wchar_t *log_pathw = NULL;
+#endif
+
 #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000
 #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000
 static int supports_code_page(UINT cp) {
 static int supports_code_page(UINT cp) {
   if (cp == 0) {
   if (cp == 0) {
@@ -225,7 +229,8 @@ static int mkdir_parent(const char *path) {
 static int setup_logging(const char *path, int append) {
 static int setup_logging(const char *path, int append) {
 #ifdef _WIN32
 #ifdef _WIN32
   // Does it start with a tilde?  Perform tilde expansion if so.
   // Does it start with a tilde?  Perform tilde expansion if so.
-  wchar_t pathw[MAX_PATH * 2];
+  wchar_t *pathw = (wchar_t *)malloc(sizeof(wchar_t) * MAX_PATH);
+  pathw[0] = 0;
   size_t offset = 0;
   size_t offset = 0;
   if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
   if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
     // Strip off the tilde.
     // Strip off the tilde.
@@ -233,6 +238,7 @@ static int setup_logging(const char *path, int append) {
 
 
     // Get the home directory path for the current user.
     // Get the home directory path for the current user.
     if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
     if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
+      free(pathw);
       return 0;
       return 0;
     }
     }
     offset = wcslen(pathw);
     offset = wcslen(pathw);
@@ -240,26 +246,30 @@ static int setup_logging(const char *path, int append) {
 
 
   // We need to convert the rest of the path from UTF-8 to UTF-16.
   // We need to convert the rest of the path from UTF-8 to UTF-16.
   if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
   if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
-                          (int)(_countof(pathw) - offset)) == 0) {
+                          (int)(MAX_PATH - offset)) == 0) {
+    free(pathw);
     return 0;
     return 0;
   }
   }
 
 
   DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
   DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
   int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
   int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
-  HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ,
+  HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
                               NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
 
 
   if (handle == INVALID_HANDLE_VALUE) {
   if (handle == INVALID_HANDLE_VALUE) {
     // Make the parent directories first.
     // Make the parent directories first.
     mkdir_parent(pathw);
     mkdir_parent(pathw);
-    handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ,
+    handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                          NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
                          NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
   }
   }
 
 
   if (handle == INVALID_HANDLE_VALUE) {
   if (handle == INVALID_HANDLE_VALUE) {
+    free(pathw);
     return 0;
     return 0;
   }
   }
 
 
+  log_pathw = pathw;
+
   if (append) {
   if (append) {
     SetFilePointer(handle, 0, NULL, FILE_END);
     SetFilePointer(handle, 0, NULL, FILE_END);
   }
   }
@@ -282,7 +292,7 @@ static int setup_logging(const char *path, int append) {
 
 
   // Now replace the stdout and stderr file descriptors with one pointing to
   // Now replace the stdout and stderr file descriptors with one pointing to
   // our desired handle.
   // our desired handle.
-  int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | (append ? _O_APPEND : 0));
+  int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | _O_APPEND);
   _dup2(fd, _fileno(stdout));
   _dup2(fd, _fileno(stdout));
   _dup2(fd, _fileno(stderr));
   _dup2(fd, _fileno(stderr));
   _close(fd);
   _close(fd);
@@ -421,6 +431,32 @@ int Py_FrozenMain(int argc, char **argv)
 #endif
 #endif
 
 
 #if defined(MS_WINDOWS) && PY_VERSION_HEX < 0x03040000
 #if defined(MS_WINDOWS) && PY_VERSION_HEX < 0x03040000
+    /* We can't rely on our overriding of the standard I/O to work on older
+     * versions of Python, since they are compiled with an incompatible CRT.
+     * The best solution I've found was to just replace sys.stdout/stderr with
+     * the log file reopened in append mode (which requires not locking it for
+     * write, and also passing in _O_APPEND above, and disabling buffering).
+     * It's not the most elegant solution, but it's better than crashing. */
+#if PY_MAJOR_VERSION < 3
+    if (log_pathw != NULL) {
+      PyObject *uniobj = PyUnicode_FromWideChar(log_pathw, (Py_ssize_t)wcslen(log_pathw));
+      PyObject *file = PyObject_CallFunction((PyObject*)&PyFile_Type, "Nsi", uniobj, "a", 0);
+
+      if (file != NULL) {
+        PyFile_SetEncodingAndErrors(file, "utf-8", NULL);
+
+        PySys_SetObject("stdout", file);
+        PySys_SetObject("stderr", file);
+        PySys_SetObject("__stdout__", file);
+        PySys_SetObject("__stderr__", file);
+
+        /* Be sure to disable buffering, otherwise we'll get overlap */
+        setbuf(stdout, (char *)NULL);
+        setbuf(stderr, (char *)NULL);
+      }
+    }
+    else
+#endif
     if (!supports_code_page(GetConsoleOutputCP()) ||
     if (!supports_code_page(GetConsoleOutputCP()) ||
         !supports_code_page(GetConsoleCP())) {
         !supports_code_page(GetConsoleCP())) {
       /* Same hack as before except for Python 2.7, which doesn't seem to have
       /* Same hack as before except for Python 2.7, which doesn't seem to have

+ 3 - 0
pandatool/src/win-stats/winStats.cxx

@@ -16,6 +16,9 @@
 #include "winStatsServer.h"
 #include "winStatsServer.h"
 #include "config_pstatclient.h"
 #include "config_pstatclient.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 static const char *toplevel_class_name = "pstats";
 static const char *toplevel_class_name = "pstats";

+ 3 - 0
pandatool/src/win-stats/winStatsChartMenu.h

@@ -16,6 +16,9 @@
 
 
 #include "pandatoolbase.h"
 #include "pandatoolbase.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsMonitor;
 class WinStatsMonitor;

+ 3 - 0
pandatool/src/win-stats/winStatsGraph.h

@@ -18,6 +18,9 @@
 #include "winStatsLabelStack.h"
 #include "winStatsLabelStack.h"
 #include "pmap.h"
 #include "pmap.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsMonitor;
 class WinStatsMonitor;

+ 3 - 0
pandatool/src/win-stats/winStatsLabel.h

@@ -16,6 +16,9 @@
 
 
 #include "pandatoolbase.h"
 #include "pandatoolbase.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsMonitor;
 class WinStatsMonitor;

+ 3 - 0
pandatool/src/win-stats/winStatsLabelStack.h

@@ -17,6 +17,9 @@
 #include "pandatoolbase.h"
 #include "pandatoolbase.h"
 #include "pvector.h"
 #include "pvector.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsLabel;
 class WinStatsLabel;

+ 3 - 0
pandatool/src/win-stats/winStatsMonitor.h

@@ -23,6 +23,9 @@
 #include "pvector.h"
 #include "pvector.h"
 #include "pmap.h"
 #include "pmap.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsServer;
 class WinStatsServer;

+ 3 - 0
pandatool/src/win-stats/winStatsPianoRoll.h

@@ -20,6 +20,9 @@
 #include "pStatPianoRoll.h"
 #include "pStatPianoRoll.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsMonitor;
 class WinStatsMonitor;

+ 3 - 0
pandatool/src/win-stats/winStatsStripChart.h

@@ -20,6 +20,9 @@
 #include "pStatStripChart.h"
 #include "pStatStripChart.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 #include <windows.h>
 
 
 class WinStatsMonitor;
 class WinStatsMonitor;

+ 1 - 1
samples/fireflies/main.py

@@ -65,7 +65,7 @@ class FireflyDemo(ShowBase):
         # doesn't support the necessary OpenGL extensions.
         # doesn't support the necessary OpenGL extensions.
 
 
         if self.modelbuffer is None or self.lightbuffer is None:
         if self.modelbuffer is None or self.lightbuffer is None:
-            self.t = addTitle("Toon Shader: Video driver does not support "
+            self.t = addTitle("Firefly Demo: Video driver does not support "
                               "multiple render targets")
                               "multiple render targets")
             return
             return
 
 

+ 278 - 0
samples/particles/advanced.py

@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+
+# This program shows a shader-based particle system. With this approach, you
+# can define an inertial particle system with a moving emitter whose position
+# can not be pre-determined.
+
+from array import array
+from itertools import chain
+from random import uniform
+from math import pi, sin, cos
+from panda3d.core import TextNode
+from panda3d.core import AmbientLight, DirectionalLight
+from panda3d.core import LVector3
+from panda3d.core import NodePath
+from panda3d.core import GeomPoints
+from panda3d.core import GeomEnums
+from panda3d.core import GeomVertexFormat
+from panda3d.core import GeomVertexData
+from panda3d.core import GeomNode
+from panda3d.core import Geom
+from panda3d.core import OmniBoundingVolume
+from panda3d.core import Texture
+from panda3d.core import TextureStage
+from panda3d.core import TexGenAttrib
+from panda3d.core import Shader
+from panda3d.core import ShaderAttrib
+from panda3d.core import loadPrcFileData
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.OnscreenText import OnscreenText
+import sys
+
+HELP_TEXT = """
+left/right arrow: Rotate teapot
+ESC: Quit
+"""
+
+# We need to use GLSL 1.50 for these, and some drivers (notably Mesa) require
+# us to explicitly ask for an OpenGL 3.2 context in that case.
+config = """
+gl-version 3 2
+"""
+
+vert = """
+#version 150
+#extension GL_ARB_shader_image_load_store : require
+
+layout(rgba32f) uniform imageBuffer positions;       // current positions
+layout(rgba32f) uniform imageBuffer start_vel;       // emission velocities
+layout(rgba32f) uniform imageBuffer velocities;      // current velocities
+layout(rgba32f) uniform imageBuffer emission_times;  // emission times
+uniform mat4 p3d_ModelViewProjectionMatrix;
+uniform vec3 emitter_pos;     // emitter's position
+uniform vec3 accel;           // the acceleration of the particles
+uniform float osg_FrameTime;  // time of the current frame (absolute)
+uniform float osg_DeltaFrameTime;// time since last frame
+uniform float start_time;     // particle system's start time (absolute)
+uniform float part_duration;  // single particle's duration
+
+out float from_emission;      // time from specific particle's emission
+out vec4 color;
+
+void main() {
+    float emission_time = imageLoad(emission_times, gl_VertexID).x;
+    vec4 pos = imageLoad(positions, gl_VertexID);
+    vec4 vel = imageLoad(velocities, gl_VertexID);
+    float from_start = osg_FrameTime - start_time;  // time from system's start
+    from_emission = 0;
+    color = vec4(1);
+    if (from_start > emission_time) {  // we've to show the particle
+        from_emission = from_start - emission_time;
+        if (from_emission <= osg_DeltaFrameTime + .01) {
+            // it's particle's emission frame: let's set its position at the
+            // emitter's position and set the initial velocity
+            pos = vec4(emitter_pos, 1);
+            vel = imageLoad(start_vel, gl_VertexID);
+        }
+        pos += vec4((vel * osg_DeltaFrameTime).xyz, 0);
+        vel += vec4(accel, 0) * osg_DeltaFrameTime;
+    } else color = vec4(0);
+
+    // update the emission time (for particle recycling)
+    if (from_start >= emission_time + part_duration) {
+        imageStore(emission_times, gl_VertexID, vec4(from_start, 0, 0, 1));
+    }
+    gl_PointSize = 10;
+    gl_Position = p3d_ModelViewProjectionMatrix * pos;
+    imageStore(positions, gl_VertexID, pos);
+    imageStore(velocities, gl_VertexID, vel);
+}
+"""
+
+frag = """
+#version 150
+
+in float from_emission;       // time elapsed from particle's emission
+in vec4 color;
+uniform float part_duration;  // single particle's duration
+uniform sampler2D image;      // particle's texture
+out vec4 p3d_FragData[1];
+
+void main() {
+    vec4 col = texture(image, gl_PointCoord) * color;
+    // fade the particle considering the time from its emission
+    float alpha = clamp(1 - from_emission / part_duration, 0, 1);
+    p3d_FragData[0] = vec4(col.rgb, col.a * alpha);
+}
+"""
+
+
+class Particle:
+
+    def __init__(
+            self,
+            emitter,          # the node which is emitting
+            texture,          # particle's image
+            rate=.001,        # the emission rate
+            gravity=-9.81,    # z-component of the gravity force
+            vel=1.0,          # length of emission vector
+            partDuration=1.0  # single particle's duration
+            ):
+        self.__emitter = emitter
+        self.__texture = texture
+        # let's compute the total number of particles
+        self.__numPart = int(round(partDuration * 1 / rate))
+        self.__rate = rate
+        self.__gravity = gravity
+        self.__vel = vel
+        self.__partDuration = partDuration
+        self.__nodepath = render.attachNewNode(self.__node())
+        self.__nodepath.setTransparency(True)  # particles have alpha
+        self.__nodepath.setBin("fixed", 0)     # render it at the end
+        self.__setTextures()
+        self.__setShader()
+        self.__nodepath.setRenderModeThickness(10)  # we want sprite particles
+        self.__nodepath.setTexGen(TextureStage.getDefault(),
+                                  TexGenAttrib.MPointSprite)
+        self.__nodepath.setDepthWrite(False)   # don't sort the particles
+        self.__upd_tsk = taskMgr.add(self.__update, "update")
+
+    def __node(self):
+        # this function creates and returns particles' GeomNode
+        points = GeomPoints(GeomEnums.UH_static)
+        points.addNextVertices(self.__numPart)
+        format_ = GeomVertexFormat.getEmpty()
+        geom = Geom(GeomVertexData("abc", format_, GeomEnums.UH_static))
+        geom.addPrimitive(points)
+        geom.setBounds(OmniBoundingVolume())  # always render it
+        node = GeomNode("node")
+        node.addGeom(geom)
+        return node
+
+    def __setTextures(self):
+        # initial positions are all zeros (each position is denoted by 4 values)
+        # positions are stored in a texture
+        positions = [(0, 0, 0, 1) for i in range(self.__numPart)]
+        posLst = list(chain.from_iterable(positions))
+        self.__texPos = self.__buffTex(posLst)
+
+        # define emission times' texture
+        emissionTimes = [(self.__rate * i, 0, 0, 0)
+                          for i in range(self.__numPart)]
+        timesLst = list(chain.from_iterable(emissionTimes))
+        self.__texTimes = self.__buffTex(timesLst)
+
+        # define a list with emission velocities
+        velocities = [self.__rndVel() for _ in range(self.__numPart)]
+        velLst = list(chain.from_iterable(velocities))
+        # we need two textures,
+        # the first one contains the emission velocity (we need to keep it for
+        # particle recycling)...
+        self.__texStartVel = self.__buffTex(velLst)
+        # ... and the second one contains the current velocities
+        self.__texCurrVel = self.__buffTex(velLst)
+
+    def __buffTex(self, values):
+        # this function returns a buffer texture with the received values
+        data = array("f", values)
+        tex = Texture("tex")
+        tex.setupBufferTexture(self.__numPart, Texture.T_float,
+                               Texture.F_rgba32, GeomEnums.UH_static)
+        tex.setRamImage(data)
+        return tex
+
+    def __rndVel(self):
+        # this method returns a random vector for emitting the particle
+        theta = uniform(0, pi / 12)
+        phi = uniform(0, 2 * pi)
+        vec = LVector3(
+            sin(theta) * cos(phi),
+            sin(theta) * sin(phi),
+            cos(theta))
+        vec *= uniform(self.__vel * .8, self.__vel * 1.2)
+        return [vec.x, vec.y, vec.z, 1]
+
+    def __setShader(self):
+        shader = Shader.make(Shader.SL_GLSL, vert, frag)
+
+        # Apply the shader to the node, but set a special flag indicating that
+        # the point size is controlled bythe shader.
+        attrib = ShaderAttrib.make(shader)
+        attrib = attrib.setFlag(ShaderAttrib.F_shader_point_size, True)
+        self.__nodepath.setAttrib(attrib)
+
+        self.__nodepath.setShaderInputs(
+            positions=self.__texPos,
+            emitter_pos=self.__emitter.getPos(render),
+            start_vel=self.__texStartVel,
+            velocities=self.__texCurrVel,
+            accel=(0, 0, self.__gravity),
+            start_time=globalClock.getFrameTime(),
+            emission_times=self.__texTimes,
+            part_duration=self.__partDuration,
+            image=loader.loadTexture(self.__texture))
+
+    def __update(self, task):
+        pos = self.__emitter.getPos(render)
+        self.__nodepath.setShaderInput("emitter_pos", pos)
+        return task.again
+
+
+class ParticleDemo(ShowBase):
+
+    def __init__(self):
+        loadPrcFileData("config", config)
+
+        ShowBase.__init__(self)
+
+        # Standard title and instruction text
+        self.title = OnscreenText(
+            text="Panda3D: Tutorial - Shader-based Particles",
+            parent=base.a2dBottomCenter,
+            style=1, fg=(1, 1, 1, 1), pos=(0, 0.1), scale=.08)
+        self.escapeEvent = OnscreenText(
+            text=HELP_TEXT, parent=base.a2dTopLeft,
+            style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.06),
+            align=TextNode.ALeft, scale=.05)
+
+        # More standard initialization
+        self.accept('escape', sys.exit)
+        self.accept('arrow_left', self.rotate, ['left'])
+        self.accept('arrow_right', self.rotate, ['right'])
+        base.disableMouse()
+        base.camera.setPos(0, -20, 2)
+        base.setBackgroundColor(0, 0, 0)
+
+        self.teapot = loader.loadModel("teapot")
+        self.teapot.setPos(0, 10, 0)
+        self.teapot.reparentTo(render)
+        self.setupLights()
+
+        # we define a nodepath as particle's emitter
+        self.emitter = NodePath("emitter")
+        self.emitter.reparentTo(self.teapot)
+        self.emitter.setPos(3.000, 0.000, 2.550)
+
+        # let's create the particle system
+        Particle(self.emitter, "smoke.png", gravity=.01, vel=1.2,
+                 partDuration=5.0)
+
+    def rotate(self, direction):
+        direction_factor = (1 if direction == "left" else -1)
+        self.teapot.setH(self.teapot.getH() + 10 * direction_factor)
+
+    # Set up lighting
+    def setupLights(self):
+        ambientLight = AmbientLight("ambientLight")
+        ambientLight.setColor((.4, .4, .35, 1))
+        directionalLight = DirectionalLight("directionalLight")
+        directionalLight.setDirection(LVector3(0, 8, -2.5))
+        directionalLight.setColor((0.9, 0.8, 0.9, 1))
+
+        # Set lighting on teapot so steam doesn't get affected
+        self.teapot.setLight(self.teapot.attachNewNode(directionalLight))
+        self.teapot.setLight(self.teapot.attachNewNode(ambientLight))
+
+
+demo = ParticleDemo()
+demo.run()

+ 70 - 37
samples/roaming-ralph/main.py

@@ -12,6 +12,7 @@
 from direct.showbase.ShowBase import ShowBase
 from direct.showbase.ShowBase import ShowBase
 from panda3d.core import CollisionTraverser, CollisionNode
 from panda3d.core import CollisionTraverser, CollisionNode
 from panda3d.core import CollisionHandlerQueue, CollisionRay
 from panda3d.core import CollisionHandlerQueue, CollisionRay
+from panda3d.core import CollisionHandlerPusher, CollisionSphere
 from panda3d.core import Filename, AmbientLight, DirectionalLight
 from panda3d.core import Filename, AmbientLight, DirectionalLight
 from panda3d.core import PandaNode, NodePath, Camera, TextNode
 from panda3d.core import PandaNode, NodePath, Camera, TextNode
 from panda3d.core import CollideMask
 from panda3d.core import CollideMask
@@ -40,12 +41,15 @@ class RoamingRalphDemo(ShowBase):
         # Set up the window, camera, etc.
         # Set up the window, camera, etc.
         ShowBase.__init__(self)
         ShowBase.__init__(self)
 
 
-        # Set the background color to black
-        self.win.setClearColor((0, 0, 0, 1))
-
         # This is used to store which keys are currently pressed.
         # This is used to store which keys are currently pressed.
         self.keyMap = {
         self.keyMap = {
-            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0}
+            "left": 0,
+            "right": 0,
+            "forward": 0,
+            "backward": 0,
+            "cam-left": 0,
+            "cam-right": 0,
+        }
 
 
         # Post the instructions
         # Post the instructions
         self.title = addTitle(
         self.title = addTitle(
@@ -54,8 +58,9 @@ class RoamingRalphDemo(ShowBase):
         self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left")
         self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left")
         self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right")
         self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right")
         self.inst4 = addInstructions(0.24, "[Up Arrow]: Run Ralph Forward")
         self.inst4 = addInstructions(0.24, "[Up Arrow]: Run Ralph Forward")
-        self.inst6 = addInstructions(0.30, "[A]: Rotate Camera Left")
-        self.inst7 = addInstructions(0.36, "[S]: Rotate Camera Right")
+        self.inst5 = addInstructions(0.30, "[Down Arrow]: Walk Ralph Backward")
+        self.inst6 = addInstructions(0.36, "[A]: Rotate Camera Left")
+        self.inst7 = addInstructions(0.42, "[S]: Rotate Camera Right")
 
 
         # Set up the environment
         # Set up the environment
         #
         #
@@ -72,6 +77,9 @@ class RoamingRalphDemo(ShowBase):
         self.environ = loader.loadModel("models/world")
         self.environ = loader.loadModel("models/world")
         self.environ.reparentTo(render)
         self.environ.reparentTo(render)
 
 
+        # We do not have a skybox, so we will just use a sky blue background color
+        self.setBackgroundColor(0.53, 0.80, 0.92, 1)
+
         # Create the main character, Ralph
         # Create the main character, Ralph
 
 
         ralphStartPos = self.environ.find("**/start_point").getPos()
         ralphStartPos = self.environ.find("**/start_point").getPos()
@@ -80,7 +88,7 @@ class RoamingRalphDemo(ShowBase):
                             "walk": "models/ralph-walk"})
                             "walk": "models/ralph-walk"})
         self.ralph.reparentTo(render)
         self.ralph.reparentTo(render)
         self.ralph.setScale(.2)
         self.ralph.setScale(.2)
-        self.ralph.setPos(ralphStartPos + (0, 0, 0.5))
+        self.ralph.setPos(ralphStartPos + (0, 0, 1.5))
 
 
         # Create a floater object, which floats 2 units above ralph.  We
         # Create a floater object, which floats 2 units above ralph.  We
         # use this as a target for the camera to look at.
         # use this as a target for the camera to look at.
@@ -95,31 +103,51 @@ class RoamingRalphDemo(ShowBase):
         self.accept("arrow_left", self.setKey, ["left", True])
         self.accept("arrow_left", self.setKey, ["left", True])
         self.accept("arrow_right", self.setKey, ["right", True])
         self.accept("arrow_right", self.setKey, ["right", True])
         self.accept("arrow_up", self.setKey, ["forward", True])
         self.accept("arrow_up", self.setKey, ["forward", True])
+        self.accept("arrow_down", self.setKey, ["backward", True])
         self.accept("a", self.setKey, ["cam-left", True])
         self.accept("a", self.setKey, ["cam-left", True])
         self.accept("s", self.setKey, ["cam-right", True])
         self.accept("s", self.setKey, ["cam-right", True])
         self.accept("arrow_left-up", self.setKey, ["left", False])
         self.accept("arrow_left-up", self.setKey, ["left", False])
         self.accept("arrow_right-up", self.setKey, ["right", False])
         self.accept("arrow_right-up", self.setKey, ["right", False])
         self.accept("arrow_up-up", self.setKey, ["forward", False])
         self.accept("arrow_up-up", self.setKey, ["forward", False])
+        self.accept("arrow_down-up", self.setKey, ["backward", False])
         self.accept("a-up", self.setKey, ["cam-left", False])
         self.accept("a-up", self.setKey, ["cam-left", False])
         self.accept("s-up", self.setKey, ["cam-right", False])
         self.accept("s-up", self.setKey, ["cam-right", False])
 
 
         taskMgr.add(self.move, "moveTask")
         taskMgr.add(self.move, "moveTask")
 
 
-        # Game state variables
-        self.isMoving = False
-
         # Set up the camera
         # Set up the camera
         self.disableMouse()
         self.disableMouse()
         self.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
         self.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
 
 
+        self.cTrav = CollisionTraverser()
+
+        # Use a CollisionHandlerPusher to handle collisions between Ralph and
+        # the environment. Ralph is added as a "from" object which will be
+        # "pushed" out of the environment if he walks into obstacles.
+        #
+        # Ralph is composed of two spheres, one around the torso and one
+        # around the head.  They are slightly oversized since we want Ralph to
+        # keep some distance from obstacles.
+        self.ralphCol = CollisionNode('ralph')
+        self.ralphCol.addSolid(CollisionSphere(center=(0, 0, 2), radius=1.5))
+        self.ralphCol.addSolid(CollisionSphere(center=(0, -0.25, 4), radius=1.5))
+        self.ralphCol.setFromCollideMask(CollideMask.bit(0))
+        self.ralphCol.setIntoCollideMask(CollideMask.allOff())
+        self.ralphColNp = self.ralph.attachNewNode(self.ralphCol)
+        self.ralphPusher = CollisionHandlerPusher()
+        self.ralphPusher.horizontal = True
+
+        # Note that we need to add ralph both to the pusher and to the
+        # traverser; the pusher needs to know which node to push back when a
+        # collision occurs!
+        self.ralphPusher.addCollider(self.ralphColNp, self.ralph)
+        self.cTrav.addCollider(self.ralphColNp, self.ralphPusher)
+
         # We will detect the height of the terrain by creating a collision
         # We will detect the height of the terrain by creating a collision
         # ray and casting it downward toward the terrain.  One ray will
         # ray and casting it downward toward the terrain.  One ray will
         # start above ralph's head, and the other will start above the camera.
         # start above ralph's head, and the other will start above the camera.
         # A ray may hit the terrain, or it may hit a rock or a tree.  If it
         # A ray may hit the terrain, or it may hit a rock or a tree.  If it
-        # hits the terrain, we can detect the height.  If it hits anything
-        # else, we rule that the move is illegal.
-        self.cTrav = CollisionTraverser()
-
+        # hits the terrain, we can detect the height.
         self.ralphGroundRay = CollisionRay()
         self.ralphGroundRay = CollisionRay()
         self.ralphGroundRay.setOrigin(0, 0, 9)
         self.ralphGroundRay.setOrigin(0, 0, 9)
         self.ralphGroundRay.setDirection(0, 0, -1)
         self.ralphGroundRay.setDirection(0, 0, -1)
@@ -143,7 +171,7 @@ class RoamingRalphDemo(ShowBase):
         self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
         self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
 
 
         # Uncomment this line to see the collision rays
         # Uncomment this line to see the collision rays
-        #self.ralphGroundColNp.show()
+        #self.ralphColNp.show()
         #self.camGroundColNp.show()
         #self.camGroundColNp.show()
 
 
         # Uncomment this line to show a visual representation of the
         # Uncomment this line to show a visual representation of the
@@ -181,11 +209,6 @@ class RoamingRalphDemo(ShowBase):
         if self.keyMap["cam-right"]:
         if self.keyMap["cam-right"]:
             self.camera.setX(self.camera, +20 * dt)
             self.camera.setX(self.camera, +20 * dt)
 
 
-        # save ralph's initial position so that we can restore it,
-        # in case he falls off the map or runs into something.
-
-        startpos = self.ralph.getPos()
-
         # If a move-key is pressed, move ralph in the specified direction.
         # If a move-key is pressed, move ralph in the specified direction.
 
 
         if self.keyMap["left"]:
         if self.keyMap["left"]:
@@ -193,17 +216,28 @@ class RoamingRalphDemo(ShowBase):
         if self.keyMap["right"]:
         if self.keyMap["right"]:
             self.ralph.setH(self.ralph.getH() - 300 * dt)
             self.ralph.setH(self.ralph.getH() - 300 * dt)
         if self.keyMap["forward"]:
         if self.keyMap["forward"]:
-            self.ralph.setY(self.ralph, -25 * dt)
+            self.ralph.setY(self.ralph, -20 * dt)
+        if self.keyMap["backward"]:
+            self.ralph.setY(self.ralph, +10 * dt)
 
 
         # If ralph is moving, loop the run animation.
         # If ralph is moving, loop the run animation.
         # If he is standing still, stop the animation.
         # If he is standing still, stop the animation.
+        currentAnim = self.ralph.getCurrentAnim()
 
 
-        if self.keyMap["forward"] or self.keyMap["left"] or self.keyMap["right"]:
-            if self.isMoving is False:
+        if self.keyMap["forward"]:
+            if currentAnim != "run":
                 self.ralph.loop("run")
                 self.ralph.loop("run")
-                self.isMoving = True
+        elif self.keyMap["backward"]:
+            # Play the walk animation backwards.
+            if currentAnim != "walk":
+                self.ralph.loop("walk")
+            self.ralph.setPlayRate(-1.0, "walk")
+        elif self.keyMap["left"] or self.keyMap["right"]:
+            if currentAnim != "walk":
+                self.ralph.loop("walk")
+            self.ralph.setPlayRate(1.0, "walk")
         else:
         else:
-            if self.isMoving:
+            if currentAnim is not None:
                 self.ralph.stop()
                 self.ralph.stop()
                 self.ralph.pose("walk", 5)
                 self.ralph.pose("walk", 5)
                 self.isMoving = False
                 self.isMoving = False
@@ -228,25 +262,24 @@ class RoamingRalphDemo(ShowBase):
         #self.cTrav.traverse(render)
         #self.cTrav.traverse(render)
 
 
         # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
         # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
-        # update his Z. If it hit anything else, or didn't hit anything, put
-        # him back where he was last frame.
+        # update his Z
 
 
-        entries = list(self.ralphGroundHandler.getEntries())
+        entries = list(self.ralphGroundHandler.entries)
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
 
 
-        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
-            self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
-        else:
-            self.ralph.setPos(startpos)
+        for entry in entries:
+            if entry.getIntoNode().getName() == "terrain":
+                self.ralph.setZ(entry.getSurfacePoint(render).getZ())
 
 
-        # Keep the camera at one foot above the terrain,
-        # or two feet above ralph, whichever is greater.
+        # Keep the camera at one unit above the terrain,
+        # or two units above ralph, whichever is greater.
 
 
-        entries = list(self.camGroundHandler.getEntries())
+        entries = list(self.camGroundHandler.entries)
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
 
 
-        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
-            self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0)
+        for entry in entries:
+            if entry.getIntoNode().getName() == "terrain":
+                self.camera.setZ(entry.getSurfacePoint(render).getZ() + 1.5)
         if self.camera.getZ() < self.ralph.getZ() + 2.0:
         if self.camera.getZ() < self.ralph.getZ() + 2.0:
             self.camera.setZ(self.ralph.getZ() + 2.0)
             self.camera.setZ(self.ralph.getZ() + 2.0)
 
 

BIN
samples/roaming-ralph/models/world.egg.pz


+ 8 - 0
samples/shader-terrain/main.py

@@ -20,6 +20,14 @@ class ShaderTerrainDemo(ShowBase):
             textures-power-2 none
             textures-power-2 none
             gl-coordinate-system default
             gl-coordinate-system default
             window-title Panda3D ShaderTerrainMesh Demo
             window-title Panda3D ShaderTerrainMesh Demo
+
+            # As an optimization, set this to the maximum number of cameras
+            # or lights that will be rendering the terrain at any given time.
+            stm-max-views 8
+
+            # Further optimize the performance by reducing this to the max
+            # number of chunks that will be visible at any given time.
+            stm-max-chunk-count 2048
         """)
         """)
 
 
         # Initialize the showbase
         # Initialize the showbase

+ 30 - 0
tests/collide/collisions.py

@@ -0,0 +1,30 @@
+from panda3d.core import CollisionNode, NodePath
+from panda3d.core import CollisionTraverser, CollisionHandlerQueue
+from panda3d.core import CollisionSphere, CollisionBox, CollisionPolygon, CollisionCapsule
+from panda3d.core import CollisionLine, CollisionRay, CollisionSegment, CollisionParabola
+from panda3d.core import CollisionPlane
+from panda3d.core import Point3, Vec3, Plane, LParabola
+
+
+def make_collision(solid_from, solid_into):
+    node_from = CollisionNode("from")
+    node_from.add_solid(solid_from)
+    node_into = CollisionNode("into")
+    node_into.add_solid(solid_into)
+
+    root = NodePath("root")
+    trav = CollisionTraverser()
+    queue = CollisionHandlerQueue()
+
+    np_from = root.attach_new_node(node_from)
+    np_into = root.attach_new_node(node_into)
+
+    trav.add_collider(np_from, queue)
+    trav.traverse(root)
+
+    entry = None
+    for e in queue.get_entries():
+        if e.get_into() == solid_into:
+            entry = e
+
+    return (entry, np_from, np_into)

+ 45 - 0
tests/collide/test_into_box.py

@@ -0,0 +1,45 @@
+from collisions import *
+
+
+def test_sphere_into_box():
+    sphere = CollisionSphere(0, 0, 4, 3)
+    box = CollisionBox((0, 0, 0), 2, 3, 4)
+    entry = make_collision(sphere, box)[0]
+    assert entry is not None
+    assert entry.get_from() == sphere
+    assert entry.get_into() == box
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionSphere(0, 0, 10, 6), box)
+    assert entry.get_surface_point(np_from) == Point3(0, 0, 4)
+    assert entry.get_surface_normal(np_into) == Vec3(0, 0, 1)  # Testing surface normal
+
+    # No collision
+    entry = make_collision(CollisionSphere(100, 100, 100, 100), box)[0]
+    assert entry is None
+
+
+def test_plane_into_box():
+    # CollisionPlane is not a 'from' object
+    plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))
+    box = CollisionBox((0, 0, 0), 2, 3, 4)
+
+    entry = make_collision(plane, box)[0]
+    assert entry is None
+
+
+def test_ray_into_box():
+    ray = CollisionRay(1, 1, 1, 0, 1, 0)
+    box = CollisionBox((0, 0, 0), 3, 3, 5)
+    entry = make_collision(ray, box)[0]
+    assert entry is not None
+    assert entry.get_from() == ray
+    assert entry.get_into() == box
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionRay(3, 3, 0, 1, -1, 0), box)
+    assert entry.get_surface_point(np_from) == Point3(3, 3, 0)
+
+    # No collision
+    entry = make_collision(CollisionRay(0, 0, 100, 1, 0, 0), box)[0]
+    assert entry is None

+ 24 - 0
tests/collide/test_into_lines.py

@@ -0,0 +1,24 @@
+# Testing that all variants of CollisionLine
+# cannot be used as "into" objects
+from collisions import *
+
+
+def test_sphere_into_line():
+    entry = make_collision(CollisionSphere(0, 0, 0, 3), CollisionLine(0, 0, 0, 1, 0, 0))[0]
+    assert entry is None
+
+
+def test_sphere_into_ray():
+    entry = make_collision(CollisionSphere(0, 0, 0, 3), CollisionRay(0, 0, 0, 3, 3, 3))[0]
+    assert entry is None
+
+
+def test_sphere_into_segment():
+    entry = make_collision(CollisionSphere(0, 0, 0, 3), CollisionSegment(0, 0, 0, 3, 3, 3))[0]
+    assert entry is None
+
+
+def test_sphere_into_parabola():
+    parabola = LParabola((1, 0, 0), (0, 1, 0), (0, 0, 1))
+    entry = make_collision(CollisionSphere(0, 0, 0, 3), CollisionParabola(parabola, 1, 2))[0]
+    assert entry is None

+ 48 - 0
tests/collide/test_into_poly.py

@@ -0,0 +1,48 @@
+from collisions import *
+
+
+def test_box_into_poly():
+    box = CollisionBox((0, 0, 0), 2, 3, 4)
+    poly = CollisionPolygon(Point3(0, 0, 0), Point3(0, 0, 1), Point3(0, 1, 1), Point3(0, 1, 0))
+
+    entry = make_collision(box, poly)[0]
+    assert entry is not None
+    assert entry.get_from() == box
+    assert entry.get_into() == poly
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionBox((0, 3, 0), 1, 2, 1), poly)
+    assert entry.get_surface_point(np_from) == Point3(0, 3, 0)
+    assert entry.get_surface_normal(np_into) == Vec3(-1, 0, 0)  # Testing surface normal
+
+    # No collision
+    entry = make_collision(CollisionBox((10, 10, 10), 8, 9, 10), poly)[0]
+    assert entry is None
+
+
+def test_sphere_into_poly():
+    sphere = CollisionSphere(0, 0, 0, 1)
+    poly = CollisionPolygon(Point3(0, 0, 0), Point3(0, 0, 1), Point3(0, 1, 1), Point3(0, 1, 0))
+
+    entry = make_collision(sphere, poly)[0]
+    assert entry is not None
+    assert entry.get_from() == sphere
+    assert entry.get_into() == poly
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionSphere(0, 0, 3, 2), poly)
+    assert entry.get_surface_point(np_from) == Point3(0, 0, 3)
+    assert entry.get_surface_normal(np_into) == Vec3(-1, 0, 0)  # Testing surface normal
+
+    # No collision
+    entry = make_collision(CollisionSphere(100, 100, 100, 100), poly)[0]
+    assert entry is None
+
+
+def test_plane_into_poly():
+    # CollisionPlane is not a 'from' object
+    plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))
+    poly = CollisionPolygon(Point3(0, 0, 0), Point3(0, 0, 1), Point3(0, 1, 1), Point3(0, 1, 0))
+
+    entry = make_collision(plane, poly)[0]
+    assert entry is None

+ 89 - 0
tests/collide/test_into_sphere.py

@@ -0,0 +1,89 @@
+from collisions import *
+
+
+def test_sphere_into_sphere():
+    sphere1 = CollisionSphere(0, 0, 3, 3)
+    sphere2 = CollisionSphere(0, 0, 0, 3)
+
+    entry = make_collision(sphere1, sphere2)[0]
+    assert entry is not None
+    assert entry.get_from() == sphere1
+    assert entry.get_into() == sphere2
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionSphere(0, 0, 10, 7), sphere2)
+    assert entry.get_surface_point(np_from) == Point3(0, 0, 3)
+    assert entry.get_surface_normal(np_into) == Vec3(0, 0, 1)  # Testing surface normal
+
+    # No collision
+    entry = make_collision(CollisionSphere(0, 0, 10, 6), sphere2)[0]
+    assert entry is None
+
+
+def test_box_into_sphere():
+    box = CollisionBox((0, 0, 0), 2, 3, 4)
+    sphere = CollisionSphere(0, 0, 0, 3)
+
+    entry = make_collision(box, sphere)[0]
+    assert entry is not None
+    assert entry.get_from() == box
+    assert entry.get_into() == sphere
+
+    # Colliding just on the edge
+    entry, np_from, np_into = make_collision(CollisionBox((0, 0, 10), 6, 6, 7), sphere)
+    assert entry.get_surface_point(np_from) == Point3(0, 0, 3)
+    assert entry.get_surface_normal(np_into) == Vec3(0, 0, 1)  # Testing surface normal
+
+    # No collision
+    entry = make_collision(CollisionBox((0, 0, 10), 6, 6, 6), sphere)[0]
+    assert entry is None
+
+
+def test_capsule_into_sphere():
+    # First test a sphere that is fully touching the inner line of the capsule
+    capsule = CollisionCapsule((0, 0, 1.0), (10, 0, 1.0), 1.0)
+    sphere = CollisionSphere(5, 0, 1.5, 1.0)
+
+    entry = make_collision(capsule, sphere)[0]
+    assert entry is not None
+    assert entry.get_from() == capsule
+    assert entry.get_into() == sphere
+
+    # Now test one that merely grazes.
+    entry = make_collision(CollisionCapsule((0, 0, 0), (10, 0, 0), 1.0), sphere)[0]
+    assert entry is not None
+
+    # No collision
+    entry = make_collision(CollisionCapsule((0, 0, 0), (10, 0, 0), 0.25), sphere)[0]
+    assert entry is None
+
+    # Degenerate case: capsule is actually a sphere.
+    entry = make_collision(CollisionCapsule((5, 0, 0), (5, 0, 0), 1.0), sphere)[0]
+    assert entry is not None
+
+    # Degenerate case, but not colliding.
+    entry = make_collision(CollisionCapsule((5, 0, 0), (5, 0, 0), 0.25), sphere)[0]
+    assert entry is None
+
+
+def test_segment_into_sphere():
+    segment = CollisionSegment((0, 0, 0), (10, 0, 0))
+    sphere = CollisionSphere(5, 0, 0.5, 1.0)
+
+    entry = make_collision(segment, sphere)[0]
+    assert entry is not None
+    assert entry.get_from() == segment
+    assert entry.get_into() == sphere
+
+    # No collision
+    entry = make_collision(CollisionSegment((0, 0, 0), (3, 0, 0)), sphere)[0]
+    assert entry is None
+
+
+def test_plane_into_sphere():
+    # CollisionPlane is not a 'from' object
+    plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))
+    sphere = CollisionSphere(0, 0, 0, 1)
+
+    entry = make_collision(plane, sphere)[0]
+    assert entry is None

+ 6 - 0
tests/display/test_color_buffer.py

@@ -105,6 +105,12 @@ def render_color_pixel(region, state, vertex_color=None):
     """Renders a fragment using the specified render settings, and returns the
     """Renders a fragment using the specified render settings, and returns the
     resulting color value."""
     resulting color value."""
 
 
+    # Skip auto-shader tests if we don't support Cg shaders.
+    if not region.window.gsg.supports_basic_shaders:
+        sattr = state.get_attrib(core.ShaderAttrib)
+        if sattr and sattr.auto_shader():
+            pytest.skip("Cannot test auto-shader without Cg shader support")
+
     # Set up the scene with a blank card rendering at specified distance.
     # Set up the scene with a blank card rendering at specified distance.
     scene = core.NodePath("root")
     scene = core.NodePath("root")
     scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))
     scene.set_attrib(core.DepthTestAttrib.make(core.RenderAttrib.M_always))

+ 77 - 0
tests/gobj/test_lenses.py

@@ -0,0 +1,77 @@
+from panda3d.core import PerspectiveLens, Point3, Point2
+
+
+def test_perspectivelens_extrude():
+    lens = PerspectiveLens()
+    lens.set_fov(90, 90)
+    lens.set_near_far(0.5, 100)
+
+    near = Point3()
+    far = Point3()
+
+    assert lens.extrude((0, 0), near, far)
+    assert near.almost_equal((0, 0.5, 0), 0.001)
+    assert far.almost_equal((0, 100, 0), 0.1)
+
+    assert lens.extrude((-1, -1), near, far)
+    assert near.almost_equal((-0.5, 0.5, -0.5), 0.001)
+    assert far.almost_equal((-100, 100, -100), 0.1)
+
+    assert lens.extrude((1, 0), near, far)
+    assert near.almost_equal((0.5, 0.5, 0), 0.001)
+    assert far.almost_equal((100, 100, 0), 0.1)
+
+
+def test_perspectivelens_extrude_depth():
+    lens = PerspectiveLens()
+    lens.set_fov(90, 90)
+    lens.set_near_far(0.5, 100)
+
+    point = Point3()
+
+    assert lens.extrude_depth((0, 0, -1), point)
+    assert point.almost_equal((0, 0.5, 0), 0.001)
+
+    assert lens.extrude_depth((0, 0, 1), point)
+    assert point.almost_equal((0, 100, 0), 0.001)
+
+    assert lens.extrude_depth((-1, -1, -1), point)
+    assert point.almost_equal((-0.5, 0.5, -0.5), 0.001)
+
+    assert lens.extrude_depth((-1, -1, 1), point)
+    assert point.almost_equal((-100, 100, -100), 0.1)
+
+    assert lens.extrude_depth((1, 0, -1), point)
+    assert point.almost_equal((0.5, 0.5, 0), 0.001)
+
+    assert lens.extrude_depth((1, 0, 1), point)
+    assert point.almost_equal((100, 100, 0), 0.1)
+
+
+def test_perspectivelens_project():
+    lens = PerspectiveLens()
+    lens.set_fov(90, 90)
+    lens.set_near_far(0.5, 100)
+
+    point = Point2()
+
+    assert not lens.project((0, 0, 0), point)
+    assert not lens.project((-1, 0.5, 0), point)
+
+    assert lens.project((0, 0.5, 0), point)
+    assert point.almost_equal((0, 0), 0.001)
+
+    assert lens.project((0, 100, 0), point)
+    assert point.almost_equal((0, 0), 0.001)
+
+    assert lens.project((-0.5, 0.5, -0.5), point)
+    assert point.almost_equal((-1, -1), 0.001)
+
+    assert lens.project((-100, 100, -100), point)
+    assert point.almost_equal((-1, -1), 0.001)
+
+    assert lens.project((0.5, 0.5, 0), point)
+    assert point.almost_equal((1, 0), 0.001)
+
+    assert lens.project((100, 100, 0), point)
+    assert point.almost_equal((1, 0), 0.001)

+ 6 - 0
tests/putil/conftest.py

@@ -0,0 +1,6 @@
+import pytest
+from panda3d.core import ClockObject
+
[email protected]
+def clockobj():
+    return ClockObject()

+ 23 - 0
tests/putil/test_clockobject.py

@@ -0,0 +1,23 @@
+import time
+
+def test_get_frame_time(clockobj):
+    current_time = clockobj.get_frame_time()
+    time.sleep(2)
+    assert clockobj.get_frame_time() == current_time
+
+def test_jump_frame_time(clockobj):
+    current_time = clockobj.get_frame_time()
+    clockobj.tick()
+    assert clockobj.get_frame_time() == current_time + clockobj.get_frame_time()
+
+def test_get_real_time(clockobj):
+    current_time = clockobj.get_real_time()
+    time.sleep(2)
+    assert current_time != clockobj.get_real_time()
+
+def test_get_dt(clockobj):
+    clockobj.tick()
+    first_tick = clockobj.get_frame_time()
+    clockobj.tick()
+    second_tick = clockobj.get_frame_time()
+    assert clockobj.get_dt() == second_tick - first_tick