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
-[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
 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:
 
 ```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
 ```
 

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

@@ -13,6 +13,17 @@
 
 #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::endl;
 using std::string;

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

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

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

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

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

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

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

@@ -13,6 +13,8 @@
 
 #include "pathFind.h"
 
+#include "pathFollow.h"
+
 using std::cout;
 using std::endl;
 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 "pathFind.h"
+
 PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
     _follow_weight = follow_wt;
   _curr_path_waypoint = -1;

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

@@ -28,7 +28,7 @@
 #define SHADOWATLAS_H
 
 #include "pandabase.h"
-#include "lvecBase4.h"
+#include "luse.h"
 
 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.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):
         assert self.notify.debugStateCall(self)
         self.avatarControlForwardSpeed=forward

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

@@ -67,51 +67,6 @@ class PhysicsWalker(DirectObject.DirectObject):
         self.isAirborne = 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):
         assert self.debugPrint("setWalkSpeed()")
         self.avatarControlForwardSpeed=forward

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

@@ -624,8 +624,9 @@ class Messenger:
             for key in list(acceptorDict.keys()):
                 function, extraArgs, persistent = acceptorDict[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:
                     className = "Not a class"
                 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
     global dtoolSuperBase
     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:
         # C++ object instances appear to be types via type()
         # check if this is a C++ object
         if dtoolSuperBase is None:
             _getDtoolSuperBase()
         if isinstance(obj, dtoolSuperBase):
-            return '%s of %s>' % (repr(types.InstanceType)[:-1],
-                                  str(obj.__class__))
+            return "<type 'instance' of %s>" % (obj.__class__)
         return t
 
 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):
         if isinstance(funcOrTask, AsyncTask):
             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)
             if name is None:
                 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')
 
 
-def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
+def MakeInstallerFreeBSD(version, runtime=False, python_versions=[], **kwargs):
     outputdir = GetOutputDir()
 
     oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST")
@@ -758,7 +758,7 @@ def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
     if runtime:
         InstallRuntime(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
     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"):
         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.
     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.
@@ -3003,9 +3014,6 @@ else:
 if (GetTarget() == 'darwin'):
     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':
     # Convert to Windows newlines.
     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')
 
   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=['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='libp3android.dll')
     TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS)
-    TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID'])
+    TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID', 'PYTHON'])
 
 #
 # 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\weakPointerTo.I"></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\vector_float.cxx"></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\weakPointerToVoid.h"></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\datagram.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\vector_uchar.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\hashGeneratorBase.h"></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\trueClock.I"></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\vector_uchar.cxx"></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\compress_string.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\express_composite.cxx"></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\virtualFile.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>
 			</Filter>
 			<Filter Name="iphonedisplay">

+ 8 - 2
makepanda/makepandacore.py

@@ -2797,8 +2797,14 @@ def SetupVisualStudioEnviron():
         elif not win_kit.endswith('\\'):
             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.
         CopyAllFiles(GetOutputDir() + "/bin/", win_kit + "Redist\\ucrt\\DLLs\\" + arch + "\\")

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

@@ -11,12 +11,12 @@
  * @date 2000-07-06
  */
 
-#ifndef __AUDIO_H__
-#define __AUDIO_H__
+#ifndef AUDIO_H
+#define AUDIO_H
 
 #include "filterProperties.h"
 #include "audioLoadRequest.h"
 #include "audioSound.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
  */
 
-#ifndef __AUDIO_MANAGER_H__
-#define __AUDIO_MANAGER_H__
+#ifndef AUDIOMANAGER_H
+#define AUDIOMANAGER_H
 
 #include "config_audio.h"
 #include "audioSound.h"
@@ -222,4 +222,4 @@ operator << (std::ostream &out, const AudioManager &mgr) {
 
 #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
  */
 
-#ifndef __AUDIOSOUND_H__
-#define __AUDIOSOUND_H__
+#ifndef AUDIOSOUND_H
+#define AUDIOSOUND_H
 
 #include "config_audio.h"
 #include "typedReferenceCount.h"
@@ -160,4 +160,4 @@ operator << (std::ostream &out, const AudioSound &sound) {
 EXPCL_PANDA_AUDIO std::ostream &
 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
  */
 
-#ifndef __NULL_AUDIO_MANAGER_H__
-#define __NULL_AUDIO_MANAGER_H__
+#ifndef NULLAUDIOMANAGER_H
+#define NULLAUDIOMANAGER_H
 
 #include "audioManager.h"
 #include "nullAudioSound.h"
@@ -89,4 +89,4 @@ private:
   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
  */
 
-#ifndef __NULL_AUDIO_SOUND_H__
-#define __NULL_AUDIO_SOUND_H__
+#ifndef NULLAUDIOSOUND_H
+#define NULLAUDIOSOUND_H
 
 #include "audioSound.h"
 
@@ -91,4 +91,4 @@ private:
   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::
 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);
 
-  nassertr(A != 0.0, false);
-
   LVector3 fc = from - get_center();
-  double B = 2.0f* dot(delta, fc);
   double fc_d2 = dot(fc, fc);
   double radius = get_radius() + inflate_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;
 
   if (IS_NEARLY_ZERO(radical)) {

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

@@ -15,6 +15,7 @@
 #include "gamepadButton.h"
 #include "mouseButton.h"
 #include "buttonRegistry.h"
+#include "winInputDeviceManager.h"
 
 #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.
       // Special exception for light 0, which defaults to white.
       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);
   }
@@ -1494,7 +1489,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     } else if (index == 0) {
       // Apply the default OpenGL lights otherwise.
       // 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;
   }
