Browse Source

Merge branch 'master' into cmake

Sam Edwards 10 years ago
parent
commit
25e2e0221d
98 changed files with 4026 additions and 738 deletions
  1. 5 3
      direct/src/directbase/ppython.cxx
  2. 5 7
      direct/src/directscripts/eggcacher.py
  3. 24 10
      direct/src/gui/DirectDialog.py
  4. 5 3
      direct/src/gui/DirectRadioButton.py
  5. 8 6
      direct/src/showbase/Transitions.py
  6. 1 1
      dtool/src/dtoolutil/executionEnvironment.cxx
  7. 69 0
      dtool/src/dtoolutil/filename.I
  8. 11 1
      dtool/src/dtoolutil/filename.h
  9. 6 3
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  10. 1276 0
      makepanda/installer.nsi
  11. 18 22
      makepanda/makepanda.py
  12. 5 1
      makepanda/makepandacore.py
  13. 0 0
      makepanda/panda-install.bmp
  14. 1 1
      panda/src/bullet/bulletAllHitsRayResult.cxx
  15. 1 1
      panda/src/bullet/bulletAllHitsRayResult.h
  16. 5 4
      panda/src/bullet/bulletWorld.cxx
  17. 2 1
      panda/src/bullet/bulletWorld.h
  18. 10 6
      panda/src/collide/collisionBox.cxx
  19. 45 24
      panda/src/display/graphicsStateGuardian.cxx
  20. 8 3
      panda/src/doc/eggSyntax.txt
  21. 6 0
      panda/src/downloader/httpDate.cxx
  22. 23 22
      panda/src/egg2pg/eggLoader.cxx
  23. 49 2
      panda/src/egg2pg/eggSaver.cxx
  24. 2 1
      panda/src/egg2pg/eggSaver.h
  25. 7 7
      panda/src/egldisplay/eglGraphicsBuffer.cxx
  26. 1 1
      panda/src/egldisplay/eglGraphicsBuffer.h
  27. 7 7
      panda/src/egldisplay/eglGraphicsPixmap.cxx
  28. 2 2
      panda/src/egldisplay/eglGraphicsPixmap.h
  29. 27 0
      panda/src/express/datagram.I
  30. 5 0
      panda/src/express/datagram.h
  31. 90 0
      panda/src/express/pointerToArray.I
  32. 23 0
      panda/src/express/pointerToArray.h
  33. 25 11
      panda/src/express/pointerToArrayBase.I
  34. 4 0
      panda/src/express/pointerToArrayBase.h
  35. 41 23
      panda/src/express/pointerToArray_ext.I
  36. 20 0
      panda/src/express/pointerToArray_ext.h
  37. 8 3
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  38. 5 0
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  39. 39 5
      panda/src/framework/windowFramework.cxx
  40. 12 13
      panda/src/gles2gsg/gles2gsg.h
  41. 3 0
      panda/src/glesgsg/glesgsg.h
  42. 4 11
      panda/src/glstuff/glCgShaderContext_src.cxx
  43. 0 1
      panda/src/glstuff/glCgShaderContext_src.h
  44. 1 1
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  45. 6 1
      panda/src/glstuff/glGraphicsStateGuardian_src.I
  46. 98 83
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  47. 4 0
      panda/src/glstuff/glSamplerContext_src.cxx
  48. 4 0
      panda/src/glstuff/glSamplerContext_src.h
  49. 22 2
      panda/src/glstuff/glShaderContext_src.cxx
  50. 2 0
      panda/src/glstuff/glmisc_src.cxx
  51. 1 1
      panda/src/gobj/internalName_ext.cxx
  52. 1 0
      panda/src/gobj/samplerContext.h
  53. 1 0
      panda/src/gobj/samplerState.h
  54. 75 70
      panda/src/gobj/texture.cxx
  55. 7 0
      panda/src/gobj/texture.h
  56. 1 1
      panda/src/linmath/lmatrix3_src.cxx
  57. 1 1
      panda/src/linmath/lmatrix4_src.cxx
  58. 28 0
      panda/src/mathutil/pta_LMatrix3_ext.h
  59. 28 0
      panda/src/mathutil/pta_LMatrix4_ext.h
  60. 30 0
      panda/src/mathutil/pta_LVecBase2_ext.h
  61. 30 0
      panda/src/mathutil/pta_LVecBase3_ext.h
  62. 30 0
      panda/src/mathutil/pta_LVecBase4_ext.h
  63. 3 0
      panda/src/pgraph/Sources.pp
  64. 2 0
      panda/src/pgraph/config_pgraph.cxx
  65. 1 0
      panda/src/pgraph/cullBinManager.h
  66. 4 4
      panda/src/pgraph/loader.cxx
  67. 3 3
      panda/src/pgraph/nodePath.h
  68. 1 0
      panda/src/pgraph/p3pgraph_composite3.cxx
  69. 7 5
      panda/src/pgraph/pandaNode.I
  70. 60 0
      panda/src/pgraph/paramNodePath.I
  71. 108 0
      panda/src/pgraph/paramNodePath.cxx
  72. 75 0
      panda/src/pgraph/paramNodePath.h
  73. 2 3
      panda/src/pgraph/polylightNode.I
  74. 24 0
      panda/src/pgraph/polylightNode.cxx
  75. 4 4
      panda/src/pgraph/polylightNode.h
  76. 0 27
      panda/src/pgraph/shaderInput.I
  77. 30 2
      panda/src/pgraph/shaderInput.cxx
  78. 4 4
      panda/src/pgraph/shaderInput.h
  79. 1 0
      panda/src/pgraph/shaderPool.cxx
  80. 175 0
      panda/src/pnmimage/convert_srgb.I
  81. 165 0
      panda/src/pnmimage/convert_srgb.cxx
  82. 59 0
      panda/src/pnmimage/convert_srgb.h
  83. 151 0
      panda/src/pnmimage/convert_srgb_sse2.cxx
  84. 1 0
      panda/src/pnmimage/p3pnmimage_composite1.cxx
  85. 2 0
      panda/src/putil/Sources.pp
  86. 20 10
      panda/src/putil/bamCache.cxx
  87. 6 4
      panda/src/putil/bamCache.h
  88. 2 0
      panda/src/putil/callbackObject.h
  89. 32 0
      panda/src/putil/callbackObject_ext.I
  90. 47 0
      panda/src/putil/callbackObject_ext.h
  91. 1 1
      panda/src/tinydisplay/zbuffer.h
  92. 270 42
      pandatool/src/daeegg/daeCharacter.cxx
  93. 43 12
      pandatool/src/daeegg/daeCharacter.h
  94. 11 10
      pandatool/src/daeegg/daeMaterials.cxx
  95. 396 221
      pandatool/src/daeegg/daeToEggConverter.cxx
  96. 23 20
      pandatool/src/daeegg/daeToEggConverter.h
  97. 7 0
      pandatool/src/daeegg/pre_fcollada_include.h
  98. 8 0
      pandatool/src/daeprogs/daeToEgg.cxx

+ 5 - 3
direct/src/directbase/ppython.cxx

@@ -7,6 +7,8 @@
 //
 ///////////////////////////////////////////////////////////////////////
 
+#include "dtoolbase.h"
+
 #include <Python.h>
 #if PY_MAJOR_VERSION >= 3
 #include <wchar.h>
@@ -71,7 +73,7 @@ int main(int argc, char *argv[]) {
   char *path = getenv("PATH");
   char *result = strtok(path, ";");
   while (result != NULL) {
-    struct stat st;       
+    struct stat st;
     char *ppython = (char*) malloc(strlen(result) + 13);
     strcpy(ppython, result);
     strcat(ppython, "\\ppython.exe");
@@ -79,13 +81,13 @@ int main(int argc, char *argv[]) {
       Py_SetPythonHome(result);
       free(ppython);
       break;
-    }                                
+    }
     result = strtok(NULL, ";");
     free(ppython);
   }
 #endif
 #endif
-  
+
   Py_Initialize();
 
   if (Py_VerboseFlag) {

+ 5 - 7
direct/src/directscripts/eggcacher.py

@@ -9,15 +9,15 @@
 ##############################################################################
 
 import os,sys,gc
-from pandac.PandaModules import *
+from panda3d.core import *
 
 class EggCacher:
     def __init__(self, args):
         maindir = Filename.fromOsSpecific(os.getcwd()).getFullpath()
         ExecutionEnvironment.setEnvironmentVariable("MAIN_DIR", maindir)
         self.bamcache = BamCache.getGlobalPtr()
-        self.pandaloader = PandaLoader()
-        self.loaderopts = LoaderOptions()
+        self.pandaloader = Loader()
+        self.loaderopts = LoaderOptions(LoaderOptions.LF_no_ram_cache)
         if (self.bamcache.getActive() == 0):
             print "The model cache is not currently active."
             print "You must set a model-cache-dir in your config file."
@@ -69,12 +69,12 @@ class EggCacher:
 
     def processFiles(self, files):
         total = 0
-        for (path,size) in files:
+        for (path, size) in files:
             total += size
         progress = 0
         for (path,size) in files:
             fn = Filename.fromOsSpecific(path)
-            cached = self.bamcache.lookup(fn,"bam")
+            cached = self.bamcache.lookup(fn, "bam")
             percent = (progress * 100) / total
             report = path
             if (self.concise): report = os.path.basename(report)
@@ -87,6 +87,4 @@ class EggCacher:
             TexturePool.releaseAllTextures()
             progress += size
 
-
 cacher = EggCacher(sys.argv[1:])
-

+ 24 - 10
direct/src/gui/DirectDialog.py

@@ -94,8 +94,9 @@ class DirectDialog(DirectFrame):
             ('text',              '',            None),
             ('text_align',        TextNode.ALeft,   None),
             ('text_scale',        0.06,          None),
-            ('image',  DGG.getDefaultDialogGeom(),   None),
-            ('relief',            None,          None),
+            ('image',             None,          None),
+            ('relief',            DGG.RAISED,     None),
+            ('borderWidth',       (0.01, 0.01),  None),
             ('buttonTextList',    [],            DGG.INITOPT),
             ('buttonGeomList',    [],            DGG.INITOPT),
             ('buttonImageList',   [],            DGG.INITOPT),
@@ -210,10 +211,16 @@ class DirectDialog(DirectFrame):
         bounds = self.stateNodePath[0].getTightBounds()
         if image:
             image.reparentTo(self.stateNodePath[0])
-        l = bounds[0][0]
-        r = bounds[1][0]
-        b = bounds[0][2]
-        t = bounds[1][2]
+        if bounds is None:
+            l = 0
+            r = 0
+            b = 0
+            t = 0
+        else:
+            l = bounds[0][0]
+            r = bounds[1][0]
+            b = bounds[0][2]
+            t = bounds[1][2]
         # Center text and geom around origin
         # How far is center of text from origin?
         xOffset = -(l+r)*0.5
@@ -246,10 +253,16 @@ class DirectDialog(DirectFrame):
                 bl = br = bb = bt = 0
                 for button in self.buttonList:
                     bounds = button.stateNodePath[0].getTightBounds()
-                    bl = min(bl, bounds[0][0])
-                    br = max(br, bounds[1][0])
-                    bb = min(bb, bounds[0][2])
-                    bt = max(bt, bounds[1][2])
+                    if bounds is None:
+                        bl = 0
+                        br = 0
+                        bb = 0
+                        bt = 0
+                    else:
+                        bl = min(bl, bounds[0][0])
+                        br = max(br, bounds[1][0])
+                        bb = min(bb, bounds[0][2])
+                        bt = max(bt, bounds[1][2])
                 bl -= bpad[0]
                 br += bpad[0]
                 bb -= bpad[1]
@@ -303,6 +316,7 @@ class DirectDialog(DirectFrame):
         # reduce bottom by pad, button height and 2*button pad
         b = min(b - self['midPad'] - bpad[1] - bHeight - bpad[1], b) - pad[1]
         t = t + self['topPad'] + pad[1]
+        self['frameSize'] = (l, r, b, t)
         self['image_scale'] = (r - l, 1, t - b)
         # Center frame about text and buttons
         self['image_pos'] = ((l+r)*0.5, 0.0, (b+t)*0.5)

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

@@ -43,7 +43,7 @@ class DirectRadioButton(DirectButton):
             ('boxGeom', None, None),
             ('boxGeomColor', None, None),
             ('boxGeomScale', 1.0, None),
-            ('boxImage', loader.loadModel('models/gui/radio_button_gui'), None),
+            ('boxImage', None, None),
             ('boxImageScale', 1.0, None),
             ('boxImageColor', VBase4(1, 1, 1, 1), None),
             ('boxRelief', None, None),
@@ -69,9 +69,11 @@ class DirectRadioButton(DirectButton):
         # Call option initialization functions
         self.initialiseoptions(DirectRadioButton)
         # After initialization with X giving it the correct size, put back space
-        if self['boxGeom'] ==  None:
+        if self['boxGeom'] is None:
+            if not 'boxRelief' in kw and self['boxImage'] is None:
+                self.indicator['relief'] = DGG.SUNKEN
             self.indicator['text'] = (' ', '*')
-            self.indicator['text_pos'] = (0, -.5)
+            self.indicator['text_pos'] = (0, -.25)
         else:
             self.indicator['text'] = (' ', ' ')
 

+ 8 - 6
direct/src/showbase/Transitions.py

@@ -59,15 +59,12 @@ class Transitions:
 
         # Reload fade if its already been created
         if self.fade:
-            del self.fade
+            self.fade.destroy()
             self.fade = None
             self.loadFade()
 
     def loadFade(self):
-        if not self.fadeModel:
-            self.fadeModel = loader.loadModel(self.FadeModelName)
-
-        if self.fade == None:
+        if self.fade is None:
             # We create a DirectFrame for the fade polygon, instead of
             # simply loading the polygon model and using it directly,
             # so that it will also obscure mouse events for objects
@@ -80,10 +77,15 @@ class Transitions:
                 image_scale = (4, 2, 2),
                 state = DGG.NORMAL,
                 )
+            if not self.fadeModel:
+                # No fade model was given, so we make this the fade model.
+                self.fade["relief"] = DGG.FLAT
+                self.fade["frameSize"] = (-2, 2, -1, 1)
+                self.fade["frameColor"] = (0, 0, 0, 1)
+                self.fade.setTransparency(TransparencyAttrib.MAlpha)
             self.fade.setBin('unsorted', 0)
             self.fade.setColor(0,0,0,0)
 