@@ -1539,7 +1534,6 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
     Light *light = node->as_light();
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     LColor c = light->get_color();
-    c.componentwise_mult(_light_color_scale);
     t.set_row(3, c);
     return &t;
 
@@ -1551,7 +1545,6 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
     nassertr(light != nullptr, &LMatrix4::ident_mat());
     if (node->is_ambient_light()) {
       LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
       t.set_row(3, c);
     } else {
       // 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));
     } else {
       LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
       t.set_row(3, c);
     }
     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);
 
   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;
   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.
     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());
-
-    // 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 we also specified to keep the geometry, continue the traversal.
@@ -2773,7 +2774,7 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
  */
 bool EggLoader::
 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);
   if (geom_group != nullptr) {
     // Collect all of the vertices.
@@ -2802,13 +2803,12 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
     }
 
     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) {
       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(min_pd[1], pos[1]),
                  min(min_pd[2], pos[2]));
@@ -2817,13 +2817,25 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
                  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 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
  * 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);
         if (csplane != nullptr) {
           apply_collision_flags(csplane, flags);
+          csplane->xform(cnode->get_transform()->get_mat());
           cnode->add_solid(csplane);
           return;
         }
@@ -3004,6 +3017,7 @@ make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode,
     CollisionSphere *cssphere =
       new CollisionSphere(center, radius);
     apply_collision_flags(cssphere, flags);
+    cssphere->xform(cnode->get_transform()->get_mat());
     cnode->add_solid(cssphere);
   }
 }
@@ -3017,8 +3031,8 @@ make_collision_box(EggGroup *egg_group, CollisionNode *cnode,
                    EggGroup::CollideFlags flags) {
   LPoint3 min_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 =
       new CollisionBox(min_p, max_p);
     apply_collision_flags(csbox, flags);
@@ -3040,6 +3054,7 @@ make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode,
     CollisionInvSphere *cssphere =
       new CollisionInvSphere(center, radius);
     apply_collision_flags(cssphere, flags);
+    cssphere->xform(cnode->get_transform()->get_mat());
     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),
                             radius);
         apply_collision_flags(cscapsule, flags);
+        cscapsule->xform(cnode->get_transform()->get_mat());
         cnode->add_solid(cscapsule);
       }
     }
@@ -3395,6 +3411,7 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
         new CollisionPolygon(vertices_begin, vertices_end);
       if (cspoly->is_valid()) {
         apply_collision_flags(cspoly, flags);
+        cspoly->xform(cnode->get_transform()->get_mat());
         cnode->add_solid(cspoly);
       }
     }
@@ -3485,6 +3502,7 @@ create_collision_floor_mesh(CollisionNode *cnode,
     CollisionFloorMesh::TriangleIndices triangle = *ti;
     csfloor->add_triangle(triangle.p1, triangle.p2, triangle.p3);
   }
+  csfloor->xform(cnode->get_transform()->get_mat());
   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,
                    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,
                 LPoint3 &min_p, LPoint3 &max_p, LColor &color);
 

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

@@ -8,8 +8,6 @@
 #include "subStream.cxx"
 #include "subStreamBuf.cxx"
 #include "temporaryFile.cxx"
-#include "threadSafePointerTo.cxx"
-#include "threadSafePointerToBase.cxx"
 #include "trueClock.cxx"
 #include "typedReferenceCount.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 "ffmpegAudio.h"
 #include "ffmpegAudioCursor.h"
-
 #include "movieTypeRegistry.h"