-    
     def getFadeInIval(self, t=0.5, finishIval=None):
         """
         Returns an interval without starting it.  This is particularly useful in

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

@@ -60,7 +60,7 @@ extern char **environ;
 #include <sys/sysctl.h>
 #endif
 
-#if defined(IS_LINUX) || defined(IS_OSX)
+#if defined(IS_LINUX) || defined(IS_OSX) || defined(IS_FREEBSD)
 // For link_map and dlinfo.
 #include <link.h>
 #include <dlfcn.h>

+ 69 - 0
dtool/src/dtoolutil/filename.I

@@ -64,6 +64,39 @@ Filename(const Filename &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename::
+Filename(string &&filename) NOEXCEPT {
+  _flags = 0;
+  (*this) = move(filename);
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename::
+Filename(Filename &&from) NOEXCEPT :
+  _filename(move(from._filename)),
+  _dirname_end(from._dirname_end),
+  _basename_start(from._basename_start),
+  _basename_end(from._basename_end),
+  _extension_start(from._extension_start),
+  _hash_start(from._hash_start),
+  _hash_end(from._hash_end),
+  _flags(from._flags)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Filename::text_filename named constructor
 //       Access: Published
@@ -216,6 +249,42 @@ operator = (const Filename &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move assignment operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename &Filename::
+operator = (string &&filename) NOEXCEPT {
+  _filename = move(filename);
+
+  locate_basename();
+  locate_extension();
+  locate_hash();
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move assignment operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename &Filename::
+operator = (Filename &&from) NOEXCEPT {
+  _filename = MOVE(from._filename);
+  _dirname_end = from._dirname_end;
+  _basename_start = from._basename_start;
+  _basename_end = from._basename_end;
+  _extension_start = from._extension_start;
+  _hash_start = from._hash_start;
+  _hash_end = from._hash_end;
+  _flags = from._flags;
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Filename::string typecast operator

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

@@ -68,6 +68,11 @@ PUBLISHED:
   Filename(const Filename &dirname, const Filename &basename);
   INLINE ~Filename();
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Filename(string &&filename) NOEXCEPT;
+  INLINE Filename(Filename &&from) NOEXCEPT;
+#endif
+
 #ifdef HAVE_PYTHON
   PyObject *__reduce__(PyObject *self) const;
 #endif
@@ -88,7 +93,7 @@ PUBLISHED:
                                    Type type = T_general);
   static Filename from_os_specific_w(const wstring &os_specific,
                                      Type type = T_general);
-  static Filename expand_from(const string &user_string, 
+  static Filename expand_from(const string &user_string,
                               Type type = T_general);
   static Filename temporary(const string &dirname, const string &prefix,
                             const string &suffix = string(),
@@ -105,6 +110,11 @@ PUBLISHED:
   INLINE Filename &operator = (const char *filename);
   INLINE Filename &operator = (const Filename &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Filename &operator = (string &&filename) NOEXCEPT;
+  INLINE Filename &operator = (Filename &&from) NOEXCEPT;
+#endif
+
   // And retrieval is by any of the classic string operations.
   INLINE operator const string & () const;
   INLINE const char *c_str() const;

+ 6 - 3
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -3204,7 +3204,8 @@ write_function_instance(ostream &out, InterfaceMaker::Object *obj,
       }
       extra_convert += "PyObject *" + param_name + "_long = PyNumber_Long(" + param_name + ");";
       extra_param_check += " && " + param_name + "_long != NULL";
-      pexpr_string = "PyLong_AsUnsignedLongLong(" + param_name + "_long)";
+      pexpr_string = "(" + type->get_local_name(&parser) + ")" +
+                     "PyLong_AsUnsignedLongLong(" + param_name + "_long)";
       extra_cleanup += "Py_XDECREF(" + param_name + "_long);";
       expected_params += "unsigned long long";
       ++num_params;
@@ -3219,7 +3220,8 @@ write_function_instance(ostream &out, InterfaceMaker::Object *obj,
       }
       extra_convert += "PyObject *" + param_name + "_long = PyNumber_Long(" + param_name + ");";
       extra_param_check += " && " + param_name + "_long != NULL";
-      pexpr_string = "PyLong_AsLongLong(" + param_name + "_long)";
+      pexpr_string = "(" + type->get_local_name(&parser) + ")" +
+                     "PyLong_AsLongLong(" + param_name + "_long)";
       extra_cleanup += "Py_XDECREF(" + param_name + "_long);";
       expected_params += "long long";
       ++num_params;
@@ -3234,7 +3236,8 @@ write_function_instance(ostream &out, InterfaceMaker::Object *obj,
       }
       extra_convert += "PyObject *" + param_name + "_long = PyNumber_Long(" + param_name + ");";
       extra_param_check += " && " + param_name + "_long != NULL";
-      pexpr_string = "PyLong_AsUnsignedLong(" + param_name + "_long)";
+      pexpr_string = "(" + type->get_local_name(&parser) + ")" +
+                     "PyLong_AsUnsignedLong(" + param_name + "_long)";
       extra_cleanup += "Py_XDECREF(" + param_name + "_long);";
       expected_params += "unsigned int";
       ++num_params;

+ 1276 - 0
makepanda/installer.nsi

@@ -0,0 +1,1276 @@
+; Panda3D installation script for the Nullsoft Installation System (NSIS).
+; Jon Parise <[email protected]>
+; with Ben Johnson <[email protected]>
+; with Jason Pratt <[email protected]>
+; mangled by Josh Yelon <[email protected]>
+; Heavily restructured by rdb
+
+; Caller needs to define these variables:
+;
+;   COMPRESSOR    - either zlib or lzma
+;   TITLE         - title                         (eg. "Panda3D SDK 1.9.0")
+;   INSTALLDIR    - default install location      (eg. "C:\Panda3D-1.9.0-x64")
+;   OUTFILE       - where to put the output file  (eg. "..\nsis-output.exe")
+;
+;   BUILT         - location of panda install tree.
+;   SOURCE        - location of the panda source-tree if available, OR location of panda install tree.
+;   PYVER         - version of Python that Panda was built with (ie, "2.7")
+;   PYEXTRAS      - directory containing python extras, if any.
+;   REGVIEW       - either 32 or 64, depending on the build architecture.
+;
+
+Name "${TITLE}"
+InstallDir "${INSTALLDIR}"
+OutFile "${OUTFILE}"
+
+RequestExecutionLevel user
+
+SetCompress auto
+SetCompressor ${COMPRESSOR}
+
+!include "MUI2.nsh"
+!include "Sections.nsh"
+!include "WinMessages.nsh"
+!include "WordFunc.nsh"
+
+!define MUI_WELCOMEFINISHPAGE_BITMAP "panda-install.bmp"
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP "panda-install.bmp"
+
+!define MUI_ABORTWARNING
+!define MUI_FINISHPAGE_NOREBOOTSUPPORT
+!define MUI_FINISHPAGE_RUN
+!define MUI_FINISHPAGE_RUN_FUNCTION runFunction
+!define MUI_FINISHPAGE_RUN_TEXT "Visit the Panda3D Manual"
+
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_LICENSE "../doc/LICENSE"
+!insertmacro MUI_PAGE_DIRECTORY
+
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ConfirmPythonSelection
+!insertmacro MUI_PAGE_COMPONENTS
+
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+
+!insertmacro MUI_LANGUAGE "English"
+
+ShowInstDetails hide
+ShowUninstDetails hide
+
+LicenseData "${LICENSE}"
+
+InstType "Full (Recommended)"
+InstType "Minimal"
+
+LangString DESC_SecCore ${LANG_ENGLISH} "The Panda3D core libraries, configuration files and models/textures that are needed to use Panda3D."
+LangString DESC_SecOpenGL ${LANG_ENGLISH} "The OpenGL graphics back-end is the most well-supported renderer."
+LangString DESC_SecDirect3D9 ${LANG_ENGLISH} "The optional Direct3D 9 renderer."
+LangString DESC_SecOpenAL ${LANG_ENGLISH} "Support for playing audio via the OpenAL library.  You need either OpenAL or FMOD to be able to play audio."
+LangString DESC_SecFMOD ${LANG_ENGLISH} "Support for decoding and playing audio via the FMOD Ex library.  You need either OpenAL or FMOD to be able to play audio."
+LangString DESC_SecFFMpeg ${LANG_ENGLISH} "Support for decoding video and audio via the FFMpeg library.  Without this option, Panda3D will only be able to play .wav and .ogg audio files."
+LangString DESC_SecBullet ${LANG_ENGLISH} "Support for the Bullet physics engine."
+LangString DESC_SecODE ${LANG_ENGLISH} "Support for the Open Dynamics Engine to implement physics."
+LangString DESC_SecPhysX ${LANG_ENGLISH} "Support for NVIDIA PhysX to implement physics."
+LangString DESC_SecRocket ${LANG_ENGLISH} "Support for the libRocket GUI library.  This is an optional library that offers an HTML/CSS-like approach to creating user interfaces."
+LangString DESC_SecTools ${LANG_ENGLISH} "Useful tools and model converters to help with Panda3D development.  Recommended."
+LangString DESC_SecPyBindings ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using Python.  These will only work with a ${REGVIEW}-bit version of Python ${PYVER}."
+LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${PYVER} preconfigured to make use of Panda3D."
+LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D."
+LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D."
+LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D."
+LangString DESC_SecMayaPlugins ${LANG_ENGLISH} "Plug-ins and scripts for Autodesk Maya (${REGVIEW}-bit) that can be used to export models to Panda3D."
+
+var READABLE
+var MANPAGE
+
+; See http://nsis.sourceforge.net/Check_if_a_file_exists_at_compile_time for documentation
+!macro !defineifexist _VAR_NAME _FILE_NAME
+    !tempfile _TEMPFILE
+    !ifdef NSIS_WIN32_MAKENSIS
+        ; Windows - cmd.exe
+        !system 'if exist "${_FILE_NAME}" echo !define ${_VAR_NAME} > "${_TEMPFILE}"'
+    !else
+        ; Posix - sh
+        !system 'if [ -e "${_FILE_NAME}" ]; then echo "!define ${_VAR_NAME}" > "${_TEMPFILE}"; fi'
+    !endif
+    !include '${_TEMPFILE}'
+    !delfile '${_TEMPFILE}'
+    !undef _TEMPFILE
+!macroend
+
+!insertmacro !defineifexist HAVE_GL "${BUILT}\bin\libpandagl.dll"
+!insertmacro !defineifexist HAVE_DX9 "${BUILT}\bin\libpandadx9.dll"
+!insertmacro !defineifexist HAVE_OPENAL "${BUILT}\bin\libp3openal_audio.dll"
+!insertmacro !defineifexist HAVE_FMOD "${BUILT}\bin\libp3fmod_audio.dll"
+!insertmacro !defineifexist HAVE_FFMPEG "${BUILT}\bin\libp3ffmpeg.dll"
+!insertmacro !defineifexist HAVE_BULLET "${BUILT}\bin\libpandabullet.dll"
+!insertmacro !defineifexist HAVE_ODE "${BUILT}\bin\libpandaode.dll"
+!insertmacro !defineifexist HAVE_PHYSX "${BUILT}\bin\libpandaphysx.dll"
+!insertmacro !defineifexist HAVE_ROCKET "${BUILT}\bin\libp3rocket.dll"
+!insertmacro !defineifexist HAVE_PYTHON "${BUILT}\python"
+!insertmacro !defineifexist HAVE_SAMPLES "${SOURCE}\samples"
+!insertmacro !defineifexist HAVE_MAX_PLUGINS "${BUILT}\plugins\*.dlo"
+!insertmacro !defineifexist HAVE_MAYA_PLUGINS "${BUILT}\plugins\*.mll"
+
+Function runFunction
+    ExecShell "open" "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk"
+FunctionEnd
+
+SectionGroup "Panda3D Libraries"
+    Section "Core Libraries" SecCore
+        SectionIn 1 2 RO
+
+        SetShellVarContext current
+        SetOverwrite try
+
+        SetDetailsPrint both
+        DetailPrint "Installing Panda3D libraries..."
+        SetDetailsPrint listonly
+
+        SetOutPath "$INSTDIR"
+        File "${BUILT}\LICENSE"
+        File /r /x CVS "${BUILT}\ReleaseNotes"
+        SetOutPath $INSTDIR\bin
+        File /r /x libpandagl.dll /x libpandadx9.dll /x cgD3D*.dll /x python*.dll /x libpandaode.dll /x libp3fmod_audio.dll /x fmodex*.dll /x libp3ffmpeg.dll /x av*.dll /x postproc*.dll /x swscale*.dll /x swresample*.dll /x libp3rocket.dll /x boost_python*.dll /x Rocket*.dll /x _rocket*.pyd /x libpandabullet.dll /x OpenAL32.dll /x *_oal.dll /x libp3openal_audio.dll "${BUILT}\bin\*.dll"
+        File /nonfatal /r "${BUILT}\bin\Microsoft.*.manifest"
+        SetOutPath $INSTDIR\etc
+        File /r "${BUILT}\etc\*"
+
+        SetDetailsPrint both
+        DetailPrint "Installing models..."
+        SetDetailsPrint listonly
+
+        SetOutPath $INSTDIR\models
+        File /r /x CVS "${BUILT}\models\*"
+
+        RMDir /r "$SMPROGRAMS\${TITLE}"
+        CreateDirectory "$SMPROGRAMS\${TITLE}"
+    SectionEnd
+
+    !ifdef HAVE_GL
+    Section "OpenGL" SecOpenGL
+        SectionIn 1 2 RO
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libpandagl.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_DX9
+    Section "Direct3D 9" SecDirect3D9
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libpandadx9.dll"
+        File /nonfatal /r "${BUILT}\bin\cgD3D9.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_OPENAL
+    Section "OpenAL Audio" SecOpenAL
+        SectionIn 1 2
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libp3openal_audio.dll"
+        File /nonfatal /r "${BUILT}\bin\OpenAL32.dll"
+        File /nonfatal /r "${BUILT}\bin\*_oal.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_FMOD
+    Section "FMOD Audio" SecFMOD
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libp3fmod_audio.dll"
+        File /r "${BUILT}\bin\fmodex*.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_FFMPEG
+    Section "FFMpeg" SecFFMpeg
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libp3ffmpeg.dll"
+        File /nonfatal /r "${BUILT}\bin\av*.dll"
+        File /nonfatal /r "${BUILT}\bin\swscale*.dll"
+        File /nonfatal /r "${BUILT}\bin\swresample*.dll"
+        File /nonfatal /r "${BUILT}\bin\postproc*.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_BULLET
+    Section "Bullet Physics" SecBullet
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libpandabullet.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_ODE
+    Section "ODE Physics" SecODE
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libpandaode.dll"
+    SectionEnd
+    !endif
+
+    !ifdef HAVE_ROCKET
+    Section "libRocket GUI" SecRocket
+        SectionIn 1
+
+        SetOutPath "$INSTDIR\bin"
+        File "${BUILT}\bin\libp3rocket.dll"
+        File /nonfatal /r "${BUILT}\bin\Rocket*.dll"
+        File /nonfatal /r "${BUILT}\bin\_rocket*.pyd"
+        File /nonfatal /r "${BUILT}\bin\boost_python*.dll"
+    SectionEnd
+    !endif
+SectionGroupEnd
+
+Section "Tools and utilities" SecTools
+    SectionIn 1 2
+
+    SetDetailsPrint both
+    DetailPrint "Installing utilities..."
+    SetDetailsPrint listonly
+
+    SetOutPath "$INSTDIR\bin"
+    File /r "${BUILT}\bin\*.exe"
+    File /nonfatal /r "${BUILT}\bin\*.p3d"
+    SetOutPath "$INSTDIR\NSIS"
+    File /r /x CVS "${NSISDIR}\*"
+SectionEnd
+
+SectionGroup "Python support"
+    Section "Python bindings" SecPyBindings
+        SectionIn 1 2
+
+        SetDetailsPrint both
+        DetailPrint "Installing Panda3D Python modules..."
+        SetDetailsPrint listonly
+
+        SetOutPath "$INSTDIR\bin"
+        File /nonfatal /r "${BUILT}\bin\*.pyd"
+
+        SetOutPath $INSTDIR\direct\directscripts
+        File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*"
+        SetOutPath $INSTDIR\direct\filter
+        File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\filter\*.sha"
+        SetOutPath $INSTDIR\direct
+        File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\*.py"
+
+        Delete "$INSTDIR\panda3d.py"
+        Delete "$INSTDIR\panda3d.pyc"
+        Delete "$INSTDIR\panda3d.pyo"
+        SetOutPath $INSTDIR\pandac
+        File /r "${BUILT}\pandac\*.py"
+        SetOutPath $INSTDIR\panda3d
+        File /r "${BUILT}\panda3d\*.py"
+
+        File /r /x bullet.pyd /x ode.pyd /x physx.pyd /x rocket.pyd "${BUILT}\panda3d\*.pyd"
+
+        !ifdef HAVE_BULLET
+            SectionGetFlags ${SecBullet} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipBulletPyd
+            File /nonfatal /r "${BUILT}\panda3d\bullet.pyd"
+            SkipBulletPyd:
+        !endif
+
+        !ifdef HAVE_ODE
+            SectionGetFlags ${SecODE} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipODEPyd
+            File /nonfatal /r "${BUILT}\panda3d\ode.pyd"
+            SkipODEPyd:
+        !endif
+
+        !ifdef HAVE_PHYSX
+            SectionGetFlags ${SecPhysX} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipPhysXPyd
+            File /nonfatal /r "${BUILT}\panda3d\physx.pyd"
+            SkipPhysXPyd:
+        !endif
+
+        !ifdef HAVE_ROCKET
+            SectionGetFlags ${SecRocket} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipRocketPyd
+            File /nonfatal /r "${BUILT}\panda3d\rocket.pyd"
+            SkipRocketPyd:
+        !endif
+
+        SetOutPath $INSTDIR\pandac\input
+        File /r "${BUILT}\pandac\input\*"
+        SetOutPath $INSTDIR\Pmw
+        File /r /x CVS "${BUILT}\Pmw\*"
+
+        ; Check for a user installation of Python.
+        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" CheckSystemWidePython 0
+        StrCmp $0 "" CheckSystemWidePython 0
+        IfFileExists "$0\ppython.exe" CheckSystemWidePython 0
+        IfFileExists "$0\python.exe" AskExternalPth 0
+
+        ; Check for a system-wide Python installation.
+        CheckSystemWidePython:
+        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" SkipExternalPth 0
+        StrCmp $0 "" SkipExternalPth 0
+        IfFileExists "$0\ppython.exe" SkipExternalPth 0
+        IfFileExists "$0\python.exe" AskExternalPth SkipExternalPth
+
+        AskExternalPth:
+        MessageBox MB_YESNO|MB_ICONQUESTION \
+            "Your system already has a ${REGVIEW}-bit copy of Python ${PYVER} installed in:$\r$\n$0$\r$\nWould you like to configure it to be able to use the Panda3D libraries?$\r$\nIf you choose no, you will only be able to use Panda3D's own copy of Python." \
+            IDNO SkipExternalPth
+
+        FileOpen $1 "$0\Lib\site-packages\panda.pth" w
+        FileWrite $1 "$INSTDIR$\r$\n"
+        FileWrite $1 "$INSTDIR\bin$\r$\n"
+        FileClose $1
+        SkipExternalPth:
+    SectionEnd
+
+    !ifdef HAVE_PYTHON
+    Section "Python ${PYVER}" SecPython
+        SectionIn 1 2
+
+        !ifdef REGVIEW
+        SetRegView ${REGVIEW}
+        !endif
+
+        SetDetailsPrint both
+        DetailPrint "Installing Python ${PYVER} (${REGVIEW}-bit)..."
+        SetDetailsPrint listonly
+
+        SetOutPath "$INSTDIR\bin"
+        File /nonfatal "${BUILT}\bin\python*.dll"
+
+        SetOutPath "$INSTDIR\python"
+        File /r "${BUILT}\python\*"
+
+        !ifdef PYEXTRAS
+        SetOutPath "$INSTDIR\python\lib"
+        File /nonfatal /r "${PYEXTRAS}\*"
+        !endif
+
+        SetDetailsPrint both
+        DetailPrint "Adding registry keys for Python..."
+        SetDetailsPrint listonly
+
+        ; Check if a copy of Python is installed for this user.
+        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+        StrCmp "$0" "$INSTDIR\python" RegPath 0
+        StrCmp "$0" "" SkipFileCheck 0
+        IfFileExists "$0\python.exe" AskRegPath 0
+        SkipFileCheck:
+
+        ; Check if a system-wide copy of Python is installed.
+        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+        StrCmp "$0" "$INSTDIR\python" RegPath 0
+        StrCmp "$0" "" RegPath 0
+        IfFileExists "$0\python.exe" AskRegPath RegPath
+
+        AskRegPath:
+        MessageBox MB_YESNO|MB_ICONQUESTION \
+            "Your system already has a ${REGVIEW}-bit copy of Python ${PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${PYVER}, which will install alongside your existing copy.  Would you like to make Panda's copy the default Python for your user account?" \
+            IDNO SkipRegPath
+
+        RegPath:
+        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" "$INSTDIR\python"
+        SkipRegPath:
+
+    SectionEnd
+    !endif
+SectionGroupEnd
+
+Function ConfirmPythonSelection
+    ; Check the current state of the "Python" section selection.
+    SectionGetFlags ${SecPython} $R0
+    IntOp $R1 $R0 & ${SF_SELECTED}
+
+    ; Is the "Python" selection deselected?
+    StrCmp $R1 ${SF_SELECTED} SkipCheck 0
+
+    ; Maybe the user just doesn't want Python support at all?
+    SectionGetFlags ${SecPyBindings} $R1
+    IntOp $R1 $R1 & ${SF_SELECTED}
+    StrCmp $R1 ${SF_SELECTED} 0 SkipCheck
+
+    ; Check for a user installation of Python.
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    StrCmp $0 "$INSTDIR\python" CheckSystemWidePython 0
+    StrCmp $0 "" CheckSystemWidePython 0
+    IfFileExists "$0\ppython.exe" CheckSystemWidePython 0
+    IfFileExists "$0\python.exe" SkipCheck CheckSystemWidePython
+
+    ; Check for a system-wide Python installation.
+    CheckSystemWidePython:
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    StrCmp $0 "$INSTDIR\python" AskConfirmation 0
+    StrCmp $0 "" AskConfirmation 0
+    IfFileExists "$0\ppython.exe" AskConfirmation 0
+    IfFileExists "$0\python.exe" SkipCheck AskConfirmation
+
+    ; No compatible Python version found (that wasn't shipped as part
+    ; of a different Panda3D build.)  Ask the user if he's sure about this.
+    AskConfirmation:
+    MessageBox MB_YESNO|MB_ICONQUESTION \
+        "You do not appear to have a ${REGVIEW}-bit version of Python ${PYVER} installed that is compatible with Panda3D.  Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python ${PYVER} and manually configure it to be able to use Panda3D." \
+        IDYES SkipCheck
+
+    ; User clicked no, so re-enable the select box and abort.
+    IntOp $R0 $R0 | ${SF_SELECTED}
+    SectionSetFlags ${SecPython} $R0
+    Abort
+
+    SkipCheck:
+FunctionEnd
+
+Section "C++ support" SecHeadersLibs
+    SectionIn 1
+
+    SetDetailsPrint both
+    DetailPrint "Installing header files..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR\include
+    File /r /x *.exp "${BUILT}\include\*"
+
+    SetDetailsPrint both
+    DetailPrint "Installing library archives..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR\lib
+    File /r /x *.exp "${BUILT}\lib\*"
+SectionEnd
+
+!ifdef HAVE_SAMPLES
+Section "Sample programs" SecSamples
+    SectionIn 1
+
+    ; Necessary for proper start menu shortcut installation
+    SetShellVarContext current
+
+    SetDetailsPrint both
+    DetailPrint "Installing sample programs..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR\samples
+    File /nonfatal /r /x CVS "${SOURCE}\samples\*"
+
+    SetDetailsPrint both
+    DetailPrint "Creating shortcuts..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR
+    WriteINIStr $INSTDIR\Website.url "InternetShortcut" "URL" "https://www.panda3d.org/"
+    WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php"
+    WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://www.panda3d.org/manual/index.php/Sample_Programs_in_the_Distribution"
+    SetOutPath $INSTDIR
+    CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\bin\eggcacher.exe" 0 "" "" "Panda3D Manual"
+    CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\bin\eggcacher.exe" 0 "" "" "Panda3D Website"
+    CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\bin\eggcacher.exe" 0 "" "" "Sample Program Manual"
+
+    FindFirst $0 $1 $INSTDIR\samples\*
+    loop:
+        StrCmp $1 "" done
+        StrCmp $1 "." next
+        StrCmp $1 ".." next
+        Push $1
+        Push "-"
+        Push " "
+        Call StrRep
+        Call Capitalize
+        Pop $R0
+        StrCpy $READABLE $R0
+        Push $1
+        Push "-"
+        Push "_"
+        Call StrRep
+        Pop $R0
+        StrCpy $MANPAGE $R0
+        DetailPrint "Creating shortcuts for sample program $READABLE"
+        CreateDirectory "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE"
+        SetOutPath $INSTDIR\samples\$1
+        WriteINIStr $INSTDIR\samples\$1\ManualPage.url "InternetShortcut" "URL" "http://panda3d.org/wiki/index.php/Sample_Programs:_$MANPAGE"
+        CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\Manual Page.lnk" "$INSTDIR\samples\$1\ManualPage.url" "" "$INSTDIR\bin\eggcacher.exe" 0 "" "" "Manual Entry on this Sample Program"
+        CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\View Source Code.lnk" "$INSTDIR\samples\$1"
+        FindFirst $2 $3 $INSTDIR\samples\$1\*.py
+        iloop:
+            StrCmp $3 "" idone
+            CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Programs\$READABLE\Run $3.lnk" "$INSTDIR\python\python.exe" "-E $3" "$INSTDIR\bin\eggcacher.exe" 0 SW_SHOWMINIMIZED "" "Run $3"
+            CreateShortCut "$INSTDIR\samples\$1\Run $3.lnk" "$INSTDIR\python\python.exe" "-E $3" "$INSTDIR\bin\eggcacher.exe" 0 SW_SHOWMINIMIZED "" "Run $3"
+            FindNext $2 $3
+            goto iloop
+        idone:
+    next:
+        FindNext $0 $1
+        Goto loop
+    done:
+SectionEnd
+!endif
+
+!ifdef HAVE_MAX_PLUGINS
+Section "3ds Max plug-ins" SecMaxPlugins
+    SectionIn 1 3
+
+    SetDetailsPrint both
+    DetailPrint "Installing Autodesk 3ds Max plug-ins..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR\plugins
+    File /nonfatal /r "${BUILT}\plugins\*.dle"
+    File /nonfatal /r "${BUILT}\plugins\*.dlo"
+    File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
+SectionEnd
+!endif
+
+!ifdef HAVE_MAYA_PLUGINS
+Section "Maya plug-ins" SecMayaPlugins
+    SectionIn 1 3
+
+    SetDetailsPrint both
+    DetailPrint "Installing Autodesk Maya plug-ins..."
+    SetDetailsPrint listonly
+
+    SetOutPath $INSTDIR\plugins
+    File /nonfatal /r "${BUILT}\plugins\*.mll"
+    File /nonfatal /r "${BUILT}\plugins\*.mel"
+    File /nonfatal /r "${BUILT}\plugins\*.ms"
+    File "${SOURCE}\doc\INSTALLING-PLUGINS.TXT"
+SectionEnd
+!endif
+
+Section -post
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
+
+    ; Run eggcacher.  We can't do this in SecCore because we haven't
+    ; installed eggcacher at that point yet.
+    SetDetailsPrint both
+    DetailPrint "Preloading .egg files into the model cache..."
+    SetDetailsPrint listonly
+
+    ; We need to set the $PATH for eggcacher.
+    SetOutPath $INSTDIR
+    ReadEnvStr $R0 "PATH"
+    StrCpy $R0 "$INSTDIR\python;$INSTDIR\bin;$R0"
+    System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("PATH", R0).r2'
+
+    nsExec::ExecToLog '"$INSTDIR\bin\eggcacher.exe" --concise models samples'
+
+    SetDetailsPrint both
+    DetailPrint "Writing the uninstaller ..."
+    SetDetailsPrint listonly
+
+    Delete "$INSTDIR\uninst.exe"
+    WriteUninstaller "$INSTDIR\uninst.exe"
+    WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${TITLE}" "DisplayName" "${TITLE}"
+    WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${TITLE}" "UninstallString" '"$INSTDIR\uninst.exe"'
+    SetOutPath $INSTDIR
+    CreateShortcut "$SMPROGRAMS\${TITLE}\Uninstall ${TITLE}.lnk" "$INSTDIR\uninst.exe" ""
+
+    SetDetailsPrint both
+    DetailPrint "Adding directories to system PATH..."
+    SetDetailsPrint listonly
+
+    # Add the "bin" directory to the PATH.
+    Push "$INSTDIR\python"
+    Call RemoveFromPath
+    Push "$INSTDIR\python\Scripts"
+    Call RemoveFromPath
+    Push "$INSTDIR\bin"
+    Call RemoveFromPath
+    Push "$INSTDIR\python"
+    Call AddToPath
+    Push "$INSTDIR\python\Scripts"
+    Call AddToPath
+    Push "$INSTDIR\bin"
+    Call AddToPath
+
+    # This is needed for the environment variable changes to take effect.
+    DetailPrint "Broadcasting WM_WININICHANGE message..."
+    SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=500
+
+SectionEnd
+
+Section Uninstall
+    SetDetailsPrint listonly
+
+    SetShellVarContext current
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
+
+    SetDetailsPrint both
+    DetailPrint "Removing registry entries..."
+    SetDetailsPrint listonly
+
+    DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${TITLE}"
+    DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${TITLE}"
+
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    StrCmp $0 "$INSTDIR\python" 0 SkipUnRegHKLM
+    DeleteRegKey HKLM "Software\Python\PythonCore\${PYVER}"
+    SkipUnRegHKLM:
+
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    StrCmp $0 "$INSTDIR\python" 0 SkipUnRegHKCU
+    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}"
+    SkipUnRegHKCU:
+
+    SetDetailsPrint both
+    DetailPrint "Deleting files..."
+    SetDetailsPrint listonly
+
+    Delete "$INSTDIR\uninst.exe"
+    RMDir /r "$INSTDIR"
+
+    SetDetailsPrint both
+    DetailPrint "Removing Start Menu entries..."
+    SetDetailsPrint listonly
+
+    SetShellVarContext current
+    RMDir /r "$SMPROGRAMS\${TITLE}"
+    SetShellVarContext all
+    RMDir /r "$SMPROGRAMS\${TITLE}"
+
+    SetDetailsPrint both
+    DetailPrint "Removing entries from PATH..."
+    SetDetailsPrint listonly
+
+    Push "$INSTDIR\python"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\python\Scripts"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\bin"
+    Call un.RemoveFromPath
+
+    # This is needed for the environment variable changes to take effect.
+    DetailPrint "Broadcasting WM_WININICHANGE message..."
+    SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=500
+
+SectionEnd
+
+!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecCore} $(DESC_SecCore)
+  !ifdef HAVE_GL
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecOpenGL} $(DESC_SecOpenGL)
+  !endif
+  !ifdef HAVE_DX9
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecDirect3D9} $(DESC_SecDirect3D9)
+  !endif
+  !ifdef HAVE_OPENAL
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecOpenAL} $(DESC_SecOpenAL)
+  !endif
+  !ifdef HAVE_FMOD
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecFMOD} $(DESC_SecFMOD)
+  !endif
+  !ifdef HAVE_FFMPEG
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecFFMpeg} $(DESC_SecFFMpeg)
+  !endif
+  !ifdef HAVE_BULLET
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecBullet} $(DESC_SecBullet)
+  !endif
+  !ifdef HAVE_ODE
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecODE} $(DESC_SecODE)
+  !endif
+  !ifdef HAVE_PHYSX
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPhysX} $(DESC_SecPhysX)
+  !endif
+  !ifdef HAVE_ROCKET
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecRocket} $(DESC_SecRocket)
+  !endif
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecTools} $(DESC_SecTools)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings} $(DESC_SecPyBindings)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecSamples} $(DESC_SecSamples)
+  !ifdef HAVE_MAX_PLUGINS
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecMaxPlugins} $(DESC_SecMaxPlugins)
+  !endif
+  !ifdef HAVE_MAYA_PLUGINS
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecMayaPlugins} $(DESC_SecMayaPlugins)
+  !endif
+!insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+# --[ Utility Functions ]------------------------------------------------------
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function IsNT
+        Push $0
+        ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
+        StrCmp $0 "" 0 IsNT_yes
+        ; we are not NT.
+        Pop $0
+        Push 0
+        Return
+        IsNT_yes:
+                ; NT!!!
+                Pop $0
+                Push 1
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function un.IsNT
+        Push $0
+        ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
+        StrCmp $0 "" 0 unIsNT_yes
+        ; we are not NT.
+        Pop $0
+        Push 0
+        Return
+        unIsNT_yes:
+                ; NT!!!
+                Pop $0
+                Push 1
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function StrStr
+        Push $0
+        Exch
+        Pop $0 ; $0 now have the string to find
+        Push $1
+        Exch 2
+        Pop $1 ; $1 now have the string to find in
+        Exch
+        Push $2
+        Push $3
+        Push $4
+        Push $5
+        StrCpy $2 -1
+        StrLen $3 $0
+        StrLen $4 $1
+        IntOp $4 $4 - $3
+        unStrStr_loop:
+                IntOp $2 $2 + 1
+                IntCmp $2 $4 0 0 unStrStrReturn_notFound
+                StrCpy $5 $1 $3 $2
+                StrCmp $5 $0 unStrStr_done unStrStr_loop
+        unStrStrReturn_notFound:
+                StrCpy $2 -1
+        unStrStr_done:
+                Pop $5
+                Pop $4
+                Pop $3
+                Exch $2
+                Exch 2
+                Pop $0
+                Pop $1
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function un.StrStr
+        Push $0
+        Exch
+        Pop $0 ; $0 now have the string to find
+        Push $1
+        Exch 2
+        Pop $1 ; $1 now have the string to find in
+        Exch
+        Push $2
+        Push $3
+        Push $4
+        Push $5
+        StrCpy $2 -1
+        StrLen $3 $0
+        StrLen $4 $1
+        IntOp $4 $4 - $3
+        unStrStr_loop:
+                IntOp $2 $2 + 1
+                IntCmp $2 $4 0 0 unStrStrReturn_notFound
+                StrCpy $5 $1 $3 $2
+                StrCmp $5 $0 unStrStr_done unStrStr_loop
+        unStrStrReturn_notFound:
+                StrCpy $2 -1
+        unStrStr_done:
+                Pop $5
+                Pop $4
+                Pop $3
+                Exch $2
+                Exch 2
+                Pop $0
+                Pop $1
+FunctionEnd
+
+; Capitalizes the first letter of every word.
+Function Capitalize
+        Exch $R0
+        Push $0
+        Push $1
+        Push $2
+
+        StrCpy $0 0
+
+        capNext:
+        ; Grab the next character.
+        StrCpy $1 $R0 1 $0
+        StrCmp $1 '' end
+
+        ; Capitalize it.
+        ${StrFilter} '$1' '+eng' '' '' $1
+        ${StrFilter} '$1' '+rus' '' '' $1
+
+        ; Splice it into the string.
+        StrCpy $2 $R0 $0
+        IntOp $0 $0 + 1
+        StrCpy $R0 $R0 '' $0
+        StrCpy $R0 '$2$1$R0'
+
+        ; Keep looping through the characters until we find a
+        ; delimiter or reach the end of the string.
+        loop:
+        StrCpy $1 $R0 1 $0
+        IntOp $0 $0 + 1
+        StrCmp $1 '' end
+        StrCmp $1 ' ' capNext
+        StrCmp $1 '_' capNext
+        StrCmp $1 '-' capNext
+        StrCmp $1 '(' capNext
+        StrCmp $1 '[' capNext
+        Goto loop
+
+        end:
+        Pop $2
+        Pop $1
+        Pop $0
+        Exch $R0
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+; Commentary and smarter ';' checking by Jon Parise <[email protected]>
+Function AddToPath
+        Exch $0
+        Push $1
+        Push $2
+        Push $3
+        Call IsNT
+        Pop $1
+
+        DetailPrint "Adding to PATH: $0"
+
+        StrCmp $1 1 AddToPath_NT
+                ; We're not on NT, so modify the AUTOEXEC.BAT file.
+                StrCpy $1 $WINDIR 2
+                FileOpen $1 "$1\autoexec.bat" a
+                FileSeek $1 0 END
+                GetFullPathName /SHORT $0 $0
+                FileWrite $1 "$\r$\nSET PATH=%PATH%;$0$\r$\n"
+                FileClose $1
+                Goto AddToPath_done
+
+        AddToPath_NT:
+                ReadRegStr $1 HKCU "Environment" "PATH"
+                Call IsUserAdmin
+                Pop $3
+                ; If this is an Admin user, use the System env. variable instead of the user's env. variable
+                StrCmp $3 1 0 +2
+                        ReadRegStr $1 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH"
+
+                ; If the PATH string is empty, jump over the mangling routines.
+                StrCmp $1 "" AddToPath_NTdoIt
+
+                ; Pull off the last character of the PATH string.  If it's a semicolon,
+                ; we don't need to add another one, so jump to the section where we
+                ; append the new PATH component(s).
+                StrCpy $2 $1 1 -1
+                StrCmp $2 ";" AddToPath_NTAddPath AddToPath_NTAddSemi
+
+                AddToPath_NTAddSemi:
+                        StrCpy $1 "$1;"
+                        Goto AddToPath_NTAddPath
+                AddToPath_NTAddPath:
+                        StrCpy $0 "$1$0"
+                        Goto AddToPath_NTdoIt
+                AddToPath_NTdoIt:
+                        Call IsUserAdmin
+                        Pop $3
+                        StrCmp $3 1 0 NotAdmin
+                                WriteRegExpandStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" $0
+                                Goto AddToPath_done
+
+                        NotAdmin:
+                                WriteRegExpandStr HKCU "Environment" "PATH" $0
+        AddToPath_done:
+                Pop $3
+                Pop $2
+                Pop $1
+                Pop $0
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function RemoveFromPath
+        Exch $0
+        Push $1
+        Push $2
+        Push $3
+        Push $4
+        Push $5
+        Call IsNT
+        Pop $1
+
+        DetailPrint "Removing from PATH: $0"
+
+        StrCmp $1 1 unRemoveFromPath_NT
+                ; Not on NT
+                StrCpy $1 $WINDIR 2
+                FileOpen $1 "$1\autoexec.bat" r
+                GetTempFileName $4
+                FileOpen $2 $4 w
+                GetFullPathName /SHORT $0 $0
+                StrCpy $0 "SET PATH=%PATH%;$0"
+                SetRebootFlag true
+                Goto unRemoveFromPath_dosLoop
+
+                unRemoveFromPath_dosLoop:
+                        FileRead $1 $3
+                        StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoop
+                        StrCmp $3 "$0$\n" unRemoveFromPath_dosLoop
+                        StrCmp $3 "$0" unRemoveFromPath_dosLoop
+                        StrCmp $3 "" unRemoveFromPath_dosLoopEnd
+                        FileWrite $2 $3
+                        Goto unRemoveFromPath_dosLoop
+
+                unRemoveFromPath_dosLoopEnd:
+                        FileClose $2
+                        FileClose $1
+                        StrCpy $1 $WINDIR 2
+                        Delete "$1\autoexec.bat"
+                        CopyFiles /SILENT $4 "$1\autoexec.bat"
+                        Delete $4
+                        Goto unRemoveFromPath_done
+
+                unRemoveFromPath_NT:
+                        StrLen $2 $0
+                        Call IsUserAdmin
+                        Pop $5
+                        StrCmp $5 1 0 NotAdmin
+                                ReadRegStr $1 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH"
+                                Push $1
+                                Push $0
+                                Call StrStr ; Find $0 in $1
+                                Pop $0 ; pos of our dir
+                                IntCmp $0 -1 unRemoveFromPath_done
+                                        ; else, it is in path
+                                        StrCpy $3 $1 $0 ; $3 now has the part of the path before our dir
+                                        IntOp $2 $2 + $0 ; $2 now contains the pos after our dir in the path (';')
+                                        IntOp $2 $2 + 1 ; $2 now containts the pos after our dir and the semicolon.
+                                        StrLen $0 $1
+                                        StrCpy $1 $1 $0 $2
+                                        StrCpy $3 "$3$1"
+                                        WriteRegExpandStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" $3
+                                        Goto unRemoveFromPath_done
+
+
+                        NotAdmin:
+                                ReadRegStr $1 HKCU "Environment" "PATH"
+                                Push $1
+                                Push $0
+                                Call StrStr ; Find $0 in $1
+                                Pop $0 ; pos of our dir
+                                IntCmp $0 -1 unRemoveFromPath_done
+                                        ; else, it is in path
+                                        StrCpy $3 $1 $0 ; $3 now has the part of the path before our dir
+                                        IntOp $2 $2 + $0 ; $2 now contains the pos after our dir in the path (';')
+                                        IntOp $2 $2 + 1 ; $2 now containts the pos after our dir and the semicolon.
+                                        StrLen $0 $1
+                                        StrCpy $1 $1 $0 $2
+                                        StrCpy $3 "$3$1"
+                                        WriteRegExpandStr HKCU "Environment" "PATH" $3
+
+                unRemoveFromPath_done:
+                        Pop $5
+                        Pop $4
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/viewpage.php?pageid=91
+Function un.RemoveFromPath
+        Exch $0
+        Push $1
+        Push $2
+        Push $3
+        Push $4
+        Push $5
+        Call un.IsNT
+        Pop $1
+        StrCmp $1 1 unRemoveFromPath_NT
+                ; Not on NT
+                StrCpy $1 $WINDIR 2
+                FileOpen $1 "$1\autoexec.bat" r
+                GetTempFileName $4
+                FileOpen $2 $4 w
+                GetFullPathName /SHORT $0 $0
+                StrCpy $0 "SET PATH=%PATH%;$0"
+                SetRebootFlag true
+                Goto unRemoveFromPath_dosLoop
+
+                unRemoveFromPath_dosLoop:
+                        FileRead $1 $3
+                        StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoop
+                        StrCmp $3 "$0$\n" unRemoveFromPath_dosLoop
+                        StrCmp $3 "$0" unRemoveFromPath_dosLoop
+                        StrCmp $3 "" unRemoveFromPath_dosLoopEnd
+                        FileWrite $2 $3
+                        Goto unRemoveFromPath_dosLoop
+
+                unRemoveFromPath_dosLoopEnd:
+                        FileClose $2
+                        FileClose $1
+                        StrCpy $1 $WINDIR 2
+                        Delete "$1\autoexec.bat"
+                        CopyFiles /SILENT $4 "$1\autoexec.bat"
+                        Delete $4
+                        Goto unRemoveFromPath_done
+
+                unRemoveFromPath_NT:
+                        StrLen $2 $0
+                        Call un.IsUserAdmin
+                        Pop $5
+                        StrCmp $5 1 0 NotAdmin
+                                ReadRegStr $1 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH"
+                                Push $1
+                                Push $0
+                                Call un.StrStr ; Find $0 in $1
+                                Pop $0 ; pos of our dir
+                                IntCmp $0 -1 unRemoveFromPath_done
+                                        ; else, it is in path
+                                        StrCpy $3 $1 $0 ; $3 now has the part of the path before our dir
+                                        IntOp $2 $2 + $0 ; $2 now contains the pos after our dir in the path (';')
+                                        IntOp $2 $2 + 1 ; $2 now containts the pos after our dir and the semicolon.
+                                        StrLen $0 $1
+                                        StrCpy $1 $1 $0 $2
+                                        StrCpy $3 "$3$1"
+                                        WriteRegExpandStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH" $3
+                                        Goto unRemoveFromPath_done
+
+
+                        NotAdmin:
+                                ReadRegStr $1 HKCU "Environment" "PATH"
+                                Push $1
+                                Push $0
+                                Call un.StrStr ; Find $0 in $1
+                                Pop $0 ; pos of our dir
+                                IntCmp $0 -1 unRemoveFromPath_done
+                                        ; else, it is in path
+                                        StrCpy $3 $1 $0 ; $3 now has the part of the path before our dir
+                                        IntOp $2 $2 + $0 ; $2 now contains the pos after our dir in the path (';')
+                                        IntOp $2 $2 + 1 ; $2 now containts the pos after our dir and the semicolon.
+                                        StrLen $0 $1
+                                        StrCpy $1 $1 $0 $2
+                                        StrCpy $3 "$3$1"
+                                        WriteRegExpandStr HKCU "Environment" "PATH" $3
+
+                unRemoveFromPath_done:
+                        Pop $5
+                        Pop $4
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+FunctionEnd
+
+; From: http://nsis.sourceforge.net/archive/nsisweb.php?page=329&instances=0,11
+; Localized by Ben Johnson ([email protected])
+Function IsUserAdmin
+        Push $0
+        Push $1
+        Push $2
+        Push $3
+        Call IsNT
+        Pop $1
+
+        ClearErrors
+        UserInfo::GetName
+        ;IfErrors Win9x
+        Pop $2
+        UserInfo::GetAccountType
+        Pop $3
+
+        ; Compare results of IsNT with "1"
+        StrCmp $1 1 0 NotNT
+                ;This is NT
+
+
+                StrCmp $3 "Admin" 0 NotAdmin
+                        ; Observation: I get here when running Win98SE. (Lilla)
+                        ; The functions UserInfo.dll looks for are there on Win98 too,
+                        ; but just don't work. So UserInfo.dll, knowing that admin isn't required
+                        ; on Win98, returns admin anyway. (per kichik)
+                        ; MessageBox MB_OK 'User "$R1" is in the Administrators group'
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+
+                        Push 1
+                        Return
+
+                NotAdmin:
+                        ; You should still check for an empty string because the functions
+                        ; UserInfo.dll looks for may not be present on Windows 95. (per kichik)
+
+                        #StrCmp $2 "" Win9x
+                        #StrCpy $0 0
+                        ;MessageBox MB_OK 'User "$2" is in the "$3" group'
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+
+                        Push 0
+                        Return
+
+        ;Because we use IsNT, this is redundant.
+        #Win9x:
+        #       ; comment/message below is by UserInfo.nsi author:
+        #       ; This one means you don't need to care about admin or
+        #       ; not admin because Windows 9x doesn't either
+        #       ;MessageBox MB_OK "Error! This DLL can't run under Windows 9x!"
+        #       StrCpy $0 0
+
+        NotNT:
+                ;We are not NT
+                ;Win9x doesn't have "admin" users.
+                ;Let the user do whatever.
+                Pop $3
+                Pop $2
+                Pop $1
+                Pop $0
+
+                Push 1
+
+FunctionEnd
+
+Function un.IsUserAdmin
+        Push $0
+        Push $1
+        Push $2
+        Push $3
+        Call un.IsNT
+        Pop $1
+
+        ClearErrors
+        UserInfo::GetName
+        ;IfErrors Win9x
+        Pop $2
+        UserInfo::GetAccountType
+        Pop $3
+
+        ; Compare results of IsNT with "1"
+        StrCmp $1 1 0 NotNT
+                ;This is NT
+
+
+                StrCmp $3 "Admin" 0 NotAdmin
+                        ; Observation: I get here when running Win98SE. (Lilla)
+                        ; The functions UserInfo.dll looks for are there on Win98 too,
+                        ; but just don't work. So UserInfo.dll, knowing that admin isn't required
+                        ; on Win98, returns admin anyway. (per kichik)
+                        ; MessageBox MB_OK 'User "$R1" is in the Administrators group'
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+
+                        Push 1
+                        Return
+
+                NotAdmin:
+                        ; You should still check for an empty string because the functions
+                        ; UserInfo.dll looks for may not be present on Windows 95. (per kichik)
+
+                        #StrCmp $2 "" Win9x
+                        #StrCpy $0 0
+                        ;MessageBox MB_OK 'User "$2" is in the "$3" group'
+                        Pop $3
+                        Pop $2
+                        Pop $1
+                        Pop $0
+
+                        Push 0
+                        Return
+
+        ;Because we use IsNT, this is redundant.
+        #Win9x:
+        #       ; comment/message below is by UserInfo.nsi author:
+        #       ; This one means you don't need to care about admin or
+        #       ; not admin because Windows 9x doesn't either
+        #       ;MessageBox MB_OK "Error! This DLL can't run under Windows 9x!"
+        #       StrCpy $0 0
+
+        NotNT:
+                ;We are not NT
+                ;Win9x doesn't have "admin" users.
+                ;Let the user do whatever.
+                Pop $3
+                Pop $2
+                Pop $1
+                Pop $0
+
+                Push 1
+
+FunctionEnd
+
+Function StrRep
+
+  ;Written by dirtydingus 2003-02-20 04:30:09
+  ; USAGE
+  ;Push String to do replacement in (haystack)
+  ;Push String to replace (needle)
+  ;Push Replacement
+  ;Call StrRep
+  ;Pop $R0 result
+  ;StrCpy $Result STR $R0
+
+  Exch $R4 ; $R4 = Replacement String
+  Exch
+  Exch $R3 ; $R3 = String to replace (needle)
+  Exch 2
+  Exch $R1 ; $R1 = String to do replacement in (haystack)
+  Push $R2 ; Replaced haystack
+  Push $R5 ; Len (needle)
+  Push $R6 ; len (haystack)
+  Push $R7 ; Scratch reg
+  StrCpy $R2 ""
+  StrLen $R5 $R3
+  StrLen $R6 $R1
+loop:
+  StrCpy $R7 $R1 $R5
+  StrCmp $R7 $R3 found
+  StrCpy $R7 $R1 1 ; - optimization can be removed if U know len needle=1
+  StrCpy $R2 "$R2$R7"
+  StrCpy $R1 $R1 $R6 1
+  StrCmp $R1 "" done loop
+found:
+  StrCpy $R2 "$R2$R4"
+  StrCpy $R1 $R1 $R6 $R5
+  StrCmp $R1 "" done loop
+done:
+  StrCpy $R3 $R2
+  Pop $R7
+  Pop $R6
+  Pop $R5
+  Pop $R2
+  Pop $R1
+  Pop $R4
+  Exch $R3
+
+FunctionEnd

+ 18 - 22
makepanda/makepanda.py

@@ -949,7 +949,7 @@ def CompileCxx(obj,src,opts):
             if PkgSkip("TOUCHINPUT") == 0:
                 cmd += "/DWINVER=0x601 "
             cmd += "/Fo" + obj + " /nologo /c"
-            if (GetTargetArch() != 'x64' and PkgSkip("SSE2") == 0):
+            if GetTargetArch() != 'x64' and (not PkgSkip("SSE2") or 'SSE2' in opts):
                 cmd += " /arch:SSE2"
             for x in ipath: cmd += " /I" + x
             for (opt,dir) in INCDIRECTORIES:
@@ -1160,7 +1160,7 @@ def CompileCxx(obj,src,opts):
                 if optlevel >= 4 or GetTarget() == "android":
                     cmd += " -fno-rtti"
 
-        if PkgSkip("SSE2") == 0 and not arch.startswith("arm"):
+        if ('SSE2' in opts or not PkgSkip("SSE2")) and not arch.startswith("arm"):
             cmd += " -msse2"
 
         if optlevel >= 3:
@@ -1705,7 +1705,7 @@ def RunGenPyCode(target, inputs, opts):
     if (PkgSkip("PYTHON") != 0):
         return
 
-    cmdstr = sys.executable + " "
+    cmdstr = BracketNameWithQuotes(SDK["PYTHONEXEC"]) + " "
     if sys.version_info >= (2, 6):
         cmdstr += "-B "
 
@@ -1729,7 +1729,7 @@ def RunGenPyCode(target, inputs, opts):
 def FreezePy(target, inputs, opts):
     assert len(inputs) > 0
     # Make sure this function isn't called before genpycode is run.
-    cmdstr = sys.executable + " "
+    cmdstr = BracketNameWithQuotes(SDK["PYTHONEXEC"]) + " "
     if sys.version_info >= (2, 6):
         cmdstr += "-B "
 
@@ -1757,7 +1757,7 @@ def FreezePy(target, inputs, opts):
 def Package(target, inputs, opts):
     assert len(inputs) == 1
     # Invoke the ppackage script.
-    command = sys.executable + " "
+    command = BracketNameWithQuotes(SDK["PYTHONEXEC"]) + " "
     if GetOptimizeOption(opts) >= 4:
         command += "-OO "
 
@@ -3192,6 +3192,7 @@ if (not RUNTIME):
   OPTS=['DIR:panda/src/pnmimage', 'BUILDING:PANDA',  'ZLIB']
   TargetAdd('p3pnmimage_composite1.obj', opts=OPTS, input='p3pnmimage_composite1.cxx')
   TargetAdd('p3pnmimage_composite2.obj', opts=OPTS, input='p3pnmimage_composite2.cxx')
+  TargetAdd('p3pnmimage_convert_srgb_sse2.obj', opts=OPTS+['SSE2'], input='convert_srgb_sse2.cxx')
 
   OPTS=['DIR:panda/src/pnmimage', 'ZLIB']
   IGATEFILES=GetDirectoryContents('panda/src/pnmimage', ["*.h", "*_composite*.cxx"])
@@ -3621,6 +3622,7 @@ if (not RUNTIME):
   TargetAdd('libpanda.dll', input='p3pnmimagetypes_composite2.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_composite1.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_composite2.obj')
+  TargetAdd('libpanda.dll', input='p3pnmimage_convert_srgb_sse2.obj')
   TargetAdd('libpanda.dll', input='p3text_composite1.obj')
   TargetAdd('libpanda.dll', input='p3text_composite2.obj')
   TargetAdd('libpanda.dll', input='p3tform_composite1.obj')
@@ -4609,7 +4611,7 @@ if (PkgSkip("DIRECT")==0):
   OPTS=['DIR:direct/src/dcparser', 'WITHINPANDA', 'BUILDING:DIRECT', 'BISONPREFIX_dcyy']
   CreateFile(GetOutputDir()+"/include/dcParser.h")
   TargetAdd('p3dcparser_dcParser.obj', opts=OPTS, input='dcParser.yxx')
-  TargetAdd('dcParser.h', input='p3egg_parser.obj', opts=['DEPENDENCYONLY'])
+  TargetAdd('dcParser.h', input='p3dcparser_dcParser.obj', opts=['DEPENDENCYONLY'])
   TargetAdd('p3dcparser_dcLexer.obj', opts=OPTS, input='dcLexer.lxx')
   TargetAdd('p3dcparser_composite1.obj', opts=OPTS, input='p3dcparser_composite1.cxx')
   TargetAdd('p3dcparser_composite2.obj', opts=OPTS, input='p3dcparser_composite2.cxx')
@@ -6171,7 +6173,7 @@ except:
 #
 ##########################################################################################
 
-def MakeInstallerNSIS(file, fullname, smdirectory, installdir):
+def MakeInstallerNSIS(file, title, installdir):
     if (os.path.isfile(file)):
         os.remove(file)
     elif (os.path.isdir(file)):
@@ -6199,7 +6201,7 @@ def MakeInstallerNSIS(file, fullname, smdirectory, installdir):
         shutil.move("direct\\src\\plugin_installer\\p3d-setup.exe", file)
         return
 
-    print("Building "+fullname+" installer. This can take up to an hour.")
+    print("Building "+title+" installer. This can take up to an hour.")
     if (COMPRESSOR != "lzma"):
         print("Note: you are using zlib, which is faster, but lzma gives better compression.")
     if (os.path.exists("nsis-output.exe")):
@@ -6210,19 +6212,13 @@ def MakeInstallerNSIS(file, fullname, smdirectory, installdir):
 
     nsis_defs = {
         'COMPRESSOR'  : COMPRESSOR,
-        'NAME'        : fullname,
-        'SMDIRECTORY' : smdirectory,
+        'TITLE'       : title,
         'INSTALLDIR'  : installdir,
         'OUTFILE'     : os.path.join(psource, 'nsis-output.exe'),
         'LICENSE'     : os.path.join(panda, 'LICENSE'),
-        'LANGUAGE'    : "English",
-        'RUNTEXT'     : "Visit the Panda Manual",
-        'IBITMAP'     : "panda-install.bmp",
-        'UBITMAP'     : "panda-install.bmp",
-        'PANDA'       : panda,
+        'BUILT'       : panda,
+        'SOURCE'      : psource,
         'PYVER'       : SDK["PYTHONVERSION"][6:9],
-        'PANDACONF'   : os.path.join(panda, 'etc'),
-        'PSOURCE'     : psource,
         'PYEXTRAS'    : os.path.join(os.path.abspath(GetThirdpartyBase()), 'win-extras'),
         'REGVIEW'     : regview,
     }
@@ -6236,7 +6232,7 @@ def MakeInstallerNSIS(file, fullname, smdirectory, installdir):
         for item in nsis_defs.items():
             cmd += ' -D%s="%s"' % item
 
-    cmd += ' "%s"' % (os.path.join(psource, 'direct', 'src', 'directscripts', 'packpanda.nsi'))
+    cmd += ' "%s"' % (os.path.join(psource, 'makepanda', 'installer.nsi'))
     oscmd(cmd)
     os.rename("nsis-output.exe", file)
 
@@ -6761,14 +6757,14 @@ try:
             if (GetOptimize() <= 2): dbg = "-dbg"
             if GetTargetArch() == 'x64':
                 if (RUNTIME):
-                    MakeInstallerNSIS("Panda3D-Runtime-"+VERSION+dbg+"-x64.exe", "Panda3D", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION+"-x64")
+                    MakeInstallerNSIS("Panda3D-Runtime-"+VERSION+dbg+"-x64.exe", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION+"-x64")
                 else:
-                    MakeInstallerNSIS("Panda3D-"+VERSION+dbg+"-x64.exe", "Panda3D", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION+"-x64")
+                    MakeInstallerNSIS("Panda3D-"+VERSION+dbg+"-x64.exe", "Panda3D SDK "+VERSION, "C:\\Panda3D-"+VERSION+"-x64")
             else:
                 if (RUNTIME):
-                    MakeInstallerNSIS("Panda3D-Runtime-"+VERSION+dbg+".exe", "Panda3D", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION)
+                    MakeInstallerNSIS("Panda3D-Runtime-"+VERSION+dbg+".exe", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION)
                 else:
-                    MakeInstallerNSIS("Panda3D-"+VERSION+dbg+".exe", "Panda3D", "Panda3D "+VERSION, "C:\\Panda3D-"+VERSION)
+                    MakeInstallerNSIS("Panda3D-"+VERSION+dbg+".exe", "Panda3D SDK "+VERSION, "C:\\Panda3D-"+VERSION)
         elif (target == 'linux'):
             MakeInstallerLinux()
         elif (target == 'darwin'):

+ 5 - 1
makepanda/makepandacore.py

@@ -210,6 +210,8 @@ def PrettyTime(t):
     return "%d sec" % (seconds)
 
 def ProgressOutput(progress, msg, target = None):
+    sys.stdout.flush()
+    sys.stderr.flush()
     prefix = ""
     thisthread = threading.currentThread()
     if thisthread is MAINTHREAD:
@@ -235,6 +237,8 @@ def ProgressOutput(progress, msg, target = None):
         suffix = GetColor()
 
     print(''.join((prefix, msg, suffix)))
+    sys.stdout.flush()
+    sys.stderr.flush()
 
 def exit(msg = ""):
     sys.stdout.flush()
@@ -498,7 +502,7 @@ def oscmd(cmd, ignoreError = False):
         print(GetColor("blue") + cmd.split(" ", 1)[0] + " " + GetColor("magenta") + cmd.split(" ", 1)[1] + GetColor())
     sys.stdout.flush()
 
-    if sys.platform == "win32":
+    if sys.platform in ("win32", "cygwin"):
         exe = cmd.split()[0]
         exe_path = LocateBinary(exe)
         if exe_path is None:

+ 0 - 0
direct/src/directscripts/panda-install.bmp → makepanda/panda-install.bmp


+ 1 - 1
panda/src/bullet/bulletAllHitsRayResult.cxx

@@ -156,7 +156,7 @@ get_hit_fraction() const {
 //       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
-PandaNode *BulletRayHit::
+const PandaNode *BulletRayHit::
 get_node() const {
 
   return (_object) ? (PandaNode *)_object->getUserPointer() : NULL;

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

@@ -33,7 +33,7 @@ struct EXPCL_PANDABULLET BulletRayHit {
 PUBLISHED:
   INLINE static BulletRayHit empty();
 
-  PandaNode *get_node() const;
+  const PandaNode *get_node() const;
   LPoint3 get_hit_pos() const;
   LVector3 get_hit_normal() const;
   PN_stdfloat get_hit_fraction() const;

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

@@ -608,11 +608,12 @@ remove_vehicle(BulletVehicle *vehicle) {
 ////////////////////////////////////////////////////////////////////
 //     Function: BulletWorld::attach_constraint
 //       Access: Published
-//  Description: Deprecated!
-//               Please use BulletWorld::attach
+//  Description: Attaches a single constraint to a world. Collision
+//               checks between the linked objects will be disabled
+//               if the second parameter is set to TRUE.
 ////////////////////////////////////////////////////////////////////
 void BulletWorld::
-attach_constraint(BulletConstraint *constraint) {
+attach_constraint(BulletConstraint *constraint, bool linked_collision) {
 
   nassertv(constraint);
 
@@ -622,7 +623,7 @@ attach_constraint(BulletConstraint *constraint) {
 
   if (found == _constraints.end()) {
     _constraints.push_back(constraint);
-    _world->addConstraint(constraint->ptr());
+    _world->addConstraint(constraint->ptr(), linked_collision);
   }
   else {
     bullet_cat.warning() << "constraint already attached" << endl;

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

@@ -70,6 +70,8 @@ PUBLISHED:
 
   // Attach/Remove
   void attach(TypedObject *object);
+  void attach_constraint(BulletConstraint *constraint, bool linked_collision=false);
+
   void remove(TypedObject *object);
 
   // Ghost object
@@ -172,7 +174,6 @@ PUBLISHED: // Deprecated methods, will become private soon
   void attach_character(BulletBaseCharacterControllerNode *node);
   void remove_character(BulletBaseCharacterControllerNode *node);
 
-  void attach_constraint(BulletConstraint *constraint);
   void remove_constraint(BulletConstraint *constraint);
 
 public:

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

@@ -144,15 +144,19 @@ test_intersection(const CollisionEntry &entry) const {
 ////////////////////////////////////////////////////////////////////
 void CollisionBox::
 xform(const LMatrix4 &mat) {
+  _min = _min * mat;
+  _max = _max * mat;
   _center = _center * mat;
-  for(int v = 0; v < 8; v++)
+  for(int v = 0; v < 8; v++) {
     _vertex[v] = _vertex[v] * mat;
-  for(int p = 0; p < 6 ; p++)
+  }
+  for(int p = 0; p < 6 ; p++) {
     _planes[p] = set_plane(p);
-  _x = _vertex[0].get_x()-_center.get_x(); 
-  _y = _vertex[0].get_y()-_center.get_y();
-  _z = _vertex[0].get_z()-_center.get_z();
-  _radius = sqrt( _x*_x + _y*_y + _z*_z );
+  }
+  _x = _vertex[0].get_x() - _center.get_x();
+  _y = _vertex[0].get_y() - _center.get_y();
+  _z = _vertex[0].get_z() - _center.get_z();
+  _radius = sqrt(_x * _x + _y * _y + _z * _z);
   setup_box();
   mark_viz_stale();
   mark_internal_bounds_stale();

+ 45 - 24
panda/src/display/graphicsStateGuardian.cxx

@@ -1407,22 +1407,29 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     static const CPT_InternalName IN_quadraticAttenuation("quadraticAttenuation");
 
     if (attrib == IN_ambient) {
-#ifndef NDEBUG
       Light *light = np.node()->as_light();
       nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
-#endif
-      // Lights don't currently have an ambient color in Panda3D.
-      // We still have to support the attribute.
-      t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        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.
+        t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
+      }
       return &t;
 
     } else if (attrib == IN_diffuse) {
       Light *light = np.node()->as_light();
       nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
-
-      LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
-      t.set_row(3, c);
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no diffuse color.
+        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;
 
     } else if (attrib == IN_specular) {
@@ -1432,7 +1439,11 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       return &t;
 
     } else if (attrib == IN_position) {
-      if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no position.
+        t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+        return &t;
+      } else if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
         DirectionalLight *light;
         DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
 
@@ -1458,7 +1469,11 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       }
 
     } else if (attrib == IN_halfVector) {
-      if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no half-vector.
+        t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+        return &t;
+      } else if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
         DirectionalLight *light;
         DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
 
@@ -1490,19 +1505,25 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       }
 
     } else if (attrib == IN_spotDirection) {
-      LightLensNode *light;
-      DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
-      Lens *lens = light->get_lens();
-      nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
-
-      CPT(TransformState) transform =
-        get_scene()->get_cs_world_transform()->compose(
-          np.get_transform(_scene_setup->get_scene_root().get_parent()));
-
-      const LMatrix4 &light_mat = transform->get_mat();
-      LVector3 dir = lens->get_view_vector() * light_mat;
-      t.set_row(3, dir);
-      return &t;
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no spot direction.
+        t.set_row(3, LVector3(0.0f, 0.0f, 0.0f));
+        return &t;
+      } else {
+        LightLensNode *light;
+        DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
+        Lens *lens = light->get_lens();
+        nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
+
+        CPT(TransformState) transform =
+          get_scene()->get_cs_world_transform()->compose(
+            np.get_transform(_scene_setup->get_scene_root().get_parent()));
+
+        const LMatrix4 &light_mat = transform->get_mat();
+        LVector3 dir = lens->get_view_vector() * light_mat;
+        t.set_row(3, dir);
+        return &t;
+      }
 
     } else if (attrib == IN_spotCutoff) {
       if (np.node()->is_of_type(Spotlight::get_class_type())) {

+ 8 - 3
panda/src/doc/eggSyntax.txt

@@ -1476,6 +1476,10 @@ GROUPING ENTRIES
     its geometry is used to define the extent of the collision
     surface (unless the "descend" flag is given; see below).
 
+    It is now deprecated to use <Collide> without "descend"; it will
+    become the default soon.  You should always specify it for best
+    compatibility.
+
     Valid types so far are:
 
     Plane
@@ -1538,11 +1542,12 @@ GROUPING ENTRIES
 
     descend
 
-      Instead of creating only one collision object of the given type,
-      each group descended from this node that contains geometry will
+      Each group descended from this node that contains geometry will
       define a new collision object of the given type.  The event
       name, if any, will also be inherited from the top node and
-      shared among all the collision objects.
+      shared among all the collision objects.  This option will soon
+      be the default; it is suggested that it is always specified for
+      most compatibility.
 
     keep
  

+ 6 - 0
panda/src/downloader/httpDate.cxx

@@ -223,8 +223,14 @@ HTTPDate(const string &format) {
   if (_time != (time_t)-1) {
     // Unfortunately, mktime() assumes local time; convert this back
     // to GMT.
+#ifdef IS_FREEBSD
+    time_t now = time(NULL);
+    struct tm *tp = localtime(&now);
+    _time -= tp->tm_gmtoff;
+#else /* IS_FREEBSD */
     extern long int timezone;
     _time -= timezone;
+#endif /* IS_FREEBSD */
   }
 #endif  // __GNUC__
 }

+ 23 - 22
panda/src/egg2pg/eggLoader.cxx

@@ -1865,6 +1865,10 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
     node = new CollisionNode(egg_group->get_name());
 
     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()));
+
     if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
       // If we also specified to keep the geometry, continue the
       // traversal.  In this case, we create a new PandaNode to be the
@@ -1925,8 +1929,11 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
     pnode->set_pos(center);
     pnode->set_color(color);
     pnode->set_radius(radius);
+
+    pnode->xform(LCAST(PN_stdfloat, egg_group->get_vertex_to_node()));
+
     node = pnode;
-    
+
   } else if (egg_group->get_switch_flag()) {
     if (egg_group->get_switch_fps() != 0.0) {
       // Create a sequence node.
@@ -2790,7 +2797,6 @@ find_first_polygon(EggGroup *egg_group) {
 bool EggLoader::
 make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags, 
             LPoint3 &center, PN_stdfloat &radius, LColor &color) {
-  bool success=false;
   EggGroup *geom_group = find_collision_geometry(egg_group, flags);
   if (geom_group != (EggGroup *)NULL) {
     // Collect all of the vertices.
@@ -2822,15 +2828,12 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
       d_center /= (double)num_vertices;
       //egg2pg_cat.debug() << "make_sphere d_center: " << d_center << "\n";
 
-      LMatrix4d mat = egg_group->get_vertex_to_node();
-      d_center = d_center * mat;
-
       // And the furthest vertex determines the radius.
       double radius2 = 0.0;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vtx = (*vi);
         LPoint3d p3 = vtx->get_pos3();
-                LVector3d v = p3 * mat - d_center;
+                LVector3d v = p3 - d_center;
         radius2 = max(radius2, v.length_squared());
       }
 
@@ -2841,10 +2844,10 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
       vi = vertices.begin();
       EggVertex *clr_vtx = (*vi);
       color = clr_vtx->get_color();
-      success = true;
+      return true;
     }
   }
-  return success;
+  return false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2900,9 +2903,8 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
                  max(max_pd[2], pos[2]));
     }
 
-    LMatrix4d mat = egg_group->get_vertex_to_node();
-    min_p = LCAST(PN_stdfloat, min_pd * mat);
-    max_p = LCAST(PN_stdfloat, max_pd * mat);
+    min_p = LCAST(PN_stdfloat, min_pd);
+    max_p = LCAST(PN_stdfloat, max_pd);
     return (min_pd != max_pd);
   }
   return false;
@@ -2968,6 +2970,10 @@ make_collision_solids(EggGroup *start_group, EggGroup *egg_group,
         make_collision_solids(start_group, DCAST(EggGroup, *ci), cnode);
       }
     }
+  } else {
+    egg2pg_cat.warning()
+      << "Using <Collide> without 'descend' is deprecated.  'descend' "
+      << "will become the default in a future version of Panda3D.\n";
   }
 }
 
@@ -3170,7 +3176,6 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
     // also determine the centroid).  We compute this in node space.
     size_t num_vertices = vertices.size();
     if (num_vertices != 0) {
-      LMatrix4d mat = egg_group->get_vertex_to_node();
       pvector<LPoint3d> vpos;
       vpos.reserve(num_vertices);
       
@@ -3178,7 +3183,7 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
       pset<EggVertex *>::const_iterator vi;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vtx = (*vi);
-        LPoint3d pos = vtx->get_pos3() * mat;
+        const LPoint3d &pos = vtx->get_pos3();
         vpos.push_back(pos);
         center += pos;
       }
@@ -3420,20 +3425,18 @@ create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) {
       << "\n";
   }
 
-  LMatrix4d mat = egg_poly->get_vertex_to_node();
-
   pvector<LVertex> vertices;
   if (!egg_poly->empty()) {
     EggPolygon::const_iterator vi;
     vi = egg_poly->begin();
 
-    LVertexd vert = (*vi)->get_pos3() * mat;
+    LVertexd vert = (*vi)->get_pos3();
     vertices.push_back(LCAST(PN_stdfloat, vert));
 
     LVertexd last_vert = vert;
     ++vi;
     while (vi != egg_poly->end()) {
-      vert = (*vi)->get_pos3() * mat;
+      vert = (*vi)->get_pos3();
       if (!vert.almost_equal(last_vert)) {
         vertices.push_back(LCAST(PN_stdfloat, vert));
       }
@@ -3461,7 +3464,6 @@ void EggLoader::
 create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
                           EggGroup *parent_group,
                           EggGroup::CollideFlags flags) {
-  LMatrix4d mat = egg_poly->get_vertex_to_node();
 
   PT(EggGroup) group = new EggGroup;
 
@@ -3489,13 +3491,13 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
       EggPolygon::const_iterator vi;
       vi = poly->begin();
 
-      LVertexd vert = (*vi)->get_pos3() * mat;
+      LVertexd vert = (*vi)->get_pos3();
       vertices.push_back(LCAST(PN_stdfloat, vert));
 
       LVertexd last_vert = vert;
       ++vi;
       while (vi != poly->end()) {
-        vert = (*vi)->get_pos3() * mat;
+        vert = (*vi)->get_pos3();
         if (!vert.almost_equal(last_vert)) {
           vertices.push_back(LCAST(PN_stdfloat, vert));
         }
@@ -3513,12 +3515,11 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
       if (cspoly->is_valid()) {
         apply_collision_flags(cspoly, flags);
         cnode->add_solid(cspoly);
-      }        
+      }
     }
   }
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::create_collision_floor_mesh
 //       Access: Private

+ 49 - 2
panda/src/egg2pg/eggSaver.cxx

@@ -22,6 +22,7 @@
 #include "transformState.h"
 #include "colorScaleAttrib.h"
 #include "colorAttrib.h"
+#include "materialAttrib.h"
 #include "textureAttrib.h"
 #include "cullFaceAttrib.h"
 #include "transparencyAttrib.h"
@@ -347,7 +348,7 @@ convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, Charac
     CharacterJoint *character_joint = DCAST(CharacterJoint, bundleNode);
 
     LMatrix4 transformf;
-    character_joint->get_net_transform(transformf);
+    character_joint->get_transform(transformf);
     LMatrix4d transformd(LCAST(double, transformf));
     EggGroup *joint = new EggGroup(bundleNode->get_name());
     joint->add_matrix4(transformd);
@@ -577,6 +578,13 @@ convert_primitive(const GeomVertexData *vertex_data,
     }
   }
 
+  // Check for a material.
+  EggMaterial *egg_mat = (EggMaterial *)NULL;
+  const MaterialAttrib *ma = DCAST(MaterialAttrib, net_state->get_attrib(MaterialAttrib::get_class_type()));
+  if (ma != (const MaterialAttrib *)NULL) {
+    egg_mat = get_egg_material(ma->get_material());
+  }
+
   // Check for a texture.
   EggTexture *egg_tex = (EggTexture *)NULL;
   const TextureAttrib *ta = DCAST(TextureAttrib, net_state->get_attrib(TextureAttrib::get_class_type()));
@@ -703,11 +711,15 @@ convert_primitive(const GeomVertexData *vertex_data,
     // Huh, an unknown geometry type.
     return;
   }
-  
+
   for (int i = 0; i < num_primitives; ++i) {
     PT(EggPrimitive) egg_prim = (*make_func)();
 
     egg_parent->add_child(egg_prim);
+
+    if (egg_mat != (EggMaterial *)NULL) {
+      egg_prim->set_material(egg_mat);
+    }
     if (egg_tex != (EggTexture *)NULL) {
       egg_prim->set_texture(egg_tex);
     }
@@ -947,6 +959,41 @@ apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::get_egg_material
+//       Access: Private
+//  Description: Returns an EggMaterial pointer that corresponds to
+//               the indicated Material.
+////////////////////////////////////////////////////////////////////
+EggMaterial *EggSaver::
+get_egg_material(Material *mat) {
+  if (mat != (Material *)NULL) {
+    EggMaterial temp(mat->get_name());
+    if (mat->has_ambient()) {
+      temp.set_amb(mat->get_ambient());
+    }
+
+    if (mat->has_diffuse()) {
+      temp.set_diff(mat->get_diffuse());
+    }
+
+    if (mat->has_specular()) {
+      temp.set_spec(mat->get_specular());
+    }
+
+    if (mat->has_emission()) {
+      temp.set_emit(mat->get_emission());
+    }
+
+    temp.set_shininess(mat->get_shininess());
+    temp.set_local(mat->get_local());
+
+    return _materials.create_unique_material(temp, ~EggMaterial::E_mref_name);
+  }
+
+  return NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggSaver::get_egg_texture
 //       Access: Private

+ 2 - 1
panda/src/egg2pg/eggSaver.h

@@ -92,6 +92,7 @@ private:
   bool apply_tags(EggGroup *egg_group, PandaNode *node);
   bool apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag);
 
+  EggMaterial *get_egg_material(Material *tex);
   EggTexture *get_egg_texture(Texture *tex);
 
   static EggPrimitive *make_egg_polygon();
@@ -102,8 +103,8 @@ private:
   PT(EggData) _data;
 
   PT(EggVertexPool) _vpool;
-  EggTextureCollection _textures;
   EggMaterialCollection _materials;
+  EggTextureCollection _textures;
 };
 
 #include "eggSaver.I"

+ 7 - 7
panda/src/egldisplay/eglGraphicsBuffer.cxx

@@ -1,5 +1,5 @@
 // Filename: eglGraphicsBuffer.cxx
-// Created by:  pro-rsoft (13Jun09)
+// Created by:  rdb (13Jun09)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -28,7 +28,7 @@ TypeHandle eglGraphicsBuffer::_type_handle;
 //  Description:
 ////////////////////////////////////////////////////////////////////
 eglGraphicsBuffer::
-eglGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe, 
+eglGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
                   const string &name,
                   const FrameBufferProperties &fb_prop,
                   const WindowProperties &win_prop,
@@ -187,7 +187,7 @@ open_buffer() {
       _gsg = eglgsg;
     }
   }
-  
+
   if (eglgsg->_fbconfig == None) {
     // If we didn't use an fbconfig to create the GSG, we can't create
     // a PBuffer.
@@ -195,11 +195,11 @@ open_buffer() {
   }
 
   int attrib_list[] = {
-    EGL_WIDTH, _x_size,
-    EGL_HEIGHT, _y_size,
+    EGL_WIDTH, _size.get_x(),
+    EGL_HEIGHT, _size.get_y(),
     EGL_NONE
   };
-  
+
   _pbuffer = eglCreatePbufferSurface(eglgsg->_egl_display, eglgsg->_fbconfig, attrib_list);
 
   if (_pbuffer == EGL_NO_SURFACE) {
@@ -224,7 +224,7 @@ open_buffer() {
     return false;
   }
   _fb_properties = eglgsg->get_fb_properties();
-  
+
   _is_valid = true;
   return true;
 }

+ 1 - 1
panda/src/egldisplay/eglGraphicsBuffer.h

@@ -1,5 +1,5 @@
 // Filename: eglGraphicsBuffer.h
-// Created by:  pro-rsoft (13Jun09)
+// Created by:  rdb (13Jun09)
 //
 ////////////////////////////////////////////////////////////////////
 //

+ 7 - 7
panda/src/egldisplay/eglGraphicsPixmap.cxx

@@ -1,5 +1,5 @@
 // Filename: eglGraphicsPixmap.cxx
-// Created by:  pro-rsoft (13Jun09)
+// Created by:  rdb (13Jun09)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -29,7 +29,7 @@ TypeHandle eglGraphicsPixmap::_type_handle;
 //  Description:
 ////////////////////////////////////////////////////////////////////
 eglGraphicsPixmap::
-eglGraphicsPixmap(GraphicsEngine *engine, GraphicsPipe *pipe, 
+eglGraphicsPixmap(GraphicsEngine *engine, GraphicsPipe *pipe,
                   const string &name,
                   const FrameBufferProperties &fb_prop,
                   const WindowProperties &win_prop,
@@ -105,7 +105,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     }
     clear_cube_map_selection();
   }
-  
+
   _gsg->set_current_properties(&get_fb_properties());
   return _gsg->begin_frame(current_thread);
 }
@@ -195,7 +195,7 @@ open_buffer() {
       _gsg = eglgsg;
     }
   }
-  
+
   if (eglgsg->_fbconfig == None) {
     // If we didn't use an fbconfig to create the GSG, we can't create
     // a PBuffer.
@@ -221,8 +221,8 @@ open_buffer() {
     }
   }
 
-  _x_pixmap = XCreatePixmap(_display, _drawable, 
-                            _x_size, _y_size, visual_info->depth);
+  _x_pixmap = XCreatePixmap(_display, _drawable,
+                            _size.get_x(), _size.get_y(), visual_info->depth);
   if (_x_pixmap == None) {
     egldisplay_cat.error()
       << "Failed to create X pixmap.\n";
@@ -253,7 +253,7 @@ open_buffer() {
     return false;
   }
   _fb_properties = eglgsg->get_fb_properties();
-  
+
   _is_valid = true;
   return true;
 }

+ 2 - 2
panda/src/egldisplay/eglGraphicsPixmap.h

@@ -1,5 +1,5 @@
 // Filename: eglGraphicsPixmap.h
-// Created by:  pro-rsoft (13Jun09)
+// Created by:  rdb (13Jun09)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -29,7 +29,7 @@
 ////////////////////////////////////////////////////////////////////
 class eglGraphicsPixmap : public GraphicsBuffer {
 public:
-  eglGraphicsPixmap(GraphicsEngine *engine, GraphicsPipe *pipe, 
+  eglGraphicsPixmap(GraphicsEngine *engine, GraphicsPipe *pipe,
                     const string &name,
                     const FrameBufferProperties &fb_prop,
                     const WindowProperties &win_prop,

+ 27 - 0
panda/src/express/datagram.I

@@ -82,6 +82,33 @@ operator = (const Datagram &copy) {
   _stdfloat_double = copy._stdfloat_double;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Datagram::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Datagram::
+Datagram(Datagram &&from) NOEXCEPT :
+  _data(move(from._data)),
+  _stdfloat_double(from._stdfloat_double)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Datagram::Move Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void Datagram::
+operator = (Datagram &&from) NOEXCEPT {
+  _data = move(from._data);
+  _stdfloat_double = from._stdfloat_double;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Datagram::add_bool
 //       Access: Public

+ 5 - 0
panda/src/express/datagram.h

@@ -48,6 +48,11 @@ PUBLISHED:
   INLINE Datagram(const Datagram &copy);
   INLINE void operator = (const Datagram &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Datagram(Datagram &&from) NOEXCEPT;
+  INLINE void operator = (Datagram &&from) NOEXCEPT;
+#endif
+
   virtual ~Datagram();
 
   virtual void clear();

+ 90 - 0
panda/src/express/pointerToArray.I

@@ -77,6 +77,21 @@ PointerToArray(const PointerToArray<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArray<Element>::
+PointerToArray(PointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArray::begin
 //       Access: Public
@@ -682,6 +697,21 @@ operator = (const PointerToArray<Element> &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArray<Element> &PointerToArray<Element>::
+operator = (PointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((PointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArray::clear
 //       Access: Public
@@ -736,6 +766,36 @@ ConstPointerToArray(const ConstPointerToArray<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element>::
+ConstPointerToArray(PointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element>::
+ConstPointerToArray(ConstPointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ConstPointerToArray::begin
 //       Access: Public
@@ -1141,6 +1201,36 @@ operator = (const ConstPointerToArray<Element> &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element> &ConstPointerToArray<Element>::
+operator = (PointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((ConstPointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element> &ConstPointerToArray<Element>::
+operator = (ConstPointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((ConstPointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ConstPointerToArray::clear
 //       Access: Public

+ 23 - 0
panda/src/express/pointerToArray.h

@@ -151,6 +151,10 @@ public:
   INLINE PointerToArray(size_type n, const Element &value, TypeHandle type_handle = get_type_handle(Element));
   INLINE PointerToArray(const PointerToArray<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArray(PointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
 public:
   // Duplicating the interface of vector.  The following member
   // functions are all const, because they do not reassign the
@@ -231,6 +235,12 @@ public:
   operator = (ReferenceCountedVector<Element> *ptr);
   INLINE PointerToArray<Element> &
   operator = (const PointerToArray<Element> &copy);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArray<Element> &
+  operator = (PointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   INLINE void clear();
 
 private:
@@ -305,6 +315,11 @@ PUBLISHED:
   INLINE ConstPointerToArray(const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const ConstPointerToArray<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ConstPointerToArray(PointerToArray<Element> &&from) NOEXCEPT;
+  INLINE ConstPointerToArray(ConstPointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   // Duplicating the interface of vector.
 
   INLINE iterator begin() const;
@@ -355,6 +370,14 @@ PUBLISHED:
   operator = (const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray<Element> &
   operator = (const ConstPointerToArray<Element> &copy);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ConstPointerToArray<Element> &
+  operator = (PointerToArray<Element> &&from) NOEXCEPT;
+  INLINE ConstPointerToArray<Element> &
+  operator = (ConstPointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   INLINE void clear();
 
 private:

+ 25 - 11
panda/src/express/pointerToArrayBase.I

@@ -57,11 +57,11 @@ template<class Element>
 INLINE ReferenceCountedVector<Element>::
 ~ReferenceCountedVector() {
 }
- 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::size
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE TYPENAME ReferenceCountedVector<Element>::size_type ReferenceCountedVector<Element>::
@@ -72,7 +72,7 @@ size() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::insert
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE TYPENAME ReferenceCountedVector<Element>::iterator ReferenceCountedVector<Element>::
@@ -83,7 +83,7 @@ insert(iterator position, const Element &x) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::insert
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -94,7 +94,7 @@ insert(iterator position, size_type n, const Element &x) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::erase
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -105,7 +105,7 @@ erase(iterator position) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::erase
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -116,7 +116,7 @@ erase(iterator first, iterator last) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::pop_back
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -127,7 +127,7 @@ pop_back() {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::clear
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -138,7 +138,7 @@ clear() {
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Constructor
 //       Access: Protected
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::
@@ -150,7 +150,7 @@ PointerToArrayBase(ReferenceCountedVector<Element> *ptr) :
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Copy Constructor
 //       Access: Protected
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::
@@ -159,10 +159,24 @@ PointerToArrayBase(const PointerToArrayBase<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArrayBase::Move Constructor
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArrayBase<Element>::
+PointerToArrayBase(PointerToArrayBase<Element> &&from) NOEXCEPT :
+  PointerToBase<ReferenceCountedVector<Element> >(move(from))
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Destructor
 //       Access: Published
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::

+ 4 - 0
panda/src/express/pointerToArrayBase.h

@@ -82,6 +82,10 @@ protected:
   INLINE PointerToArrayBase(ReferenceCountedVector<Element> *ptr);
   INLINE PointerToArrayBase(const PointerToArrayBase<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArrayBase(PointerToArrayBase<Element> &&from) NOEXCEPT;
+#endif
+
 PUBLISHED:
   INLINE ~PointerToArrayBase();
 };

+ 41 - 23
panda/src/express/pointerToArray_ext.I

@@ -22,26 +22,33 @@
 //               Python buffer protocol.
 ////////////////////////////////////////////////////////////////////
 template<class Element>
-void Extension<PointerToArray<Element> >::
+INLINE void Extension<PointerToArray<Element> >::
 __init__(PyObject *self, PyObject *source) {
 #if PY_VERSION_HEX >= 0x02060000
   if (PyObject_CheckBuffer(source)) {
     // User passed a buffer object.
     Py_buffer view;
     if (PyObject_GetBuffer(source, &view, PyBUF_CONTIG_RO) == -1) {
-      PyErr_SetString(PyExc_TypeError, "PointerToArray constructor requires a contiguous buffer");
+      PyErr_SetString(PyExc_TypeError,
+                      "PointerToArray constructor requires a contiguous buffer");
       return;
     }
 
     if (view.itemsize != 1 && view.itemsize != sizeof(Element)) {
-      PyErr_SetString(PyExc_TypeError, "buffer.itemsize does not match PointerToArray element size");
+      PyErr_SetString(PyExc_TypeError,
+                      "buffer.itemsize does not match PointerToArray element size");
       return;
     }
 
-    int num_elements = view.len / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
+    if (view.len % sizeof(Element) != 0) {
+      PyErr_Format(PyExc_ValueError,
+                   "byte buffer is not a multiple of %zu bytes",
+                   sizeof(Element));
+      return;
+    }
 
     if (view.len > 0) {
+      this->_this->resize(view.len / sizeof(Element));
       memcpy(this->_this->p(), view.buf, view.len);
     }
 
@@ -52,21 +59,22 @@ __init__(PyObject *self, PyObject *source) {
 
   if (!PySequence_Check(source)) {
     // If passed with a non-sequence, this isn't the right constructor.
-    PyErr_SetString(PyExc_TypeError, "PointerToArray constructor requires a sequence or buffer object");
+    PyErr_SetString(PyExc_TypeError,
+                    "PointerToArray constructor requires a sequence or buffer object");
     return;
   }
 
   // If we were passed a Python string, then instead of storing it
   // character-at-a-time, just load the whole string as a data
-  // buffer.
+  // buffer.  Not sure if this case is still necessary - don't Python
+  // str/bytes objects export the buffer protocol, as above?
 #if PY_MAJOR_VERSION >= 3
   if (PyBytes_Check(source)) {
     int size = PyBytes_Size(source);
     if (size % sizeof(Element) != 0) {
-      ostringstream stream;
-      stream << "Buffer not a multiple of " << sizeof(Element) << " bytes";
-      string str = stream.str();
-      PyErr_SetString(PyExc_ValueError, str.c_str());
+      PyErr_Format(PyExc_ValueError,
+                   "bytes object is not a multiple of %zu bytes",
+                   sizeof(Element));
       return;
     }
 
@@ -85,10 +93,9 @@ __init__(PyObject *self, PyObject *source) {
   if (PyString_CheckExact(source)) {
     int size = PyString_Size(source);
     if (size % sizeof(Element) != 0) {
-      ostringstream stream;
-      stream << "Buffer not a multiple of " << sizeof(Element) << " bytes";
-      string str = stream.str();
-      PyErr_SetString(PyExc_ValueError, str.c_str());
+      PyErr_Format(PyExc_ValueError,
+                   "str object is not a multiple of %zu bytes",
+                   sizeof(Element));
       return;
     }
 
@@ -107,20 +114,29 @@ __init__(PyObject *self, PyObject *source) {
 
   // Now construct the internal list by copying the elements
   // one-at-a-time from Python.
+  PyObject *push_back = PyObject_GetAttrString(self, "push_back");
+  if (push_back == NULL) {
+    PyErr_BadArgument();
+    return;
+  }
+
+  // We need to initialize the this pointer before we can call push_back.
+  ((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
+
   int size = PySequence_Size(source);
   for (int i = 0; i < size; ++i) {
     PyObject *item = PySequence_GetItem(source, i);
     if (item == NULL) {
       return;
     }
-    PyObject *result = PyObject_CallMethod(self, (char *)"push_back", (char *)"O", item);
+    PyObject *result = PyObject_CallFunctionObjArgs(push_back, item, NULL);
     Py_DECREF(item);
     if (result == NULL) {
       // Unable to add item--probably it wasn't of the appropriate type.
-      ostringstream stream;
-      stream << "Element " << i << " in sequence passed to PointerToArray constructor could not be added";
-      string str = stream.str();
-      PyErr_SetString(PyExc_TypeError, str.c_str());
+      PyErr_Print();
+      PyErr_Format(PyExc_TypeError,
+                   "Element %d in sequence passed to PointerToArray "
+                   "constructor could not be added", i);
       return;
     }
     Py_DECREF(result);
@@ -161,7 +177,9 @@ __setitem__(size_t n, const Element &value) {
 template<class Element>
 INLINE void Extension<ConstPointerToArray<Element> >::
 __init__(PyObject *self, PyObject *source) {
-  new (this->_this) ConstPointerToArray<Element>(get_type_handle(Element));
+  PointerToArray<Element> array;
+  invoke_extension(&array).__init__(self, source);
+  *(this->_this) = MOVE(array);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -240,7 +258,7 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
 
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
-    ((const PointerToArray<Element> *) view->internal)->unref();
+    unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
 }
@@ -314,7 +332,7 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
 
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
-    ((const PointerToArray<Element> *) view->internal)->unref();
+    unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
 }

+ 20 - 0
panda/src/express/pointerToArray_ext.h

@@ -66,6 +66,26 @@ public:
 #endif
 };
 
+#ifdef _MSC_VER
+// Ugh... MSVC needs this because they still don't have a decent linker.
+#include "PTA_uchar.h"
+#include "PTA_ushort.h"
+#include "PTA_float.h"
+#include "PTA_double.h"
+#include "PTA_int.h"
+
+template class EXPORT_THIS Extension<PTA_uchar>;
+template class EXPORT_THIS Extension<PTA_ushort>;
+template class EXPORT_THIS Extension<PTA_float>;
+template class EXPORT_THIS Extension<PTA_double>;
+template class EXPORT_THIS Extension<PTA_int>;
+template class EXPORT_THIS Extension<CPTA_uchar>;
+template class EXPORT_THIS Extension<CPTA_ushort>;
+template class EXPORT_THIS Extension<CPTA_float>;
+template class EXPORT_THIS Extension<CPTA_double>;
+template class EXPORT_THIS Extension<CPTA_int>;
+#endif
+
 // This macro is used to map a data type to a format code
 // as used in the Python 'struct' and 'array' modules.
 #define get_format_code(type) _get_format_code((const type *)0)

+ 8 - 3
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -148,7 +148,11 @@ FfmpegAudioCursor(FfmpegAudio *src) :
   _can_seek = true;
   _can_seek_fast = true;
 
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+  _frame = av_frame_alloc();
+#else
   _frame = avcodec_alloc_frame();
+#endif
 
   _packet = new AVPacket;
   _buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / 2;
@@ -194,7 +198,9 @@ FfmpegAudioCursor::
 void FfmpegAudioCursor::
 cleanup() {
   if (_frame) {
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
+    av_frame_free(&_frame);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
     avcodec_free_frame(&_frame);
 #else
     av_free(&_frame);
@@ -313,8 +319,7 @@ reload_buffer() {
 #ifdef HAVE_SWRESAMPLE
         if (_resample_ctx) {
           // Resample the data to signed 16-bit sample format.
-          uint8_t* out[SWR_CH_MAX] = {(uint8_t*) _buffer, NULL};
-          bufsize = swr_convert(_resample_ctx, out, _buffer_size / 2, (const uint8_t**)_frame->extended_data, _frame->nb_samples);
+          bufsize = swr_convert(_resample_ctx, (uint8_t **)&_buffer, _buffer_size / 2, (const uint8_t**)_frame->extended_data, _frame->nb_samples);
           bufsize *= _audio_channels * 2;
         } else
 #endif

+ 5 - 0
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -94,8 +94,13 @@ init_from(FfmpegVideo *source) {
                                 PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
 #endif  // HAVE_SWSCALE
 
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+  _frame = av_frame_alloc();
+  _frame_out = av_frame_alloc();
+#else
   _frame = avcodec_alloc_frame();
   _frame_out = avcodec_alloc_frame();
+#endif
 
   if ((_frame == 0)||(_frame_out == 0)) {
     cleanup();

+ 39 - 5
panda/src/framework/windowFramework.cxx

@@ -798,15 +798,28 @@ next_anim_control() {
   if (_anim_controls_enabled) {
     destroy_anim_controls();
 
+    if (_anim_controls.get_num_anims() == 0) {
+      set_anim_controls(false);
+      return;
+    }
+
+    // Stop the active animation.
+    pause_button();
     ++_anim_index;
+
     if (_anim_index >= _anim_controls.get_num_anims()) {
       set_anim_controls(false);
+      _anim_controls.loop_all(true);
     } else {
       create_anim_controls();
+      play_button();
     }
   } else {
     _anim_index = 0;
     set_anim_controls(true);
+    if (_anim_controls.get_num_anims() > 0) {
+      play_button();
+    }
   }
 }
 
@@ -1451,6 +1464,23 @@ create_anim_controls() {
   AnimControl *control = _anim_controls.get_anim(_anim_index);
   nassertv(control != (AnimControl *)NULL);
 
+  if (control->get_num_frames() <= 1) {
+    // Don't show the controls when the animation has only 0 or 1 frames.
+    ostringstream text;
+    text << _anim_controls.get_anim_name(_anim_index);
+    text << " (" << control->get_num_frames() << " frame"
+         << ((control->get_num_frames() == 1) ? "" : "s") << ")";
+
+    PT(TextNode) label = new TextNode("label");
+    label->set_align(TextNode::A_center);
+    label->set_text(text.str());
+    NodePath tnp = _anim_controls_group.attach_new_node(label);
+    tnp.set_pos(0.0f, 0.0f, 0.07f);
+    tnp.set_scale(0.1f);
+
+    return;
+  }
+
   PT(TextNode) label = new TextNode("anim_name");
   label->set_align(TextNode::A_left);
   label->set_text(_anim_controls.get_anim_name(_anim_index));
@@ -1525,13 +1555,17 @@ update_anim_controls() {
   AnimControl *control = _anim_controls.get_anim(_anim_index);
   nassertv(control != (AnimControl *)NULL);
 
-  if (_anim_slider->is_button_down()) {
-    control->pose((int)(_anim_slider->get_value() + 0.5));
-  } else {
-    _anim_slider->set_value((PN_stdfloat)control->get_frame());
+  if (_anim_slider != NULL) {
+    if (_anim_slider->is_button_down()) {
+      control->pose((int)(_anim_slider->get_value() + 0.5));
+    } else {
+      _anim_slider->set_value((PN_stdfloat)control->get_frame());
+    }
   }
 
-  _frame_number->set_text(format_string(control->get_frame()));
+  if (_frame_number != NULL) {
+    _frame_number->set_text(format_string(control->get_frame()));
+  }
 
   control->set_play_rate(_play_rate_slider->get_value());
 }

+ 12 - 13
panda/src/gles2gsg/gles2gsg.h

@@ -57,7 +57,7 @@
 //  #include <GLES2/gl2ext.h>
 #endif
 
-#include "panda_esgl2ext.h" 
+#include "panda_esgl2ext.h"
 
 // This helps to keep the source clean of hundreds of #ifdefs.
 typedef char GLchar;
@@ -100,20 +100,19 @@ typedef char GLchar;
 #define GL_RGBA16F GL_RGBA16F_EXT
 #define GL_RGB32F GL_RGB32F_EXT
 #define GL_RGBA32F GL_RGBA32F_EXT
-#define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
-#define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
-#define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
-#define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
-#define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
-#define GL_FRAMEBUFFER_SRGB GL_FRAMEBUFFER_SRGB_EXT
 #define GL_SRGB GL_SRGB_EXT
 #define GL_SRGB_ALPHA GL_SRGB_ALPHA_EXT
-#define GL_SRGB8 GL_SRGB8_EXT
-#define GL_SRGB8_ALPHA GL_SRGB8_ALPHA_EXT
-#define GL_SLUMINANCE GL_SLUMINANCE_NV
-#define GL_SLUMINANCE_ALPHA GL_SLUMINANCE_ALPHA_NV
-#define GL_SLUMINANCE8 GL_SLUMINANCE8_NV
-#define GL_SLUMINANCE8_ALPHA GL_SLUMINANCE8_ALPHA_NV
+#define GL_SRGB8_ALPHA8 GL_SRGB8_ALPHA8_EXT
+#define GL_RGBA8 GL_RGBA8_OES
+#define GL_R8 GL_R8_EXT
+#define GL_RG8 GL_RG8_EXT
+#define GL_ALPHA8 GL_ALPHA8_OES
+#define GL_LUMINANCE8 GL_LUMINANCE8_OES
+#define GL_LUMINANCE8_ALPHA8 GL_LUMINANCE8_ALPHA8_EXT
+#define GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_EXT
+#define GL_R32F GL_R32F_EXT
+#define GL_RG32F GL_RG32F_EXT
+#define GL_RGB8 GL_RGB8_OES
 
 #undef SUPPORT_IMMEDIATE_MODE
 #define APIENTRY

+ 3 - 0
panda/src/glesgsg/glesgsg.h

@@ -115,6 +115,9 @@
 #define GL_RGBA16F GL_RGBA16F_EXT
 #define GL_RGB32F GL_RGB32F_EXT
 #define GL_RGBA32F GL_RGBA32F_EXT
+#define GL_ALPHA8 GL_ALPHA8_EXT
+#define GL_LUMINANCE8 GL_LUMINANCE8_EXT
+#define GL_LUMINANCE8_ALPHA8 GL_LUMINANCE8_ALPHA8_EXT
 
 #undef SUPPORT_IMMEDIATE_MODE
 #define APIENTRY

+ 4 - 11
panda/src/glstuff/glCgShaderContext_src.cxx

@@ -41,7 +41,6 @@ CLP(CgShaderContext)::
 CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext(s) {
   _glgsg = glgsg;
   _cg_program = 0;
-  _glsl_profile = false;
 
   nassertv(s->get_language() == Shader::SL_Cg);
 
@@ -63,10 +62,6 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte
     release_resources();
 
   } else {
-    if (cgGetProgramProfile(_cg_program) == CG_PROFILE_GLSLC) {
-      _glsl_profile = true;
-    }
-
     cgGLLoadProgram(_cg_program);
     CGerror error = cgGetError();
     if (error != CG_NO_ERROR) {
@@ -412,7 +407,7 @@ disable_shader_vertex_arrays() {
     CGparameter p = _cg_parameter_map[_shader->_var_spec[i]._id._seqno];
     if (p == 0) continue;
 
-    if (_glsl_profile && cgGetParameterBaseResource(p) == CG_ATTR0) {
+    if (cgGetParameterBaseResource(p) == CG_ATTR0) {
       int index = cgGetParameterResourceIndex(p);
       if (index >= 8) {
         _glgsg->_glClientActiveTexture(GL_TEXTURE0 + (index - 8));
@@ -501,11 +496,9 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) {
           num_values = GL_BGRA;
         }
 
-        // This is truly the most preposterous hack.  When using the GLSL
-        // profiles, cgGLSetParameterPointer relies on the the driver mapping
-        // standard attributes to fixed indices (and breaking the spec doing
-        // so), which only the NVIDIA drivers do.  Unbelievable.
-        if (_glsl_profile && cgGetParameterBaseResource(p) == CG_ATTR0) {
+        // cgGLSetParameterPointer is just stupidly bugged on every level.
+        // Sigh.  This seems to work on both NVIDIA and AMD cards now.
+        if (cgGetParameterBaseResource(p) == CG_ATTR0) {
           int index = cgGetParameterResourceIndex(p);
           switch (index) {
           case 0:  // gl_Vertex

+ 0 - 1
panda/src/glstuff/glCgShaderContext_src.h

@@ -52,7 +52,6 @@ public:
 
 private:
   CGprogram _cg_program;
-  bool _glsl_profile;
 
   pvector<CGparameter> _cg_parameter_map;
 

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

@@ -1674,7 +1674,7 @@ resolve_multisamples() {
                               GL_NEAREST);
   }
   // Now handle the other color buffers.
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   int next = GL_COLOR_ATTACHMENT1_EXT;
   if (_fb_properties.is_stereo()) {
     glReadBuffer(next);

+ 6 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.I

@@ -745,9 +745,12 @@ call_glTexParameterfv(GLenum target, GLenum pname, const LVecBase4 &value) {
 //       Access: Public
 //  Description: Convert index to gl light id
 ////////////////////////////////////////////////////////////////////
-INLINE GLenum CLP(GraphicsStateGuardian)::get_light_id(int index) const {
+INLINE GLenum CLP(GraphicsStateGuardian)::
+get_light_id(int index) const {
 #ifndef OPENGLES_2
   return GL_LIGHT0 + index;
+#else
+  return 0;
 #endif
 }
 
@@ -760,6 +763,8 @@ INLINE GLenum CLP(GraphicsStateGuardian)::
 get_clip_plane_id(int index) const {
 #ifndef OPENGLES_2
   return GL_CLIP_PLANE0 + index;
+#else
+  return 0;
 #endif
 }
 

+ 98 - 83
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -127,16 +127,16 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 // a fixed-function pipeline.
 // This default shader just outputs a red color, telling
 // the user that something went wrong.
-CPT(Shader::ShaderFile) default_shader_name = new Shader::ShaderFile("default-shader");
-CPT(Shader::ShaderFile) default_shader_body = new Shader::ShaderFile("\
-uniform mediump mat4 p3d_ModelViewProjectionMatrix;\
-attribute highp vec4 p3d_Vertex;\
-void main(void) {\
-  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\
-}\n",
-"void main(void) {\
-  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\
-}\n", "", "", "");
+static const string default_vshader =
+  "uniform mediump mat4 p3d_ModelViewProjectionMatrix;\n"
+  "attribute highp vec4 p3d_Vertex;\n"
+  "void main(void) {\n"
+  "  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\n"
+  "}\n";
+static const string default_fshader =
+  "void main(void) {\n"
+  "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+  "}\n";
 #endif
 
 
@@ -558,10 +558,13 @@ reset() {
   _glPrimitiveRestartIndex = NULL;
 
   if (gl_support_primitive_restart_index) {
-    if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_ES3_compatibility")) {
+    if ((is_at_least_gl_version(4, 3) || has_extension("GL_ARB_ES3_compatibility")) &&
+        _gl_renderer.substr(0, 7) != "Gallium") {
       // As long as we enable this, OpenGL will always use the highest possible index
       // for a numeric type as strip cut index, which coincides with our convention.
-      // This saves us a call to glPrimitiveRestartIndex.
+      // This saves us a call to glPrimitiveRestartIndex
+      // ... of course, though, the Gallium driver bugs out here.  See also:
+      // https://www.panda3d.org/forums/viewtopic.php?f=5&t=17512
       glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
       _supported_geom_rendering |= Geom::GR_strip_cut_index;
 
@@ -1370,7 +1373,7 @@ reset() {
   // if it failed to compile. This default shader just outputs
   // a red color, indicating that something went wrong.
   if (_default_shader == NULL) {
-    _default_shader = new Shader(default_shader_name, default_shader_body, Shader::SL_GLSL);
+    _default_shader = Shader::load(Shader::SL_GLSL, default_vshader, default_fshader);
   }
 #endif
 
@@ -1915,6 +1918,8 @@ reset() {
   }
 
   _supports_sampler_objects = false;
+
+#ifndef OPENGLES
   if (gl_support_sampler_objects &&
       ((is_at_least_gl_version(3, 3) || has_extension("GL_ARB_sampler_objects")))) {
     _glGenSamplers = (PFNGLGENSAMPLERSPROC) get_extension_func("glGenSamplers");
@@ -1935,6 +1940,7 @@ reset() {
       _supports_sampler_objects = true;
     }
   }
+#endif  // OPENGLES
 
   // Check availability of multi-bind functions.
   _supports_multi_bind = false;
@@ -1944,10 +1950,12 @@ reset() {
     _glBindImageTextures = (PFNGLBINDIMAGETEXTURESPROC)
       get_extension_func("glBindImageTextures");
 
+#ifndef OPENGLES
     if (_supports_sampler_objects) {
       _glBindSamplers = (PFNGLBINDSAMPLERSPROC)
         get_extension_func("glBindSamplers");
     }
+#endif  // OPENGLES
 
     if (_glBindTextures != NULL && _glBindImageTextures != NULL) {
       _supports_multi_bind = true;
@@ -2011,14 +2019,18 @@ reset() {
 
   _supports_stencil_wrap =
     has_extension("GL_EXT_stencil_wrap") || has_extension("GL_OES_stencil_wrap");
-  _supports_two_sided_stencil = has_extension("GL_EXT_stencil_two_side");
-  if (_supports_two_sided_stencil) {
+
+
+  _supports_two_sided_stencil = false;
+#ifndef OPENGLES
+  if (has_extension("GL_EXT_stencil_two_side")) {
     _glActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXTPROC)
       get_extension_func("glActiveStencilFaceEXT");
-  }
-  else {
+    _supports_two_sided_stencil = true;
+  } else {
     _glActiveStencilFaceEXT = 0;
   }
+#endif
 
 #ifndef OPENGLES
   // Some drivers expose one, some expose the other. ARB seems to be the newer one.
@@ -2618,9 +2630,11 @@ clear_before_callback() {
 
   // Clear the bound sampler object, so that we do not inadvertently
   // override the callback's desired sampler settings.
+#ifndef OPENGLES
   if (_supports_sampler_objects) {
     _glBindSampler(0, 0);
   }
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2761,7 +2775,7 @@ begin_frame(Thread *current_thread) {
   }*/
 #endif
 
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   if (_current_properties->get_srgb_color()) {
     glEnable(GL_FRAMEBUFFER_SRGB);
   }
@@ -2819,7 +2833,7 @@ void CLP(GraphicsStateGuardian)::
 end_frame(Thread *current_thread) {
   report_my_gl_errors();
 
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   if (_current_properties->get_srgb_color()) {
     glDisable(GL_FRAMEBUFFER_SRGB);
   }
@@ -4521,8 +4535,10 @@ prepare_sampler(const SamplerState &sampler) {
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 release_sampler(SamplerContext *sc) {
+#ifndef OPENGLES
   CLP(SamplerContext) *gsc = DCAST(CLP(SamplerContext), sc);
   delete gsc;
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -5205,7 +5221,7 @@ make_geom_munger(const RenderState *state, Thread *current_thread) {
 //               from the camera plane.  The point is assumed to be
 //               in the GSG's internal coordinate system.
 ////////////////////////////////////////////////////////////////////
-PN_stdfloat GLGraphicsStateGuardian::
+PN_stdfloat CLP(GraphicsStateGuardian)::
 compute_distance_to(const LPoint3 &point) const {
   return -point[2];
 }
@@ -7447,7 +7463,7 @@ get_external_image_format(Texture *tex) const {
       break;
 
     case Texture::CM_dxt1:
-#ifndef OPENGLES_1
+#ifndef OPENGLES
       if (format == Texture::F_srgb_alpha) {
         return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
       } else if (format == Texture::F_srgb) {
@@ -7484,13 +7500,13 @@ get_external_image_format(Texture *tex) const {
 
 #else
     case Texture::CM_pvr1_2bpp:
-#ifndef OPENGLES_1
+#ifndef OPENGLES
       if (format == Texture::F_srgb_alpha) {
         return GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT;
       } else if (format == Texture::F_srgb) {
         return GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT;
       } else
-#endif  // OPENGLES_1
+#endif  // OPENGLES
       if (Texture::has_alpha(format)) {
         return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
       } else {
@@ -7498,13 +7514,13 @@ get_external_image_format(Texture *tex) const {
       }
 
     case Texture::CM_pvr1_4bpp:
-#ifndef OPENGLES_1
+#ifndef OPENGLES
       if (format == Texture::F_srgb_alpha) {
         return GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT;
       } else if (format == Texture::F_srgb) {
         return GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT;
       } else
-#endif  // OPENGLES_1
+#endif  // OPENGLES
       if (Texture::has_alpha(format)) {
         return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
       } else {
@@ -7762,7 +7778,7 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
 #endif
 
     case Texture::CM_dxt1:
-#ifndef OPENGLES_1
+#ifndef OPENGLES
       if (format == Texture::F_srgb_alpha) {
         return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
       } else if (format == Texture::F_srgb) {
@@ -7798,13 +7814,6 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
       }
 #else
     case Texture::CM_pvr1_2bpp:
-#ifndef OPENGLES_1
-      if (format == Texture::F_srgb_alpha) {
-        return GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT;
-      } else if (format == Texture::F_srgb) {
-        return GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT;
-      } else
-#endif  // OPENGLES_1
       if (Texture::has_alpha(format)) {
         return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
       } else {
@@ -7812,13 +7821,6 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
       }
 
     case Texture::CM_pvr1_4bpp:
-#ifndef OPENGLES_1
-      if (format == Texture::F_srgb_alpha) {
-        return GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT;
-      } else if (format == Texture::F_srgb) {
-        return GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT;
-      } else
-#endif  // OPENGLES_1
       if (Texture::has_alpha(format)) {
         return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
       } else {
@@ -7843,7 +7845,7 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
 
   case Texture::F_depth_stencil:
     if (_supports_depth_stencil) {
-#ifndef OPENGLES_1
+#ifndef OPENGLES
       if (tex->get_component_type() == Texture::T_float) {
         return GL_DEPTH32F_STENCIL8;
       } else
@@ -7903,7 +7905,10 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
 #ifndef OPENGLES_1
     if (tex->get_component_type() == Texture::T_float) {
       return GL_RGBA16F;
-    } else if (tex->get_component_type() == Texture::T_unsigned_short) {
+    } else
+#endif
+#ifndef OPENGLES
+    if (tex->get_component_type() == Texture::T_unsigned_short) {
       return GL_RGBA16;
     } else
 #endif
@@ -7949,7 +7954,7 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
   case Texture::F_rgba12:
     return GL_RGBA12;
 #endif  // OPENGLES
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   case Texture::F_rgba16:
     if (tex->get_component_type() == Texture::T_float) {
       return GL_RGBA16F;
@@ -8040,26 +8045,36 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
     return force_sized ? GL_ALPHA8 : GL_ALPHA;
 
   case Texture::F_luminance:
+#ifndef OPENGLES
     if (tex->get_component_type() == Texture::T_float) {
       return GL_LUMINANCE16F_ARB;
     } else if (tex->get_component_type() == Texture::T_unsigned_short) {
       return GL_LUMINANCE16;
-    } else {
+    } else
+#endif  // OPENGLES
+    {
       return force_sized ? GL_LUMINANCE8 : GL_LUMINANCE;
     }
   case Texture::F_luminance_alpha:
   case Texture::F_luminance_alphamask:
+#ifndef OPENGLES
     if (tex->get_component_type() == Texture::T_float || tex->get_component_type() == Texture::T_unsigned_short) {
       return GL_LUMINANCE_ALPHA16F_ARB;
-    } else {
+    } else
+#endif  // OPENGLES
+    {
       return force_sized ? GL_LUMINANCE8_ALPHA8 : GL_LUMINANCE_ALPHA;
     }
 
 #ifndef OPENGLES_1
   case Texture::F_srgb:
+#ifndef OPENGLES
     return GL_SRGB8;
+#endif
   case Texture::F_srgb_alpha:
     return GL_SRGB8_ALPHA8;
+#endif
+#ifndef OPENGLES
   case Texture::F_sluminance:
     return GL_SLUMINANCE8;
   case Texture::F_sluminance_alpha:
@@ -9043,7 +9058,7 @@ set_state_and_transform(const RenderState *target,
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 free_pointers() {
-#ifdef HAVE_CG
+#if defined(HAVE_CG) && !defined(OPENGLES)
   if (_cg_context != 0) {
     cgDestroyContext(_cg_context);
     _cg_context = 0;
@@ -10081,6 +10096,7 @@ bool CLP(GraphicsStateGuardian)::
 apply_sampler(GLuint unit, const SamplerState &sampler, TextureContext *tc) {
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
+#ifndef OPENGLES
   if (_supports_sampler_objects) {
     // We support sampler objects.  Prepare the sampler object and
     // bind it to the indicated texture unit.
@@ -10097,7 +10113,9 @@ apply_sampler(GLuint unit, const SamplerState &sampler, TextureContext *tc) {
         << "bind " << unit << " " << sampler << "\n";
     }
 
-  } else {
+  } else
+#endif  // OPENGLES
+  {
     // We don't support sampler objects.  We'll have to bind the
     // texture and change the texture parameters if they don't match.
     if (gtc->_active_sampler != sampler) {
@@ -10678,6 +10696,7 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
           if (tex->has_clear_color()) {
             // The texture has a clear color, so we should fill this mipmap
             // level to a solid color.
+#ifndef OPENGLES
             if (_supports_clear_texture) {
               // We can do that with the convenient glClearTexImage function.
               string clear_data = tex->get_clear_data();
@@ -10685,12 +10704,13 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
               _glClearTexImage(gtc->_index, n - mipmap_bias, external_format,
                                component_type, (void *)clear_data.data());
               continue;
-            } else {
-              // Ask the Texture class to create the mipmap level in RAM.
-              // It'll fill it in with the correct clear color, which we
-              // can then upload.
-              ptimage = tex->make_ram_mipmap_image(n);
             }
+#endif  // OPENGLES
+            // Ask the Texture class to create the mipmap level in RAM.
+            // It'll fill it in with the correct clear color, which we
+            // can then upload.
+            ptimage = tex->make_ram_mipmap_image(n);
+
           } else {
             // No clear color and no more images.
             break;
@@ -10726,13 +10746,8 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
       _data_transferred_pcollector.add_level(view_size);
 #endif
       switch (texture_target) {
-#ifdef OPENGLES_2
-      case GL_TEXTURE_3D_OES:
-#endif
-#ifndef OPENGLES
-      case GL_TEXTURE_3D:
-#endif
 #ifndef OPENGLES_1
+      case GL_TEXTURE_3D:
         if (_supports_3d_texture) {
           if (image_compression == Texture::CM_off) {
             _glTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
@@ -10746,7 +10761,9 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
           return false;
         }
         break;
+#endif  // OPENGLES_1
 
+#ifndef OPENGLES
       case GL_TEXTURE_1D:
         if (image_compression == Texture::CM_off) {
           glTexSubImage1D(page_target, n - mipmap_bias, 0, width,
@@ -10756,7 +10773,8 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
                                      external_format, view_size, image_ptr);
         }
         break;
-#endif
+#endif  // OPENGLES
+
 #ifndef OPENGLES
       case GL_TEXTURE_2D_ARRAY_EXT:
         if (_supports_2d_texture_array) {
@@ -10772,7 +10790,8 @@ upload_texture_image(CLP(TextureContext) *gtc, bool needs_reload,
           return false;
         }
         break;
-#endif
+#endif  // OPENGLES
+
       default:
         if (image_compression == Texture::CM_off) {
           if (n==0) {
@@ -11319,7 +11338,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
     type = Texture::T_unsigned_int_24_8;
     format = Texture::F_depth_stencil;
     break;
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   case GL_DEPTH32F_STENCIL8:
     type = Texture::T_float;
     format = Texture::F_depth_stencil;
@@ -11483,26 +11502,34 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
     format = Texture::F_alpha;
     break;
   case GL_LUMINANCE:
+#ifndef OPENGLES
   case GL_LUMINANCE16:
   case GL_LUMINANCE16F_ARB:
+#endif
   case 1:
     format = Texture::F_luminance;
     break;
   case GL_LUMINANCE_ALPHA:
+#ifndef OPENGLES
   case GL_LUMINANCE_ALPHA16F_ARB:
+#endif
   case 2:
     format = Texture::F_luminance_alpha;
     break;
 
 #ifndef OPENGLES_1
   case GL_SRGB:
+#ifndef OPENGLES
   case GL_SRGB8:
+#endif
     format = Texture::F_srgb;
     break;
   case GL_SRGB_ALPHA:
   case GL_SRGB8_ALPHA8:
     format = Texture::F_srgb_alpha;
     break;
+#endif  // OPENGLES_1
+#ifndef OPENGLES
   case GL_SLUMINANCE:
   case GL_SLUMINANCE8:
     format = Texture::F_sluminance;
@@ -11511,7 +11538,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
   case GL_SLUMINANCE8_ALPHA8:
     format = Texture::F_sluminance_alpha;
     break;
-#endif
+#endif  // OPENGLES
 
 #ifndef OPENGLES
   case GL_COMPRESSED_RGB:
@@ -11561,7 +11588,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
     format = Texture::F_rgbm;
     compression = Texture::CM_dxt1;
     break;
-#ifndef OPENGLES_1
+#ifndef OPENGLES
   case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
     format = Texture::F_srgb;
     compression = Texture::CM_dxt1;
@@ -11572,25 +11599,6 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
     break;
 #endif
 
-#ifdef OPENGLES_2
-  case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
-    format = Texture::F_srgb;
-    compression = Texture::CM_pvr1_2bpp;
-    break;
-  case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
-    format = Texture::F_srgb_alpha;
-    compression = Texture::CM_pvr1_2bpp;
-    break;
-  case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:
-    format = Texture::F_srgb;
-    compression = Texture::CM_pvr1_4bpp;
-    break;
-  case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:
-    format = Texture::F_srgb_alpha;
-    compression = Texture::CM_pvr1_4bpp;
-    break;
-#endif  // OPENGLES_2
-
 #ifdef OPENGLES
   case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
     format = Texture::F_rgb;
@@ -11963,6 +11971,7 @@ do_issue_stencil() {
                     << "SRS_back_stencil_pass_z_pass_operation " << (int)stencil->get_render_state(StencilAttrib::SRS_back_stencil_pass_z_pass_operation) << "\n";
     }
 
+#ifndef OPENGLES
     if (_supports_two_sided_stencil) {
       //TODO: add support for OpenGL 2.0-style glStencilFuncSeparate.
       unsigned int back_compare;
@@ -11989,6 +11998,7 @@ do_issue_stencil() {
 
       _glActiveStencilFaceEXT(GL_FRONT);
     }
+#endif  // OPENGLES
 
     unsigned int front_compare;
     front_compare = stencil->get_render_state(StencilAttrib::SRS_front_comparison_function);
@@ -12018,9 +12028,11 @@ do_issue_stencil() {
     }
   } else {
     glDisable(GL_STENCIL_TEST);
+#ifndef OPENGLES
     if (_supports_two_sided_stencil) {
       glDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
     }
+#endif  // OPENGLES
   }
 }
 
@@ -12067,9 +12079,12 @@ do_issue_scissor() {
     if (_scissor_array.size() > 0) {
       // Scissoring is enabled on the display region.
       // Revert to the scissor state specified in the DisplayRegion.
+#ifndef OPENGLES
       if (_supports_viewport_arrays) {
         _glScissorArrayv(0, _scissor_array.size(), _scissor_array[0].get_data());
-      } else {
+      } else
+#endif  // OPENGLES
+      {
         const LVecBase4i sr = _scissor_array[0];
         glScissor(sr[0], sr[1], sr[2], sr[3]);
       }

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

@@ -14,6 +14,8 @@
 
 #include "pnotify.h"
 
+#ifndef OPENGLES
+
 TypeHandle CLP(SamplerContext)::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
@@ -81,3 +83,5 @@ reset_data() {
   // re-load the sampler later.
   //glGenSamplers(1, &_index);
 }
+
+#endif  // OPENGLES

+ 4 - 0
panda/src/glstuff/glSamplerContext_src.h

@@ -12,6 +12,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#ifndef OPENGLES
+
 #include "pandabase.h"
 #include "samplerContext.h"
 #include "deletedChain.h"
@@ -56,3 +58,5 @@ public:
 private:
   static TypeHandle _type_handle;
 };
+
+#endif  // OPENGLES

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

@@ -412,6 +412,20 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
             s->_mat_spec.push_back(bind);
             continue;
           }
+          if (noprefix == "Color") {
+            Shader::ShaderMatSpec bind;
+            bind._id = arg_id;
+            bind._piece = Shader::SMP_row3;
+            bind._func = Shader::SMF_first;
+            bind._part[0] = Shader::SMO_attr_color;
+            bind._arg[0] = NULL;
+            bind._dep[0] = Shader::SSD_general | Shader::SSD_color;
+            bind._part[1] = Shader::SMO_identity;
+            bind._arg[1] = NULL;
+            bind._dep[1] = Shader::SSD_NONE;
+            s->_mat_spec.push_back(bind);
+            continue;
+          }
           if (noprefix == "ClipPlane") {
             for (int i = 0; i < param_size; ++i) {
               Shader::ShaderMatSpec bind;
@@ -821,10 +835,12 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
                        param_type == GL_INT_VEC2 ||
                        param_type == GL_INT_VEC3 ||
                        param_type == GL_INT_VEC4 ||
-                       param_type == GL_UNSIGNED_INT ||
+#ifndef OPENGLES
                        param_type == GL_UNSIGNED_INT_VEC2 ||
                        param_type == GL_UNSIGNED_INT_VEC3 ||
-                       param_type == GL_UNSIGNED_INT_VEC4);
+                       param_type == GL_UNSIGNED_INT_VEC4 ||
+#endif
+                       param_type == GL_UNSIGNED_INT );
 
       if (noprefix.empty()) {
         // Arbitrarily named attribute.
@@ -863,12 +879,16 @@ CLP(ShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderContext
       // Get the number of bind points.
       switch (param_type) {
       case GL_FLOAT_MAT3:
+#ifndef OPENGLES
       case GL_DOUBLE_MAT3:
+#endif
         bind._elements = 3 * param_size;
         break;
 
       case GL_FLOAT_MAT4:
+#ifndef OPENGLES
       case GL_DOUBLE_MAT4:
+#endif
         bind._elements = 4 * param_size;
         break;
 

+ 2 - 0
panda/src/glstuff/glmisc_src.cxx

@@ -283,7 +283,9 @@ void CLP(init_classes)() {
   CLP(ShaderContext)::init_type();
 #endif
   CLP(TextureContext)::init_type();
+#ifndef OPENGLES
   CLP(SamplerContext)::init_type();
+#endif
   CLP(VertexBufferContext)::init_type();
   CLP(GraphicsBuffer)::init_type();
 

+ 1 - 1
panda/src/gobj/internalName_ext.cxx

@@ -36,7 +36,7 @@ make(PyUnicodeObject *str) {
     }
 
     string name(c_str, len);
-    return InternalName::make(c_str, len);
+    return InternalName::make(name);
   }
 
   InternalName::PyInternTable::const_iterator it;

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

@@ -18,6 +18,7 @@
 #include "pandabase.h"
 
 #include "adaptiveLru.h"
+#include "simpleLru.h"
 #include "samplerState.h"
 #include "savedContext.h"
 

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

@@ -21,6 +21,7 @@
 #include "namable.h"
 #include "luse.h"
 #include "numeric_types.h"
+#include "bamReader.h"
 #include "config_gobj.h"
 
 class FactoryParams;

+ 75 - 70
panda/src/gobj/texture.cxx

@@ -41,6 +41,7 @@
 #include "pbitops.h"
 #include "streamReader.h"
 #include "texturePeeker.h"
+#include "convert_srgb.h"
 
 #ifdef HAVE_SQUISH
 #include <squish.h>
@@ -131,46 +132,6 @@ struct DDSHeader {
   DDSCaps2 caps;
 };
 
-// This table is used for converting unsigned char texture values in an sRGB
-// texture to linear RGB values, for use in mipmap generation.
-static float srgb_to_lrgbf[256] = {0.000000f, 0.000304f, 0.000607f, 0.000911f,
-  0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
-  0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f,
-  0.006049f, 0.006512f, 0.006995f, 0.007499f, 0.008023f, 0.008568f, 0.009134f,
-  0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f,
-  0.014444f, 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f,
-  0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, 0.025187f, 0.026241f,
-  0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f,
-  0.035601f, 0.036889f, 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f,
-  0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, 0.054480f,
-  0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f,
-  0.068478f, 0.070360f, 0.072272f, 0.074214f, 0.076185f, 0.078187f, 0.080220f,
-  0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
-  0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f,
-  0.114435f, 0.116971f, 0.119538f, 0.122139f, 0.124772f, 0.127438f, 0.130136f,
-  0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f,
-  0.152926f, 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f,
-  0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, 0.191202f, 0.194618f,
-  0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f,
-  0.223228f, 0.226966f, 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f,
-  0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, 0.274677f,
-  0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f,
-  0.309469f, 0.313989f, 0.318547f, 0.323143f, 0.327778f, 0.332452f, 0.337164f,
-  0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
-  0.376262f, 0.381326f, 0.386429f, 0.391572f, 0.396755f, 0.401978f, 0.407240f,
-  0.412543f, 0.417885f, 0.423268f, 0.428690f, 0.434154f, 0.439657f, 0.445201f,
-  0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473531f, 0.479320f, 0.485150f,
-  0.491021f, 0.496933f, 0.502886f, 0.508881f, 0.514918f, 0.520996f, 0.527115f,
-  0.533276f, 0.539479f, 0.545724f, 0.552011f, 0.558340f, 0.564712f, 0.571125f,
-  0.577580f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f,
-  0.623960f, 0.630757f, 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f,
-  0.672443f, 0.679542f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, 0.715694f,
-  0.723055f, 0.730461f, 0.737910f, 0.745404f, 0.752942f, 0.760525f, 0.768151f,
-  0.775822f, 0.783538f, 0.791298f, 0.799103f, 0.806952f, 0.814847f, 0.822786f,
-  0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
-  0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f,
-  0.947307f, 0.955973f, 0.964686f, 0.973445f, 0.982251f, 0.991102f, 1.000000f};
-
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::Constructor
 //       Access: Published
@@ -6990,7 +6951,13 @@ do_filter_2d_mipmap_pages(const CData *cdata,
     // We currently only support sRGB mipmap generation for
     // unsigned byte textures, due to our use of a lookup table.
     nassertv(cdata->_component_type == T_unsigned_byte);
-    filter_component = &filter_2d_unsigned_byte_srgb;
+
+    if (has_sse2_sRGB_encode()) {
+      filter_component = &filter_2d_unsigned_byte_srgb_sse2;
+    } else {
+      filter_component = &filter_2d_unsigned_byte_srgb;
+    }
+
     // Alpha is always linear.
     filter_alpha = &filter_2d_unsigned_byte;
 
@@ -7140,7 +7107,13 @@ do_filter_3d_mipmap_level(const CData *cdata,
     // We currently only support sRGB mipmap generation for
     // unsigned byte textures, due to our use of a lookup table.
     nassertv(cdata->_component_type == T_unsigned_byte);
-    filter_component = &filter_3d_unsigned_byte_srgb;
+
+    if (has_sse2_sRGB_encode()) {
+      filter_component = &filter_3d_unsigned_byte_srgb_sse2;
+    } else {
+      filter_component = &filter_3d_unsigned_byte_srgb;
+    }
+
     // Alpha is always linear.
     filter_alpha = &filter_3d_unsigned_byte;
 
@@ -7385,18 +7358,32 @@ filter_2d_unsigned_byte(unsigned char *&p, const unsigned char *&q,
 void Texture::
 filter_2d_unsigned_byte_srgb(unsigned char *&p, const unsigned char *&q,
                              size_t pixel_size, size_t row_size) {
-  float result = (srgb_to_lrgbf[q[0]] +
-                  srgb_to_lrgbf[q[pixel_size]] +
-                  srgb_to_lrgbf[q[row_size]] +
-                  srgb_to_lrgbf[q[pixel_size + row_size]]) / 4.0f;
-
-  // This is based on the formula out of the EXT_texture_sRGB
-  // specification, except the factors are multiplied with 255.0f.
-  if (result < 0.0031308f) {
-    *p = (unsigned char)(result * 3294.6f);
-  } else {
-    *p = (unsigned char)(269.025f * powf(result, 0.41666f) - 14.025f);
-  }
+  float result = (decode_sRGB_float(q[0]) +
+                  decode_sRGB_float(q[pixel_size]) +
+                  decode_sRGB_float(q[row_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size]));
+
+  *p = encode_sRGB_uchar(result * 0.25f);
+  ++p;
+  ++q;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::filter_2d_unsigned_byte_srgb_sse2
+//       Access: Public, Static
+//  Description: Averages a 2x2 block of pixel components into a
+//               single pixel component, for producing the next mipmap
+//               level.  Increments p and q to the next component.
+////////////////////////////////////////////////////////////////////
+void Texture::
+filter_2d_unsigned_byte_srgb_sse2(unsigned char *&p, const unsigned char *&q,
+                                  size_t pixel_size, size_t row_size) {
+  float result = (decode_sRGB_float(q[0]) +
+                  decode_sRGB_float(q[pixel_size]) +
+                  decode_sRGB_float(q[row_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size]));
+
+  *p = encode_sRGB_uchar_sse2(result * 0.25f);
   ++p;
   ++q;
 }
@@ -7470,22 +7457,40 @@ filter_3d_unsigned_byte(unsigned char *&p, const unsigned char *&q,
 void Texture::
 filter_3d_unsigned_byte_srgb(unsigned char *&p, const unsigned char *&q,
                              size_t pixel_size, size_t row_size, size_t page_size) {
-  float result = (srgb_to_lrgbf[q[0]] +
-                  srgb_to_lrgbf[q[pixel_size]] +
-                  srgb_to_lrgbf[q[row_size]] +
-                  srgb_to_lrgbf[q[pixel_size + row_size]] +
-                  srgb_to_lrgbf[q[page_size]] +
-                  srgb_to_lrgbf[q[pixel_size + page_size]] +
-                  srgb_to_lrgbf[q[row_size + page_size]] +
-                  srgb_to_lrgbf[q[pixel_size + row_size + page_size]]) / 8.0f;
-
-  // This is based on the formula out of the EXT_texture_sRGB
-  // specification, except the factors are multiplied with 255.0f.
-  if (result < 0.0031308f) {
-    *p = (unsigned char)(result * 3294.6f);
-  } else {
-    *p = (unsigned char)(269.025f * powf(result, 0.41666f) - 14.025f);
-  }
+  float result = (decode_sRGB_float(q[0]) +
+                  decode_sRGB_float(q[pixel_size]) +
+                  decode_sRGB_float(q[row_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size]) +
+                  decode_sRGB_float(q[page_size]) +
+                  decode_sRGB_float(q[pixel_size + page_size]) +
+                  decode_sRGB_float(q[row_size + page_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size + page_size]));
+
+  *p = encode_sRGB_uchar(result * 0.125f);
+  ++p;
+  ++q;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::filter_3d_unsigned_byte_srgb_sse2
+//       Access: Public, Static
+//  Description: Averages a 2x2x2 block of pixel components into a
+//               single pixel component, for producing the next mipmap
+//               level.  Increments p and q to the next component.
+////////////////////////////////////////////////////////////////////
+void Texture::
+filter_3d_unsigned_byte_srgb_sse2(unsigned char *&p, const unsigned char *&q,
+                                  size_t pixel_size, size_t row_size, size_t page_size) {
+  float result = (decode_sRGB_float(q[0]) +
+                  decode_sRGB_float(q[pixel_size]) +
+                  decode_sRGB_float(q[row_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size]) +
+                  decode_sRGB_float(q[page_size]) +
+                  decode_sRGB_float(q[pixel_size + page_size]) +
+                  decode_sRGB_float(q[row_size + page_size]) +
+                  decode_sRGB_float(q[pixel_size + row_size + page_size]));
+
+  *p = encode_sRGB_uchar_sse2(result * 0.125f);
   ++p;
   ++q;
 }

+ 7 - 0
panda/src/gobj/texture.h

@@ -749,6 +749,9 @@ private:
   static void filter_2d_unsigned_byte_srgb(unsigned char *&p,
                                            const unsigned char *&q,
                                            size_t pixel_size, size_t row_size);
+  static void filter_2d_unsigned_byte_srgb_sse2(unsigned char *&p,
+                                                const unsigned char *&q,
+                                                size_t pixel_size, size_t row_size);
   static void filter_2d_unsigned_short(unsigned char *&p,
                                        const unsigned char *&q,
                                        size_t pixel_size, size_t row_size);
@@ -763,6 +766,10 @@ private:
                                            const unsigned char *&q,
                                            size_t pixel_size, size_t row_size,
                                            size_t page_size);
+  static void filter_3d_unsigned_byte_srgb_sse2(unsigned char *&p,
+                                                const unsigned char *&q,
+                                                size_t pixel_size, size_t row_size,
+                                                size_t page_size);
   static void filter_3d_unsigned_short(unsigned char *&p,
                                        const unsigned char *&q,
                                        size_t pixel_size, size_t row_size,

+ 1 - 1
panda/src/linmath/lmatrix3_src.cxx

@@ -353,7 +353,7 @@ bool FLOATNAME(LMatrix3)::
 almost_equal(const FLOATNAME(LMatrix3) &other, FLOATTYPE threshold) const {
   TAU_PROFILE("bool LMatrix3::almost_equal(const LMatrix3 &, FLOATTYPE)", " ", TAU_USER);
 #ifdef HAVE_EIGEN
-  return ((_m - other._m).cwiseAbs().maxCoeff() < NEARLY_ZERO(FLOATTYPE));
+  return ((_m - other._m).cwiseAbs().maxCoeff() < threshold);
 #else
   return (IS_THRESHOLD_EQUAL((*this)(0, 0), other(0, 0), threshold) &&
           IS_THRESHOLD_EQUAL((*this)(0, 1), other(0, 1), threshold) &&

+ 1 - 1
panda/src/linmath/lmatrix4_src.cxx

@@ -296,7 +296,7 @@ bool FLOATNAME(LMatrix4)::
 almost_equal(const FLOATNAME(LMatrix4) &other, FLOATTYPE threshold) const {
   TAU_PROFILE("bool LMatrix4::almost_equal(const LMatrix4 &, FLOATTYPE)", " ", TAU_USER);
 #ifdef HAVE_EIGEN
-  return ((_m - other._m).cwiseAbs().maxCoeff() < NEARLY_ZERO(FLOATTYPE));
+  return ((_m - other._m).cwiseAbs().maxCoeff() < threshold);
 #else
   return (IS_THRESHOLD_EQUAL((*this)(0, 0), other(0, 0), threshold) &&
           IS_THRESHOLD_EQUAL((*this)(0, 1), other(0, 1), threshold) &&

+ 28 - 0
panda/src/mathutil/pta_LMatrix3_ext.h

@@ -0,0 +1,28 @@
+// Filename: pta_LMatrix3_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PTA_LMATRIX3_EXT_H
+#define PTA_LMATRIX3_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LMatrix3.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LMatrix3f>;
+template class EXPORT_THIS Extension<PTA_LMatrix3d>;
+template class EXPORT_THIS Extension<CPTA_LMatrix3f>;
+template class EXPORT_THIS Extension<CPTA_LMatrix3d>;
+#endif
+
+#endif

+ 28 - 0
panda/src/mathutil/pta_LMatrix4_ext.h

@@ -0,0 +1,28 @@
+// Filename: pta_LMatrix4_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PTA_LMATRIX4_EXT_H
+#define PTA_LMATRIX4_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LMatrix4.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LMatrix4f>;
+template class EXPORT_THIS Extension<PTA_LMatrix4d>;
+template class EXPORT_THIS Extension<CPTA_LMatrix4f>;
+template class EXPORT_THIS Extension<CPTA_LMatrix4d>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase2_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase2_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PTA_LVECBASE2_EXT_H
+#define PTA_LVECBASE2_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase2.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase2f>;
+template class EXPORT_THIS Extension<PTA_LVecBase2d>;
+template class EXPORT_THIS Extension<PTA_LVecBase2i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2i>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase3_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase3_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PTA_LVECBASE3_EXT_H
+#define PTA_LVECBASE3_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase3.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase3f>;
+template class EXPORT_THIS Extension<PTA_LVecBase3d>;
+template class EXPORT_THIS Extension<PTA_LVecBase3i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3i>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase4_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase4_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PTA_LVECBASE4_EXT_H
+#define PTA_LVECBASE4_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase4.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase4f>;
+template class EXPORT_THIS Extension<PTA_LVecBase4d>;
+template class EXPORT_THIS Extension<PTA_LVecBase4i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4i>;
+#endif
+
+#endif

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -79,6 +79,7 @@
     occluderNode.I occluderNode.h \
     pandaNode.I pandaNode.h \
     pandaNodeChain.I pandaNodeChain.h \
+    paramNodePath.I paramNodePath.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
     polylightNode.I polylightNode.h \
@@ -184,6 +185,7 @@
     occluderNode.cxx \
     pandaNode.cxx \
     pandaNodeChain.cxx \
+    paramNodePath.cxx \
     planeNode.cxx \
     polylightEffect.cxx \
     polylightNode.cxx \
@@ -286,6 +288,7 @@
     pandaNode.I pandaNode.h \
     pandaNode_ext.h pandaNode_ext.cxx \
     pandaNodeChain.I pandaNodeChain.h \
+    paramNodePath.I paramNodePath.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
     polylightNode.I polylightNode.h \

+ 2 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -60,6 +60,7 @@
 #include "nodePath.h"
 #include "nodePathComponent.h"
 #include "pandaNode.h"
+#include "paramNodePath.h"
 #include "planeNode.h"
 #include "polylightEffect.h"
 #include "polylightNode.h"
@@ -436,6 +437,7 @@ init_libpgraph() {
   NodePathComponent::init_type();
   PandaNode::init_type();
   PandaNodePipelineReader::init_type();
+  ParamNodePath::init_type();
   PlaneNode::init_type();
   PolylightNode::init_type();
   PolylightEffect::init_type();

+ 1 - 0
panda/src/pgraph/cullBinManager.h

@@ -20,6 +20,7 @@
 #include "cullBinEnums.h"
 #include "pointerTo.h"
 #include "pvector.h"
+#include "epvector.h"
 #include "pmap.h"
 #include "vector_int.h"
 #include "pStatCollector.h"

+ 4 - 4
panda/src/pgraph/loader.cxx

@@ -343,11 +343,11 @@ try_load_file(const Filename &pathname, const LoaderOptions &options,
         return result;
       }
     }
-  }
 
-  if (loader_cat.is_debug()) {
-    loader_cat.debug()
-      << "Model " << pathname << " not found in cache.\n";
+    if (loader_cat.is_debug()) {
+      loader_cat.debug()
+        << "Model " << pathname << " not found in cache.\n";
+    }
   }
 
   bool cache_only = (options.get_flags() & LoaderOptions::LF_cache_only) != 0;

+ 3 - 3
panda/src/pgraph/nodePath.h

@@ -38,6 +38,9 @@
 #include "pta_LVecBase3.h"
 #include "pta_LVecBase2.h"
 #include "stl_compares.h"
+#include "shaderInput.h"
+#include "textureCollection.h"
+#include "textureStageCollection.h"
 
 class NodePathCollection;
 class FindApproxPath;
@@ -1008,9 +1011,6 @@ private:
 
 INLINE ostream &operator << (ostream &out, const NodePath &node_path);
 
-// We have to put this down here, to work around a circular include.
-#include "shaderInput.h"
-
 #include "nodePath.I"
 
 #endif

+ 1 - 0
panda/src/pgraph/p3pgraph_composite3.cxx

@@ -21,6 +21,7 @@
 #include "occluderNode.cxx"
 #include "pandaNode.cxx"
 #include "pandaNodeChain.cxx"
+#include "paramNodePath.cxx"
 #include "planeNode.cxx"
 #include "polylightEffect.cxx"
 #include "polylightNode.cxx"

+ 7 - 5
panda/src/pgraph/pandaNode.I

@@ -721,13 +721,15 @@ get_user_bounds(int pipeline_stage, Thread *current_thread) const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PandaNode::
 mark_bounds_stale(int pipeline_stage, Thread *current_thread) const {
-  // It's important that we don't hold the lock during the call to
-  // force_bounds_stale().
+  // We check whether it is already marked stale.  If so, we don't have
+  // to make the call to force_bounds_stale().
   bool is_stale_bounds;
   {
     CDStageReader cdata(_cycler, pipeline_stage, current_thread);
-    is_stale_bounds = (cdata->_last_bounds_update != cdata->_next_update);
+    is_stale_bounds = (cdata->_last_update != cdata->_next_update);
   }
+  // It's important that we don't hold the lock during the call to
+  // force_bounds_stale().
   if (!is_stale_bounds) {
     ((PandaNode *)this)->force_bounds_stale(pipeline_stage, current_thread);
   }
@@ -1833,7 +1835,7 @@ get_off_clip_planes() const {
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(BoundingVolume) PandaNodePipelineReader::
 get_bounds() const {
-  nassertr(_cdata->_last_update == _cdata->_next_update, _cdata->_external_bounds);
+  nassertr(_cdata->_last_bounds_update == _cdata->_next_update, _cdata->_external_bounds);
   return _cdata->_external_bounds;
 }
 
@@ -1852,7 +1854,7 @@ get_bounds() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int PandaNodePipelineReader::
 get_nested_vertices() const {
-  nassertr(_cdata->_last_update == _cdata->_next_update, _cdata->_nested_vertices);
+  nassertr(_cdata->_last_bounds_update == _cdata->_next_update, _cdata->_nested_vertices);
   return _cdata->_nested_vertices;
 }
 

+ 60 - 0
panda/src/pgraph/paramNodePath.I

@@ -0,0 +1,60 @@
+// Filename: paramNodePath.I
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::Constructor
+//       Access: Published
+//  Description: Creates a new ParamNodePath storing the given
+//               node path object.
+////////////////////////////////////////////////////////////////////
+INLINE ParamNodePath::
+ParamNodePath(const NodePath &node_path) :
+  _node_path(node_path)
+{
+}
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::Move Constructor
+//       Access: Published
+//  Description: Creates a new ParamNodePath storing the given
+//               node path object.
+////////////////////////////////////////////////////////////////////
+INLINE ParamNodePath::
+ParamNodePath(NodePath &&node_path) NOEXCEPT :
+  _node_path(move(node_path))
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::get_value_type
+//       Access: Published, Virtual
+//  Description: Returns NodePath::get_class_type().
+////////////////////////////////////////////////////////////////////
+INLINE TypeHandle ParamNodePath::
+get_value_type() const {
+  return NodePath::get_class_type();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::get_value
+//       Access: Published
+//  Description: Retrieves the NodePath stored in the parameter.
+////////////////////////////////////////////////////////////////////
+INLINE const NodePath &ParamNodePath::
+get_value() const {
+  return _node_path;
+}

+ 108 - 0
panda/src/pgraph/paramNodePath.cxx

@@ -0,0 +1,108 @@
+// Filename: paramNodePath.cxx
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "paramNodePath.h"
+#include "dcast.h"
+#include "pandaNode.h"
+
+TypeHandle ParamNodePath::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::output
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+output(ostream &out) const {
+  out << "node path " << _node_path;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               ParamValue.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  ParamValueBase::write_datagram(manager, dg);
+
+  // We can't store a NodePath, so we store a pointer to the
+  // underlying node, and pray that there is an unambiguous path
+  // from the root to it.
+  if (_node_path.is_empty()) {
+    manager->write_pointer(dg, NULL);
+  } else {
+    manager->write_pointer(dg, _node_path.node());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::complete_pointers
+//       Access: Public, Virtual
+//  Description: Receives an array of pointers, one for each time
+//               manager->read_pointer() was called in fillin().
+//               Returns the number of pointers processed.
+////////////////////////////////////////////////////////////////////
+int ParamNodePath::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = ParamValueBase::complete_pointers(p_list, manager);
+  _node_path = NodePath(DCAST(PandaNode, p_list[pi++]));
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type ParamValue is encountered
+//               in the Bam file.  It should create the ParamValue
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *ParamNodePath::
+make_from_bam(const FactoryParams &params) {
+  ParamNodePath *param = new ParamNodePath;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  param->fillin(scan, manager);
+
+  return param;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new ParamValue.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  ParamValueBase::fillin(scan, manager);
+  manager->read_pointer(scan);
+}

+ 75 - 0
panda/src/pgraph/paramNodePath.h

@@ -0,0 +1,75 @@
+// Filename: paramNodePath.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PARAMNODEPATH_H
+#define PARAMNODEPATH_H
+
+#include "pandabase.h"
+#include "paramValue.h"
+#include "nodePath.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ParamNodePath
+// Description : A class object for storing a NodePath as a parameter.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_GOBJ ParamNodePath : public ParamValueBase {
+protected:
+  INLINE ParamNodePath() {};
+
+PUBLISHED:
+  INLINE ParamNodePath(const NodePath &node_path);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ParamNodePath(NodePath &&node_path) NOEXCEPT;
+#endif
+
+  INLINE virtual TypeHandle get_value_type() const;
+  INLINE const NodePath &get_value() const;
+
+  virtual void output(ostream &out) const;
+
+private:
+  NodePath _node_path;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual int complete_pointers(TypedWritable **plist,
+                                BamReader *manager);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ParamValueBase::init_type();
+    register_type(_type_handle, "ParamNodePath",
+                  ParamValueBase::get_class_type());
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "paramNodePath.I"
+
+#endif

+ 2 - 3
panda/src/pgraph/polylightNode.I

@@ -85,11 +85,10 @@ disable(){
 //  Description: Set this light's position
 ////////////////////////////////////////////////////////////////////
 INLINE void PolylightNode::
-set_pos(const LVecBase3 &position){
+set_pos(const LPoint3 &position) {
   _position = position;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PolylightNode::set_pos
 //       Access: Published
@@ -107,7 +106,7 @@ set_pos(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z){
 //       Access: Published
 //  Description: Returns position as a LPoint3
 ////////////////////////////////////////////////////////////////////
-INLINE LVecBase3 PolylightNode::
+INLINE LPoint3 PolylightNode::
 get_pos() const {
   return _position;
 }

+ 24 - 0
panda/src/pgraph/polylightNode.cxx

@@ -65,6 +65,30 @@ make_copy() const {
   return new PolylightNode(*this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PolylightNode::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void PolylightNode::
+xform(const LMatrix4 &mat) {
+  nassertv(!mat.is_nan());
+
+  if (mat.almost_equal(LMatrix4::ident_mat())) {
+    return;
+  }
+
+  _position = _position * mat;
+
+  // This is a little cheesy and fails miserably in the presence of a
+  // non-uniform scale.
+  LVector3 radius_v = LVector3(_radius, 0.0f, 0.0f) * mat;
+  _radius = length(radius_v);
+  mark_internal_bounds_stale();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PolylightNode::Constructor
 //       Access: Public

+ 4 - 4
panda/src/pgraph/polylightNode.h

@@ -58,9 +58,9 @@ PUBLISHED:
   PolylightNode(const string &name);
   INLINE void enable();
   INLINE void disable();
-  INLINE void set_pos(const LVecBase3 &position);
+  INLINE void set_pos(const LPoint3 &position);
   INLINE void set_pos(PN_stdfloat x,PN_stdfloat y, PN_stdfloat z);
-  INLINE LVecBase3 get_pos() const;
+  INLINE LPoint3 get_pos() const;
   INLINE void set_color(const LColor &color);
   INLINE void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b);
   INLINE LColor get_color() const;
@@ -100,11 +100,11 @@ PUBLISHED:
 public:
   LColor flicker() const;
   virtual PandaNode *make_copy() const;
+  virtual void xform(const LMatrix4 &mat);
 
-  
 private:
   bool _enabled;
-  LVecBase3 _position;
+  LPoint3 _position;
   LColor _color;
   PN_stdfloat _radius;
   Attenuation_Type _attenuation_type;

+ 0 - 27
panda/src/pgraph/shaderInput.I

@@ -119,23 +119,6 @@ ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority) :
 {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: ShaderInput::Constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE ShaderInput::
-ShaderInput(CPT_InternalName name, const NodePath &np, int priority) :
-  _name(MOVE(name)),
-  _type(M_nodepath),
-  _priority(priority),
-  _stored_nodepath(np),
-  _bind_layer(0),
-  _bind_level(0),
-  _access(A_read)
-{
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::Constructor
 //       Access: Published
@@ -678,16 +661,6 @@ get_texture() const {
   return DCAST(Texture, _value);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: ShaderInput::get_nodepath
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE const NodePath &ShaderInput::
-get_nodepath() const {
-  return _stored_nodepath;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::get_vector
 //       Access: Published

+ 30 - 2
panda/src/pgraph/shaderInput.cxx

@@ -13,6 +13,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "shaderInput.h"
+#include "paramNodePath.h"
 
 TypeHandle ShaderInput::_type_handle;
 
@@ -32,13 +33,40 @@ get_blank() {
   return blank;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ShaderInput::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+ShaderInput::
+ShaderInput(CPT_InternalName name, const NodePath &np, int priority) :
+  _name(MOVE(name)),
+  _type(M_nodepath),
+  _priority(priority),
+  _value(new ParamNodePath(np)),
+  _bind_layer(0),
+  _bind_level(0),
+  _access(A_read)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ShaderInput::get_nodepath
+//       Access: Published
+//  Description: Warning: no error checking is done.  This *will*
+//               crash if get_value_type() is not M_nodepath.
+////////////////////////////////////////////////////////////////////
+const NodePath &ShaderInput::
+get_nodepath() const {
+  return DCAST(ParamNodePath, _value)->get_value();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::register_with_read_factory
 //       Access: Public, Static
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 void ShaderInput::
 register_with_read_factory() {
   // IMPLEMENT ME
 }
-

+ 4 - 4
panda/src/pgraph/shaderInput.h

@@ -19,7 +19,6 @@
 #include "pandabase.h"
 #include "typedWritableReferenceCount.h"
 #include "pointerTo.h"
-#include "nodePath.h"
 #include "internalName.h"
 #include "paramValue.h"
 #include "pta_float.h"
@@ -53,7 +52,6 @@ PUBLISHED:
 
   static const ShaderInput *get_blank();
   INLINE ShaderInput(CPT_InternalName name, int priority=0);
-  INLINE ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, const SamplerState &sampler, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
@@ -90,6 +88,8 @@ PUBLISHED:
   INLINE ShaderInput(CPT_InternalName name, const LVecBase3i &vec, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const LVecBase2i &vec, int priority=0);
 
+  ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
+
   enum ShaderInputType {
     M_invalid = 0,
     M_texture,
@@ -105,11 +105,12 @@ PUBLISHED:
   INLINE int get_value_type() const;
   INLINE int get_priority() const;
   INLINE Texture *get_texture() const;
-  INLINE const NodePath &get_nodepath() const;
   INLINE const LVecBase4 &get_vector() const;
   INLINE const Shader::ShaderPtrData &get_ptr() const;
   INLINE const SamplerState &get_sampler() const;
 
+  const NodePath &get_nodepath() const;
+
 public:
   INLINE ParamValueBase *get_param() const;
 
@@ -118,7 +119,6 @@ public:
 private:
   SamplerState _sampler;
   LVecBase4 _stored_vector;
-  NodePath _stored_nodepath;
   Shader::ShaderPtrData _stored_ptr;
   CPT_InternalName _name;
   PT(TypedWritableReferenceCount) _value;

+ 1 - 0
panda/src/pgraph/shaderPool.cxx

@@ -18,6 +18,7 @@
 #include "virtualFileSystem.h"
 #include "loader.h"
 #include "shader.h"
+#include "string_utils.h"
 
 ShaderPool *ShaderPool::_global_ptr = (ShaderPool *)NULL;
 

+ 175 - 0
panda/src/pnmimage/convert_srgb.I

@@ -0,0 +1,175 @@
+// Filename: convert_srgb.I
+// Created by:  rdb (29Oct14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: decode_sRGB_float
+//  Description: Decodes the sRGB-encoded unsigned char value to
+//               a linearized float in the range 0-1.
+////////////////////////////////////////////////////////////////////
+CONSTEXPR float decode_sRGB_float(unsigned char val) {
+  return to_linear_float_table[val];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_float
+//  Description: Decodes the sRGB-encoded floating-point value in
+//               the range 0-1 to a linearized float in the range
+//               0-1.  Inputs outside this range produce invalid
+//               results.
+////////////////////////////////////////////////////////////////////
+INLINE float decode_sRGB_float(float val) {
+  return (val <= 0.04045f)
+    ? (val * (1.f / 12.92f))
+    : cpow((val + 0.055f) * (1.f / 1.055f), 2.4f);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: decode_sRGB_uchar
+//  Description: Decodes the sRGB-encoded unsigned char value to
+//               a linearized unsigned char value.
+////////////////////////////////////////////////////////////////////
+CONSTEXPR unsigned char decode_sRGB_uchar(unsigned char val) {
+  return to_linear_uchar_table[val];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: decode_sRGB_uchar
+//  Description: Decodes the sRGB-encoded floating-point value in
+//               the range 0-1 to a linearized unsigned char value.
+//               Inputs outside this range are clamped.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned char decode_sRGB_uchar(float val) {
+  return (val <= 0.04045f)
+    ? (unsigned char)(max(0.f, val) * (255.f / 12.92f) + 0.5f)
+    : (unsigned char)(cpow((min(val, 1.f) + 0.055f) * (1.f / 1.055f), 2.4f) * 255.f + 0.5f);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_float
+//  Description: Encodes the linearized unsigned char value to an
+//               sRGB-encoded floating-point value in ther range 0-1.
+////////////////////////////////////////////////////////////////////
+INLINE float
+encode_sRGB_float(unsigned char val) {
+  // This seems like a very unlikely use case, so I didn't bother
+  // making a look-up table for this.
+  return (val == 0) ? 0
+    : (1.055f * cpow((float)val * (1.f / 255.f), 0.41666f) - 0.055);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_float
+//  Description: Encodes the linearized floating-point value in the
+//               range 0-1 to an sRGB-encoded float in the range
+//               0-1.  Inputs outside this range produce invalid
+//               results.
+////////////////////////////////////////////////////////////////////
+INLINE float
+encode_sRGB_float(float val) {
+  return (val < 0.0031308f)
+    ? (val * 12.92f)
+    : (1.055f * cpow(val, 0.41666f) - 0.055);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_uchar
+//  Description: Encodes the linearized unsigned char value to an
+//               sRGB-encoded unsigned char value.
+////////////////////////////////////////////////////////////////////
+CONSTEXPR unsigned char
+encode_sRGB_uchar(unsigned char val) {
+  return to_srgb8_table[val];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_uchar
+//  Description: Encodes the linearized floating-point value in the
+//               range 0-1 to an sRGB-encoded unsigned char value.
+//               Inputs outside this range are clamped.
+//
+//               When SSE2 support is known at compile time, this
+//               automatically uses an optimized version.  Otherwise,
+//               it does not attempt runtime CPU detection.  If you
+//               know that SSE2 is supported (ie. if the function
+//               has_sse2_sRGB_encode() returns true) you should
+//               call encode_sRGB_uchar_sse2 instead.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned char
+encode_sRGB_uchar(float val) {
+#if defined(__SSE2__) || (_M_IX86_FP >= 2) || defined(_M_X64) || defined(_M_AMD64)
+  // Use a highly optimized approximation that has more than enough
+  // accuracy for an unsigned char.
+  return encode_sRGB_uchar_sse2(val);
+#else
+  return (val < 0.0031308f)
+    ? (unsigned char) (max(0.f, val) * 3294.6f + 0.5f)
+    : (unsigned char) (269.025f * cpow(min(val, 1.f), 0.41666f) - 13.525f);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_uchar
+//  Description: Encodes the linearized floating-point color value
+//               an sRGB-encoded xel in the range 0-255.
+//
+//               When SSE2 support is known at compile time, this
+//               automatically uses an optimized version.  Otherwise,
+//               it does not attempt runtime CPU detection.  If you
+//               know that SSE2 is supported (ie. if the function
+//               has_sse2_sRGB_encode() returns true) you should
+//               call encode_sRGB_uchar_sse2 instead.
+////////////////////////////////////////////////////////////////////
+INLINE void
+encode_sRGB_uchar(const LColorf &color, xel &into) {
+#if defined(__SSE2__) || (_M_IX86_FP >= 2) || defined(_M_X64) || defined(_M_AMD64)
+  // SSE2 support compiled-in; we're guaranteed to have it.
+  encode_sRGB_uchar_sse2(color, into);
+#else
+  // Boring, slow, non-SSE2 version.
+  PPM_ASSIGN(into,
+    encode_sRGB_uchar(color[0]),
+    encode_sRGB_uchar(color[1]),
+    encode_sRGB_uchar(color[2]));
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: encode_sRGB_uchar
+//  Description: Encodes the linearized floating-point color value
+//               an sRGB-encoded xel and alpha in the range 0-255.
+//               The alpha value is not sRGB-encoded.
+//
+//               When SSE2 support is known at compile time, this
+//               automatically uses an optimized version.  Otherwise,
+//               it does not attempt runtime CPU detection.  If you
+//               know that SSE2 is supported (ie. if the function
+//               has_sse2_sRGB_encode() returns true) you should
+//               call encode_sRGB_uchar_sse2 instead.
+////////////////////////////////////////////////////////////////////
+INLINE void
+encode_sRGB_uchar(const LColorf &color, xel &into, xelval &into_alpha) {
+#if defined(__SSE2__) || (_M_IX86_FP >= 2) || defined(_M_X64) || defined(_M_AMD64)
+  // SSE2 support compiled-in; we're guaranteed to have it.
+  encode_sRGB_uchar_sse2(color, into, into_alpha);
+#else
+  // Boring, slow, non-SSE2 version.
+  PPM_ASSIGN(into,
+    encode_sRGB_uchar(color[0]),
+    encode_sRGB_uchar(color[1]),
+    encode_sRGB_uchar(color[2]));
+
+  into_alpha = (xelval) (color[3] * 255.f + 0.5f);
+#endif
+}

+ 165 - 0
panda/src/pnmimage/convert_srgb.cxx

@@ -0,0 +1,165 @@
+// Filename: convert_srgb.cxx
+// Created by:  rdb (13Nov14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "convert_srgb.h"
+
+#ifdef __GNUC__
+#include <cpuid.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <windows.h>
+#endif
+
+// Lookup tables for converting from unsigned char formats.
+ALIGN_64BYTE const
+unsigned char to_srgb8_table[256] = { 0x00, 0x0d, 0x16, 0x1c, 0x22, 0x26, 0x2a,
+  0x2e, 0x32, 0x35, 0x38, 0x3b, 0x3d, 0x40, 0x42, 0x45, 0x47, 0x49, 0x4b, 0x4d,
+  0x4f, 0x51, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5d, 0x5f, 0x60, 0x62, 0x63,
+  0x65, 0x66, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x70, 0x71, 0x72, 0x73, 0x75,
+  0x76, 0x77, 0x78, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+  0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90,
+  0x91, 0x92, 0x93, 0x94, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9b,
+  0x9c, 0x9d, 0x9e, 0x9f, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa3, 0xa4, 0xa5, 0xa6,
+  0xa7, 0xa7, 0xa8, 0xa9, 0xaa, 0xaa, 0xab, 0xac, 0xad, 0xad, 0xae, 0xaf, 0xaf,
+  0xb0, 0xb1, 0xb2, 0xb2, 0xb3, 0xb4, 0xb4, 0xb5, 0xb6, 0xb6, 0xb7, 0xb8, 0xb9,
+  0xb9, 0xba, 0xbb, 0xbb, 0xbc, 0xbd, 0xbd, 0xbe, 0xbe, 0xbf, 0xc0, 0xc0, 0xc1,
+  0xc2, 0xc2, 0xc3, 0xc4, 0xc4, 0xc5, 0xc5, 0xc6, 0xc7, 0xc7, 0xc8, 0xc8, 0xc9,
+  0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf, 0xd0, 0xd0, 0xd1,
+  0xd1, 0xd2, 0xd2, 0xd3, 0xd4, 0xd4, 0xd5, 0xd5, 0xd6, 0xd6, 0xd7, 0xd7, 0xd8,
+  0xd8, 0xd9, 0xda, 0xda, 0xdb, 0xdb, 0xdc, 0xdc, 0xdd, 0xdd, 0xde, 0xde, 0xdf,
+  0xdf, 0xe0, 0xe0, 0xe1, 0xe2, 0xe2, 0xe3, 0xe3, 0xe4, 0xe4, 0xe5, 0xe5, 0xe6,
+  0xe6, 0xe7, 0xe7, 0xe8, 0xe8, 0xe9, 0xe9, 0xea, 0xea, 0xeb, 0xeb, 0xec, 0xec,
+  0xed, 0xed, 0xee, 0xee, 0xee, 0xef, 0xef, 0xf0, 0xf0, 0xf1, 0xf1, 0xf2, 0xf2,
+  0xf3, 0xf3, 0xf4, 0xf4, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf8, 0xf8,
+  0xf9, 0xf9, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfd, 0xfd, 0xfe, 0xfe,
+  0xff, 0xff};
+
+ALIGN_64BYTE const
+unsigned char to_linear_uchar_table[256] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02,
+  0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04,
+  0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07,
+  0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0b,
+  0x0b, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10,
+  0x11, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17,
+  0x17, 0x18, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1e,
+  0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23, 0x24, 0x25, 0x25, 0x26, 0x27, 0x28,
+  0x29, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33,
+  0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4c, 0x4d,
+  0x4e, 0x4f, 0x50, 0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x5a, 0x5b, 0x5c,
+  0x5d, 0x5f, 0x60, 0x61, 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d,
+  0x6f, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7f, 0x80,
+  0x82, 0x83, 0x85, 0x86, 0x88, 0x8a, 0x8b, 0x8d, 0x8e, 0x90, 0x92, 0x93, 0x95,
+  0x97, 0x98, 0x9a, 0x9c, 0x9d, 0x9f, 0xa1, 0xa3, 0xa4, 0xa6, 0xa8, 0xaa, 0xab,
+  0xad, 0xaf, 0xb1, 0xb3, 0xb5, 0xb7, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc4,
+  0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
+  0xe0, 0xe2, 0xe5, 0xe7, 0xe9, 0xeb, 0xed, 0xef, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa,
+  0xfd, 0xff};
+
+ALIGN_64BYTE
+const float to_linear_float_table[256] = { 0, 0.000304f, 0.000607f, 0.000911f,
+  0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
+  0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f,
+  0.006049f, 0.006512f, 0.006995f, 0.007499f, 0.008023f, 0.008568f, 0.009134f,
+  0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f,
+  0.014444f, 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f,
+  0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, 0.025187f, 0.026241f,
+  0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f,
+  0.035601f, 0.036889f, 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f,
+  0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, 0.054480f,
+  0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f,
+  0.068478f, 0.070360f, 0.072272f, 0.074214f, 0.076185f, 0.078187f, 0.080220f,
+  0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
+  0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f,
+  0.114435f, 0.116971f, 0.119538f, 0.122139f, 0.124772f, 0.127438f, 0.130136f,
+  0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f,
+  0.152926f, 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f,
+  0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, 0.191202f, 0.194618f,
+  0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f,
+  0.223228f, 0.226966f, 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f,
+  0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, 0.274677f,
+  0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f,
+  0.309469f, 0.313989f, 0.318547f, 0.323143f, 0.327778f, 0.332452f, 0.337164f,
+  0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
+  0.376262f, 0.381326f, 0.386429f, 0.391572f, 0.396755f, 0.401978f, 0.407240f,
+  0.412543f, 0.417885f, 0.423268f, 0.428690f, 0.434154f, 0.439657f, 0.445201f,
+  0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473531f, 0.479320f, 0.485150f,
+  0.491021f, 0.496933f, 0.502886f, 0.508881f, 0.514918f, 0.520996f, 0.527115f,
+  0.533276f, 0.539479f, 0.545724f, 0.552011f, 0.558340f, 0.564712f, 0.571125f,
+  0.577580f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f,
+  0.623960f, 0.630757f, 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f,
+  0.672443f, 0.679542f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, 0.715694f,
+  0.723055f, 0.730461f, 0.737910f, 0.745404f, 0.752942f, 0.760525f, 0.768151f,
+  0.775822f, 0.783538f, 0.791298f, 0.799103f, 0.806952f, 0.814847f, 0.822786f,
+  0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
+  0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f,
+  0.947307f, 0.955973f, 0.964686f, 0.973445f, 0.982251f, 0.991102f, 1.000000f};
+
+
+#if defined(__SSE2__) || (_M_IX86_FP >= 2) || defined(_M_X64) || defined(_M_AMD64)
+// SSE2 support enabled at compile time.  No runtime detection mechanism needed.
+bool
+has_sse2_sRGB_encode() {
+  return true;
+}
+
+#else
+// SSE2 support not guaranteed.  Use a runtime detection mechanism.
+
+bool
+has_sse2_sRGB_encode() {
+#if defined(__GNUC__)
+  unsigned int a, b, c, d;
+  static const bool has_support =
+    (__get_cpuid(1, &a, &b, &c, &d) == 1 && (d & 0x04000000) != 0);
+
+#elif defined(_WIN32)
+  static const bool has_support =
+    (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE) != FALSE);
+
+#else
+  static const bool has_support = false;
+#endif
+
+  if (pnmimage_cat.is_debug()) {
+    static bool checked = false;
+    if (!checked) {
+#if defined(__GNUC__) || defined(_WIN32)
+      if (has_support) {
+        pnmimage_cat.debug()
+          << "Runtime detection reports SSE2 instructions available: "
+          << "SSE2-optimized sRGB encoding routines enabled.\n";
+      } else {
+        pnmimage_cat.debug()
+          << "Runtime detection reports SSE2 instructions unavailable: "
+          << "SSE2-optimized sRGB encoding routines disabled.\n";
+      }
+#else
+      pnmimage_cat.debug()
+        << "No runtime detection mechanism for SSE2 instructions available: "
+        << "SSE2-optimized sRGB encoding routines disabled.\n";
+#endif
+      checked = true;
+    }
+  }
+
+  return has_support;
+}
+
+#endif  // __SSE2__

+ 59 - 0
panda/src/pnmimage/convert_srgb.h

@@ -0,0 +1,59 @@
+// Filename: convert_srgb.h
+// Created by:  rdb (13Nov14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONVERT_SRGB_H
+#define CONVERT_SRGB_H
+
+#include "pandabase.h"
+#include "luse.h"
+#include "pnmimage_base.h"
+
+// The below functions can encode and decode sRGB colors in various
+// representations.  Some of them are implemented using look-up tables,
+// some others using SSE2 intrinsics.
+extern EXPCL_PANDA_PNMIMAGE const unsigned char to_srgb8_table[256];
+extern EXPCL_PANDA_PNMIMAGE const unsigned char to_linear_uchar_table[256];
+extern EXPCL_PANDA_PNMIMAGE const float to_linear_float_table[256];
+
+EXPCL_PANDA_PNMIMAGE CONSTEXPR float decode_sRGB_float(unsigned char val);
+EXPCL_PANDA_PNMIMAGE INLINE float decode_sRGB_float(float val);
+EXPCL_PANDA_PNMIMAGE CONSTEXPR unsigned char decode_sRGB_uchar(unsigned char val);
+EXPCL_PANDA_PNMIMAGE INLINE unsigned char decode_sRGB_uchar(float val);
+
+EXPCL_PANDA_PNMIMAGE INLINE float encode_sRGB_float(unsigned char val);
+EXPCL_PANDA_PNMIMAGE INLINE float encode_sRGB_float(float val);
+EXPCL_PANDA_PNMIMAGE CONSTEXPR unsigned char encode_sRGB_uchar(unsigned char val);
+EXPCL_PANDA_PNMIMAGE INLINE unsigned char encode_sRGB_uchar(float val);
+
+// These functions convert more than one component in one go,
+// which can be faster due to vectorization.
+EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from,
+                                                   xel &into);
+EXPCL_PANDA_PNMIMAGE INLINE void encode_sRGB_uchar(const LColorf &from,
+                                                   xel &into, xelval &into_alpha);
+
+// Use these functions if you know that SSE2 support is available.
+// Otherwise, they will crash!
+EXPCL_PANDA_PNMIMAGE unsigned char encode_sRGB_uchar_sse2(float val);
+EXPCL_PANDA_PNMIMAGE void encode_sRGB_uchar_sse2(const LColorf &from,
+                                                 xel &into);
+EXPCL_PANDA_PNMIMAGE void encode_sRGB_uchar_sse2(const LColorf &from,
+                                                 xel &into, xelval &into_alpha);
+
+// Use the following to find out if you can call either of the above.
+EXPCL_PANDA_PNMIMAGE bool has_sse2_sRGB_encode();
+
+#include "convert_srgb.I"
+
+#endif

+ 151 - 0
panda/src/pnmimage/convert_srgb_sse2.cxx

@@ -0,0 +1,151 @@
+// Filename: convert_srgb_sse2.cxx
+// Created by:  rdb (13Nov14)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+// This file should always be compiled with SSE2 support.  These
+// functions will only be called when SSE2 support is detected at
+// run-time.
+
+#include "convert_srgb.h"
+#include "luse.h"
+
+#if defined(__SSE2__) || (_M_IX86_FP >= 2) || defined(_M_X64) || defined(_M_AMD64)
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+static INLINE __m128i _encode_sRGB_sse2_mul255(__m128 val) {
+  // This an SSE2-based approximation of the sRGB encode function.
+  // It has a maximum error of around 0.001, which is by far small
+  // enough for a uchar.  It is also at least 10x as fast as the
+  // original; up to 40x when taking advantage of vectorization.
+  // Note that the fourth float is only multiplied with 255.
+
+  // Part of the code in this function is derived from:
+  // http://stackoverflow.com/a/6486630/2135754
+
+  // Clamp to 0-1 range.
+  val = _mm_max_ps(val, _mm_set1_ps(0.0f));
+  val = _mm_min_ps(val, _mm_set1_ps(1.0f));
+
+  // Pre-multiply with constant factor to adjust for exp bias.
+  __m128 xf = _mm_mul_ps(val, _mm_set1_ps(6.3307e18f));
+
+  // Approximate logarithm by... casting!
+  xf = _mm_cvtepi32_ps(_mm_castps_si128(xf));
+
+  // Multiply 'logarithm' by power.
+  xf = _mm_mul_ps(xf, _mm_set1_ps(2.0f / 3.0f));
+
+  // Reverse operation of above: cast the other way.
+  xf = _mm_castsi128_ps(_mm_cvtps_epi32(xf));
+
+  // Make an overestimate and an underestimate.
+  __m128 xover = _mm_mul_ps(val, xf);
+  __m128 xunder = _mm_mul_ps(_mm_mul_ps(val, val),
+                             _mm_rsqrt_ps(xf));
+
+  // Average the two factors, with a slight bias.
+  __m128 xavg = _mm_mul_ps(_mm_add_ps(xover, xunder),
+                           _mm_set1_ps(0.5286098f));
+
+  // Take square root twice.  Note that this is faster than
+  // the more expensive _mm_sqrt_ps instruction.
+  xavg = _mm_mul_ps(xavg, _mm_rsqrt_ps(xavg));
+  xavg = _mm_mul_ps(xavg, _mm_rsqrt_ps(xavg));
+
+  // Bring it into the correct range.  These factors are determined
+  // not on the basis of accuracy, but are chosen such that the
+  // decoder lookup table produces an equivalent result for any value.
+  xavg = _mm_mul_ps(xavg, _mm_set1_ps(269.122f));
+  xavg = _mm_sub_ps(xavg, _mm_set1_ps(13.55f));
+
+  // Compute the linear section.  This is also the path that
+  // the alpha channel takes, so we set the alpha multiplier
+  // to 255 (since alpha is not sRGB-converted).
+  __m128 lval = _mm_mul_ps(val,
+    _mm_set_ps(255.0f, 3294.6f, 3294.6f, 3294.6f));
+
+  lval = _mm_add_ps(lval, _mm_set1_ps(0.5f));
+
+  // Decide which version to return.  Rig the alpha
+  // comparator to always fail so that the linear path
+  // is always chosen for alpha.
+  __m128 mask = _mm_cmpge_ps(val,
+    _mm_set_ps(2.0f, 0.0031308f, 0.0031308f, 0.0031308f));
+
+  // This is a non-branching way to return one or the other value.
+  return _mm_cvttps_epi32(_mm_or_ps(
+    _mm_and_ps(mask, xavg),
+    _mm_andnot_ps(mask, lval)));
+}
+
+unsigned char
+encode_sRGB_uchar_sse2(float val) {
+  // Running only a single component through this function is still
+  // way faster than the equivalent non-SSE2 version.
+  return (unsigned char)
+    _mm_extract_epi16(_encode_sRGB_sse2_mul255(_mm_set1_ps(val)), 0);
+}
+
+void
+encode_sRGB_uchar_sse2(const LColorf &color, xel &into) {
+#ifdef LINMATH_ALIGN
+  __m128 vec = _mm_load_ps(color.get_data());
+#else
+  __m128 vec = _mm_loadu_ps(color.get_data());
+#endif
+
+  __m128i vals = _encode_sRGB_sse2_mul255(vec);
+  into.r = _mm_extract_epi16(vals, 0);
+  into.g = _mm_extract_epi16(vals, 2);
+  into.b = _mm_extract_epi16(vals, 4);
+}
+
+void
+encode_sRGB_uchar_sse2(const LColorf &color, xel &into, xelval &into_alpha) {
+#ifdef LINMATH_ALIGN
+  __m128 vec = _mm_load_ps(color.get_data());
+#else
+  __m128 vec = _mm_loadu_ps(color.get_data());
+#endif
+
+  __m128i vals = _encode_sRGB_sse2_mul255(vec);
+  into.r = _mm_extract_epi16(vals, 0);
+  into.g = _mm_extract_epi16(vals, 2);
+  into.b = _mm_extract_epi16(vals, 4);
+  into_alpha = _mm_extract_epi16(vals, 6);
+}
+
+#else
+// Somehow we're still compiling this without SSE2 support.  We'll
+// still have to define these functions, but emit a warning that the
+// build system isn't configured properly.
+#warning convert_srgb_sse2.cxx is being compiled without SSE2 support!
+
+unsigned char
+encode_sRGB_uchar_sse2(float val) {
+  return encode_sRGB_uchar(val);
+}
+
+void
+encode_sRGB_uchar_sse2(const LColorf &color, xel &into) {
+  encode_sRGB_uchar(color, into);
+}
+
+void
+encode_sRGB_uchar_sse2(const LColorf &color, xel &into, xelval &into_alpha) {
+  encode_sRGB_uchar(color, into, into_alpha);
+}
+
+#endif

+ 1 - 0
panda/src/pnmimage/p3pnmimage_composite1.cxx

@@ -1,4 +1,5 @@
 #include "config_pnmimage.cxx"
+#include "convert_srgb.cxx"
 #include "pfmFile.cxx"
 #include "pnm-image-filter.cxx"
 #include "pnmbitio.cxx"

+ 2 - 0
panda/src/putil/Sources.pp

@@ -27,6 +27,7 @@
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \
+    callbackObject_ext.h callbackObject_ext.I \
     clockObject.h clockObject.I \
     collideMask.h \
     copyOnWriteObject.h copyOnWriteObject.I \
@@ -139,6 +140,7 @@
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \
+    callbackObject_ext.h callbackObject_ext.I \
     clockObject.h clockObject.I \
     collideMask.h \
     copyOnWriteObject.h copyOnWriteObject.I \

+ 20 - 10
panda/src/putil/bamCache.cxx

@@ -43,7 +43,7 @@ BamCache() :
   _index_stale_since(0)
 {
   ConfigVariableFilename model_cache_dir
-    ("model-cache-dir", Filename(), 
+    ("model-cache-dir", Filename(),
      PRC_DESC("The full path to a directory, local to this computer, in which "
               "model and texture files will be cached on load.  If a directory "
               "name is specified here, files may be loaded from the cache "
@@ -51,7 +51,7 @@ BamCache() :
               "especially if you are loading egg files instead of bam files, "
               "or if you are loading models from a shared network drive.  "
               "If this is the empty string, no cache will be used."));
-  
+
   ConfigVariableInt model_cache_flush
     ("model-cache-flush", 30,
      PRC_DESC("This is the amount of time, in seconds, between automatic "
@@ -138,7 +138,7 @@ set_root(const Filename &root) {
 ////////////////////////////////////////////////////////////////////
 //     Function: BamCache::lookup
 //       Access: Published
-//  Description: Looks up a file in the cache.  
+//  Description: Looks up a file in the cache.
 //
 //               If the file is cacheable, then regardless of whether
 //               the file is found in the cache or not, this returns a
@@ -163,7 +163,7 @@ lookup(const Filename &source_filename, const string &cache_extension) {
   consider_flush_index();
 
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  
+
   Filename source_pathname(source_filename);
   source_pathname.make_absolute(vfs->get_cwd());
 
@@ -200,7 +200,7 @@ store(BamCacheRecord *record) {
   if (_read_only) {
     return false;
   }
-  
+
   consider_flush_index();
 
 #ifndef NDEBUG
@@ -231,7 +231,7 @@ store(BamCacheRecord *record) {
     emergency_read_only();
     return false;
   }
-  
+
   if (!dout.write_header(_bam_header)) {
     util_cat.error()
       << "Unable to write to " << temp_pathname << "\n";
@@ -247,7 +247,7 @@ store(BamCacheRecord *record) {
       vfs->delete_file(temp_pathname);
       return false;
     }
-    
+
     TypeRegistry *type_registry = TypeRegistry::ptr();
     TypeHandle texture_type = type_registry->find_type("Texture");
     if (record->get_data()->is_of_type(texture_type)) {
@@ -257,14 +257,14 @@ store(BamCacheRecord *record) {
       // Any other kinds of objects write texture references.
       writer.set_file_texture_mode(BamWriter::BTM_fullpath);
     }
-    
+
     if (!writer.write_object(record)) {
       util_cat.error()
         << "Unable to write object to " << temp_pathname << "\n";
       vfs->delete_file(temp_pathname);
       return false;
     }
-    
+
     if (!writer.write_object(record->get_data())) {
       util_cat.error()
         << "Unable to write object data to " << temp_pathname << "\n";
@@ -354,7 +354,7 @@ flush_index() {
       emergency_read_only();
       return;
     }
-    
+
     // Now atomically write the name of this index file to the index
     // reference file.
     VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
@@ -385,6 +385,16 @@ flush_index() {
   check_cache_size();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamCache::list_index
+//       Access: Published
+//  Description: Writes the contents of the index to standard output.
+////////////////////////////////////////////////////////////////////
+void BamCache::
+list_index(ostream &out, int indent_level) const {
+  _index->write(out, indent_level);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamCache::read_index
 //       Access: Private

+ 6 - 4
panda/src/putil/bamCache.h

@@ -73,13 +73,15 @@ PUBLISHED:
   INLINE void set_read_only(bool ro);
   INLINE bool get_read_only() const;
 
-  PT(BamCacheRecord) lookup(const Filename &source_filename, 
+  PT(BamCacheRecord) lookup(const Filename &source_filename,
                             const string &cache_extension);
   bool store(BamCacheRecord *record);
 
   void consider_flush_index();
   void flush_index();
-  
+
+  void list_index(ostream &out, int indent_level = 0) const;
+
   INLINE static BamCache *get_global_ptr();
 
 private:
@@ -96,7 +98,7 @@ private:
   void check_cache_size();
 
   void emergency_read_only();
-  
+
   static BamCacheIndex *do_read_index(const Filename &index_pathname);
   static bool do_write_index(const Filename &index_pathname, const BamCacheIndex *index);
 
@@ -105,7 +107,7 @@ private:
   PT(BamCacheRecord) read_record(const Filename &source_pathname,
                                  const Filename &cache_filename,
                                  int pass);
-  static PT(BamCacheRecord) do_read_record(const Filename &cache_pathname, 
+  static PT(BamCacheRecord) do_read_record(const Filename &cache_pathname,
                                            bool read_data);
 
   static string hash_filename(const string &filename);

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

@@ -38,6 +38,8 @@ public:
 PUBLISHED:
   virtual void output(ostream &out) const;
 
+  EXTENSION(static PT(CallbackObject) make(PyObject *function));
+
 public:
   virtual void do_callback(CallbackData *cbdata);
 

+ 32 - 0
panda/src/putil/callbackObject_ext.I

@@ -0,0 +1,32 @@
+// Filename: callbackObject_ext.I
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<CallbackObject>::make
+//       Access: Published
+//  Description: This static constructor is merely provided so that
+//               interrogate can automatically coerce Python
+//               functions when passing them to a C++ function that
+//               accepts a CallbackObject.
+////////////////////////////////////////////////////////////////////
+INLINE PT(CallbackObject) Extension<CallbackObject>::
+make(PyObject *function) {
+  if (function != Py_None && !PyCallable_Check(function)) {
+    PyErr_SetString(PyExc_TypeError, "expected callable or None");
+    return NULL;
+  } else {
+    return new PythonCallbackObject(function);
+  }
+}

+ 47 - 0
panda/src/putil/callbackObject_ext.h

@@ -0,0 +1,47 @@
+// Filename: callbackObject_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CALLBACKOBJECT_EXT_H
+#define CALLBACKOBJECT_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pythonCallbackObject.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<CallbackObject>
+// Description : This class defines the extension methods for
+//               CallbackObject, which are called instead of
+//               any C++ methods with the same prototype.
+//
+//               This just defines a static constructor, which makes
+//               it possible for Interrogate to automatically accept
+//               a Python function wherever a CallbackObject is
+//               accepted.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<CallbackObject> : public ExtensionBase<CallbackObject> {
+public:
+  INLINE static PT(CallbackObject) make(PyObject *function);
+};
+
+#include "callbackObject_ext.I"
+
+#endif  // HAVE_PYTHON
+
+#endif  // CALLBACKOBJECT_EXT_H

+ 1 - 1
panda/src/tinydisplay/zbuffer.h

@@ -75,7 +75,7 @@ typedef unsigned int ZPOINT;
   ((((unsigned int)(a) << 24) & 0xff000000) | (((unsigned int)(r) << 16) & 0xff0000) | (((unsigned int)(g) << 8) & 0xff00) | (unsigned int)(b))
 
 #define SRGB_TO_PIXEL(r,g,b) \
-  ((encode_sRGB[(unsigned int)(r) >> 4] << 16) | (encode_sRGB10[(unsigned int)(g) >> 4] << 8) | (encode_sRGB[(unsigned int)(b) >> 4]))
+  ((encode_sRGB[(unsigned int)(r) >> 4] << 16) | (encode_sRGB[(unsigned int)(g) >> 4] << 8) | (encode_sRGB[(unsigned int)(b) >> 4]))
 #define SRGBA_TO_PIXEL(r,g,b,a) \
   ((((unsigned int)(a) << 16) & 0xff000000) | (encode_sRGB[(unsigned int)(r) >> 4] << 16) | (encode_sRGB[(unsigned int)(g) >> 4] << 8) | (encode_sRGB[(unsigned int)(b) >> 4]))
 

+ 270 - 42
pandatool/src/daeegg/daeCharacter.cxx

@@ -17,88 +17,316 @@
 #include "fcollada_utils.h"
 #include "pt_EggVertex.h"
 #include "eggXfmSAnim.h"
+#include "daeToEggConverter.h"
+#include "daeMaterials.h"
+
+#include "eggExternalReference.h"
 
 #include "FCDocument/FCDocument.h"
 #include "FCDocument/FCDController.h"
+#include "FCDocument/FCDGeometry.h"
 #include "FCDocument/FCDSceneNodeTools.h"
 
+#include "FCDocument/FCDSceneNode.h"
+#include "FCDocument/FCDTransform.h"
+#include "FCDocument/FCDAnimated.h"
+#include "FCDocument/FCDAnimationCurve.h"
+#include "FCDocument/FCDAnimationKey.h"
+
 TypeHandle DaeCharacter::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeCharacter::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeCharacter::
-DaeCharacter(const string name, const FCDControllerInstance* controller_instance) {
-  _controller_instance = (FCDControllerInstance*) controller_instance;
-  _name = name;
-  _frame_rate = 0;
-  _skin_controller = NULL;
+DaeCharacter(EggGroup *node_group, const FCDControllerInstance *instance) :
+  _node_group(node_group),
+  _name(node_group->get_name()),
+  _instance(instance),
+  _skin_controller(NULL),
+  _skin_mesh(NULL) {
+
+  _bind_shape_mat = LMatrix4d::ident_mat();
+
   // If it's a skin controller, add the controller joints.
-  FCDController* controller = (FCDController*) controller_instance->GetEntity();
-  if (controller == NULL) return;
+  const FCDController *controller = (const FCDController *)instance->GetEntity();
+  if (controller == NULL) {
+    return;
+  }
+  _skin_mesh = controller->GetBaseGeometry()->GetMesh();
+
   if (controller->IsSkin()) {
     _skin_controller = controller->GetSkinController();
-    if (_skin_controller == NULL) return;
-    for (size_t j = 0; j < _skin_controller->GetJointCount(); ++j) {
-      _controller_joints[FROM_FSTRING(_skin_controller->GetJoint(j)->GetId())] = (FCDSkinControllerJoint*) _skin_controller->GetJoint(j);
+    _bind_shape_mat = DAEToEggConverter::convert_matrix(_skin_controller->GetBindShapeTransform());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::bind_joints
+//       Access: Public
+//  Description: Binds the joints to the character.  This means
+//               changing them to the bind pose.  It is necessary
+//               to call this before process_skin_geometry.
+//
+//               Returns the root group.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+bind_joints(JointMap &joint_map) {
+  _joints.clear();
+
+  size_t num_joints = _skin_controller->GetJointCount();
+  _joints.reserve(num_joints);
+
+  // Record the bind pose for each joint.
+  for (size_t j = 0; j < num_joints; ++j) {
+    const FCDSkinControllerJoint *skin_joint = _skin_controller->GetJoint(j);
+    string sid = FROM_FSTRING(skin_joint->GetId());
+    LMatrix4d bind_pose;
+    bind_pose.invert_from(DAEToEggConverter::convert_matrix(
+                          skin_joint->GetBindPoseInverse()));
+
+    // Check that we already encountered this joint during traversal.
+    JointMap::iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      Joint &joint = ji->second;
+
+      if (joint._character != (DaeCharacter *)NULL) {
+        // In some cases, though, multiple controllers share the same joints.
+        // We can't support this without duplicating the joint structure,
+        // so we check if the bind poses are the same.
+        if (!joint._bind_pose.almost_equal(bind_pose, 0.0001)) {
+          // Ugh.  What else could we do?
+          daeegg_cat.error()
+            << "Multiple controllers share joint with sid " << sid
+            << ", with different bind poses.\n";
+        }
+      } else {
+        // Mark the joint as being controlled by this character.
+        joint._bind_pose = bind_pose;
+        joint._character = this;
+      }
+
+      _joints.push_back(joint);
+    } else {
+      daeegg_cat.warning()
+        << "Unknown joint sid being referenced: '" << sid << "'\n";
+
+      // We still have to add a dummy joint or the index will be off.
+      _joints.push_back(Joint(NULL, NULL));
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::adjust_joints
+//       Access: Public
+//  Description: Traverses through the character hierarchy in order
+//               to bind the mesh to the character.  This involves
+//               reorienting the joints to match the bind pose.
+//
+//               It is important that this is called only once.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+              const LMatrix4d &transform) {
+
+  LMatrix4d this_transform = transform;
+
+  if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
+
+    JointMap::const_iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      const Joint &joint = ji->second;
+
+      // Panda needs the joints to be in bind pose.  Not fun!  We copy the joint
+      // transform to the default pose, though, so that Panda will restore the
+      // joint transformation after binding.
+
+      if (joint._character == this) {
+        LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+                        invert(transform);
+        //LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+        //                joint._group->get_parent()->get_node_frame_inv();
+
+        this_transform = bind_pose * this_transform;
+        joint._group->set_default_pose(*joint._group);
+        joint._group->set_transform3d(bind_pose);
+
+        /*
+        PT(EggGroup) sphere = new EggGroup;
+        sphere->add_uniform_scale(0.1);
+        sphere->set_group_type(EggGroup::GT_instance);
+        sphere->add_child(new EggExternalReference("", "jack.egg"));
+        joint._group->add_child(sphere);
+        */
+      }
+    }
+  } else {
+    //this_transform = DAEToEggConverter::convert_matrix(node->ToMatrix());
+  }
+
+  // Loop through the children joints
+  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+    //if (node->GetChild(ch)->IsJoint()) {
+    adjust_joints(node->GetChild(ch), joint_map, this_transform);
+    //}
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::influence_vertex
+//       Access: Public
+//  Description: Adds the influences for the given vertex.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+influence_vertex(int index, EggVertex *vertex) {
+  const FCDSkinControllerVertex *influence = _skin_controller->GetVertexInfluence(index);
+
+  for (size_t pa = 0; pa < influence->GetPairCount(); ++pa) {
+    const FCDJointWeightPair* jwpair = influence->GetPair(pa);
+
+    if (jwpair->jointIndex >= 0 && jwpair->jointIndex < _joints.size()) {
+      EggGroup *joint = _joints[jwpair->jointIndex]._group.p();
+      if (joint != NULL) {
+        joint->ref_vertex(vertex, jwpair->weight);
+      }
+    } else {
+      daeegg_cat.error()
+        << "Invalid joint index: " << jwpair->jointIndex << "\n";
     }
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::as_egg_bundle
+//     Function: DaeCharacter::collect_keys
 //       Access: Public
-//  Description: Returns the character as a <Bundle> element,
-//               suited for the animation table.
-////////////////////////////////////////////////////////////////////
-PT(EggTable) DaeCharacter::
-as_egg_bundle() {
-  PT(EggTable) bundle = new EggTable(_name);
-  bundle->set_table_type(EggTable::TT_bundle);
-  PT(EggTable) skeleton = new EggTable("<skeleton>");
-  skeleton->set_table_type(EggTable::TT_table);
-  bundle->add_child(skeleton);
-  // Loop through the joint hierarchy
+//  Description: Collects all animation keys of animations applied
+//               to this character.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+collect_keys(pset<float> &keys) {
 #if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = _controller_instance->FindSkeletonNodes();
+  FCDSceneNodeList roots = _instance->FindSkeletonNodes();
 #else
   FCDSceneNodeList roots;
-  _controller_instance->FindSkeletonNodes(roots);
+  _instance->FindSkeletonNodes(roots);
 #endif
+
   for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_joint(skeleton, *it);
+    r_collect_keys(*it, keys);
   }
-  return bundle;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::process_joint
+//     Function: DaeCharacter::r_collect_keys
+//       Access: Public
+//  Description: Collects all animation keys found for the given
+//               node tree.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+r_collect_keys(FCDSceneNode* node, pset<float> &keys) {
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+
+    if (animated != NULL) {
+      const FCDAnimationCurveListList &all_curves = animated->GetCurves();
+
+      for (size_t ci = 0; ci < all_curves.size(); ++ci) {
+        const FCDAnimationCurveTrackList &curves = all_curves[ci];
+        if (curves.empty()) {
+          continue;
+        }
+
+        size_t num_keys = curves.front()->GetKeyCount();
+        const FCDAnimationKey **curve_keys = curves.front()->GetKeys();
+
+        for (size_t c = 0; c < num_keys; ++c) {
+          keys.insert(curve_keys[c]->input);
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::build_table
 //       Access: Public
 //  Description: Processes a joint node and its transforms.
 ////////////////////////////////////////////////////////////////////
 void DaeCharacter::
-process_joint(PT(EggTable) parent, FCDSceneNode* node) {
+build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys) {
   nassertv(node != NULL);
+
+  if (!node->IsJoint()) {
+    for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+      build_table(parent, node->GetChild(ch), keys);
+    }
+    return;
+  }
+
   string node_id = FROM_FSTRING(node->GetDaeId());
-  PT(EggTable) joint = new EggTable(node_id);
-  joint->set_table_type(EggTable::TT_table);
-  parent->add_child(joint);
+  PT(EggTable) table = new EggTable(node_id);
+  table->set_table_type(EggTable::TT_table);
+  parent->add_child(table);
+
   PT(EggXfmSAnim) xform = new EggXfmSAnim("xform");
-  joint->add_child(xform);
-  xform->set_fps(_frame_rate);
+  table->add_child(xform);
+
   // Generate the sampled animation and loop through the matrices
-  FCDSceneNodeTools::GenerateSampledAnimation(node);
-  FMMatrix44List matrices = FCDSceneNodeTools::GetSampledAnimationMatrices();
-  for (FMMatrix44List::const_iterator it = matrices.begin(); it != matrices.end(); ++it) {
-    LMatrix4d matr = DAEToEggConverter::convert_matrix(*it);
-    assert(xform->add_data(matr));
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+    if (animated != (FCDAnimated *)NULL) {
+      if (animated->HasCurve()) {
+        animateds.push_back(animated);
+      }
+    }
   }
+
+  // Sample the scene node transform
+  float last_key;
+  float timing_total = 0;
+  pset<float>::const_iterator ki;
+  for (ki = keys.begin(); ki != keys.end(); ++ki) {
+    for (FCDAnimatedList::iterator it = animateds.begin(); it != animateds.end(); ++it) {
+      // Sample each animated, which changes the transform values directly
+      (*it)->Evaluate(*ki);
+    }
+
+    if (ki != keys.begin()) {
+      timing_total += (*ki - last_key);
+    }
+    last_key = *ki;
+
+    // Retrieve the new transform matrix for the COLLADA scene node
+    FMMatrix44 fmat = node->ToMatrix();
+
+    // Work around issue in buggy exporters (like ColladaMax)
+    if (IS_NEARLY_ZERO(fmat[3][3])) {
+      fmat[3][3] = 1;
+    }
+
+    xform->add_data(DAEToEggConverter::convert_matrix(fmat));
+  }
+
+  // Quantize the FPS, otherwise Panda complains about FPS mismatches.
+  float fps = cfloor(((keys.size() - 1) / timing_total) * 100 + 0.5f) * 0.01f;
+  xform->set_fps(fps);
+
   // Loop through the children joints
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    if (node->GetChild(ch)->IsJoint()) {
-      process_joint(joint, node->GetChild(ch));
-    }
+    //if (node->GetChild(ch)->IsJoint()) {
+      build_table(table, node->GetChild(ch), keys);
+    //}
   }
 }

+ 43 - 12
pandatool/src/daeegg/daeCharacter.h

@@ -12,20 +12,22 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#ifndef DAECHARACTER_H
+#define DAECHARACTER_H
+
 #include "pandatoolbase.h"
 #include "typedReferenceCount.h"
 #include "typeHandle.h"
 #include "eggTable.h"
-#include "daeToEggConverter.h"
 
 #include "pre_fcollada_include.h"
 #include "FCollada.h"
 #include "FCDocument/FCDSceneNode.h"
 #include "FCDocument/FCDControllerInstance.h"
 #include "FCDocument/FCDSkinController.h"
+#include "FCDocument/FCDGeometryMesh.h"
 
-#ifndef DAECHARACTER_H
-#define DAECHARACTER_H
+class DAEToEggConverter;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DaeCharacter
@@ -33,17 +35,46 @@
 ////////////////////////////////////////////////////////////////////
 class DaeCharacter : public TypedReferenceCount {
 public:
-  DaeCharacter(const string name, const FCDControllerInstance* controller_instance);
-  PT(EggTable) as_egg_bundle();
-  void process_joint(PT(EggTable) parent, FCDSceneNode* node);
-  
+  DaeCharacter(EggGroup *node_group, const FCDControllerInstance* controller_instance);
+
+  struct Joint {
+    INLINE Joint(EggGroup *group, const FCDSceneNode *scene_node) :
+      _group(group),
+      _scene_node(scene_node),
+      _character(NULL),
+      _bind_pose(LMatrix4d::ident_mat()) {}
+
+    LMatrix4d _bind_pose;
+    PT(EggGroup) _group;
+    const FCDSceneNode *_scene_node;
+    DaeCharacter *_character;
+  };
+  typedef pvector<Joint> Joints;
+  typedef pmap<string, Joint> JointMap;
+
+  void bind_joints(JointMap &joint_map);
+  void adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+                     const LMatrix4d &transform = LMatrix4d::ident_mat());
+
+  void influence_vertex(int index, EggVertex *vertex);
+
+  void collect_keys(pset<float> &keys);
+  void r_collect_keys(FCDSceneNode *node, pset<float> &keys);
+
+  void build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys);
+
+public:
+  PT(EggGroup) _node_group;
+  const FCDGeometryMesh *_skin_mesh;
+  const FCDControllerInstance *_instance;
+  LMatrix4d _bind_shape_mat;
+
 private:
-  int _frame_rate;
   string _name;
-  FCDControllerInstance* _controller_instance;
-  FCDSkinController* _skin_controller;
-  pmap<string, FCDSkinControllerJoint*> _controller_joints;
-  
+  const FCDSkinController *_skin_controller;
+  Joints _joints;
+  JointMap _bound_joints;
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 11 - 10
pandatool/src/daeegg/daeMaterials.cxx

@@ -35,7 +35,7 @@ TypeHandle DaeMaterials::_type_handle;
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeMaterials::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeMaterials::
 DaeMaterials(const FCDGeometryInstance* geometry_instance) {
@@ -58,7 +58,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
     return;
   }
   _materials[semantic] = new DaeMaterial();
-  
+
   // Load in the uvsets
   for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) {
     const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib);
@@ -74,7 +74,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
 #endif
     _materials[semantic]->_uvsets.push_back(bvi);
   }
-  
+
   // Handle the material stuff
   daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl;
   PT_EggMaterial egg_material = new EggMaterial(semantic);
@@ -204,12 +204,13 @@ process_extra(const string semantic, const FCDExtra* extra) {
   for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) {
     const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided");
     if (enode != NULL) {
-      if (trim(enode->GetContent()) == "1") {
+      string content = trim(enode->GetContent());
+      if (content == "1" || content == "true") {
         _materials[semantic]->_double_sided = true;
-      } else if (trim(enode->GetContent()) == "0") {
+      } else if (content == "0" || content == "false") {
         _materials[semantic]->_double_sided = false;
       } else {
-        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << enode->GetContent() << "' instead" << endl;
+        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << content << "' instead" << endl;
       }
     }
   }
@@ -391,7 +392,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
   blend->_color = LColor::zero();
   blend->_operand_a = EggGroup::BO_unspecified;
   blend->_operand_b = EggGroup::BO_unspecified;
-  
+
   // First fill in the color value.
   if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) {
     double value = transparent[3] * transparency;
@@ -404,7 +405,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // Now figure out the operands.
   if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) {
     blend->_operand_a = EggGroup::BO_one_minus_constant_color;
@@ -417,7 +418,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // See if we can optimize out the color.
   if (blend->_operand_a == EggGroup::BO_constant_color) {
     if (blend->_color == LColor::zero()) {
@@ -447,7 +448,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
       blend->_operand_b = EggGroup::BO_zero;
     }
   }
-  
+
   // See if we can entirely disable the blend.
   if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) {
     blend->_enabled = false;

+ 396 - 221
pandatool/src/daeegg/daeToEggConverter.cxx

@@ -56,9 +56,10 @@
 ////////////////////////////////////////////////////////////////////
 DAEToEggConverter::
 DAEToEggConverter() {
+  _unit_name = "meter";
+  _unit_meters = 1.0;
   _document = NULL;
   _table = NULL;
-  _frame_rate = -1;
   _error_handler = NULL;
   _invert_transparency = false;
 }
@@ -131,18 +132,15 @@ convert_file(const Filename &filename) {
   // Reset stuff
   clear_error();
   _joints.clear();
-  _vertex_pools.clear();
-  _skeletons.clear();
-  _frame_rate = -1;
   if (_error_handler == NULL) {
     _error_handler = new FUErrorSimpleHandler;
   }
-  
+
   // The default coordinate system is Y-up
   if (_egg_data->get_coordinate_system() == CS_default) {
     _egg_data->set_coordinate_system(CS_yup_right);
   }
-  
+
   // Read the file
   FCollada::Initialize();
   _document = FCollada::LoadDocument(filename.to_os_specific().c_str());
@@ -155,201 +153,375 @@ convert_file(const Filename &filename) {
   if (_document->GetAsset() != NULL) {
     FCDocumentTools::StandardizeUpAxisAndLength(_document);
   }
-  
-  _table = new EggTable();
-  _table->set_table_type(EggTable::TT_table);
-  // Process the stuff
+
+  // Process the scene
   process_asset();
-  preprocess();
+  PT(EggGroup) scene_group;
+  string model_name = _character_name;
+
   FCDSceneNode* visual_scene = _document->GetVisualSceneInstance();
   if (visual_scene != NULL) {
-    // First check for an <extra> tag
-    const FCDExtra* extra = visual_scene->GetExtra();
-    //FIXME: eek this looks horrid
-    if (extra != NULL) {
-      const FCDEType* etype = extra->GetDefaultType();
-      if (etype != NULL) {
-        const FCDENode* enode = (const FCDENode*) etype->FindTechnique("MAX3D");
-        if (enode != NULL) {
-          enode = enode->FindChildNode("frame_rate");
-          if (enode != NULL && !string_to_int(enode->GetContent(), _frame_rate)) {
-            daeegg_cat.warning() << "Invalid integer in <frame_rate> tag: '" << enode->GetContent() << "'" << endl;
-    } } } }
-    // Now loop through the children
+    if (model_name.empty()) {
+      // By lack of anything better...
+      model_name = FROM_FSTRING(visual_scene->GetName());
+    }
+    scene_group = new EggGroup(model_name);
+    _egg_data->add_child(scene_group);
+
     for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
-      process_node(DCAST(EggGroupNode, _egg_data), visual_scene->GetChild(ch));
+      process_node(scene_group, visual_scene->GetChild(ch));
     }
+  } else {
+    daeegg_cat.warning()
+      << "No visual scene instance found in COLLADA document.\n";
   }
-  SAFE_DELETE(visual_scene);
-  
-  _egg_data->add_child(_table);
-  
+
+  // Now process the characters.  This depends on information from collected
+  // joints, which is why it's done in a second step.
+  if (get_animation_convert() != AC_none) {
+    Characters::iterator it;
+    DaeCharacter *character;
+    for (it = _characters.begin(); it != _characters.end(); ++it) {
+      character = *it;
+      if (get_animation_convert() != AC_chan) {
+        character->bind_joints(_joints);
+
+        const FCDGeometryMesh *mesh = character->_skin_mesh;
+
+        if (mesh != NULL) {
+          PT(DaeMaterials) materials = new DaeMaterials(character->_instance);
+          daeegg_cat.spam() << "Processing mesh for controller\n";
+          process_mesh(character->_node_group, mesh, materials, character);
+        }
+      }
+    }
+
+    // Put the joints in bind pose.
+    for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+      character->adjust_joints(visual_scene->GetChild(ch), _joints, LMatrix4d::ident_mat());
+    }
+
+    if (scene_group != NULL) {
+      // Mark the scene as character.
+      if (get_animation_convert() == AC_chan) {
+        _egg_data->remove_child(scene_group);
+      } else {
+        scene_group->set_dart_type(EggGroup::DT_default);
+      }
+    }
+
+    if (get_animation_convert() != AC_model) {
+      _table = new EggTable();
+      _table->set_table_type(EggTable::TT_table);
+      _egg_data->add_child(_table);
+
+      PT(EggTable) bundle = new EggTable(model_name);
+      bundle->set_table_type(EggTable::TT_bundle);
+      _table->add_child(bundle);
+
+      PT(EggTable) skeleton = new EggTable("<skeleton>");
+      skeleton->set_table_type(EggTable::TT_table);
+      bundle->add_child(skeleton);
+
+      pset<float> keys;
+
+      Characters::iterator it;
+      DaeCharacter *character;
+      for (it = _characters.begin(); it != _characters.end(); ++it) {
+        character = *it;
+
+        // Collect key frame timings.
+        if (get_animation_convert() == AC_both ||
+            get_animation_convert() == AC_chan) {
+          character->collect_keys(keys);
+        }
+      }
+
+      if (_frame_inc != 0.0) {
+        // A frame increment was given, this means that we have to sample the
+        // animation.
+        float start, end;
+        if (_end_frame != _start_frame) {
+          start = _start_frame;
+          end = _end_frame;
+        } else {
+          // No range was given.  Infer the frame range from the keys.
+          start = *keys.begin();
+          end = *keys.rbegin();
+        }
+        keys.clear();
+
+        for (float t = start; t <= end; t += _frame_inc) {
+          keys.insert(t);
+        }
+      } else {
+        // No sampling parameters given; not necessarily a failure, since the
+        // animation may already be sampled.  We use the key frames as animation
+        // frames.
+        if (_end_frame != 0.0) {
+          // An end frame was given, chop off all keys after that.
+          float end = _end_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > end && !IS_THRESHOLD_EQUAL(*ki, end, 0.001)) {
+              keys.erase(ki, keys.end());
+              break;
+            }
+          }
+        }
+        if (_start_frame != 0.0) {
+          // A start frame was given, chop off all keys before that.
+          float start = _start_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > start && !IS_THRESHOLD_EQUAL(*ki, start, 0.001)) {
+              keys.erase(keys.begin(), ki);
+              break;
+            }
+          }
+        }
+
+        // Check that this does indeed look like a sampled animation; if not,
+        // issue an appropriate warning.
+        pset<float>::const_iterator ki = keys.begin();
+        if (ki != keys.end()) {
+          float last = *ki;
+          float diff = 0;
+
+          for (++ki; ki != keys.end(); ++ki) {
+            if (diff != 0 && !IS_THRESHOLD_EQUAL((*ki - last), diff, 0.001)) {
+              daeegg_cat.error()
+                << "This does not appear to be a sampled animation.\n"
+                << "Specify the -sf, -ef and -if options to indicate how the "
+                << "animations should be sampled.\n";
+              break;
+            }
+            diff = (*ki - last);
+            last = *ki;
+          }
+        }
+      }
+
+      // It doesn't really matter which character we grab for this as
+      // it'll iterate over the whole graph right now anyway.
+      for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+        character->build_table(skeleton, visual_scene->GetChild(ch), keys);
+      }
+    }
+  }
+
   // Clean up and return
+  SAFE_DELETE(visual_scene);
   SAFE_DELETE(_document);
   FCollada::Release();
   return true;
 }
 
-void DAEToEggConverter::process_asset() {
-  if (_document->GetAsset() == NULL) return;
+////////////////////////////////////////////////////////////////////
+//     Function: DAEToEggConverter::get_input_units
+//       Access: Public, Virtual
+//  Description: This may be called after convert_file() has been
+//               called and returned true, indicating a successful
+//               conversion.  It will return the distance units
+//               represented by the converted egg file, if known, or
+//               DU_invalid if not known.
+////////////////////////////////////////////////////////////////////
+DistanceUnit DAEToEggConverter::
+get_input_units() {
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.001)) {
+    return DU_millimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.01)) {
+    return DU_centimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1.0)) {
+    return DU_meters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1000.0)) {
+    return DU_kilometers;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 3.0 * 12.0 * 0.0254)) {
+    return DU_yards;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 12.0 * 0.0254)) {
+    return DU_feet;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.0254)) {
+    return DU_inches;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1852.0)) {
+    return DU_nautical_miles;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 5280.0 * 12.0 * 0.0254)) {
+    return DU_statute_miles;
+  }
+
+  // Whatever.
+  return DU_invalid;
+}
+
+void DAEToEggConverter::
+process_asset() {
+  const FCDAsset *asset = _document->GetAsset();
+  if (_document->GetAsset() == NULL) {
+    return;
+  }
+
+  _unit_name = FROM_FSTRING(asset->GetUnitName());
+  _unit_meters = asset->GetUnitConversionFactor();
+
   // Read out the coordinate system
-  FMVector3 up_axis (_document->GetAsset()->GetUpAxis());
+  FMVector3 up_axis = asset->GetUpAxis();
+
   if (up_axis == FMVector3(0, 1, 0)) {
     _egg_data->set_coordinate_system(CS_yup_right);
+
   } else if (up_axis == FMVector3(0, 0, 1)) {
     _egg_data->set_coordinate_system(CS_zup_right);
+
   } else {
     _egg_data->set_coordinate_system(CS_invalid);
     daeegg_cat.warning() << "Unrecognized coordinate system!\n";
   }
 }
 
-// This function lists all the joints and referenced skeletons
-void DAEToEggConverter::preprocess(const FCDSceneNode* node) {
-  // If the node is NULL, take the visual scene instance.
-  if (node == NULL) {
-    assert(_document != NULL);
-    _skeletons.clear();
-    _joints.clear();
-    node = _document->GetVisualSceneInstance();
-  }
-  if (node == NULL) return;
-  if (node->IsJoint()) {
-    _joints[FROM_FSTRING(node->GetDaeId())] = NULL;
-  }
-  // Loop through the instances first.
-  for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetType() == FCDEntityInstance::CONTROLLER) {
-      // Loop through the skeleton roots now.
-#if FCOLLADA_VERSION < 0x00030005
-      FCDSceneNodeList roots = ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes();
-#else
-      FCDSceneNodeList roots;
-      ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes(roots);
-#endif
-      for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-        daeegg_cat.spam() << "Found referenced skeleton root " << FROM_FSTRING((*it)->GetDaeId()) << endl;
-        _skeletons.push_back(FROM_FSTRING((*it)->GetDaeId()));
-      }
-    }
-  }
-  // Now loop through the children and recurse.
-  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    preprocess(node->GetChild(ch));
-  }
-}
-
 // Process the node. If forced is true, it will even process it if its known to be a skeleton root.
-void DAEToEggConverter::process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced) {
+void DAEToEggConverter::
+process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) {
   nassertv(node != NULL);
   string node_id = FROM_FSTRING(node->GetDaeId());
   daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl;
-  // Important! If it's known to be a skeleton root, ignore it for now, unless we're processing forced.
-  if (!forced && count(_skeletons.begin(), _skeletons.end(), node_id) > 0) {
-    daeegg_cat.spam() << "Ignoring skeleton root node with ID '" << node_id << "', we'll process it later" << endl;
-    return;
-  }
+
   // Create an egg group for this node
-  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetName()));
+  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetDaeId()));
   process_extra(node_group, node->GetExtra());
   parent->add_child(node_group);
+
   // Check if its a joint
   if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
     node_group->set_group_type(EggGroup::GT_joint);
-    _joints[node_id] = node_group;
+
+    if (!_joints.insert(DaeCharacter::JointMap::value_type(sid,
+                        DaeCharacter::Joint(node_group, node))).second) {
+      daeegg_cat.error()
+        << "Joint with sid " << sid << " occurs more than once!\n";
+    }
   }
-  // Loop through the transforms and apply them
-  for (size_t tr = 0; tr < node->GetTransformCount(); ++tr) {
-    apply_transform(node_group, node->GetTransform(tr));
+
+  // Loop through the transforms and apply them (in reverse order)
+  for (size_t tr = node->GetTransformCount(); tr > 0; --tr) {
+    apply_transform(node_group, node->GetTransform(tr - 1));
   }
+  //node_group->set_transform3d(convert_matrix(node->ToMatrix()));
+
   // Loop through the instances and process them
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
     process_instance(node_group, node->GetInstance(in));
   }
+
   // Loop through the children and recursively process them
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
     process_node(DCAST(EggGroupNode, node_group), node->GetChild(ch));
   }
+
   // Loop through any possible scene node instances and process those, too.
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetEntity() && node->GetInstance(in)->GetEntity()->GetType() == FCDEntity::SCENE_NODE) {
-      process_node(DCAST(EggGroupNode, node_group), (const FCDSceneNode*) node->GetInstance(in)->GetEntity());
+    const FCDEntity *entity = node->GetInstance(in)->GetEntity();
+    if (entity && entity->GetType() == FCDEntity::SCENE_NODE) {
+      process_node(node_group, (const FCDSceneNode*) entity);
     }
   }
 }
 
-void DAEToEggConverter::process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance) {
+void DAEToEggConverter::
+process_instance(EggGroup *parent, const FCDEntityInstance* instance) {
   nassertv(instance != NULL);
   nassertv(instance->GetEntity() != NULL);
   // Check what kind of instance this is
   switch (instance->GetType()) {
-    case FCDEntityInstance::GEOMETRY: {
-      const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
-      assert(geometry != NULL);
-      if (geometry->IsMesh()) {
-        // Now, handle the mesh.
-        process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
-      }
-      if (geometry->IsSpline()) {
-        process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+  case FCDEntityInstance::GEOMETRY:
+    {
+      if (get_animation_convert() != AC_chan) {
+        const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
+        assert(geometry != NULL);
+        if (geometry->IsMesh()) {
+          // Now, handle the mesh.
+          process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+        }
+        if (geometry->IsSpline()) {
+          process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+        }
       }
-      break; }
-    case FCDEntityInstance::CONTROLLER: {
-      // Add the dart tag and process the controller instance
-      parent->set_dart_type(EggGroup::DT_default);
-      process_controller(parent, (const FCDControllerInstance*) instance);
-      break; }
-    case FCDEntityInstance::MATERIAL:
-      // We don't process this directly, handled per-geometry instead.
-      break;
-    case FCDEntityInstance::SIMPLE: {
-      // Grab the entity and check it's type.
+    }
+    break;
+
+  case FCDEntityInstance::CONTROLLER:
+    // Add the dart tag and process the controller instance
+    //parent->set_dart_type(EggGroup::DT_default);
+    process_controller(parent, (const FCDControllerInstance*) instance);
+    break;
+
+  case FCDEntityInstance::MATERIAL:
+    // We don't process this directly, handled per-geometry instead.
+    break;
+
+  case FCDEntityInstance::SIMPLE:
+    {
+      // Grab the entity and check its type.
       const FCDEntity* entity = instance->GetEntity();
       if (entity->GetType() != FCDEntity::SCENE_NODE) {
         daeegg_cat.warning() << "Unsupported entity type found" << endl;
       }
-      break; }
-    default:
-      daeegg_cat.warning() << "Unsupported instance type found" << endl;
+    }
+    break;
+
+  default:
+    daeegg_cat.warning() << "Unsupported instance type found" << endl;
   }
 }
 
 // Processes the given mesh.
-void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials) {
+void DAEToEggConverter::
+process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+             DaeMaterials *materials, DaeCharacter *character) {
+
   nassertv(mesh != NULL);
   daeegg_cat.debug() << "Processing mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << endl;
-  
+
   // Create the egg stuff to hold this mesh
   PT(EggGroup) mesh_group = new EggGroup(FROM_FSTRING(mesh->GetDaeId()));
   parent->add_child(mesh_group);
   PT(EggVertexPool) mesh_pool = new EggVertexPool(FROM_FSTRING(mesh->GetDaeId()));
   mesh_group->add_child(mesh_pool);
-  _vertex_pools[FROM_FSTRING(mesh->GetDaeId())] = mesh_pool;
-  
+
   // First retrieve the vertex source
   if (mesh->GetSourceCount() == 0) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl;
     return;
   }
-  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);  
+  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);
   if (vsource == NULL) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no source for POSITION data" << endl;
     return;
   }
-  
+
   // Loop through the polygon groups and add them
   daeegg_cat.spam() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has " << mesh->GetPolygonsCount() << " polygon groups" << endl;
   if (mesh->GetPolygonsCount() == 0) return;
-  
+
   // This is an array of pointers, I know. But since they are refcounted, I don't have a better idea.
   PT(EggGroup) *primitive_holders = new PT(EggGroup) [mesh->GetPolygonsCount()];
   for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
     const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
+    string material_semantic = FROM_FSTRING(polygons->GetMaterialSemantic());
+
     // Stores which group holds the primitives.
     PT(EggGroup) primitiveholder;
     // If we have materials, make a group for each material. Then, apply the material's per-group stuff.
     if (materials != NULL && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) {
-      primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + FROM_FSTRING(polygons->GetMaterialSemantic()));
+      //primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + material_semantic);
+      primitiveholder = new EggGroup;
       mesh_group->add_child(primitiveholder);
     } else {
       primitiveholder = mesh_group;
@@ -357,7 +529,7 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     primitive_holders[gr] = primitiveholder;
     // Apply the per-group data of the materials, if we have it.
     if (materials != NULL) {
-      materials->apply_to_group(FROM_FSTRING(polygons->GetMaterialSemantic()), primitiveholder, _invert_transparency);
+      materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency);
     }
     // Find the position sources
     const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION);
@@ -389,26 +561,47 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     const uint32* tindices;
     if (tinput != NULL) tindices = tinput->GetIndices();
     // Get a name for potential coordinate sets
-    string tcsetname ("");
+    string tcsetname;
     if (materials != NULL && tcinput != NULL) {
-      daeegg_cat.debug() << "Assigning texcoord set " << tcinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tcsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texcoord set " << tcinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tcsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
     }
-    string tbsetname ("");
+    string tbsetname;
     if (materials != NULL && binput != NULL) {
-      daeegg_cat.debug() << "Assigning texbinormal set " << binput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tbsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texbinormal set " << binput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tbsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
     }
-    string ttsetname ("");
+    string ttsetname;
     if (materials != NULL && tinput != NULL) {
-      daeegg_cat.debug() << "Assigning textangent set " << tinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      ttsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning textangent set " << tinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+        }
+      ttsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
     }
     // Loop through the indices and add the vertices.
     for (size_t ix = 0; ix < pinput->GetIndexCount(); ++ix) {
       PT_EggVertex vertex = mesh_pool->make_new_vertex();
       const float* data = &vsource->GetData()[indices[ix]*3];
       vertex->set_pos(LPoint3d(data[0], data[1], data[2]));
+
+      if (character != NULL) {
+        // If this is skinned geometry, add the vertex influences.
+        character->influence_vertex(indices[ix], vertex);
+      }
+
       // Process the normal
       if (nsource != NULL && ninput != NULL) {
         assert(nsource->GetStride() == 3);
@@ -469,26 +662,26 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
       PT(EggPrimitive) primitive = NULL;
       // Create a primitive that matches the fcollada type
       switch (polygons->GetPrimitiveType()) {
-        case FCDGeometryPolygons::LINES:
-          primitive = new EggLine();
-          break;
-        case FCDGeometryPolygons::POLYGONS:
-          primitive = new EggPolygon();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_FANS:
-          primitive = new EggTriangleFan();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_STRIPS:
-          primitive = new EggTriangleStrip();
-          break;
-        case FCDGeometryPolygons::POINTS:
-          primitive = new EggPoint();
-          break;
-        case FCDGeometryPolygons::LINE_STRIPS:
-          daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
-          break;
-        default:
-          daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
+      case FCDGeometryPolygons::LINES:
+        primitive = new EggLine();
+        break;
+      case FCDGeometryPolygons::POLYGONS:
+        primitive = new EggPolygon();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_FANS:
+        primitive = new EggTriangleFan();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_STRIPS:
+        primitive = new EggTriangleStrip();
+        break;
+      case FCDGeometryPolygons::POINTS:
+        primitive = new EggPoint();
+        break;
+      case FCDGeometryPolygons::LINE_STRIPS:
+        daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
+        break;
+      default:
+        daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
       }
       if (primitive != NULL) {
         primitive_holders[gr]->add_child(primitive);
@@ -506,7 +699,8 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
   delete[] primitive_holders;
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) {
   assert(geometry_spline != NULL);
   PT(EggGroup) result = new EggGroup(group_name);
   parent->add_child(result);
@@ -521,7 +715,8 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_n
   }
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const FCDSpline* spline) {
   assert(spline != NULL);
   nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS);
   // Now load in the nurbs curve to the egg library
@@ -542,70 +737,26 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spl
   }
 }
 
-void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance) {
+void DAEToEggConverter::
+process_controller(EggGroup *parent, const FCDControllerInstance *instance) {
   assert(instance != NULL);
-  const FCDController* controller = (const FCDController*) instance->GetEntity();
+  const FCDController* controller = (const FCDController *)instance->GetEntity();
   assert(controller != NULL);
-  PT(EggVertexPool) vertex_pool = NULL;
-  // Add the skin geometry
-  const FCDGeometry* geometry = controller->GetBaseGeometry();
-  if (geometry != NULL) {
-    if (geometry->IsMesh()) {
-      process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+
+  if (get_animation_convert() == AC_none) {
+    // If we're exporting a static mesh, export the base geometry as-is.
+    const FCDGeometryMesh *mesh = controller->GetBaseGeometry()->GetMesh();
+    if (mesh != NULL) {
+      PT(DaeMaterials) materials = new DaeMaterials(instance);
       daeegg_cat.spam() << "Processing mesh for controller\n";
-      if (_vertex_pools.count(FROM_FSTRING(geometry->GetMesh()->GetDaeId()))) {
-        daeegg_cat.debug() << "Using vertex pool " << FROM_FSTRING(geometry->GetMesh()->GetDaeId()) << "\n";
-        vertex_pool = _vertex_pools[FROM_FSTRING(geometry->GetMesh()->GetDaeId())];
-      }
-    }
-    if (geometry->IsSpline()) {
-      process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
-    }
-  }
-  // Add the joint hierarchy
-#if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes();
-#else
-  FCDSceneNodeList roots;
-  (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes(roots);
-#endif
-  for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_node(DCAST(EggGroupNode, parent), *it, true);
-  }
-  if (controller->IsSkin()) {
-    // Load in the vertex influences first
-    pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > > influences;
-    if (vertex_pool) {
-      for (size_t in = 0; in < controller->GetSkinController()->GetInfluenceCount(); ++in) {
-        assert(vertex_pool->has_vertex(in));
-        for (size_t pa = 0; pa < controller->GetSkinController()->GetVertexInfluence(in)->GetPairCount(); ++pa) {
-          const FCDJointWeightPair* jwpair = controller->GetSkinController()->GetVertexInfluence(in)->GetPair(pa);
-          influences[jwpair->jointIndex].push_back(pair<PT_EggVertex, PN_stdfloat> (vertex_pool->get_vertex(in), jwpair->weight));
-        }
-      }
-    }
-    // Loop through the joints in the vertex influences
-    for (pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > >::iterator it = influences.begin(); it != influences.end(); ++it) {
-      if (it->first == -1) {
-        daeegg_cat.warning() << "Ignoring vertex influence with negative joint index\n";
-        //FIXME: Why are there joints with index -1
-      } else {
-        const string joint_id = FROM_FSTRING(controller->GetSkinController()->GetJoint(it->first)->GetId());
-        //TODO: what if the joints have just not been defined yet?
-        if (_joints.count(joint_id) > 0) {
-          if (_joints[joint_id]) {
-            for (pvector<pair<PT_EggVertex, PN_stdfloat> >::iterator vi = it->second.begin(); vi != it->second.end(); ++vi) {
-              _joints[joint_id]->ref_vertex(vi->first, vi->second);
-            }
-          } else {
-            daeegg_cat.warning() << "Unprocessed joint being referenced: '" << joint_id << "'" << endl;
-          }
-        } else {
-          daeegg_cat.warning() << "Unknown joint being referenced: '" << joint_id << "'" << endl;
-        }
-      }
+      process_mesh(parent, mesh, materials);
     }
+  } else {
+    // Add a character for this to the table, the mesh is processed later
+    PT(DaeCharacter) character = new DaeCharacter(parent, instance);
+    _characters.push_back(character);
   }
+
   if (controller->IsMorph()) {
     assert(controller != NULL);
     const FCDMorphController* morph_controller = controller->GetMorphController();
@@ -628,28 +779,25 @@ void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControl
       morph->add_child(target);
     }
   }
-  
-  // Get a <Bundle> for the character and add it to the table
-  PT(DaeCharacter) character = new DaeCharacter(parent->get_name(), instance);
-  _table->add_child(character->as_egg_bundle());
 }
 
-void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra) {
+void DAEToEggConverter::
+process_extra(EggGroup *group, const FCDExtra* extra) {
   if (extra == NULL) {
     return;
   }
   nassertv(group != NULL);
-  
+
   const FCDEType* etype = extra->GetDefaultType();
   if (etype == NULL) {
     return;
   }
-  
+
   const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D");
   if (enode == NULL) {
     return;
   }
-  
+
   FCDENodeList tags;
   enode->FindChildrenNodes("param", tags);
   for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) {
@@ -660,18 +808,45 @@ void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra)
   }
 }
 
-LMatrix4d DAEToEggConverter::convert_matrix(const FMMatrix44& matrix) {
-  LMatrix4d result = LMatrix4d::zeros_mat();
-  for (char x = 0; x < 4; ++x) {
-    for (char y = 0; y < 4; ++y) {
-      result(x, y) = matrix[x][y];
-    }
-  }
-  return result;
+LMatrix4d DAEToEggConverter::
+convert_matrix(const FMMatrix44 &matrix) {
+  return LMatrix4d(
+    matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
+    matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
+    matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
+    matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]);
 }
 
-void DAEToEggConverter::apply_transform(const PT(EggGroup) to, const FCDTransform* from) {
+void DAEToEggConverter::
+apply_transform(EggGroup *to, const FCDTransform* from) {
   assert(from != NULL);
   assert(to != NULL);
-  to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  //to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  switch (from->GetType()) {
+  case FCDTransform::TRANSLATION:
+    {
+      const FCDTTranslation *trans = (const FCDTTranslation *)from;
+      to->add_translate3d(TO_VEC3(trans->GetTranslation()));
+    }
+    break;
+
+  case FCDTransform::ROTATION:
+    {
+      const FCDTRotation *rot = (const FCDTRotation *)from;
+      to->add_rotate3d(rot->GetAngle(), TO_VEC3(rot->GetAxis()));
+    }
+    break;
+
+  case FCDTransform::SCALE:
+    {
+      const FCDTScale *scale = (const FCDTScale *)from;
+      to->add_scale3d(TO_VEC3(scale->GetScale()));
+    }
+    break;
+
+  default:
+    // Either a matrix, or something we can't handle.
+    to->add_matrix4(convert_matrix(from->ToMatrix()));
+    break;
+  }
 }

+ 23 - 20
pandatool/src/daeegg/daeToEggConverter.h

@@ -35,6 +35,7 @@
 #include "FMath/FMMatrix44.h"
 
 #include "daeMaterials.h"
+#include "daeCharacter.h"
 #include "pvector.h" // Include last
 
 ////////////////////////////////////////////////////////////////////
@@ -47,39 +48,41 @@ public:
   DAEToEggConverter();
   DAEToEggConverter(const DAEToEggConverter &copy);
   ~DAEToEggConverter();
-  
+
   virtual SomethingToEggConverter *make_copy();
-  
+
   virtual string get_name() const;
   virtual string get_extension() const;
-  
+
   virtual bool convert_file(const Filename &filename);
+  virtual DistanceUnit get_input_units();
 
   bool _invert_transparency;
 
 private:
+  string _unit_name;
+  double _unit_meters;
   PT(EggTable) _table;
   FCDocument* _document;
   FUErrorSimpleHandler* _error_handler;
-  pmap<const string, PT(EggGroup)> _joints;
-  pmap<const string, PT(EggVertexPool)> _vertex_pools;
-  pvector<string> _skeletons;
-  int _frame_rate;
-  
+  DaeCharacter::JointMap _joints;
+
+  typedef pvector<PT(DaeCharacter)> Characters;
+  Characters _characters;
+
   void process_asset();
-  void preprocess(const FCDSceneNode* node = NULL);
-  void process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced = false);
-  void process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance);
-  void process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials);
-  void process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline);
-  void process_spline(PT(EggGroup) parent, const FCDSpline* spline);
-  void process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance);
-  //void process_table_joint(PT(EggTable) parent, FCDSceneNode* node);
-  void process_extra(PT(EggGroup) group, const FCDExtra* extra);
-  
+  void process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced = false);
+  void process_instance(EggGroup *parent, const FCDEntityInstance* instance);
+  void process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+                    DaeMaterials *materials, DaeCharacter *character = NULL);
+  void process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline);
+  void process_spline(EggGroup *parent, const FCDSpline* spline);
+  void process_controller(EggGroup *parent, const FCDControllerInstance* instance);
+  void process_extra(EggGroup *group, const FCDExtra* extra);
+
   static LMatrix4d convert_matrix(const FMMatrix44& matrix);
-  void apply_transform(const PT(EggGroup) to, const FCDTransform* from);
-  
+  void apply_transform(EggGroup *to, const FCDTransform* from);
+
   friend class DaeCharacter;
 };
 

+ 7 - 0
pandatool/src/daeegg/pre_fcollada_include.h

@@ -22,6 +22,13 @@
   #error You must include pre_fcollada_include.h before including FCollada.h!
 #endif
 
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <winsock2.h>
+#endif
+
 // FCollada expects LINUX to be defined on linux
 #ifdef IS_LINUX
   #ifndef LINUX

+ 8 - 0
pandatool/src/daeprogs/daeToEgg.cxx

@@ -26,6 +26,7 @@ DAEToEgg::
 DAEToEgg():
   SomethingToEgg("COLLADA", ".dae")
 {
+  add_animation_options();
   add_units_options();
   add_normals_options();
   add_transform_options();
@@ -42,6 +43,7 @@ DAEToEgg():
     ("This program converts .dae files (COLLADA Digital Asset Exchange) to .egg.");
 
   _coordinate_system = CS_yup_right;
+  _animation_convert = AC_both;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -51,6 +53,12 @@ DAEToEgg():
 ////////////////////////////////////////////////////////////////////
 void DAEToEgg::
 run() {
+  if (_animation_convert != AC_both && _animation_convert != AC_none &&
+      _animation_convert != AC_chan && _animation_convert != AC_model) {
+    cerr << "Unsupported animation convert option.\n";
+    exit(1);
+  }
+
   nout << "Reading " << _input_filename << "\n";
 
   _data->set_coordinate_system(_coordinate_system);