+#include "pandaSystem.h"
 
 extern "C" {
   #include <libavcodec/avcodec.h>
@@ -129,6 +129,9 @@ init_libffmpeg() {
   FfmpegVideo::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.
   MovieTypeRegistry *reg = MovieTypeRegistry::get_global_ptr();
   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);
       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 {
     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;
-      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.
       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);
   return
     (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 "shaderInput.h"
 #include "boundingBox.h"
+#include "boundingSphere.h"
 #include "samplerState.h"
 #include "config_grutil.h"
 #include "typeHandle.h"
@@ -106,8 +107,6 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
   _update_enabled(true),
   _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();
 }
 
+/**
+ * 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
  * @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);
 
 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
   struct Chunk {

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

@@ -101,7 +101,7 @@ get_plane(int n) const {
 INLINE_MATHUTIL void BoundingBox::
 set_min_max(const LPoint3 &min, const LPoint3 &max) {
   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;
   _max = max;
   _flags = 0;

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

@@ -173,6 +173,20 @@ initial_reserve_id(uint32_t id) {
   --_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

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

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

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

@@ -75,6 +75,10 @@ static struct _inittab extensions[] = {
 #endif
 #endif
 
+#ifdef _WIN32
+static wchar_t *log_pathw = NULL;
+#endif
+
 #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000
 static int supports_code_page(UINT cp) {
   if (cp == 0) {
@@ -225,7 +229,8 @@ static int mkdir_parent(const char *path) {
 static int setup_logging(const char *path, int append) {
 #ifdef _WIN32
   // 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;
   if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
     // 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.
     if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
+      free(pathw);
       return 0;
     }
     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.
   if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
-                          (int)(_countof(pathw) - offset)) == 0) {
+                          (int)(MAX_PATH - offset)) == 0) {
+    free(pathw);
     return 0;
   }
 
   DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
   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);
 
   if (handle == INVALID_HANDLE_VALUE) {
     // Make the parent directories first.
     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);
   }
 
   if (handle == INVALID_HANDLE_VALUE) {
+    free(pathw);
     return 0;
   }
 
+  log_pathw = pathw;
+
   if (append) {
     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
   // 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(stderr));
   _close(fd);
@@ -421,6 +431,32 @@ int Py_FrozenMain(int argc, char **argv)
 #endif
 
 #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()) ||
         !supports_code_page(GetConsoleCP())) {
       /* 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 "config_pstatclient.h"
 
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
 #include <windows.h>
 
 static const char *toplevel_class_name = "pstats";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 1 - 1
samples/fireflies/main.py

@@ -65,7 +65,7 @@ class FireflyDemo(ShowBase):
         # doesn't support the necessary OpenGL extensions.
 
         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")
             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 panda3d.core import CollisionTraverser, CollisionNode
 from panda3d.core import CollisionHandlerQueue, CollisionRay
+from panda3d.core import CollisionHandlerPusher, CollisionSphere
 from panda3d.core import Filename, AmbientLight, DirectionalLight
 from panda3d.core import PandaNode, NodePath, Camera, TextNode
 from panda3d.core import CollideMask
@@ -40,12 +41,15 @@ class RoamingRalphDemo(ShowBase):
         # Set up the window, camera, etc.
         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.
         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
         self.title = addTitle(
@@ -54,8 +58,9 @@ class RoamingRalphDemo(ShowBase):
         self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left")
         self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right")
         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
         #
@@ -72,6 +77,9 @@ class RoamingRalphDemo(ShowBase):
         self.environ = loader.loadModel("models/world")
         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
 
         ralphStartPos = self.environ.find("**/start_point").getPos()
@@ -80,7 +88,7 @@ class RoamingRalphDemo(ShowBase):
                             "walk": "models/ralph-walk"})
         self.ralph.reparentTo(render)
         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
         # 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_right", self.setKey, ["right", 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("s", self.setKey, ["cam-right", True])
         self.accept("arrow_left-up", self.setKey, ["left", False])
         self.accept("arrow_right-up", self.setKey, ["right", 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("s-up", self.setKey, ["cam-right", False])
 
         taskMgr.add(self.move, "moveTask")
 
-        # Game state variables
-        self.isMoving = False
-
         # Set up the camera
         self.disableMouse()
         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
         # ray and casting it downward toward the terrain.  One ray will
         # 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
-        # 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.setOrigin(0, 0, 9)
         self.ralphGroundRay.setDirection(0, 0, -1)
@@ -143,7 +171,7 @@ class RoamingRalphDemo(ShowBase):
         self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
 
         # Uncomment this line to see the collision rays
-        #self.ralphGroundColNp.show()
+        #self.ralphColNp.show()
         #self.camGroundColNp.show()
 
         # Uncomment this line to show a visual representation of the
@@ -181,11 +209,6 @@ class RoamingRalphDemo(ShowBase):
         if self.keyMap["cam-right"]:
             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 self.keyMap["left"]:
@@ -193,17 +216,28 @@ class RoamingRalphDemo(ShowBase):
         if self.keyMap["right"]:
             self.ralph.setH(self.ralph.getH() - 300 * dt)
         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 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.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:
-            if self.isMoving:
+            if currentAnim is not None:
                 self.ralph.stop()
                 self.ralph.pose("walk", 5)
                 self.isMoving = False
@@ -228,25 +262,24 @@ class RoamingRalphDemo(ShowBase):
         #self.cTrav.traverse(render)
 
         # 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())
 
-        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())
 
-        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:
             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
             gl-coordinate-system default
             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

+ 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
     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.
     scene = core.NodePath("root")
     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