Browse Source

Merge branch 'master' into webgl-port

rdb 7 years ago
parent
commit
9cf0328849
100 changed files with 5794 additions and 591 deletions
  1. 5 1
      .gitignore
  2. 9 1
      .travis.yml
  3. 27 9
      README.md
  4. 1 1
      contrib/src/ai/aiCharacter.h
  5. 1 1
      contrib/src/ai/aiNode.h
  6. 1 1
      contrib/src/ai/flock.h
  7. 51 0
      contrib/src/panda3dtoolsgui/build_exe.bat
  8. 40 0
      contrib/src/rplight/config_rplight.h
  9. 185 0
      contrib/src/rplight/gpuCommand.I
  10. 87 0
      contrib/src/rplight/gpuCommand.cxx
  11. 89 0
      contrib/src/rplight/gpuCommand.h
  12. 82 0
      contrib/src/rplight/gpuCommandList.cxx
  13. 54 0
      contrib/src/rplight/gpuCommandList.h
  14. 233 0
      contrib/src/rplight/iesDataset.cxx
  15. 68 0
      contrib/src/rplight/iesDataset.h
  16. 140 0
      contrib/src/rplight/internalLightManager.I
  17. 441 0
      contrib/src/rplight/internalLightManager.cxx
  18. 102 0
      contrib/src/rplight/internalLightManager.h
  19. 13 0
      contrib/src/rplight/p3rplight_composite1.cxx
  20. 239 0
      contrib/src/rplight/pointerSlotStorage.h
  21. 243 0
      contrib/src/rplight/pssmCameraRig.I
  22. 396 0
      contrib/src/rplight/pssmCameraRig.cxx
  23. 125 0
      contrib/src/rplight/pssmCameraRig.h
  24. 406 0
      contrib/src/rplight/rpLight.I
  25. 137 0
      contrib/src/rplight/rpLight.cxx
  26. 130 0
      contrib/src/rplight/rpLight.h
  27. 82 0
      contrib/src/rplight/rpPointLight.I
  28. 90 0
      contrib/src/rplight/rpPointLight.cxx
  29. 63 0
      contrib/src/rplight/rpPointLight.h
  30. 74 0
      contrib/src/rplight/rpSpotLight.I
  31. 80 0
      contrib/src/rplight/rpSpotLight.cxx
  32. 71 0
      contrib/src/rplight/rpSpotLight.h
  33. 159 0
      contrib/src/rplight/shadowAtlas.I
  34. 186 0
      contrib/src/rplight/shadowAtlas.cxx
  35. 80 0
      contrib/src/rplight/shadowAtlas.h
  36. 192 0
      contrib/src/rplight/shadowManager.I
  37. 157 0
      contrib/src/rplight/shadowManager.cxx
  38. 91 0
      contrib/src/rplight/shadowManager.h
  39. 262 0
      contrib/src/rplight/shadowSource.I
  40. 41 0
      contrib/src/rplight/shadowSource.cxx
  41. 93 0
      contrib/src/rplight/shadowSource.h
  42. 93 0
      contrib/src/rplight/tagStateManager.I
  43. 202 0
      contrib/src/rplight/tagStateManager.cxx
  44. 93 0
      contrib/src/rplight/tagStateManager.h
  45. 4 4
      contrib/src/sceneeditor/seFileSaver.py
  46. 1 8
      contrib/src/sceneeditor/seLights.py
  47. 0 10
      direct/metalibs/direct/direct.cxx
  48. 3 3
      direct/src/actor/Actor.py
  49. 6 85
      direct/src/dcparser/dcClass.cxx
  50. 0 5
      direct/src/dcparser/dcClass.h
  51. 2 2
      direct/src/dcparser/dcField.cxx
  52. 1 1
      direct/src/dcparser/dcPacker.cxx
  53. 2 0
      direct/src/dcparser/dcSimpleParameter.cxx
  54. 0 6
      direct/src/dcparser/dcbase.h
  55. 6 9
      direct/src/dcparser/dcmsgtypes.h
  56. 4 0
      direct/src/deadrec/config_deadrec.cxx
  57. 2 2
      direct/src/deadrec/config_deadrec.h
  58. 1 1
      direct/src/deadrec/smoothMover.h
  59. 54 4
      direct/src/directbase/directsymbols.h
  60. 4 0
      direct/src/directd/directd.cxx
  61. 1 1
      direct/src/directd/directd.h
  62. 2 2
      direct/src/directtools/DirectSelection.py
  63. 2 0
      direct/src/distributed/CRDataCache.py
  64. 3 3
      direct/src/distributed/ClientRepositoryBase.py
  65. 5 4
      direct/src/distributed/ConnectionRepository.py
  66. 2 0
      direct/src/distributed/DistributedNode.py
  67. 0 1
      direct/src/distributed/DistributedNodeUD.py
  68. 7 15
      direct/src/distributed/DistributedObjectAI.py
  69. 8 1
      direct/src/distributed/DistributedObjectBase.py
  70. 2 2
      direct/src/distributed/DistributedObjectUD.py
  71. 13 9
      direct/src/distributed/DoInterestManager.py
  72. 133 97
      direct/src/distributed/MsgTypes.py
  73. 1 1
      direct/src/distributed/MsgTypesCMU.py
  74. 0 208
      direct/src/distributed/OldClientRepository.py
  75. 5 8
      direct/src/distributed/PyDatagram.py
  76. 1 5
      direct/src/distributed/PyDatagramIterator.py
  77. 4 2
      direct/src/distributed/ServerRepository.py
  78. 13 11
      direct/src/distributed/cConnectionRepository.cxx
  79. 3 3
      direct/src/distributed/cConnectionRepository.h
  80. 2 2
      direct/src/distributed/cDistributedSmoothNodeBase.cxx
  81. 1 1
      direct/src/distributed/cDistributedSmoothNodeBase.h
  82. 4 0
      direct/src/distributed/config_distributed.cxx
  83. 2 2
      direct/src/distributed/config_distributed.h
  84. 7 1
      direct/src/filter/CommonFilters.py
  85. 11 0
      direct/src/gui/DirectEntry.py
  86. 22 23
      direct/src/gui/DirectGuiBase.py
  87. 1 1
      direct/src/interval/ActorInterval.py
  88. 3 1
      direct/src/interval/Interval.py
  89. 0 1
      direct/src/interval/MetaInterval.py
  90. 4 4
      direct/src/interval/cConstrainHprInterval.h
  91. 5 5
      direct/src/interval/cConstrainPosHprInterval.h
  92. 4 4
      direct/src/interval/cConstrainPosInterval.h
  93. 4 4
      direct/src/interval/cConstrainTransformInterval.h
  94. 1 1
      direct/src/interval/cConstraintInterval.h
  95. 2 2
      direct/src/interval/cInterval.h
  96. 1 1
      direct/src/interval/cIntervalManager.h
  97. 3 3
      direct/src/interval/cLerpAnimEffectInterval.h
  98. 1 1
      direct/src/interval/cLerpInterval.h
  99. 5 5
      direct/src/interval/cLerpNodePathInterval.h
  100. 2 2
      direct/src/interval/cMetaInterval.h

+ 5 - 1
.gitignore

@@ -52,6 +52,10 @@ Thumbs.db
 ehthumbs.db
 ehthumbs.db
 
 
 # Python
 # Python
-__pycache__
+__pycache__/
 *.pyc
 *.pyc
 *.pyo
 *.pyo
+
+# Test tool cache directories
+.tox/
+.cache/

+ 9 - 1
.travis.yml

@@ -34,8 +34,16 @@ addons:
     - nvidia-cg-toolkit
     - nvidia-cg-toolkit
     - python-dev
     - python-dev
     - python3-dev
     - python3-dev
+    - python-virtualenv
     - zlib1g-dev
     - zlib1g-dev
-script: $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4 && LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
+    - fakeroot
+install:
+    - virtualenv --python=$PYTHONV venv && source venv/bin/activate
+    - $PYTHONV -m pip install pytest
+script:
+    - $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
+    - LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
+    - LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest tests
 notifications:
 notifications:
   irc:
   irc:
     channels:
     channels:

+ 27 - 9
README.md

@@ -28,6 +28,8 @@ into an existing Python installation is using the following command:
 pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
 pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
 ```
 ```
 
 
+If this command fails, please make sure your version of pip is up-to-date.
+
 If you prefer to install the full SDK with all tools, the latest development
 If you prefer to install the full SDK with all tools, the latest development
 builds can be obtained from this page:
 builds can be obtained from this page:
 
 
@@ -41,21 +43,26 @@ Building Panda3D
 Windows
 Windows
 -------
 -------
 
 
-We currently build using the Microsoft Visual C++ 2010 compiler.  You do not
-need Microsoft Visual Studio to build Panda3D, though - the relevant compilers
-are included as part of the Windows 7.1 SDK.
+We currently build using the Microsoft Visual C++ 2015 compiler.  You will
+also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
+and if you intend to target Windows XP, you will also need the
+[Windows 7.1 SDK](https://www.microsoft.com/en-us/download/details.aspx?id=8279).
 
 
 You will also need to have the third-party dependency libraries available for
 You will also need to have the third-party dependency libraries available for
 the build scripts to use.  These are available from one of these two URLs,
 the build scripts to use.  These are available from one of these two URLs,
-depending on whether you are on a 32-bit or 64-bit system:
-https://www.panda3d.org/download/panda3d-1.9.4/panda3d-1.9.4-tools-win32.zip
-https://www.panda3d.org/download/panda3d-1.9.4/panda3d-1.9.4-tools-win64.zip
+depending on whether you are on a 32-bit or 64-bit system, or you can
+[click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
+building them from source.
+
+http://rdb.name/thirdparty-vc14-x64.7z
+http://rdb.name/thirdparty-vc14.7z
 
 
 After acquiring these dependencies, you may simply build Panda3D from the
 After acquiring these dependencies, you may simply build Panda3D from the
-command prompt using the following command:
+command prompt using the following command.  (Add the `--windows-sdk=10`
+option if you don't need to support Windows XP.)
 
 
 ```bash
 ```bash
-makepanda\makepanda.bat --everything --installer --no-eigen
+makepanda\makepanda.bat --everything --installer --no-eigen --threads=2
 ```
 ```
 
 
 When the build succeeds, it will produce an .exe file that you can use to
 When the build succeeds, it will produce an .exe file that you can use to
@@ -156,13 +163,24 @@ python3.6 makepanda/makepanda.py --everything --installer --no-egl --no-gles --n
 If successful, this will produce a .pkg file in the root of the source
 If successful, this will produce a .pkg file in the root of the source
 directory which you can install using `pkg install`.
 directory which you can install using `pkg install`.
 
 
+Running Tests
+=============
+
+Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
+and run the `pytest` command.  If you have not installed Panda3D, you will
+need to configure your enviroment by pointing the `PYTHONPATH` variable at
+the `built` directory.  On Linux, you will also need to point the
+`LD_LIBRARY_PATH` variable at the `built/lib` directory.
+
+As a convenience, you can alternatively pass the `--tests` option to makepanda.
+
 Reporting Issues
 Reporting Issues
 ================
 ================
 
 
 If you encounter any bugs when using Panda3D, please report them in the bug
 If you encounter any bugs when using Panda3D, please report them in the bug
 tracker.  This is hosted at:
 tracker.  This is hosted at:
 
 
-  https://bugs.launchpad.net/panda3d
+  https://github.com/panda3d/panda3d/issues
 
 
 Make sure to first use the search function to see if the bug has already been
 Make sure to first use the search function to see if the bug has already been
 reported.  When filling out a bug report, make sure that you include as much
 reported.  When filling out a bug report, make sure that you include as much

+ 1 - 1
contrib/src/ai/aiCharacter.h

@@ -62,7 +62,7 @@ PUBLISHED:
     // This function is used to enable or disable the guides for path finding.
     // This function is used to enable or disable the guides for path finding.
     void set_pf_guide(bool pf_guide);
     void set_pf_guide(bool pf_guide);
 
 
-    AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
+    explicit AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
     ~AICharacter();
     ~AICharacter();
 };
 };
 
 

+ 1 - 1
contrib/src/ai/aiNode.h

@@ -66,7 +66,7 @@ public:
   AINode *_next;
   AINode *_next;
 
 
 PUBLISHED:
 PUBLISHED:
-  AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
+  explicit AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
   ~AINode();
   ~AINode();
 
 
   bool contains(float x, float y);
   bool contains(float x, float y);

+ 1 - 1
contrib/src/ai/flock.h

@@ -44,7 +44,7 @@ public:
   AICharList _ai_char_list;
   AICharList _ai_char_list;
 
 
 PUBLISHED:
 PUBLISHED:
-  Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
+  explicit Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
     unsigned int cohesion_wt = 4, unsigned int alignment_wt = 1);
     unsigned int cohesion_wt = 4, unsigned int alignment_wt = 1);
   ~Flock();
   ~Flock();
 
 

+ 51 - 0
contrib/src/panda3dtoolsgui/build_exe.bat

@@ -0,0 +1,51 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "config_rplight.h"
+
+#include "rpLight.h"
+#include "rpPointLight.h"
+
+#include "dconfig.h"
+
+Configure(config_rplight);
+NotifyCategoryDef(rplight, "");
+
+ConfigureFn(config_rplight) {
+  init_librplight();
+}
+
+void
+init_librplight() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  // RPLight::init_type();
+  // RPPointLight::init_type();
+}

+ 40 - 0
contrib/src/rplight/config_rplight.h

@@ -0,0 +1,40 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef CONFIG_RPLIGHT_H
+#define CONFIG_RPLIGHT_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+#include "configVariableDouble.h"
+#include "configVariableString.h"
+#include "configVariableInt.h"
+
+NotifyCategoryDecl(rplight, EXPORT_CLASS, EXPORT_TEMPL);
+
+extern void init_librplight();
+
+#endif // CONFIG_RPLIGHT_H

+ 185 - 0
contrib/src/rplight/gpuCommand.I

@@ -0,0 +1,185 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "stdint.h"
+
+/**
+ * @brief Appends an integer to the GPUCommand.
+ * @details This adds an integer to the back of the GPUCommand. Depending on the
+ *   setting in convert_int_to_float, this will either just convert the int to a
+ *   float by casting it, or just do a bitwise copy.
+ *
+ * @param v The integer to append.
+ */
+inline void GPUCommand::push_int(int v) {
+  push_float(convert_int_to_float(v));
+}
+
+/**
+ * @brief Internal method to convert an integer to float
+ * @details This methods gets called by the GPUCommand::push_int, and manages
+ *   storing an integer in a floating point variable. There are two options,
+ *   which are documented inside of the method.
+ *
+ * @param v Integer to convert
+ * @return Float-representation of that integer, either casted or binary converted.s
+ */
+inline float GPUCommand::convert_int_to_float(int v) const {
+
+  #if !PACK_INT_AS_FLOAT
+    // Just round to float, can cause rounding issues tho
+    return (float)v;
+
+  #else
+    assert(sizeof(float) == 4); // We really need this for packing! Better
+                  // throw an error if the compiler uses more
+                  // than 4 bytes.
+    // Simple binary conversion, assuming sizeof(int) == sizeof(float)
+    union { int32_t _int; float _float; } converter = { (int32_t)v };
+    return converter._float;
+  #endif
+}
+
+/**
+ * @brief Appends a float to the GPUCommand.
+ * @details This adds an integer to the back of the GPUCommand. Its used by all
+ *   other push_xxx methods, and simply stores the value, then increments the write
+ *   pointer. When the amount of floats exceeds the capacity of the GPUCommand,
+ *   an error will be printed, and the method returns without doing anything else.
+ *
+ * @param v The float to append.
+ */
+inline void GPUCommand::push_float(float v) {
+  if (_current_index >= GPU_COMMAND_ENTRIES) {
+    gpucommand_cat.error() << "Out of bounds! Exceeded command size of " << GPU_COMMAND_ENTRIES << endl;
+    return;
+  }
+  _data[_current_index++] = v;
+}
+
+/**
+ * @brief Appends a 3-component floating point vector to the GPUCommand.
+ * @details This appends a 3-component floating point vector to the command.
+ *   It basically just calls push_float() for every component, in the order
+ *   x, y, z, which causes the vector to occupy the space of 3 floats.
+ *
+ * @param v Int-Vector to append.
+ */
+inline void GPUCommand::push_vec3(const LVecBase3 &v) {
+  push_float(v.get_x());
+  push_float(v.get_y());
+  push_float(v.get_z());
+}
+
+
+/**
+ * @brief Appends a 3-component integer vector to the GPUCommand.
+ * @details This appends a 3-component integer vector to the command.
+ *   It basically just calls push_int() for every component, in the order
+ *   x, y, z, which causes the vector to occupy the space of 3 floats.
+ *
+ * @param v Int-Vector to append.
+ */
+inline void GPUCommand::push_vec3(const LVecBase3i &v) {
+  push_int(v.get_x());
+  push_int(v.get_y());
+  push_int(v.get_z());
+}
+
+/**
+ * @brief Appends a 4-component floating point vector to the GPUCommand.
+ * @details This appends a 4-component floating point vector to the command.
+ *   It basically just calls push_float() for every component, in the order
+ *   x, y, z, which causes the vector to occupy the space of 3 floats.
+ *
+ * @param v Int-Vector to append.
+ */
+inline void GPUCommand::push_vec4(const LVecBase4 &v) {
+  push_float(v.get_x());
+  push_float(v.get_y());
+  push_float(v.get_z());
+  push_float(v.get_w());
+}
+
+/**
+ * @brief Appends a 4-component integer vector to the GPUCommand.
+ * @details This appends a 4-component integer vector to the command.
+ *   It basically just calls push_int() for every component, in the order
+ *   x, y, z, w, which causes the vector to occupy the space of 4 floats.
+ *
+ * @param v Int-Vector to append.
+ */
+inline void GPUCommand::push_vec4(const LVecBase4i &v) {
+  push_int(v.get_x());
+  push_int(v.get_y());
+  push_int(v.get_z());
+  push_int(v.get_w());
+}
+
+/**
+ * @brief Appends a floating point 3x3 matrix to the GPUCommand.
+ * @details This appends a floating point 3x3 matrix to the GPUCommand, by
+ *   pushing all components in row-order to the command. This occupies a space of
+ *   9 floats.
+ *
+ * @param v Matrix to append
+ */
+inline void GPUCommand::push_mat3(const LMatrix3 &v) {
+  for (size_t i = 0; i < 3; ++i) {
+    for (size_t j = 0; j < 3; ++j) {
+      push_float(v.get_cell(i, j));
+    }
+  }
+}
+
+/**
+ * @brief Appends a floating point 4x4 matrix to the GPUCommand.
+ * @details This appends a floating point 4x4 matrix to the GPUCommand, by
+ *   pushing all components in row-order to the command. This occupies a space of
+ *   16 floats.
+ *
+ * @param v Matrix to append
+ */
+inline void GPUCommand::push_mat4(const LMatrix4 &v) {
+  for (size_t i = 0; i < 4; ++i) {
+    for (size_t j = 0; j < 4; ++j) {
+      push_float(v.get_cell(i, j));
+    }
+  }
+}
+
+/**
+ * @brief Returns whether integers are packed as floats.
+ * @details This returns how integer are packed into the data stream. If the
+ *   returned value is true, then integers are packed using their binary
+ *   representation converted to floating point format. If the returned value
+ *   is false, then integers are packed by simply casting them to float,
+ *   e.g. val = (float)i;
+ * @return The integer representation flag
+ */
+inline bool GPUCommand::get_uses_integer_packing() {
+  return PACK_INT_AS_FLOAT;
+}

+ 87 - 0
contrib/src/rplight/gpuCommand.cxx

@@ -0,0 +1,87 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "gpuCommand.h"
+
+#include <iostream>
+#include <iomanip>
+#include <stdlib.h>
+
+
+NotifyCategoryDef(gpucommand, "");
+
+/**
+ * @brief Constructs a new GPUCommand with the given command type.
+ * @details This will construct a new GPUCommand of the given command type.
+ *   The command type should be of GPUCommand::CommandType, and determines
+ *   what data the GPUCommand contains, and how it will be handled.
+ *
+ * @param command_type The type of the GPUCommand
+ */
+GPUCommand::GPUCommand(CommandType command_type) {
+  _command_type = command_type;
+  _current_index = 0;
+  memset(_data, 0x0, sizeof(float) * GPU_COMMAND_ENTRIES);
+
+  // Store the command type as the first entry
+  push_int(command_type);
+}
+
+/**
+ * @brief Prints out the GPUCommand to the console
+ * @details This method prints the type, size, and data of the GPUCommand to the
+ *   console. This helps for debugging the contents of the GPUCommand. Keep
+ *   in mind that integers might be shown in their binary float representation,
+ *   depending on the setting in the GPUCommand::convert_int_to_float method.
+ */
+void GPUCommand::write(ostream &out) const {
+  out << "GPUCommand(type=" << _command_type << ", size=" << _current_index << ", data = {" << endl;
+  for (size_t k = 0; k < GPU_COMMAND_ENTRIES; ++k) {
+    out << std::setw(12) << std::fixed << std::setprecision(5) << _data[k] << " ";
+    if (k % 6 == 5 || k == GPU_COMMAND_ENTRIES - 1) out << endl;
+  }
+  out << "})" << endl;
+}
+
+/**
+ * @brief Writes the GPU command to a given target.
+ * @details This method writes all the data of the GPU command to a given target.
+ *   The target should be a pointer to memory being big enough to hold the
+ *   data. Presumably #dest will be a handle to texture memory.
+ *   The command_index controls the offset where the data will be written
+ *   to.
+ *
+ * @param dest Handle to the memory to write the command to
+ * @param command_index Offset to write the command to. The command will write
+ *   its data to command_index * GPU_COMMAND_ENTRIES. When writing
+ *   the GPUCommand in a GPUCommandList, the command_index will
+ *   most likely be the index of the command in the list.
+ */
+void GPUCommand::write_to(const PTA_uchar &dest, size_t command_index) {
+  size_t command_size = GPU_COMMAND_ENTRIES * sizeof(float);
+  size_t offset = command_index * command_size;
+  memcpy(dest.p() + offset, &_data, command_size);
+}

+ 89 - 0
contrib/src/rplight/gpuCommand.h

@@ -0,0 +1,89 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef GPUCOMMAND_H
+#define GPUCOMMAND_H
+
+#include "pandabase.h"
+#include "luse.h"
+
+NotifyCategoryDecl(gpucommand, EXPORT_CLASS, EXPORT_TEMPL);
+
+#define GPU_COMMAND_ENTRIES 32
+
+// Packs integers by storing their binary representation in floats
+// This only works if the command and light buffer is 32bit floating point.
+#define PACK_INT_AS_FLOAT 0
+
+/**
+ * @brief Class for storing data to be transferred to the GPU.
+ * @details This class can be seen like a packet, to be transferred to the GPU.
+ *   It has a command type, which tells the GPU what to do once it recieved this
+ *   "packet". It stores a limited amount of floating point components.
+ */
+class GPUCommand {
+PUBLISHED:
+  /**
+   * The different types of GPUCommands. Each type has a special case in
+   * the command queue processor. When adding new types, those need to
+   * be handled in the command target, too.
+   */
+  enum CommandType {
+    CMD_invalid = 0,
+    CMD_store_light = 1,
+    CMD_remove_light = 2,
+    CMD_store_source = 3,
+    CMD_remove_sources = 4,
+  };
+
+  GPUCommand(CommandType command_type);
+
+  inline void push_int(int v);
+  inline void push_float(float v);
+  inline void push_vec3(const LVecBase3 &v);
+  inline void push_vec3(const LVecBase3i &v);
+  inline void push_vec4(const LVecBase4 &v);
+  inline void push_vec4(const LVecBase4i &v);
+  inline void push_mat3(const LMatrix3 &v);
+  inline void push_mat4(const LMatrix4 &v);
+
+  inline static bool get_uses_integer_packing();
+
+  void write_to(const PTA_uchar &dest, size_t command_index);
+  void write(ostream &out) const;
+
+private:
+
+  inline float convert_int_to_float(int v) const;
+
+  CommandType _command_type;
+  size_t _current_index;
+  float _data[GPU_COMMAND_ENTRIES];
+};
+
+#include "gpuCommand.I"
+
+#endif // GPUCOMMAND_H

+ 82 - 0
contrib/src/rplight/gpuCommandList.cxx

@@ -0,0 +1,82 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "gpuCommandList.h"
+
+
+/**
+ * @brief Constructs a new GPUCommandList
+ * @details This constructs a new GPUCommandList. By default, there are no commands
+ *   in the list.
+ */
+GPUCommandList::GPUCommandList() {
+}
+
+/**
+ * @brief Pushes a GPUCommand to the command list.
+ * @details This adds a new GPUCommand to the list of commands to be processed.
+ *
+ * @param cmd The command to add
+ */
+void GPUCommandList::add_command(const GPUCommand& cmd) {
+  _commands.push(cmd);
+}
+
+/**
+ * @brief Returns the number of commands in this list.
+ * @details This returns the amount of commands which are currently stored in this
+ *   list, and are waiting to get processed.
+ * @return Amount of commands
+ */
+size_t GPUCommandList::get_num_commands() {
+  return _commands.size();
+}
+
+/**
+ * @brief Writes the first n-commands to a destination.
+ * @details This takes the first #limit commands, and writes them to the
+ *   destination using GPUCommand::write_to. See GPUCommand::write_to for
+ *   further information about #dest. The limit controls after how much
+ *   commands the processing will be stopped. All commands which got processed
+ *   will get removed from the list.
+ *
+ * @param dest Destination to write to, see GPUCommand::write_to
+ * @param limit Maximum amount of commands to process
+ *
+ * @return Amount of commands processed, between 0 and #limit.
+ */
+size_t GPUCommandList::write_commands_to(const PTA_uchar &dest, size_t limit) {
+  size_t num_commands_written = 0;
+
+  while (num_commands_written < limit && !_commands.empty()) {
+    // Write the first command to the stream, and delete it afterwards
+    _commands.front().write_to(dest, num_commands_written);
+    _commands.pop();
+    num_commands_written ++;
+  }
+
+  return num_commands_written;
+}

+ 54 - 0
contrib/src/rplight/gpuCommandList.h

@@ -0,0 +1,54 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef GPUCOMMANDLIST_H
+#define GPUCOMMANDLIST_H
+
+#include "pandabase.h"
+#include "gpuCommand.h"
+
+#include <queue>
+
+/**
+ * @brief Class to store a list of commands.
+ * @details This is a class to store a list of GPUCommands. It provides
+ *   functionality to only provide the a given amount of commands at one time.
+ */
+class GPUCommandList {
+PUBLISHED:
+  GPUCommandList();
+
+  void add_command(const GPUCommand& cmd);
+  size_t get_num_commands();
+  size_t write_commands_to(const PTA_uchar &dest, size_t limit = 32);
+
+  MAKE_PROPERTY(num_commands, get_num_commands);
+
+protected:
+  queue<GPUCommand> _commands;
+};
+
+#endif // GPUCOMMANDLIST_H

+ 233 - 0
contrib/src/rplight/iesDataset.cxx

@@ -0,0 +1,233 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "iesDataset.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+NotifyCategoryDef(iesdataset, "")
+
+/**
+ * @brief Constructs a new empty dataset.
+ * @details This constructs a new IESDataset with no data set.
+ */
+IESDataset::IESDataset() {
+}
+
+/**
+ * @brief Sets the vertical angles of the dataset.
+ * @details This sets the list of vertical angles of the dataset.
+ *
+ * @param vertical_angles Vector of all vertical angles.
+ */
+void IESDataset::set_vertical_angles(const PTA_float &vertical_angles) {
+  nassertv(vertical_angles.size() > 0);
+	_vertical_angles = vertical_angles;
+}
+
+/**
+ * @brief Sets the horizontal angles of the dataset.
+ * @details This sets the list of horizontal angles of the dataset.
+ *
+ * @param horizontal_angles Vector of all horizontal angles.
+ */
+void IESDataset::set_horizontal_angles(const PTA_float &horizontal_angles) {
+  nassertv(horizontal_angles.size() > 0);
+	_horizontal_angles = horizontal_angles;
+}
+
+/**
+ * @brief Sets the candela values.
+ * @details This sets the candela values of the dataset. They should be an
+ *   interleaved 2D array with the dimensions vertical_angles x horizontal_angles.
+ *   They also should be normalized by dividing by the maximum entry.
+ * @param candela_values Interleaved 2D-vector of candela values.
+ */
+void IESDataset::set_candela_values(const PTA_float &candela_values) {
+	nassertv(candela_values.size() == _horizontal_angles.size() * _vertical_angles.size());
+	_candela_values = candela_values;
+}
+
+/**
+ * @brief Internal method to access the candela data.
+ * @details This lookups a candela value in the candela values. It converts a
+ *   two dimensional index to a onedimensional index and then returns the candela
+ *   value at that position.
+ *
+ * @param vertical_angle_idx Index of the vertical angle
+ * @param horizontal_angle_idx Index of the horizontal angle
+ *
+ * @return Candela value between 0 .. 1
+ */
+float IESDataset::get_candela_value_from_index(size_t vertical_angle_idx, size_t horizontal_angle_idx) const {
+	size_t index = vertical_angle_idx + horizontal_angle_idx * _vertical_angles.size();
+	nassertr(index >= 0 && index < _candela_values.size(), 0.0);
+	return _candela_values[index];
+}
+
+/**
+ * @brief Samples the dataset at the given position
+ * @details This looks up a value in the dataset, by specifying a horizontal and
+ *   vertical angle. This is used for generating the LUT. The vertical and horizontal
+ *   angle should be inside of the bounds of the vertical and horizontal angle arrays.
+ *
+ * @param vertical_angle Vertical angle, from 0 .. 90 or 0 .. 180 depending on the dataset
+ * @param horizontal_angle Horizontal angle, from 0 .. 180 or 0 .. 360 depending on the dataset.
+ *
+ * @return Candela value between 0 .. 1
+ */
+float IESDataset::get_candela_value(float vertical_angle, float horizontal_angle) const {
+
+  // Special case for datasets without horizontal angles
+  if (_horizontal_angles.size() == 1) {
+    return get_vertical_candela_value(0, vertical_angle);
+  }
+
+  float max_angle = _horizontal_angles[_horizontal_angles.size() - 1];
+
+  // Wrap angle to fit from 0 .. 360 degree. Most profiles only distribute
+  // candela values from 0 .. 180 or even 0 .. 90. We have to mirror the
+  // values at those borders (so 2 times for 180 degree and 4 times for 90 degree)
+  horizontal_angle = fmod(horizontal_angle, 2.0f * max_angle);
+  if (horizontal_angle > max_angle) {
+    horizontal_angle = 2.0 * max_angle - horizontal_angle;
+  }
+
+  // Simlar to the vertical step, we now try interpolating a horizontal angle,
+  // but we need to evaluate the vertical value for each row instead of fetching
+  // the value directly
+  for (size_t horizontal_index = 1; horizontal_index < _horizontal_angles.size(); ++horizontal_index) {
+    float curr_angle = _horizontal_angles[horizontal_index];
+
+    if (curr_angle >= horizontal_angle) {
+
+      // Get previous angle data
+      float prev_angle = _horizontal_angles[horizontal_index - 1];
+      float prev_value = get_vertical_candela_value(horizontal_index - 1, vertical_angle);
+      float curr_value = get_vertical_candela_value(horizontal_index, vertical_angle);
+
+      // Interpolate lineary
+      float lerp = (horizontal_angle - prev_angle) / (curr_angle - prev_angle);
+
+      // Should never occur, but to be safe:
+      if (lerp < 0.0 || lerp > 1.0) {
+        iesdataset_cat.error() << "Invalid horizontal lerp: " << lerp
+                     << ", requested angle was " << horizontal_angle
+                     << ", prev = " << prev_angle << ", cur = " << curr_angle
+                     << endl;
+      }
+
+      return curr_value * lerp + prev_value * (1-lerp);
+    }
+  }
+
+  return 0.0;
+}
+
+/**
+ * @brief Fetches a vertical candela value
+ * @details Fetches a vertical candela value, using a given horizontal position.
+ *   This does an 1D interpolation in the candela values array.
+ *
+ * @param horizontal_angle_idx The index of the horizontal angle in the horizontal
+ *   angle array.
+ * @param vertical_angle The vertical angle. Interpolation will be done if the
+ *   vertical angle is not in the vertical angles array.
+ *
+ * @return Candela value between 0 .. 1
+ */
+float IESDataset::get_vertical_candela_value(size_t horizontal_angle_idx, float vertical_angle) const {
+  nassertr(horizontal_angle_idx >= 0 && horizontal_angle_idx < _horizontal_angles.size(), 0.0);
+
+  // Lower bound
+  if (vertical_angle < 0.0) return 0.0;
+
+  // Upper bound
+  if (vertical_angle > _vertical_angles[_vertical_angles.size() - 1] ) return 0.0;
+
+  // Find lowest enclosing angle
+  for (size_t vertical_index = 1; vertical_index < _vertical_angles.size(); ++vertical_index) {
+    float curr_angle = _vertical_angles[vertical_index];
+
+    // Found value
+    if (curr_angle > vertical_angle) {
+
+      // Get previous angle data
+      float prev_angle = _vertical_angles[vertical_index - 1];
+      float prev_value = get_candela_value_from_index(vertical_index - 1, horizontal_angle_idx);
+      float curr_value = get_candela_value_from_index(vertical_index, horizontal_angle_idx);
+
+      // Interpolate lineary
+      float lerp = (vertical_angle - prev_angle) / (curr_angle - prev_angle);
+
+      // Should never occur, but to be safe:
+      if (lerp < 0.0 || lerp > 1.0) {
+        iesdataset_cat.error() << "ERROR: Invalid vertical lerp: " << lerp
+                     << ", requested angle was " << vertical_angle
+                     << ", prev = " << prev_angle << ", cur = " << curr_angle
+                     << endl;
+      }
+
+      return curr_value * lerp + prev_value * (1-lerp);
+    }
+  }
+  return 0.0;
+}
+
+/**
+ * @brief Generates the IES LUT
+ * @details This generates the LUT into a given dataset texture. The x-axis
+ *   referes to the vertical_angle, whereas the y-axis refers to the
+ *   horizontal angle.
+ *
+ * @param dest_tex Texture to write the LUT into
+ * @param z Layer to write the LUT into, in case the texture is a 3D Texture or
+ *   2D Texture Array.
+ */
+void IESDataset::generate_dataset_texture_into(Texture* dest_tex, size_t z) const {
+
+  size_t resolution_vertical = dest_tex->get_y_size();
+  size_t resolution_horizontal = dest_tex->get_x_size();
+
+  // Candla values are stored flippped - vertical angles in the x - Axis
+  // and horizontal angles in the y - Axis
+  PNMImage dest = PNMImage(resolution_vertical, resolution_horizontal, 1, 65535);
+
+  for (size_t vert = 0; vert < resolution_vertical; ++vert) {
+    for (size_t horiz = 0; horiz < resolution_horizontal; ++horiz) {
+      float vert_angle = (float)vert / (float)(resolution_vertical-1);
+      vert_angle = cos(vert_angle * M_PI) * 90.0 + 90.0;
+      float horiz_angle = (float)horiz / (float)(resolution_horizontal-1) * 360.0;
+      float candela = get_candela_value(vert_angle, horiz_angle);
+      dest.set_xel(vert, horiz, candela);
+    }
+  }
+
+
+  dest_tex->load(dest, z, 0);
+}

+ 68 - 0
contrib/src/rplight/iesDataset.h

@@ -0,0 +1,68 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef IESDATASET_H
+#define IESDATASET_H
+
+#include "pandabase.h"
+#include "pta_float.h"
+#include "pointerToArray.h"
+#include "texture.h"
+#include "pnmImage.h"
+
+NotifyCategoryDecl(iesdataset, EXPORT_CLASS, EXPORT_TEMPL);
+
+
+/**
+ * @brief This class generates a LUT from IES data.
+ * @details This class is used by the IESLoader to generate a LUT texture which
+ *   is used in the shaders to perform IES lighting. It takes a set of vertical
+ *   and horizontal angles, as well as a set of candela values, which then are
+ *   lineary interpolated onto a 2D LUT Texture.
+ */
+class IESDataset {
+PUBLISHED:
+  IESDataset();
+
+  void set_vertical_angles(const PTA_float &vertical_angles);
+  void set_horizontal_angles(const PTA_float &horizontal_angles);
+  void set_candela_values(const PTA_float &candela_values);
+
+  void generate_dataset_texture_into(Texture* dest_tex, size_t z) const;
+
+public:
+
+  float get_candela_value(float vertical_angle, float horizontal_angle) const;
+  float get_candela_value_from_index(size_t vertical_angle_idx, size_t horizontal_angle_idx) const;
+  float get_vertical_candela_value(size_t horizontal_angle_idx, float vertical_angle) const;
+
+private:
+  PTA_float _vertical_angles;
+  PTA_float _horizontal_angles;
+  PTA_float _candela_values;
+};
+
+#endif // IESDATASET_H

+ 140 - 0
contrib/src/rplight/internalLightManager.I

@@ -0,0 +1,140 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Returns the maximum light index
+ * @details This returns the maximum light index (also called slot). Any lights
+ *   after that slot are guaranteed to be zero-lights. This is useful when
+ *   iterating over the list of lights, because iteration can be stopped when
+ *   the maximum light index is reached.
+ *
+ *   The maximum light index points to the last slot which is used. If no lights
+ *   are attached, -1 is returned. If one light is attached at slot 0, the index
+ *   is 0, if two are attached at the slots 0 and 1, the index is 1, and so on.
+ *
+ *   If, for example, two lights are attached at the slots 2 and 5, then the
+ *   index will be 5. Keep in mind that the max-index is not an indicator for
+ *   how many lights are attached. Also, zero lights still may occur when iterating
+ *   over the light lists
+ *
+ * @return Maximum light index
+ */
+inline int InternalLightManager::get_max_light_index() const {
+  return _lights.get_max_index();
+}
+
+/**
+ * @brief Returns the amount of stored lights.
+ * @details This returns the amount of stored lights. This behaves unlike
+ *   InternalLightManager::get_max_light_index, and instead returns the true
+ *   amount of lights, which is completely unrelated to the amount of used slots.
+ *
+ * @return Amount of stored lights
+ */
+inline size_t InternalLightManager::get_num_lights() const {
+  return _lights.get_num_entries();
+}
+
+/**
+ * @brief Returns the amount of shadow sources.
+ * @details This returns the total amount of stored shadow sources. This does
+ *   not denote the amount of updated sources, but instead takes into account
+ *   all sources, even those out of frustum.
+ * @return Amount of shadow sources.
+ */
+inline size_t InternalLightManager::get_num_shadow_sources() const {
+  return _shadow_sources.get_num_entries();
+}
+
+/**
+ * @brief Sets the handle to the shadow manager
+ * @details This sets the handle to the global shadow manager. It is usually
+ *   constructed on the python side, so we need to get a handle to it.
+ *
+ *   The manager should be a handle to a ShadowManager instance, and will be
+ *   stored somewhere on the python side most likely. The light manager does not
+ *   keep a reference to it, so the python side should make sure to keep one.
+ *
+ *   Be sure to call this before the InternalLightManager::update() method is
+ *   called, otherwise an assertion will get triggered.
+ *
+ * @param mgr The ShadowManager instance
+ */
+inline void InternalLightManager::set_shadow_manager(ShadowManager* mgr) {
+  _shadow_manager = mgr;
+}
+
+/**
+ * @brief Sets a handle to the command list
+ * @details This sets a handle to the global GPUCommandList. This is required to
+ *   emit GPUCommands, which are used for attaching and detaching lights, as well
+ *   as shadow source updates.
+ *
+ *   The cmd_list should be a handle to a GPUCommandList handle, and will be
+ *   stored somewhere on the python side most likely. The light manager does not
+ *   keep a reference to it, so the python side should make sure to keep one.
+ *
+ *   Be sure to call this before the InternalLightManager::update() method is
+ *   called, otherwise an assertion will get triggered.
+ *
+ * @param cmd_list The GPUCommandList instance
+ */
+inline void InternalLightManager::set_command_list(GPUCommandList *cmd_list) {
+  _cmd_list = cmd_list;
+}
+
+/**
+ * @brief Sets the camera position
+ * @details This sets the camera position, which will be used to determine which
+ *   shadow sources have to get updated
+ *
+ * @param mat View projection mat
+ */
+inline void InternalLightManager::set_camera_pos(const LPoint3 &pos) {
+  _camera_pos = pos;
+}
+
+/**
+ * @brief Sets the maximum shadow update distance
+ * @details This controls the maximum distance until which shadows are updated.
+ *   If a shadow source is past that distance, it is ignored and no longer recieves
+ *   updates until it is in range again
+ *
+ * @param dist Distance in world space units
+ */
+inline void InternalLightManager::set_shadow_update_distance(PN_stdfloat dist) {
+  _shadow_update_distance = dist;
+}
+
+/**
+ * @brief Returns the internal used ShadowManager
+ * @details This returns a handle to the internally used shadow manager
+ * @return Shadow manager
+ */
+inline ShadowManager* InternalLightManager::get_shadow_manager() const {
+  return _shadow_manager;
+}

+ 441 - 0
contrib/src/rplight/internalLightManager.cxx

@@ -0,0 +1,441 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "internalLightManager.h"
+
+#include <algorithm>
+
+NotifyCategoryDef(lightmgr, "");
+
+
+/**
+ * @brief Constructs the light manager
+ * @details This constructs the light manager, initializing the light and shadow
+ *   storage. You should set a command list and shadow manager before calling
+ *   InternalLightManager::update. s
+ */
+InternalLightManager::InternalLightManager() {
+  _shadow_update_distance = 100.0;
+  _cmd_list = nullptr;
+  _shadow_manager = nullptr;
+}
+
+/**
+ * @brief Adds a new light.
+ * @details This adds a new light to the list of lights. This will throw an
+ *   error and return if the light is already attached. You may only call
+ *   this after the ShadowManager was already set.
+ *
+ *   While the light is attached, the light manager keeps a reference to it, so
+ *   the light does not get destructed.
+ *
+ *   This also setups the shadows on the light, in case shadows are enabled.
+ *   While a light is attached, you can not change whether it casts shadows or not.
+ *   To do so, detach the light, change the setting, and re-add the light.
+ *
+ *   In case no free light slot is available, an error will be printed and no
+ *   action will be performed.
+ *
+ *   If no shadow manager was set, an assertion will be triggered.
+ *
+ * @param light The light to add.
+ */
+void InternalLightManager::add_light(PT(RPLight) light) {
+  nassertv(_shadow_manager != nullptr); // Shadow manager not set yet!
+
+  // Don't attach the light in case its already attached
+  if (light->has_slot()) {
+    lightmgr_cat.error() << "could not add light because it already is attached! "
+               << "Detach the light first, then try it again." << endl;
+    return;
+  }
+
+  // Find a free slot
+  size_t slot;
+  if (!_lights.find_slot(slot)) {
+    lightmgr_cat.error() << "Light limit of " << MAX_LIGHT_COUNT << " reached, "
+               << "all light slots used!" << endl;
+    return;
+  }
+
+  // Reference the light because we store it, to avoid it getting destructed
+  // on the python side while we still work with it. The reference will be
+  // removed when the light gets detached.
+  light->ref();
+
+  // Reserve the slot
+  light->assign_slot(slot);
+  _lights.reserve_slot(slot, light);
+
+  // Setup the shadows in case the light uses them
+  if (light->get_casts_shadows()) {
+    setup_shadows(light);
+  }
+
+  // Store the light on the gpu, to make sure the GPU directly knows about it.
+  // We could wait until the next update cycle, but then we might be one frame
+  // too late already.
+  gpu_update_light(light);
+}
+
+/**
+ * @brief Internal method to setup shadows for a light
+ * @details This method gets called by the InternalLightManager::add_light method
+ *   to setup a lights shadow sources, in case shadows are enabled on that light.
+ *
+ *   It finds a slot for all shadow sources of the ilhgt, and inits the shadow
+ *   sources as well. If no slot could be found, an error is printed an nothing
+ *   happens.
+ *
+ * @param light The light to init the shadow sources for
+ */
+void InternalLightManager::setup_shadows(RPLight* light) {
+
+  // Init the lights shadow sources, and also call update once to make sure
+  // the sources are properly initialized
+  light->init_shadow_sources();
+  light->update_shadow_sources();
+
+  // Find consecutive slots, this is important for PointLights so we can just
+  // store the first index of the source, and get the other slots by doing
+  // first_index + 1, +2 and so on.
+  size_t base_slot;
+  size_t num_sources = light->get_num_shadow_sources();
+  if (!_shadow_sources.find_consecutive_slots(base_slot, num_sources)) {
+    lightmgr_cat.error() << "Failed to find slot for shadow sources! "
+               << "Shadow-Source limit of " << MAX_SHADOW_SOURCES
+               << " reached!" << endl;
+    return;
+  }
+
+  // Init all sources
+  for (int i = 0; i < num_sources; ++i) {
+    ShadowSource* source = light->get_shadow_source(i);
+
+    // Set the source as dirty, so it gets updated in the beginning
+    source->set_needs_update(true);
+
+    // Assign the slot to the source. Since we got consecutive slots, we can
+    // just do base_slot + N.
+    size_t slot = base_slot + i;
+    _shadow_sources.reserve_slot(slot, source);
+    source->set_slot(slot);
+  }
+}
+
+/**
+ * @brief Removes a light
+ * @details This detaches a light. This prevents it from being rendered, and also
+ *   cleans up all resources used by that light. If no reference is kept on the
+ *   python side, the light will also get destructed.
+ *
+ *   If the light was not previously attached with InternalLightManager::add_light,
+ *   an error will be triggered and nothing happens.
+ *
+ *   In case the light was set to cast shadows, all shadow sources are cleaned
+ *   up, and their regions in the shadow atlas are freed.
+ *
+ *   All resources used by the light in the light and shadow storage are also
+ *   cleaned up, by emitting cleanup GPUCommands.
+ *
+ *   If no shadow manager was set, an assertion will be triggered.
+ *
+ * @param light [description]
+ */
+void InternalLightManager::remove_light(PT(RPLight) light) {
+  nassertv(_shadow_manager != nullptr);
+
+  if (!light->has_slot()) {
+    lightmgr_cat.error() << "Could not detach light, light was not attached!" << endl;
+    return;
+  }
+
+  // Free the lights slot in the light storage
+  _lights.free_slot(light->get_slot());
+
+  // Tell the GPU we no longer need the lights data
+  gpu_remove_light(light);
+
+  // Mark the light as detached. After this call, we can not call get_slot
+  // anymore, so its important we do this after we unregistered the light
+  // from everywhere.
+  light->remove_slot();
+
+  // Clear shadow related stuff, in case the light casts shadows
+  if (light->get_casts_shadows()) {
+
+    // Free the slots of all sources, and also unregister their regions from
+    // the shadow atlas.
+    for (size_t i = 0; i < light->get_num_shadow_sources(); ++i) {
+      ShadowSource* source = light->get_shadow_source(i);
+      if (source->has_slot()) {
+        _shadow_sources.free_slot(source->get_slot());
+      }
+      if (source->has_region()) {
+        _shadow_manager->get_atlas()->free_region(source->get_region());
+        source->clear_region();
+      }
+    }
+
+    // Remove all sources of the light by emitting a consecutive remove command
+    gpu_remove_consecutive_sources(light->get_shadow_source(0),
+                     light->get_num_shadow_sources());
+
+    // Finally remove all shadow sources. This is important in case the light
+    // will be re-attached. Otherwise an assertion will get triggered.
+    light->clear_shadow_sources();
+  }
+
+  // Since we referenced the light when we stored it, we have to decrease
+  // the reference now. In case no reference was kept on the python side,
+  // the light will get destructed soon.
+  light->unref();
+}
+
+/**
+ * @brief Internal method to remove consecutive sources from the GPU.
+ * @details This emits a GPUCommand to consecutively remove shadow sources from
+ *   the GPU. This is called when a light gets removed, to free the space its
+ *   shadow sources took. Its not really required, because as long as the light
+ *   is not used, there is no reference to the sources. However, it can't hurt to
+ *   cleanup the memory.
+ *
+ *   All sources starting at first_source->get_slot() until
+ *   first_source->get_slot() + num_sources will get cleaned up.
+ *
+ * @param first_source First source of the light
+ * @param num_sources Amount of consecutive sources to clear
+ */
+void InternalLightManager::gpu_remove_consecutive_sources(ShadowSource *first_source,
+                              size_t num_sources) {
+  nassertv(_cmd_list != nullptr);    // No command list set yet
+  nassertv(first_source->has_slot()); // Source has no slot!
+  GPUCommand cmd_remove(GPUCommand::CMD_remove_sources);
+  cmd_remove.push_int(first_source->get_slot());
+  cmd_remove.push_int(num_sources);
+  _cmd_list->add_command(cmd_remove);
+}
+
+/**
+ * @brief Internal method to remove a light from the GPU.
+ * @details This emits a GPUCommand to clear a lights data. This sets the data
+ *   to all zeros, marking that no light is stored anymore.
+ *
+ *   This throws an assertion in case the light is not currently attached. Be
+ *   sure to call this before detaching the light.
+ *
+ * @param light The light to remove, must be attached.
+ */
+void InternalLightManager::gpu_remove_light(RPLight* light) {
+  nassertv(_cmd_list != nullptr);  // No command list set yet
+  nassertv(light->has_slot());  // Light has no slot!
+  GPUCommand cmd_remove(GPUCommand::CMD_remove_light);
+  cmd_remove.push_int(light->get_slot());
+  _cmd_list->add_command(cmd_remove);
+}
+
+/**
+ * @brief Updates a lights data on the GPU
+ * @details This method emits a GPUCommand to update a lights data. This can
+ *   be used to initially store the lights data, or to update the data whenever
+ *   the light changed.
+ *
+ *   This throws an assertion in case the light is not currently attached. Be
+ *   sure to call this after attaching the light.
+ *
+ * @param light The light to update
+ */
+void InternalLightManager::gpu_update_light(RPLight* light) {
+  nassertv(_cmd_list != nullptr);  // No command list set yet
+  nassertv(light->has_slot());  // Light has no slot!
+  GPUCommand cmd_update(GPUCommand::CMD_store_light);
+  cmd_update.push_int(light->get_slot());
+  light->write_to_command(cmd_update);
+  light->set_needs_update(false);
+  _cmd_list->add_command(cmd_update);
+}
+
+/**
+ * @brief Updates a shadow source data on the GPU
+ * @details This emits a GPUCommand to update a given shadow source, storing all
+ *   data of the source on the GPU. This can also be used to initially store a
+ *   ShadowSource, since all data will be overridden.
+ *
+ *   This throws an assertion if the source has no slot yet.
+ *
+ * @param source The source to update
+ */
+void InternalLightManager::gpu_update_source(ShadowSource* source) {
+  nassertv(_cmd_list != nullptr);  // No command list set yet
+  nassertv(source->has_slot()); // Source has no slot!
+  GPUCommand cmd_update(GPUCommand::CMD_store_source);
+  cmd_update.push_int(source->get_slot());
+  source->write_to_command(cmd_update);
+  _cmd_list->add_command(cmd_update);
+}
+
+/**
+ * @brief Internal method to update all lights
+ * @details This is called by the main update method, and iterates over the list
+ *   of lights. If a light is marked as dirty, it will recieve an update of its
+ *   data and its shadow sources.
+ */
+void InternalLightManager::update_lights() {
+  for (auto iter = _lights.begin(); iter != _lights.end(); ++iter) {
+    RPLight* light = *iter;
+    if (light && light->get_needs_update()) {
+      if (light->get_casts_shadows()) {
+        light->update_shadow_sources();
+      }
+      gpu_update_light(light);
+    }
+  }
+}
+
+/**
+ * @brief Compares shadow sources by their priority
+ * @details Returns if a has a greater priority than b. This depends on the
+ *   resolution of the source, and also if the source has a region or not.
+ *   This method can be passed to std::sort.
+ *
+ * @param a First source
+ * @param b Second source
+ *
+ * @return true if a is more important than b, else false
+ */
+bool InternalLightManager::compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const {
+
+  // Make sure that sources which already have a region (but maybe outdated)
+  // come after sources which have no region at all.
+  if (a->has_region() != b->has_region()) {
+    return b->has_region();
+  }
+
+  // Compare sources based on their distance to the camera
+  PN_stdfloat dist_a = (_camera_pos - a->get_bounds().get_center()).length_squared();
+  PN_stdfloat dist_b = (_camera_pos - a->get_bounds().get_center()).length_squared();
+
+  // XXX: Should also compare based on source size, so that huge sources recieve
+  // more updates
+
+  return dist_b > dist_a;
+}
+
+/**
+ * @brief Internal method to update all shadow sources
+ * @details This updates all shadow sources which are marked dirty. It will sort
+ *   the list of all dirty shadow sources by their resolution, take the first
+ *   n entries, and update them. The amount of sources processed depends on the
+ *   max_updates of the ShadowManager.
+ */
+void InternalLightManager::update_shadow_sources() {
+
+  // Find all dirty shadow sources and make a list of them
+  vector<ShadowSource*> sources_to_update;
+   for (auto iter = _shadow_sources.begin(); iter != _shadow_sources.end(); ++iter) {
+    ShadowSource* source = *iter;
+    if (source) {
+      const BoundingSphere& bounds = source->get_bounds();
+
+      // Check if source is in range
+      PN_stdfloat distance_to_camera = (_camera_pos - bounds.get_center()).length() - bounds.get_radius();
+      if (distance_to_camera < _shadow_update_distance) {
+        if (source->get_needs_update()) {
+          sources_to_update.push_back(source);
+        }
+      } else {
+
+        // Free regions of sources which are out of the update radius,
+        // to make space for other regions
+        if (source->has_region()) {
+          _shadow_manager->get_atlas()->free_region(source->get_region());
+          source->clear_region();
+        }
+      }
+    }
+
+  }
+
+  // Sort the sources based on their importance, so that sources with a bigger
+  // priority come first. This helps to get a better packing on the shadow atlas.
+  // However, we also need to prioritize sources which have no current region,
+  // because no shadows are worse than outdated-shadows.
+  std::sort(sources_to_update.begin(), sources_to_update.end(), [this](const ShadowSource* a, const ShadowSource* b) {
+    return this->compare_shadow_sources(a, b);
+  });
+
+  // Get a handle to the atlas, will be frequently used
+  ShadowAtlas *atlas = _shadow_manager->get_atlas();
+
+  // Free the regions of all sources which will get updated. We have to take into
+  // account that only a limited amount of sources can get updated per frame.
+  size_t update_slots = min(sources_to_update.size(),
+                _shadow_manager->get_num_update_slots_left());
+  for(size_t i = 0; i < update_slots; ++i) {
+    if (sources_to_update[i]->has_region()) {
+       atlas->free_region(sources_to_update[i]->get_region());
+    }
+  }
+
+  // Find an atlas spot for all regions which are supposed to get an update
+  for (size_t i = 0; i < update_slots; ++i) {
+    ShadowSource *source = sources_to_update[i];
+
+    if(!_shadow_manager->add_update(source)) {
+      // In case the ShadowManager lied about the number of updates left
+      lightmgr_cat.error() << "ShadowManager ensured update slot, but slot is taken!" << endl;
+      break;
+    }
+
+    // We have an update slot, and are guaranteed to get updated as soon
+    // as possible, so we can start getting a new atlas position.
+    size_t region_size = atlas->get_required_tiles(source->get_resolution());
+    LVecBase4i new_region = atlas->find_and_reserve_region(region_size, region_size);
+    LVecBase4 new_uv_region = atlas->region_to_uv(new_region);
+    source->set_region(new_region, new_uv_region);
+
+    // Mark the source as updated
+    source->set_needs_update(false);
+    gpu_update_source(source);
+  }
+}
+
+/**
+ * @brief Main update method
+ * @details This is the main update method of the InternalLightManager. It
+ *   processes all lights and shadow sources, updates them, and notifies the
+ *   GPU about it. This should be called on a per-frame basis.
+ *
+ *   If the InternalLightManager was not initialized yet, an assertion is thrown.
+ */
+void InternalLightManager::update() {
+  nassertv(_shadow_manager != nullptr); // Not initialized yet!
+  nassertv(_cmd_list != nullptr);     // Not initialized yet!
+
+  update_lights();
+  update_shadow_sources();
+}

+ 102 - 0
contrib/src/rplight/internalLightManager.h

@@ -0,0 +1,102 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef INTERNALLIGHTMANAGER_H
+#define INTERNALLIGHTMANAGER_H
+
+#include "referenceCount.h"
+#include "rpLight.h"
+#include "shadowSource.h"
+#include "shadowAtlas.h"
+#include "shadowManager.h"
+#include "pointerSlotStorage.h"
+#include "gpuCommandList.h"
+
+#define MAX_LIGHT_COUNT 65535
+#define MAX_SHADOW_SOURCES 2048
+
+NotifyCategoryDecl(lightmgr, EXPORT_CLASS, EXPORT_TEMPL);
+
+/**
+ * @brief Internal class used for handling lights and shadows.
+ * @details This is the internal class used by the pipeline to handle all
+ *   lights and shadows. It stores references to the lights, manages handling
+ *   the light and shadow slots, and also communicates with the GPU with the
+ *   GPUCommandQueue to store light and shadow source data.
+ */
+class InternalLightManager {
+PUBLISHED:
+  InternalLightManager();
+
+  void add_light(PT(RPLight) light);
+  void remove_light(PT(RPLight) light);
+
+  void update();
+  inline void set_camera_pos(const LPoint3 &pos);
+  inline void set_shadow_update_distance(PN_stdfloat dist);
+
+  inline int get_max_light_index() const;
+  MAKE_PROPERTY(max_light_index, get_max_light_index);
+
+  inline size_t get_num_lights() const;
+  MAKE_PROPERTY(num_lights, get_num_lights);
+
+  inline size_t get_num_shadow_sources() const;
+  MAKE_PROPERTY(num_shadow_sources, get_num_shadow_sources);
+
+  inline void set_shadow_manager(ShadowManager* mgr);
+  inline ShadowManager* get_shadow_manager() const;
+  MAKE_PROPERTY(shadow_manager, get_shadow_manager, set_shadow_manager);
+
+  inline void set_command_list(GPUCommandList *cmd_list);
+
+protected:
+
+  void gpu_update_light(RPLight* light);
+  void gpu_update_source(ShadowSource* source);
+  void gpu_remove_light(RPLight* light);
+  void gpu_remove_consecutive_sources(ShadowSource *first_source, size_t num_sources);
+
+  void setup_shadows(RPLight* light);
+  bool compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const;
+
+  void update_lights();
+  void update_shadow_sources();
+
+  GPUCommandList* _cmd_list;
+  ShadowManager* _shadow_manager;
+
+  PointerSlotStorage<RPLight*, MAX_LIGHT_COUNT> _lights;
+  PointerSlotStorage<ShadowSource*, MAX_SHADOW_SOURCES> _shadow_sources;
+
+  LPoint3 _camera_pos;
+  float _shadow_update_distance;
+
+};
+
+#include "internalLightManager.I"
+
+#endif // INTERNALLIGHTMANAGER_H

+ 13 - 0
contrib/src/rplight/p3rplight_composite1.cxx

@@ -0,0 +1,13 @@
+#include "config_rplight.cxx"
+#include "gpuCommand.cxx"
+#include "gpuCommandList.cxx"
+#include "iesDataset.cxx"
+#include "internalLightManager.cxx"
+#include "pssmCameraRig.cxx"
+#include "rpLight.cxx"
+#include "rpPointLight.cxx"
+#include "rpSpotLight.cxx"
+#include "shadowAtlas.cxx"
+#include "shadowManager.cxx"
+#include "shadowSource.cxx"
+#include "tagStateManager.cxx"

+ 239 - 0
contrib/src/rplight/pointerSlotStorage.h

@@ -0,0 +1,239 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef POINTERSLOTSTORAGE_H
+#define POINTERSLOTSTORAGE_H
+
+
+#ifdef CPPPARSER
+
+// Dummy implementation for interrogate
+template <typename T, int SIZE>
+class PointerSlotStorage {};
+
+#else // CPPPARSER
+
+
+#include "pandabase.h"
+
+// Apple has an outdated libstdc++, so pull the class from TR1.
+#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
+#include <tr1/array>
+using std::tr1::array;
+#else
+#include <array>
+#endif
+
+/**
+ * @brief Class to keep a list of pointers and nullpointers.
+ * @details This class stores a fixed size list of pointers, whereas pointers
+ *   may be a nullptr as well. It provides functionality to find free slots,
+ *   and also to find free consecutive slots, as well as taking care of reserving slots.
+ *
+ * @tparam T* Pointer-Type
+ * @tparam SIZE Size of the storage
+ */
+template <typename T, int SIZE>
+class PointerSlotStorage {
+public:
+  /**
+   * @brief Constructs a new PointerSlotStorage
+   * @details This constructs a new PointerSlotStorage, with all slots
+   *   initialized to a nullptr.
+   */
+  PointerSlotStorage() {
+#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
+    _data.assign(nullptr);
+#else
+    _data.fill(nullptr);
+#endif
+    _max_index = 0;
+    _num_entries = 0;
+  }
+
+  /**
+   * @brief Returns the maximum index of the container
+   * @details This returns the greatest index of any element which is not zero.
+   *   This can be useful for iterating the container, since all elements
+   *   coming after the returned index are guaranteed to be a nullptr.
+   *
+   *   If no elements are in this container, -1 is returned.
+   * @return Maximum index of the container
+   */
+  int get_max_index() const {
+    return _max_index;
+  }
+
+  /**
+   * @brief Returns the amount of elements of the container
+   * @details This returns the amount of elements in the container which are
+   *   no nullptr.
+   * @return Amount of elements
+   */
+  size_t get_num_entries() const {
+    return _num_entries;
+  }
+
+  /**
+   * @brief Finds a free slot
+   * @details This finds the first slot which is a nullptr and returns it.
+   *   This is most likely useful in combination with reserve_slot.
+   *
+   *   When no slot found was found, slot will be undefined, and false will
+   *   be returned.
+   *
+   * @param slot Output-Variable, slot will be stored there
+   * @return true if a slot was found, otherwise false
+   */
+  bool find_slot(size_t &slot) const {
+    for (size_t i = 0; i < SIZE; ++i) {
+      if (_data[i] == nullptr) {
+        slot = i;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @brief Finds free consecutive slots
+   * @details This behaves like find_slot, but it tries to find a slot
+   *   after which <num_consecutive-1> free slots follow as well.
+   *
+   *   When no slot found was found, slot will be undefined, and false will
+   *   be returned.
+   *
+   * @param slot Output-Variable, index of the first slot of the consecutive
+   *   slots will be stored there.
+   * @param num_consecutive Amount of consecutive slots to find, including the
+   *   first slot.
+   *
+   * @return true if consecutive slots were found, otherwise false.
+   */
+  bool find_consecutive_slots(size_t &slot, size_t num_consecutive) const {
+    nassertr(num_consecutive > 0, false);
+
+    // Fall back to default search algorithm in case the parameters are equal
+    if (num_consecutive == 1) {
+      return find_slot(slot);
+    }
+
+    // Try to find consecutive slots otherwise
+    for (size_t i = 0; i < SIZE; ++i) {
+      bool any_taken = false;
+      for (size_t k = 0; !any_taken && k < num_consecutive; ++k) {
+        any_taken = _data[i + k] != nullptr;
+      }
+      if (!any_taken) {
+        slot = i;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @brief Frees an allocated slot
+   * @details This frees an allocated slot. If the slot was already freed
+   *   before, this method throws an assertion.
+   *
+   * @param slot Slot to free
+   */
+  void free_slot(size_t slot) {
+    nassertv(slot >= 0 && slot < SIZE);
+    nassertv(_data[slot] != nullptr); // Slot was already empty!
+    _data[slot] = nullptr;
+    _num_entries--;
+
+    // Update maximum index
+    if (slot == _max_index) {
+      while (_max_index >= 0 && !_data[_max_index--]);
+    }
+  }
+
+  /**
+   * @brief Frees consecutive allocated slots
+   * @details This behaves like PointerSlotStorage::free_slot, but deletes
+   *   consecutive slots.
+   *
+   * @param slot Start of the consecutive slots to free
+   * @param num_consecutive Number of consecutive slots
+   */
+  void free_consecutive_slots(size_t slot, size_t num_consecutive) {
+    for (size_t i = slot; i < slot + num_consecutive; ++i) {
+      free_slot(i);
+    }
+  }
+
+  /**
+   * @brief Reserves a slot
+   * @details This reserves a slot by storing a pointer in it. If the slot
+   *   was already taken, throws an assertion.
+   *   If the ptr is a nullptr, also throws an assertion.
+   *   If the slot was out of bounds, also throws an assertion.
+   *
+   * @param slot Slot to reserve
+   * @param ptr Pointer to store
+   */
+  void reserve_slot(size_t slot, T ptr) {
+    nassertv(slot >= 0 && slot < SIZE);
+    nassertv(_data[slot] == nullptr); // Slot already taken!
+    nassertv(ptr != nullptr); // nullptr passed as argument!
+    _max_index = max(_max_index, (int)slot);
+    _data[slot] = ptr;
+    _num_entries++;
+  }
+
+  typedef array<T, SIZE> InternalContainer;
+
+  /**
+   * @brief Returns an iterator to the begin of the container
+   * @details This returns an iterator to the beginning of the container
+   * @return Begin-Iterator
+   */
+  typename InternalContainer::iterator begin() {
+    return _data.begin();
+  }
+
+  /**
+   * @brief Returns an iterator to the end of the container
+   * @details This returns an iterator to the end of the iterator. This only
+   *   iterates to PointerSlotStorage::get_max_index()
+   * @return [description]
+   */
+  typename InternalContainer::iterator end() {
+    return _data.begin() + _max_index + 1;
+  }
+
+private:
+  int _max_index;
+  size_t _num_entries;
+  InternalContainer _data;
+};
+
+#endif // CPPPARSER
+
+#endif // POINTERSLOTSTORAGE_H

+ 243 - 0
contrib/src/rplight/pssmCameraRig.I

@@ -0,0 +1,243 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Sets the maximum pssm distance.
+ * @details This sets the maximum distance in world space until which shadows
+ *   are rendered. After this distance, no shadows will be rendered.
+ *
+ *   If the distance is below zero, an assertion is triggered.
+ *
+ * @param distance Maximum distance in world space
+ */
+inline void PSSMCameraRig::set_pssm_distance(float distance) {
+  nassertv(distance > 0.0 && distance < 100000.0);
+  _pssm_distance = distance;
+}
+
+/**
+ * @brief Sets the suns distance
+ * @details This sets the distance the cameras will have from the cameras frustum.
+ *   This prevents far objects from having no shadows, which can occur when these
+ *   objects are between the cameras frustum and the sun, but not inside of the
+ *   cameras frustum. Setting the sun distance high enough will move the cameras
+ *   away from the camera frustum, being able to cover those distant objects too.
+ *
+ *   If the sun distance is set too high, artifacts will occur due to the reduced
+ *   range of depth. If a value below zero is passed, an assertion will get
+ *   triggered.
+ *
+ * @param distance The sun distance
+ */
+inline void PSSMCameraRig::set_sun_distance(float distance) {
+  nassertv(distance > 0.0 && distance < 100000.0);
+  _sun_distance = distance;
+}
+
+/**
+ * @brief Sets the logarithmic factor
+ * @details This sets the logarithmic factor, which is the core of the algorithm.
+ *   PSSM splits the camera frustum based on a linear and a logarithmic factor.
+ *   While a linear factor provides a good distribution, it often is not applicable
+ *   for wider distances. A logarithmic distribution provides a better distribution
+ *   at distance, but suffers from splitting in the near areas.
+ *
+ *   The logarithmic factor mixes the logarithmic and linear split distribution,
+ *   to get the best of both. A greater factor will make the distribution more
+ *   logarithmic, while a smaller factor will make it more linear.
+ *
+ *   If the factor is below zero, an ssertion is triggered.
+ *
+ * @param factor The logarithmic factor
+ */
+inline void PSSMCameraRig::set_logarithmic_factor(float factor) {
+  nassertv(factor > 0.0);
+  _logarithmic_factor = factor;
+}
+
+/**
+ * @brief Sets whether to use a fixed film size
+ * @details This controls if a fixed film size should be used. This will cause
+ *   the camera rig to cache the current film size, and only change it in case
+ *   it gets too small. This provides less flickering when moving, because the
+ *   film size will stay roughly constant. However, to prevent the cached film
+ *   size getting too big, one should call PSSMCameraRig::reset_film_size
+ *   once in a while, otherwise there might be a lot of wasted space.
+ *
+ * @param flag Whether to use a fixed film size
+ */
+inline void PSSMCameraRig::set_use_fixed_film_size(bool flag) {
+  _use_fixed_film_size = flag;
+}
+
+/**
+ * @brief Sets the resolution of each split
+ * @details This sets the resolution of each split. Currently it is equal for
+ *   each split. This is required when using PSSMCameraRig::set_use_stable_csm,
+ *   to compute how bix a texel is.
+ *
+ *   It has to match the y-resolution of the pssm shadow map. If an invalid
+ *   resolution is triggered, an assertion is thrown.
+ *
+ * @param resolution The resolution of each split.
+ */
+inline void PSSMCameraRig::set_resolution(size_t resolution) {
+  nassertv(resolution >= 0 && resolution < 65535);
+  _resolution = resolution;
+}
+
+/**
+ * @brief Sets whether to use stable CSM snapping.
+ * @details This option controls if stable CSM snapping should be used. When the
+ *   option is enabled, all splits will snap to their texels, so that when moving,
+ *   no flickering will occur. However, this only works when the splits do not
+ *   change their film size, rotation and angle.
+ *
+ * @param flag Whether to use stable CSM snapping
+ */
+inline void PSSMCameraRig::set_use_stable_csm(bool flag) {
+  _use_stable_csm = flag;
+}
+
+/**
+ * @brief Sets the border bias for each split
+ * @details This sets the border bias for every split. This increases each
+ *   splits frustum by multiplying it by (1 + bias), and helps reducing artifacts
+ *   at the borders of the splits. Artifacts can occur when the bias is too low,
+ *   because then the filtering will go over the bounds of the split, producing
+ *   invalid results.
+ *
+ *   If the bias is below zero, an assertion is thrown.
+ *
+ * @param bias Border bias
+ */
+inline void PSSMCameraRig::set_border_bias(float bias) {
+  nassertv(bias >= 0.0);
+  _border_bias = bias;
+}
+
+/**
+ * @brief Resets the film size cache
+ * @details In case PSSMCameraRig::set_use_fixed_film_size is used, this resets
+ *   the film size cache. This might lead to a small "jump" in the shadows,
+ *   because the film size changes, however it leads to a better shadow distribution.
+ *
+ *   This is the case because when using a fixed film size, the cache will get
+ *   bigger and bigger, whenever the camera moves to a grazing angle. However,
+ *   when moving back to a normal angle, the film size cache still stores this
+ *   big angle, and thus the splits will have a much bigger film size than actualy
+ *   required. To prevent this, call this method once in a while, so an optimal
+ *   distribution is ensured.
+ */
+inline void PSSMCameraRig::reset_film_size_cache() {
+  for (size_t i = 0; i < _max_film_sizes.size(); ++i) {
+    _max_film_sizes[i].fill(0);
+  }
+}
+
+/**
+ * @brief Returns the n-th camera
+ * @details This returns the n-th camera of the camera rig, which can be used
+ *   for various stuff like showing its frustum, passing it as a shader input,
+ *   and so on.
+ *
+ *   The first camera is the camera which is the camera of the first split,
+ *   which is the split closest to the camera. All cameras follow in descending
+ *   order until to the last camera, which is the split furthest away from the
+ *   camera.
+ *
+ *   If an invalid index is passed, an assertion is thrown.
+ *
+ * @param index Index of the camera.
+ * @return [description]
+ */
+inline NodePath PSSMCameraRig::get_camera(size_t index) {
+  nassertr(index >= 0 && index < _cam_nodes.size(), NodePath());
+  return _cam_nodes[index];
+}
+
+/**
+ * @brief Internal method to compute the distance of a split
+ * @details This is the internal method to perform the weighting of the
+ *   logarithmic and linear distribution. It computes the distance to the
+ *   camera from which a given split starts, by weighting the logarithmic and
+ *   linear factor.
+ *
+ *   The return value is a value ranging from 0 .. 1. To get the distance in
+ *   world space, the value has to get multiplied with the maximum shadow distance.
+ *
+ * @param split_index The index of the split
+ * @return Distance of the split, ranging from 0 .. 1
+ */
+inline float PSSMCameraRig::get_split_start(size_t split_index) {
+  float x = (float)split_index / (float)_cam_nodes.size();
+  return (exp(_logarithmic_factor*x)-1) / (exp(_logarithmic_factor)-1);
+}
+
+/**
+ * @brief Internal method for interpolating a point along the camera frustum
+ * @details This method takes a given distance in the 0 .. 1 range, whereas
+ *   0 denotes the camera near plane, and 1 denotes the camera far plane,
+ *   and lineary interpolates between them.
+ *
+ * @param origin Edge of the frustum
+ * @param depth Distance in the 0 .. 1 range
+ *
+ * @return interpolated point in world space
+ */
+inline LPoint3 PSSMCameraRig::get_interpolated_point(CoordinateOrigin origin, float depth) {
+  nassertr(depth >= 0.0 && depth <= 1.0, LPoint3());
+  return _curr_near_points[origin] * (1.0 - depth) + _curr_far_points[origin] * depth;
+}
+
+/**
+ * @brief Returns a handle to the MVP array
+ * @details This returns a handle to the array of view-projection matrices
+ *   of the different splits. This can be used for computing shadows. The array
+ *   is a PTALMatrix4 and thus can be directly bound to a shader.
+ *
+ * @return view-projection matrix array
+ */
+inline const PTA_LMatrix4 &PSSMCameraRig::get_mvp_array() {
+  return _camera_mvps;
+}
+
+/**
+ * @brief Returns a handle to the near and far planes array
+ * @details This returns a handle to the near and far plane array. Each split
+ *   has an entry in the array, whereas the x component of the vecto denotes the
+ *   near plane, and the y component denotes the far plane of the split.
+ *
+ *   This is required because the near and far planes of the splits change
+ *   constantly. To access them in a shader, the shader needs access to the
+ *   array.
+ *
+ * @return Array of near and far planes
+ */
+inline const PTA_LVecBase2 &PSSMCameraRig::get_nearfar_array() {
+  return _camera_nearfar;
+}

+ 396 - 0
contrib/src/rplight/pssmCameraRig.cxx

@@ -0,0 +1,396 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "pssmCameraRig.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include "orthographicLens.h"
+
+
+PStatCollector PSSMCameraRig::_update_collector("App:Show code:RP_PSSM_update");
+
+/**
+ * @brief Constructs a new PSSM camera rig
+ * @details This constructs a new camera rig, with a given amount of splits.
+ *   The splits can not be changed later on. Splits are also called Cascades.
+ *
+ *   An assertion will be triggered if the splits are below zero.
+ *
+ * @param num_splits Amount of PSSM splits
+ */
+PSSMCameraRig::PSSMCameraRig(size_t num_splits) {
+  nassertv(num_splits > 0);
+  _num_splits = num_splits;
+  _pssm_distance = 100.0;
+  _sun_distance = 500.0;
+  _use_fixed_film_size = false;
+  _use_stable_csm = true;
+  _logarithmic_factor = 1.0;
+  _resolution = 512;
+  _border_bias = 0.1;
+  _camera_mvps = PTA_LMatrix4::empty_array(num_splits);
+  _camera_nearfar = PTA_LVecBase2::empty_array(num_splits);
+  init_cam_nodes();
+}
+
+/**
+ * @brief Destructs the camera rig
+ * @details This destructs the camera rig, cleaning up all used resources.
+ */
+PSSMCameraRig::~PSSMCameraRig() {
+  // TODO: Detach all cameras and call remove_node. Most likely this is not
+  // an issue tho, because the camera rig will never get destructed.
+}
+
+/**
+ * @brief Internal method to init the cameras
+ * @details This method constructs all cameras and their required lens nodes
+ *   for all splits. It also resets the film size array.
+ */
+void PSSMCameraRig::init_cam_nodes() {
+  _cam_nodes.reserve(_num_splits);
+  _max_film_sizes.resize(_num_splits);
+  _cameras.resize(_num_splits);
+  for (size_t i = 0; i < _num_splits; ++i)
+  {
+    // Construct a new lens
+    Lens *lens = new OrthographicLens();
+    lens->set_film_size(1, 1);
+    lens->set_near_far(1, 1000);
+
+    // Construct a new camera
+    _cameras[i] = new Camera("pssm-cam-" + format_string(i), lens);
+    _cam_nodes.push_back(NodePath(_cameras[i]));
+    _max_film_sizes[i].fill(0);
+  }
+}
+
+/**
+ * @brief Reparents the camera rig
+ * @details This reparents all cameras to the given parent. Usually the parent
+ *   will be ShowBase.render. The parent should be the same node where the
+ *   main camera is located in, too.
+ *
+ *   If an empty parrent is passed, an assertion will get triggered.
+ *
+ * @param parent Parent node path
+ */
+void PSSMCameraRig::reparent_to(NodePath parent) {
+  nassertv(!parent.is_empty());
+  for (size_t i = 0; i < _num_splits; ++i) {
+    _cam_nodes[i].reparent_to(parent);
+  }
+  _parent = parent;
+}
+
+/**
+ * @brief Internal method to compute the view-projection matrix of a camera
+ * @details This returns the view-projection matrix of the given split. No bounds
+ *   checking is done. If an invalid index is passed, undefined behaviour occurs.
+ *
+ * @param split_index Index of the split
+ * @return view-projection matrix of the split
+ */
+LMatrix4 PSSMCameraRig::compute_mvp(size_t split_index) {
+  LMatrix4 transform = _parent.get_transform(_cam_nodes[split_index])->get_mat();
+  return transform * _cameras[split_index]->get_lens()->get_projection_mat();
+}
+
+/**
+ * @brief Internal method used for stable CSM
+ * @details This method is used when stable CSM is enabled. It ensures that each
+ *   source only moves in texel-steps, thus preventing flickering. This works by
+ *   projecting the point (0, 0, 0) to NDC space, making sure that it gets projected
+ *   to a texel center, and then projecting that texel back.
+ *
+ *   This only works if the camera does not rotate, change its film size, or change
+ *   its angle.
+ *
+ * @param mat view-projection matrix of the camera
+ * @param resolution resolution of the split
+ *
+ * @return Offset to add to the camera position to achieve stable snapping
+ */
+LVecBase3 PSSMCameraRig::get_snap_offset(const LMatrix4& mat, size_t resolution) {
+  // Transform origin to camera space
+  LPoint4 base_point = mat.get_row(3) * 0.5 + 0.5;
+
+  // Compute the snap offset
+  float texel_size = 1.0 / (float)(resolution);
+  float offset_x = fmod(base_point.get_x(), texel_size);
+  float offset_y = fmod(base_point.get_y(), texel_size);
+
+  // Reproject the offset back, for that we need the inverse MVP
+  LMatrix4 inv_mat(mat);
+  inv_mat.invert_in_place();
+  LVecBase3 new_base_point = inv_mat.xform_point(LVecBase3(
+      (base_point.get_x() - offset_x) * 2.0 - 1.0,
+      (base_point.get_y() - offset_y) * 2.0 - 1.0,
+      base_point.get_z() * 2.0 - 1.0
+    ));
+  return -new_base_point;
+}
+
+/**
+ * @brief Computes the average of a list of points
+ * @details This computes the average over a given set of points in 3D space.
+ *   It returns the average of those points, namely sum_of_points / num_points.
+ *
+ *   It is designed to work with a frustum, which is why it takes two arrays
+ *   with a dimension of 4. Usually the first array are the camera near points,
+ *   and the second array are the camera far points.
+ *
+ * @param starts First array of points
+ * @param ends Second array of points
+ * @return Average of points
+ */
+LPoint3 get_average_of_points(LVecBase3 const (&starts)[4], LVecBase3 const (&ends)[4]) {
+  LPoint3 mid_point(0, 0, 0);
+  for (size_t k = 0; k < 4; ++k) {
+    mid_point += starts[k];
+    mid_point += ends[k];
+  }
+  return mid_point / 8.0;
+}
+
+/**
+ * @brief Finds the minimum and maximum extends of the given projection
+ * @details This projects each point of the given array of points using the
+ *   cameras view-projection matrix, and computes the minimum and maximum
+ *   of the projected points.
+ *
+ * @param min_extent Will store the minimum extent of the projected points in NDC space
+ * @param max_extent Will store the maximum extent of the projected points in NDC space
+ * @param transform The transformation matrix of the camera
+ * @param proj_points The array of points to project
+ * @param cam The camera to be used to project the points
+ */
+void find_min_max_extents(LVecBase3 &min_extent, LVecBase3 &max_extent, const LMatrix4 &transform, LVecBase3 const (&proj_points)[8], Camera *cam) {
+
+  min_extent.fill(1e10);
+  max_extent.fill(-1e10);
+  LPoint2 screen_points[8];
+
+  // Now project all points to the screen space of the current camera and also
+  // find the minimum and maximum extents
+  for (size_t k = 0; k < 8; ++k) {
+    LVecBase4 point(proj_points[k], 1);
+    LPoint4 proj_point = transform.xform(point);
+    LPoint3 proj_point_3d(proj_point.get_x(), proj_point.get_y(), proj_point.get_z());
+    cam->get_lens()->project(proj_point_3d, screen_points[k]);
+
+    // Find min / max extents
+    if (screen_points[k].get_x() > max_extent.get_x()) max_extent.set_x(screen_points[k].get_x());
+    if (screen_points[k].get_y() > max_extent.get_y()) max_extent.set_y(screen_points[k].get_y());
+
+    if (screen_points[k].get_x() < min_extent.get_x()) min_extent.set_x(screen_points[k].get_x());
+    if (screen_points[k].get_y() < min_extent.get_y()) min_extent.set_y(screen_points[k].get_y());
+
+    // Find min / max projected depth to adjust far plane
+    if (proj_point.get_y() > max_extent.get_z()) max_extent.set_z(proj_point.get_y());
+    if (proj_point.get_y() < min_extent.get_z()) min_extent.set_z(proj_point.get_y());
+  }
+}
+
+/**
+ * @brief Computes a film size from a given minimum and maximum extend
+ * @details This takes a minimum and maximum extent in NDC space and computes
+ *   the film size and film offset needed to cover that extent.
+ *
+ * @param film_size Output film size, can be used for Lens::set_film_size
+ * @param film_offset Output film offset, can be used for Lens::set_film_offset
+ * @param min_extent Minimum extent
+ * @param max_extent Maximum extent
+ */
+inline void get_film_properties(LVecBase2 &film_size, LVecBase2 &film_offset, const LVecBase3 &min_extent, const LVecBase3 &max_extent) {
+  float x_center = (min_extent.get_x() + max_extent.get_x()) * 0.5;
+  float y_center = (min_extent.get_y() + max_extent.get_y()) * 0.5;
+  float x_size = max_extent.get_x() - x_center;
+  float y_size = max_extent.get_y() - y_center;
+  film_size.set(x_size, y_size);
+  film_offset.set(x_center * 0.5, y_center * 0.5);
+}
+
+/**
+ * @brief Merges two arrays
+ * @details This takes two arrays which each 4 members and produces an array
+ *   with both arrays contained.
+ *
+ * @param dest Destination array
+ * @param array1 First array
+ * @param array2 Second array
+ */
+inline void merge_points_interleaved(LVecBase3 (&dest)[8], LVecBase3 const (&array1)[4], LVecBase3 const (&array2)[4]) {
+  for (size_t k = 0; k < 4; ++k) {
+    dest[k] = array1[k];
+    dest[k+4] = array2[k];
+  }
+}
+
+
+/**
+ * @brief Internal method to compute the splits
+ * @details This is the internal update method to update the PSSM splits.
+ *   It distributes the camera splits over the frustum, and updates the
+ *   MVP array aswell as the nearfar array.
+ *
+ * @param transform Main camera transform
+ * @param max_distance Maximum pssm distance, relative to the camera far plane
+ * @param light_vector Sun-Vector
+ */
+void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_distance, const LVecBase3& light_vector) {
+  nassertv(!_parent.is_empty());
+
+  // PSSM Distance should never be smaller than camera far plane.
+  nassertv(max_distance <= 1.0);
+
+  float filmsize_bias = 1.0 + _border_bias;
+
+  // Compute the positions of all cameras
+  for (size_t i = 0; i < _cam_nodes.size(); ++i) {
+    float split_start = get_split_start(i) * max_distance;
+    float split_end = get_split_start(i + 1) * max_distance;
+
+    LVecBase3 start_points[4];
+    LVecBase3 end_points[4];
+    LVecBase3 proj_points[8];
+
+    // Get split bounding box, and collect all points which define the frustum
+    for (size_t k = 0; k < 4; ++k) {
+      start_points[k] = get_interpolated_point((CoordinateOrigin)k, split_start);
+      end_points[k] = get_interpolated_point((CoordinateOrigin)k, split_end);
+      proj_points[k] = start_points[k];
+      proj_points[k + 4] = end_points[k];
+    }
+
+    // Compute approximate split mid point
+    LPoint3 split_mid = get_average_of_points(start_points, end_points);
+    LPoint3 cam_start = split_mid + light_vector * _sun_distance;
+
+    // Reset the film size, offset and far-plane
+    Camera* cam = DCAST(Camera, _cam_nodes[i].node());
+    cam->get_lens()->set_film_size(1, 1);
+    cam->get_lens()->set_film_offset(0, 0);
+    cam->get_lens()->set_near_far(1, 100);
+
+    // Find a good initial position
+    _cam_nodes[i].set_pos(cam_start);
+    _cam_nodes[i].look_at(split_mid);
+
+    LVecBase3 best_min_extent, best_max_extent;
+
+    // Find minimum and maximum extents of the points
+    LMatrix4 merged_transform = _parent.get_transform(_cam_nodes[i])->get_mat();
+    find_min_max_extents(best_min_extent, best_max_extent, merged_transform, proj_points, cam);
+
+    // Find the film size to cover all points
+    LVecBase2 film_size, film_offset;
+    get_film_properties(film_size, film_offset, best_min_extent, best_max_extent);
+
+    if (_use_fixed_film_size) {
+      // In case we use a fixed film size, store the maximum film size, and
+      // only change the film size if a new maximum is there
+      if (_max_film_sizes[i].get_x() < film_size.get_x()) _max_film_sizes[i].set_x(film_size.get_x());
+      if (_max_film_sizes[i].get_y() < film_size.get_y()) _max_film_sizes[i].set_y(film_size.get_y());
+
+      cam->get_lens()->set_film_size(_max_film_sizes[i] * filmsize_bias);
+    } else {
+      // If we don't use a fixed film size, we can just set the film size
+      // on the lens.
+      cam->get_lens()->set_film_size(film_size * filmsize_bias);
+    }
+
+    // Compute new film offset
+    cam->get_lens()->set_film_offset(film_offset);
+    cam->get_lens()->set_near_far(10, best_max_extent.get_z());
+    _camera_nearfar[i] = LVecBase2(10, best_max_extent.get_z());
+
+    // Compute the camera MVP
+    LMatrix4 mvp = compute_mvp(i);
+
+    // Stable CSM Snapping
+    if (_use_stable_csm) {
+      LPoint3 snap_offset = get_snap_offset(mvp, _resolution);
+      _cam_nodes[i].set_pos(_cam_nodes[i].get_pos() + snap_offset);
+
+      // Compute the new mvp, since we changed the snap offset
+      mvp = compute_mvp(i);
+    }
+
+    _camera_mvps.set_element(i, mvp);
+  }
+}
+
+
+/**
+ * @brief Updates the PSSM camera rig
+ * @details This updates the rig with an updated camera position, and a given
+ *   light vector. This should be called on a per-frame basis. It will reposition
+ *   all camera sources to fit the frustum based on the pssm distribution.
+ *
+ *   The light vector should be the vector from the light source, not the
+ *   vector to the light source.
+ *
+ * @param cam_node Target camera node
+ * @param light_vector The vector from the light to any point
+ */
+void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) {
+  nassertv(!cam_node.is_empty());
+  _update_collector.start();
+
+  // Get camera node transform
+  LMatrix4 transform = cam_node.get_transform()->get_mat();
+
+  // Get Camera and Lens pointers
+  Camera* cam = DCAST(Camera, cam_node.get_child(0).node());
+  nassertv(cam != nullptr);
+  Lens* lens = cam->get_lens();
+
+  // Extract near and far points:
+  lens->extrude(LPoint2(-1, 1),  _curr_near_points[UpperLeft],  _curr_far_points[UpperLeft]);
+  lens->extrude(LPoint2(1, 1),   _curr_near_points[UpperRight], _curr_far_points[UpperRight]);
+  lens->extrude(LPoint2(-1, -1), _curr_near_points[LowerLeft],  _curr_far_points[LowerLeft]);
+  lens->extrude(LPoint2(1, -1),  _curr_near_points[LowerRight], _curr_far_points[LowerRight]);
+
+  // Construct MVP to project points to world space
+  LMatrix4 mvp = transform * lens->get_view_mat();
+
+  // Project all points to world space
+  for (size_t i = 0; i < 4; ++i) {
+    LPoint4 ws_near = mvp.xform(_curr_near_points[i]);
+    LPoint4 ws_far = mvp.xform(_curr_far_points[i]);
+    _curr_near_points[i].set(ws_near.get_x(), ws_near.get_y(), ws_near.get_z());
+    _curr_far_points[i].set(ws_far.get_x(), ws_far.get_y(), ws_far.get_z());
+  }
+
+  // Do the actual PSSM
+  compute_pssm_splits( transform, _pssm_distance / lens->get_far(), light_vector );
+
+  _update_collector.stop();
+}
+

+ 125 - 0
contrib/src/rplight/pssmCameraRig.h

@@ -0,0 +1,125 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef PSSMCAMERARIG_H
+#define PSSMCAMERARIG_H
+
+#include "pandabase.h"
+#include "luse.h"
+#include "camera.h"
+#include "nodePath.h"
+#include "pStatCollector.h"
+
+#include <vector>
+
+/**
+ * @brief Main class used for handling PSSM
+ * @details This is the main class for supporting PSSM, it is used by the PSSM
+ *   plugin to compute the position of the splits.
+ *
+ *   It supports handling a varying amount of cameras, and fitting those cameras
+ *   into the main camera frustum, to render distant shadows. It also supports
+ *   various optimizations for fitting the frustum, e.g. rotating the sources
+ *   to get a better coverage.
+ *
+ *   It also provides methods to get arrays of data about the used cameras
+ *   view-projection matrices and their near and far plane, which is required for
+ *   processing the data in the shadow sampling shader.
+ *
+ *   In this class, there is often referred to "Splits" or also called "Cascades".
+ *   These denote the different cameras which are used to split the frustum,
+ *   and are a common term related to the PSSM algorithm.
+ *
+ *   To understand the functionality of this class, a detailed knowledge of the
+ *   PSSM algorithm is helpful.
+ */
+class PSSMCameraRig {
+PUBLISHED:
+  PSSMCameraRig(size_t num_splits);
+  ~PSSMCameraRig();
+
+  inline void set_pssm_distance(float distance);
+  inline void set_sun_distance(float distance);
+  inline void set_use_fixed_film_size(bool flag);
+  inline void set_resolution(size_t resolution);
+  inline void set_use_stable_csm(bool flag);
+  inline void set_logarithmic_factor(float factor);
+  inline void set_border_bias(float bias);
+
+  void update(NodePath cam_node, const LVecBase3 &light_vector);
+  inline void reset_film_size_cache();
+
+  inline NodePath get_camera(size_t index);
+
+  void reparent_to(NodePath parent);
+  inline const PTA_LMatrix4 &get_mvp_array();
+  inline const PTA_LVecBase2 &get_nearfar_array();
+
+public:
+  // Used to access the near and far points in the array
+  enum CoordinateOrigin {
+    UpperLeft = 0,
+    UpperRight,
+    LowerLeft,
+    LowerRight
+  };
+
+protected:
+  void init_cam_nodes();
+  void compute_pssm_splits(const LMatrix4& transform, float max_distance,
+               const LVecBase3 &light_vector);
+  inline float get_split_start(size_t split_index);
+  LMatrix4 compute_mvp(size_t cam_index);
+  inline LPoint3 get_interpolated_point(CoordinateOrigin origin, float depth);
+  LVecBase3 get_snap_offset(const LMatrix4& mat, size_t resolution);
+
+  vector<NodePath> _cam_nodes;
+  vector<Camera*> _cameras;
+  vector<LVecBase2> _max_film_sizes;
+
+  // Current near and far points
+  // Order: UL, UR, LL, LR (See CoordinateOrigin)
+  LPoint3 _curr_near_points[4];
+  LPoint3 _curr_far_points[4];
+  float _pssm_distance;
+  float _sun_distance;
+  float _logarithmic_factor;
+  float _border_bias;
+  bool _use_fixed_film_size;
+  bool _use_stable_csm;
+  size_t _resolution;
+  size_t _num_splits;
+  NodePath _parent;
+
+  PTA_LMatrix4 _camera_mvps;
+  PTA_LVecBase2 _camera_nearfar;
+
+  static PStatCollector _update_collector;
+};
+
+#include "pssmCameraRig.I"
+
+#endif // PSSMCAMERARIG_H

+ 406 - 0
contrib/src/rplight/rpLight.I

@@ -0,0 +1,406 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Returns the amount of shadow sources
+ * @details This returns the amount of shadow sources attached to this light.
+ *   In case the light has no shadows enabled, or the light was not attached
+ *   yet, this returns 0.
+ *
+ * @return Amount of shadow sources
+ */
+inline int RPLight::get_num_shadow_sources() const {
+  return _shadow_sources.size();
+}
+
+/**
+ * @brief Returns the n-th shadow source
+ * @details This returns the n-th attached shadow source. This ranges from
+ *   0 .. RPLight::get_num_shadow_sources(). If an invalid index is passed,
+ *   an assertion is thrown.
+ *
+ * @param index Index of the source
+ * @return Handle to the shadow source
+ */
+inline ShadowSource* RPLight::get_shadow_source(size_t index) const {
+  nassertr(index < _shadow_sources.size(), nullptr); // Invalid shadow source index
+  return _shadow_sources[index];
+}
+
+/**
+ * @brief Clears all shadow source
+ * @details This removes and destructs all shadow sources attached to this light.
+ *   This usually gets called when the light gets detached or destructed.
+ *   All shadows sources are freed, and then removed from the shadow source list.
+ */
+inline void RPLight::clear_shadow_sources() {
+  for (size_t i = 0; i < _shadow_sources.size(); ++i) {
+    delete _shadow_sources[i];
+  }
+  _shadow_sources.clear();
+}
+
+/**
+ * @brief Sets whether the light needs an update
+ * @details This controls whether the light needs to get an update. This is the
+ *   case when a property of the light changed, e.g. position or color. It does
+ *   not affect the shadows (For that use RPLight::invalidate_shadows()).
+ *   When this flag is set to true, the light will get resubmitted to the GPU
+ *   in the next update cycle.
+ *
+ *   You should usually never set the flag to false manually. The
+ *   InternalLightManager will do this when the data got sucessfully updated.
+ *
+ * @param flag Update-Flag
+ */
+inline void RPLight::set_needs_update(bool flag) {
+  _needs_update = flag;
+}
+
+/**
+ * @brief Returns whether the light needs an update
+ * @details This returns whether the light needs an update. This might be the
+ *   case when a property of the light was changed, e.g. position or color.
+ *   It does not affect the shadows, you have to query the update flag of each
+ *   individual source for that.
+ *   The return value is the value previously set with RPLight::set_needs_update.
+ *
+ * @return Update-flag
+ */
+inline bool RPLight::get_needs_update() const {
+  return _needs_update;
+}
+
+/**
+ * @brief Returns whether the light has a slot
+ * @details This returns wheter the light currently is attached, and thus has
+ *   a slot in the InternalLightManagers light list. When the light is attached,
+ *   this returns true, otherwise it will return false.
+ *
+ * @return true if the light has a slot, false otherwise
+ */
+inline bool RPLight::has_slot() const {
+  return _slot >= 0;
+}
+
+/**
+ * @brief Returns the slot of the light
+ * @details This returns the slot of the light. This is the space on the GPU
+ *   where the light is stored. If the light is not attached yet, this will
+ *   return -1, otherwise the index of the light.
+ *
+ * @return Light-Slot
+ */
+inline int RPLight::get_slot() const {
+  return _slot;
+}
+
+/**
+ * @brief Removes the light slot
+ * @details This is an internal method to remove the slot of the light. It gets
+ *   called by the InternalLightManager when a light gets detached. It internally
+ *   sets the slot to -1 to indicate the light is no longer attached.
+ */
+inline void RPLight::remove_slot() {
+  _slot = -1;
+}
+
+/**
+ * @brief Assigns a slot to the light
+ * @details This assigns a slot to the light, marking it as attached. The slot
+ *   relates to the index in the GPU's storage of lights. This is an internal
+ *   method called by the InternalLightManager when the light got attached.
+ *
+ * @param slot Slot of the light
+ */
+inline void RPLight::assign_slot(int slot) {
+  _slot = slot;
+}
+
+/**
+ * @brief Invalidates the shadows
+ * @details This invalidates all shadows of the light, causing them to get
+ *   regenerated. This might be the case  when the lights position or similar
+ *   changed. This will cause all shadow sources to be updated, emitting a
+ *   shadow update. Be careful when calling this method if you don't want all
+ *   sources to get updated. If you only have to invalidate a single shadow source,
+ *   use get_shadow_source(n)->set_needs_update(true).
+ */
+inline void RPLight::invalidate_shadows() {
+  for (size_t i = 0; i < _shadow_sources.size(); ++i) {
+    _shadow_sources[i]->set_needs_update(true);
+  }
+}
+
+/**
+ * @brief Sets the position of the light
+ * @details This sets the position of the light in world space. It will cause
+ *   the light to get invalidated, and resubmitted to the GPU.
+ *
+ * @param pos Position in world space
+ */
+inline void RPLight::set_pos(const LVecBase3 &pos) {
+  set_pos(pos.get_x(), pos.get_y(), pos.get_z());
+}
+
+/**
+ * @brief Sets the position of the light
+ * @details @copydetails RPLight::set_pos(const LVecBase3 &pos)
+ *
+ * @param x X-component of the position
+ * @param y Y-component of the position
+ * @param z Z-component of the position
+ */
+inline void RPLight::set_pos(float x, float y, float z) {
+  _position.set(x, y, z);
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+/**
+ * @brief Returns the position of the light
+ * @details This returns the position of the light previously set with
+ *   RPLight::set_pos(). The returned position is in world space.
+ * @return Light-position
+ */
+inline const LVecBase3& RPLight::get_pos() const {
+  return _position;
+}
+
+/**
+ * @brief Sets the lights color
+ * @details This sets the lights color. The color should not include the brightness
+ *   of the light, you should control that with the energy. The color specifies
+ *   the lights "tint" and will get multiplied with its specular and diffuse
+ *   contribution.
+ *
+ *   The color will be normalized by dividing by the colors luminance. Setting
+ *   higher values than 1.0 will have no effect.
+ *
+ * @param color Light color
+ */
+inline void RPLight::set_color(const LVecBase3 &color) {
+  _color = color;
+  _color /= 0.2126 * color.get_x() + 0.7152 * color.get_y() + 0.0722 * color.get_z();
+  set_needs_update(true);
+}
+
+/**
+ * @brief Sets the lights color
+ * @details @copydetails RPLight::set_color(const LVecBase3 &color)
+ *
+ * @param r Red-component of the color
+ * @param g Green-component of the color
+ * @param b Blue-component of the color
+ */
+inline void RPLight::set_color(float r, float g, float b) {
+  set_color(LVecBase3(r, g, b));
+}
+
+/**
+ * @brief Returns the lights color
+ * @details This returns the light color, previously set with RPLight::set_color.
+ *   This does not include the energy of the light. It might differ from what
+ *   was set with set_color, because the color is normalized by dividing it
+ *   by its luminance.
+ * @return Light-color
+ */
+inline const LVecBase3& RPLight::get_color() const {
+  return _color;
+}
+
+/**
+ * @brief Sets the energy of the light
+ * @details This sets the energy of the light, which can be seen as the brightness
+ *   of the light. It will get multiplied with the normalized color.
+ *
+ * @param energy energy of the light
+ */
+inline void RPLight::set_energy(float energy) {
+  _energy = energy;
+  set_needs_update(true);
+}
+
+/**
+ * @brief Returns the energy of the light
+ * @details This returns the energy of the light, previously set with
+ *   RPLight::set_energy.
+ *
+ * @return energy of the light
+ */
+inline float RPLight::get_energy() const {
+  return _energy;
+}
+
+/**
+ * @brief Returns the type of the light
+ * @details This returns the internal type of the light, which was specified
+ *   in the lights constructor. This can be used to distinguish between light
+ *   types.
+ * @return Type of the light
+ */
+inline RPLight::LightType RPLight::get_light_type() const {
+  return _light_type;
+}
+
+/**
+ * @brief Controls whether the light casts shadows
+ * @details This sets whether the light casts shadows. You can not change this
+ *   while the light is attached. When flag is set to true, the light will be
+ *   setup to cast shadows, spawning shadow sources based on the lights type.
+ *   If the flag is set to false, the light will be inddicated to cast no shadows.
+ *
+ * @param flag Whether the light casts shadows
+ */
+inline void RPLight::set_casts_shadows(bool flag) {
+  if (has_slot()) {
+    cerr << "Light is already attached, can not call set_casts_shadows!" << endl;
+    return;
+  }
+  _casts_shadows = flag;
+}
+
+/**
+ * @brief Returns whether the light casts shadows
+ * @details This returns whether the light casts shadows, the returned value
+ *   is the one previously set with RPLight::set_casts_shadows.
+ *
+ * @return true if the light casts shadows, false otherwise
+ */
+inline bool RPLight::get_casts_shadows() const {
+  return _casts_shadows;
+}
+
+/**
+ * @brief Sets the lights shadow map resolution
+ * @details This sets the lights shadow map resolution. This has no effect
+ *   when the light is not told to cast shadows (Use RPLight::set_casts_shadows).
+ *
+ *   When calling this on a light with multiple shadow sources (e.g. PointLight),
+ *   this controls the resolution of each source. If the light has 6 shadow sources,
+ *   and you use a resolution of 512x512, the lights shadow map will occur a
+ *   space of 6 * 512x512 maps in the shadow atlas.
+ *
+ * @param resolution Resolution of the shadow map in pixels
+ */
+inline void RPLight::set_shadow_map_resolution(size_t resolution) {
+  nassertv(resolution >= 32 && resolution <= 16384);
+  _source_resolution = resolution;
+  invalidate_shadows();
+}
+
+/**
+ * @brief Returns the shadow map resolution
+ * @details This returns the shadow map resolution of each source of the light.
+ *   If the light is not setup to cast shadows, this value is meaningless.
+ *   The returned value is the one previously set with RPLight::set_shadow_map_resolution.
+ *
+ * @return Shadow map resolution in pixels
+ */
+inline size_t RPLight::get_shadow_map_resolution() const {
+  return _source_resolution;
+}
+
+/**
+ * @brief Sets the ies profile
+ * @details This sets the ies profile of the light. The parameter should be a
+ *   handle previously returned by RenderPipeline.load_ies_profile. Using a
+ *   value of -1 indicates no ies profile.
+ *
+ *   Notice that for ies profiles which cover a whole range, you should use
+ *   PointLights, whereas for ies profiles which only cover the lower hemisphere
+ *   you should use SpotLights for the best performance.
+ *
+ * @param profile IES Profile handle
+ */
+inline void RPLight::set_ies_profile(int profile) {
+  _ies_profile = profile;
+  set_needs_update(true);
+}
+
+/**
+ * @brief Returns the lights ies profile
+ * @details This returns the ies profile of a light, previously set with
+ *   RPLight::set_ies_profile. In case no ies profile was set, returns -1.
+ *
+ * @return IES Profile handle
+ */
+inline int RPLight::get_ies_profile() const {
+  return _ies_profile;
+}
+
+/**
+ * @brief Returns whether the light has an ies profile assigned
+ * @details This returns whether the light has an ies profile assigned,
+ *   previously done with RPLight::set_ies_profile.
+ *
+ * @return true if the light has an ies profile assigned, false otherwise
+ */
+inline bool RPLight::has_ies_profile() const {
+  return _ies_profile >= 0;
+}
+
+/**
+ * @brief Clears the ies profile
+ * @details This clears the ies profile of the light, telling it to no longer
+ *   use an ies profile, and instead use the default attenuation.
+ */
+inline void RPLight::clear_ies_profile() {
+  set_ies_profile(-1);
+}
+
+/**
+ * @brief Sets the near plane of the light
+ * @details This sets the near plane of all shadow sources of the light. It has
+ *   no effects if the light does not cast shadows. This prevents artifacts from
+ *   objects near to the light. It behaves like Lens::set_near_plane.
+ *
+ *   It can also help increasing shadow map precision, low near planes will
+ *   cause the precision to suffer. Try setting the near plane as big as possible.
+ *
+ *   If a negative or zero near plane is passed, an assertion is thrown.
+ *
+ * @param near_plane Near-plane
+ */
+inline void RPLight::set_near_plane(float near_plane) {
+  nassertv(near_plane > 0.00001);
+  _near_plane = near_plane;
+  invalidate_shadows();
+}
+
+/**
+ * @brief Returns the near plane of the light
+ * @details This returns the lights near plane, previously set with
+ *   RPLight::set_near_plane. If the light does not cast shadows, this value
+ *   is meaningless.
+ *
+ * @return Near-plane
+ */
+inline float RPLight::get_near_plane() const {
+  return _near_plane;
+}
+

+ 137 - 0
contrib/src/rplight/rpLight.cxx

@@ -0,0 +1,137 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "rpLight.h"
+
+
+/**
+ * @brief Constructs a new light with the given type
+ * @details This constructs a new base light with the given light type.
+ *   Sub-Classes should call this to initialize all properties.
+ *
+ * @param light_type Type of the light
+ */
+RPLight::RPLight(LightType light_type) {
+  _light_type = light_type;
+  _needs_update = false;
+  _casts_shadows = false;
+  _slot = -1;
+  _position.fill(0);
+  _color.fill(1);
+  _ies_profile = -1;
+  _source_resolution = 512;
+  _near_plane = 0.5;
+  _energy = 20.0;
+}
+
+/**
+ * @brief Writes the light to a GPUCommand
+ * @details This writes all of the lights data to the given GPUCommand handle.
+ *   Subclasses should first call this method, and then append their own
+ *   data. This makes sure that for unpacking a light, no information about
+ *   the type of the light is required.
+ *
+ * @param cmd The GPUCommand to write to
+ */
+void RPLight::write_to_command(GPUCommand &cmd) {
+  cmd.push_int(_light_type);
+  cmd.push_int(_ies_profile);
+
+  if (_casts_shadows) {
+    // If we casts shadows, write the index of the first source, we expect
+    // them to be consecutive
+    nassertv(_shadow_sources.size() >= 0);
+    nassertv(_shadow_sources[0]->has_slot());
+    cmd.push_int(_shadow_sources[0]->get_slot());
+  } else {
+    // If we cast no shadows, just push a negative number
+    cmd.push_int(-1);
+  }
+
+  cmd.push_vec3(_position);
+
+  // Get the lights color by multiplying color with energy. Divide by
+  // 100, since 16bit floating point buffers only go up to 65000.0, which
+  // prevents very bright lights 
+  cmd.push_vec3(_color * _energy / 100.0);
+}
+
+/**
+ * @brief Light destructor
+ * @details This destructs the light, cleaning up all resourced used. The light
+ *   should be detached at this point, because while the Light is attached,
+ *   the InternalLightManager holds a reference to prevent it from being
+ *   destructed.
+ */
+RPLight::~RPLight() {
+  nassertv(!has_slot()); // Light still attached - should never happen
+  clear_shadow_sources();
+}
+
+/**
+ * @brief Sets the lights color from a given color temperature
+ * @details This sets the lights color, given a temperature. This is more
+ *   physically based than setting a user defined color. The color will be
+ *   computed from the given temperature.
+ *
+ * @param temperature Light temperature
+ */
+void RPLight::set_color_from_temperature(float temperature) {
+
+  // Thanks to rdb for this conversion script
+  float mm = 1000.0 / temperature;
+  float mm2 = mm * mm;
+  float mm3 = mm2 * mm;
+  float x, y;
+
+  if (temperature < 4000) {
+    x = -0.2661239 * mm3 - 0.2343580 * mm2 + 0.8776956 * mm + 0.179910;
+  } else {
+    x = -3.0258469 * mm3 + 2.1070379 * mm2 + 0.2226347 * mm + 0.240390;
+  }
+
+  float x2 = x * x;
+  float x3 = x2 * x;
+  if (temperature < 2222) {
+    y = -1.1063814 * x3 - 1.34811020 * x2 + 2.18555832 * x - 0.20219683;
+  } else if (temperature < 4000) {
+    y = -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867;
+  } else {
+    y =  3.0817580 * x3 - 5.87338670 * x2 + 3.75112997 * x - 0.37001483;
+  }
+
+  // xyY to XYZ, assuming Y=1.
+  LVecBase3 xyz(x / y, 1, (1 - x - y) / y);
+
+  // Convert XYZ to linearized sRGB.
+  const static LMatrix3 xyz_to_rgb(
+   3.2406, -0.9689,  0.0557,
+  -1.5372,  1.8758, -0.2050,
+  -0.4986,  0.0415,  1.0570);
+
+  set_color(xyz_to_rgb.xform(xyz));
+}

+ 130 - 0
contrib/src/rplight/rpLight.h

@@ -0,0 +1,130 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RPLIGHT_H
+#define RPLIGHT_H
+
+#include "referenceCount.h"
+#include "luse.h"
+#include "gpuCommand.h"
+#include "shadowSource.h"
+
+/**
+ * @brief Base class for Lights
+ * @details This is the base class for all lights in the render pipeline. It
+ *   stores common properties, and provides methods to modify these.
+ *   It also defines some interface functions which subclasses have to implement.
+ */
+class RPLight : public ReferenceCount {
+PUBLISHED:
+  /**
+   * Different types of light.
+   */
+  enum LightType {
+    LT_empty = 0,
+    LT_point_light = 1,
+    LT_spot_light = 2,
+  };
+
+public:
+  RPLight(LightType light_type);
+  virtual ~RPLight();
+
+  virtual void init_shadow_sources() = 0;
+  virtual void update_shadow_sources() = 0;
+  virtual void write_to_command(GPUCommand &cmd);
+
+  inline int get_num_shadow_sources() const;
+  inline ShadowSource* get_shadow_source(size_t index) const;
+  inline void clear_shadow_sources();
+
+  inline void set_needs_update(bool flag);
+  inline bool get_needs_update() const;
+
+  inline bool has_slot() const;
+  inline int get_slot() const;
+  inline void remove_slot();
+  inline void assign_slot(int slot);
+
+PUBLISHED:
+  inline void invalidate_shadows();
+
+  inline void set_pos(const LVecBase3 &pos);
+  inline void set_pos(float x, float y, float z);
+  inline const LVecBase3& get_pos() const;
+  MAKE_PROPERTY(pos, get_pos, set_pos);
+
+  inline void set_color(const LVecBase3 &color);
+  inline void set_color(float r, float g, float b);
+  inline const LVecBase3& get_color() const;
+  MAKE_PROPERTY(color, get_color, set_color);
+
+  void set_color_from_temperature(float temperature);
+
+  inline void set_energy(float energy);
+  inline float get_energy() const;
+  MAKE_PROPERTY(energy, get_energy, set_energy);
+
+  inline LightType get_light_type() const;
+  MAKE_PROPERTY(light_type, get_light_type);
+
+  inline void set_casts_shadows(bool flag = true);
+  inline bool get_casts_shadows() const;
+  MAKE_PROPERTY(casts_shadows, get_casts_shadows, set_casts_shadows);
+
+  inline void set_shadow_map_resolution(size_t resolution);
+  inline size_t get_shadow_map_resolution() const;
+  MAKE_PROPERTY(shadow_map_resolution, get_shadow_map_resolution, set_shadow_map_resolution);
+
+  inline void set_ies_profile(int profile);
+  inline int get_ies_profile() const;
+  inline bool has_ies_profile() const;
+  inline void clear_ies_profile();
+  MAKE_PROPERTY2(ies_profile, has_ies_profile, get_ies_profile,
+                 set_ies_profile, clear_ies_profile);
+
+  inline void set_near_plane(float near_plane);
+  inline float get_near_plane() const;
+  MAKE_PROPERTY(near_plane, get_near_plane, set_near_plane);
+
+protected:
+  int _slot;
+  int _ies_profile;
+  size_t _source_resolution;
+  bool _needs_update;
+  bool _casts_shadows;
+  LVecBase3 _position;
+  LVecBase3 _color;
+  float _energy;
+  LightType _light_type;
+  float _near_plane;
+
+  vector<ShadowSource*> _shadow_sources;
+};
+
+#include "rpLight.I"
+
+#endif // RP_LIGHT_H

+ 82 - 0
contrib/src/rplight/rpPointLight.I

@@ -0,0 +1,82 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Sets the radius of the light
+ * @details This sets the radius of the light. It controls the lights
+ *   influence. After a distance greater than this radius, the light influence
+ *   is zero.
+ *
+ * @param radius Light radius in world space
+ */
+inline void RPPointLight::set_radius(float radius) {
+  nassertv(radius > 0); // Invalid light radius
+  _radius = radius;
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+/**
+ * @brief Returns the lights radius
+ * @details This returns the lights radius previously set with
+ *   RPPointLight::set_radius
+ * @return Light radius in world space
+ */
+inline float RPPointLight::get_radius() const {
+  return _radius;
+}
+
+/**
+ * @brief Sets the inner radius of the light
+ * @details This sets the inner radius of the light. Anything greater than
+ *   zero causes the light to get an area light. This has influence on the
+ *   specular highlights of the light aswell as the shadows.
+ *
+ *   The inner radius controls the size of the lights sphere size in world
+ *   space units. A radius of 0 means the light has no inner radius, and the
+ *   light will be have like an infinite small point light source.
+ *   A radius greater than zero will cause the light to behave like it would be
+ *   an emissive sphere with the given inner radius emitting light. This is
+ *   more physically correct.
+ *
+ * @param inner_radius Inner-radius in world space
+ */
+inline void RPPointLight::set_inner_radius(float inner_radius) {
+  nassertv(inner_radius >= 0.01); // Invalid inner radius
+  _inner_radius = inner_radius;
+  set_needs_update(true);
+}
+
+/**
+ * @brief Returns the inner radius of the light
+ * @details This returns the inner radius of the light, previously set with
+ *   RPPointLight::get_inner_radius.
+ * @return [description]
+ */
+inline float RPPointLight::get_inner_radius() const {
+  return _inner_radius;
+}

+ 90 - 0
contrib/src/rplight/rpPointLight.cxx

@@ -0,0 +1,90 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "rpPointLight.h"
+
+
+/**
+ * @brief Constructs a new point light
+ * @details This contructs a new point light with default settings. By default
+ *   the light is set to be an infinitely small point light source. You can
+ *   change this with RPPointLight::set_inner_radius.
+ */
+RPPointLight::RPPointLight() : RPLight(RPLight::LT_point_light) {
+  _radius = 10.0;
+  _inner_radius = 0.01;
+}
+
+/**
+ * @brief Writes the light to a GPUCommand
+ * @details This writes the point light data to a GPUCommand.
+ * @see RPLight::write_to_command
+ *
+ * @param cmd The target GPUCommand
+ */
+void RPPointLight::write_to_command(GPUCommand &cmd) {
+  RPLight::write_to_command(cmd);
+  cmd.push_float(_radius);
+  cmd.push_float(_inner_radius);
+}
+
+/**
+ * @brief Inits the shadow sources of the light
+ * @details This inits all required shadow sources for the point light.
+ * @see RPLight::init_shadow_sources
+ */
+void RPPointLight::init_shadow_sources() {
+  nassertv(_shadow_sources.size() == 0);
+  // Create 6 shadow sources, one for each direction
+  for(size_t i = 0; i < 6; ++i) {
+    _shadow_sources.push_back(new ShadowSource());
+  }
+}
+
+/**
+ * @brief Updates the shadow sources
+ * @details This updates all shadow sources of the light.
+ * @see RPLight::update_shadow_sources
+ */
+void RPPointLight::update_shadow_sources() {
+  LVecBase3 directions[6] = {
+    LVecBase3( 1,  0,  0),
+    LVecBase3(-1,  0,  0),
+    LVecBase3( 0,  1,  0),
+    LVecBase3( 0, -1,  0),
+    LVecBase3( 0,  0,  1),
+    LVecBase3( 0,  0, -1)
+  };
+
+  // Increase fov to prevent artifacts at the shadow map transitions
+  const float fov = 90.0f + 3.0f;
+  for (size_t i = 0; i < _shadow_sources.size(); ++i) {
+    _shadow_sources[i]->set_resolution(get_shadow_map_resolution());
+    _shadow_sources[i]->set_perspective_lens(fov, _near_plane, _radius,
+                        _position, directions[i]);
+  }
+}

+ 63 - 0
contrib/src/rplight/rpPointLight.h

@@ -0,0 +1,63 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RPPOINTLIGHT_H
+#define RPPOINTLIGHT_H
+
+#include "pandabase.h"
+#include "rpLight.h"
+
+/**
+ * @brief PointLight class
+ * @details This represents a point light, a light which has a position and
+ *   radius. Checkout the RenderPipeline documentation for more information
+ *   about this type of light.
+ */
+class RPPointLight : public RPLight {
+PUBLISHED:
+  RPPointLight();
+
+  inline void set_radius(float radius);
+  inline float get_radius() const;
+  MAKE_PROPERTY(radius, get_radius, set_radius);
+
+  inline void set_inner_radius(float inner_radius);
+  inline float get_inner_radius() const;
+  MAKE_PROPERTY(inner_radius, get_inner_radius, set_inner_radius);
+
+public:
+  virtual void write_to_command(GPUCommand &cmd);
+  virtual void update_shadow_sources();
+  virtual void init_shadow_sources();
+
+protected:
+  float _radius;
+  float _inner_radius;
+};
+
+#include "rpPointLight.I"
+
+#endif // RPPOINTLIGHT_H

+ 74 - 0
contrib/src/rplight/rpSpotLight.I

@@ -0,0 +1,74 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+
+inline void RPSpotLight::set_radius(float radius) {
+  _radius = radius;
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+inline float RPSpotLight::get_radius() const {
+  return _radius;
+}
+
+
+inline void RPSpotLight::set_fov(float fov) {
+  _fov = fov;
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+inline float RPSpotLight::get_fov() const {
+  return _fov;
+}
+
+inline void RPSpotLight::set_direction(LVecBase3 direction) {
+  _direction = direction;
+  _direction.normalize();
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+inline void RPSpotLight::set_direction(float dx, float dy, float dz) {
+  _direction.set(dx, dy, dz);
+  _direction.normalize();
+  set_needs_update(true);
+  invalidate_shadows();
+}
+
+inline const LVecBase3& RPSpotLight::get_direction() const {
+  return _direction;
+}
+
+inline void RPSpotLight::look_at(LVecBase3 point) {
+  set_direction(point - _position);
+}
+
+inline void RPSpotLight::look_at(float x, float y, float z) {
+  set_direction(LVecBase3(x, y, z) - _position);
+}

+ 80 - 0
contrib/src/rplight/rpSpotLight.cxx

@@ -0,0 +1,80 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "rpSpotLight.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+
+/**
+ * @brief Creates a new spot light
+ * @details This creates a new spot light with default properties set. You should
+ *   set at least a direction, fov, radius and position to make the light useful.
+ */
+RPSpotLight::RPSpotLight() : RPLight(RPLight::LT_spot_light) {
+  _radius = 10.0;
+  _fov = 45.0;
+  _direction.set(0, 0, -1);
+}
+
+/**
+ * @brief Writes the light to a GPUCommand
+ * @details This writes the spot light data to a GPUCommand.
+ * @see RPLight::write_to_command
+ *
+ * @param cmd The target GPUCommand
+ */
+void RPSpotLight::write_to_command(GPUCommand &cmd) {
+  RPLight::write_to_command(cmd);
+  cmd.push_float(_radius);
+
+  // Encode FOV as cos(fov)
+  cmd.push_float(cos(_fov / 360.0 * M_PI));
+  cmd.push_vec3(_direction);
+}
+
+/**
+ * @brief Inits the shadow sources of the light
+ * @details This inits all required shadow sources for the spot light.
+ * @see RPLight::init_shadow_sources
+ */
+void RPSpotLight::init_shadow_sources() {
+  nassertv(_shadow_sources.size() == 0);
+  _shadow_sources.push_back(new ShadowSource());
+}
+
+/**
+ * @brief Updates the shadow sources
+ * @details This updates all shadow sources of the light.
+ * @see RPLight::update_shadow_sources
+ */
+void RPSpotLight::update_shadow_sources() {
+  _shadow_sources[0]->set_resolution(get_shadow_map_resolution());
+  _shadow_sources[0]->set_perspective_lens(_fov, _near_plane, _radius, _position, _direction);
+}
+

+ 71 - 0
contrib/src/rplight/rpSpotLight.h

@@ -0,0 +1,71 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RPSPOTLIGHT_H
+#define RPSPOTLIGHT_H
+
+#include "pandabase.h"
+#include "rpLight.h"
+
+/**
+ * @brief SpotLight class
+ * @details This represents a spot light, a light which has a position, radius,
+ *   direction and FoV. Checkout the RenderPipeline documentation for more
+ *   information about this type of light.
+ */
+class RPSpotLight : public RPLight {
+PUBLISHED:
+  RPSpotLight();
+
+  inline void set_radius(float radius);
+  inline float get_radius() const;
+  MAKE_PROPERTY(radius, get_radius, set_radius);
+
+  inline void set_fov(float fov);
+  inline float get_fov() const;
+  MAKE_PROPERTY(fov, get_fov, set_fov);
+
+  inline void set_direction(LVecBase3 direction);
+  inline void set_direction(float dx, float dy, float dz);
+  inline const LVecBase3& get_direction() const;
+  inline void look_at(LVecBase3 point);
+  inline void look_at(float x, float y, float z);
+  MAKE_PROPERTY(direction, get_direction, set_direction);
+
+public:
+  virtual void write_to_command(GPUCommand &cmd);
+  virtual void init_shadow_sources();
+  virtual void update_shadow_sources();
+
+protected:
+  float _radius;
+  float _fov;
+  LVecBase3 _direction;
+};
+
+#include "rpSpotLight.I"
+
+#endif // RPSPOTLIGHT_H

+ 159 - 0
contrib/src/rplight/shadowAtlas.I

@@ -0,0 +1,159 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Returns the tile size of the atlas.
+ * @details This returns the tile size of the atlas, which was set in the
+ *   constructor. This is the smalles unit of the atlas, and every resolution
+ *   has to be a multiple of the tile size.
+ * @return Tile size in pixels
+ */
+inline int ShadowAtlas::get_tile_size() const {
+  return _tile_size;
+}
+
+/**
+ * @brief Sets a specific tile status.
+ * @details This marks a tile to either reserved or free, depending on the flag.
+ *   If flag is true, the tile gets marked as reserved. If flag is false, the
+ *   tile gets marked as free.
+ *
+ *   No bounds checking is done for performance reasons. Passing an invalid tile
+ *   index causes a crash. The coordinates are expected to be in tile space.
+ *
+ * @param x x-position of the tile
+ * @param y y-position of the tile
+ * @param flag Flag to set the tile to
+ */
+inline void ShadowAtlas::set_tile(size_t x, size_t y, bool flag) {
+  _flags[x + y * _num_tiles] = flag;
+}
+
+/**
+ * @brief Returns the status of a given tile.
+ * @details This returns the value of a tile. If the tile is true, this means
+ *   the tile is already reserved. If the tile is false, the tile can be
+ *   used, and is not reserved.
+ *
+ *   No bounds checking is done for performance reasons. Passing an invalid tile
+ *   index causes a crash. The coordinates are expected to be in tile space.
+ *
+ * @param x x-position of the tile
+ * @param y y-position of the tile
+ *
+ * @return Tile-Status
+ */
+inline bool ShadowAtlas::get_tile(size_t x, size_t y) const {
+  return _flags[x + y * _num_tiles];
+}
+
+/**
+ * @brief Checks wheter a given region is free.
+ * @details This checks whether a given region in the atlas is still free. This
+ *   is true if *all* tiles in that region are false, and thus are not taken yet.
+ *   The coordinates are expected to be in tile space.
+ *
+ *   Passing an invalid region, causes an assertion, in case those are enabled.
+ *   If assertions are optimized out, this method will crash when passing invalid
+ *   bounds.
+ *
+ * @param x x- start position of the region
+ * @param y y- start position of the region
+ * @param w width of the region
+ * @param h height of the region
+ * @return true if the region is completely free, else false
+ */
+inline bool ShadowAtlas::region_is_free(size_t x, size_t y, size_t w, size_t h) const {
+  // Check if we are out of bounds, this should be disabled for performance
+  // reasons at some point.
+  nassertr(x >= 0 && y >= 0 && x + w <= _num_tiles && y + h <= _num_tiles, false);
+
+  // Iterate over every tile in that region and check if it is still free.
+  for (size_t cx = 0; cx < w; ++cx) {
+    for (size_t cy = 0; cy < h; ++cy) {
+      if (get_tile(cx + x, cy + y)) return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * @brief Returns the amount of tiles required to store a resolution.
+ * @details Returns the amount of tiles which would be required to store a
+ *   given resolution. This basically just returns resolution / tile_size.
+ *
+ *   When an invalid resolution is passed (not a multiple of the tile size),
+ *   an error is printed and 1 is returned.
+ *   When a negative or zero resolution is passed, undefined behaviour occurs.
+ *
+ * @param resolution The resolution to compute the amount of tiles for
+ * @return Amount of tiles to store the resolution
+ */
+inline int ShadowAtlas::get_required_tiles(size_t resolution) const {
+  nassertr(resolution > 0, -1);
+
+  if (resolution % _tile_size != 0) {
+    shadowatlas_cat.error() << "Resolution " << resolution << " is not a multiple "
+                << "of the shadow atlas tile size (" << _tile_size << ")!" << endl;
+    return 1;
+  }
+  return resolution / _tile_size;
+}
+
+/**
+ * @brief Converts a tile-space region to uv space.
+ * @details This converts a region (presumably from ShadowAtlas::find_and_reserve_region)
+ *   to uv space (0 .. 1 range). This can be used in shaders, since they expect
+ *   floating point coordinates instead of integer coordinates.
+ *
+ * @param region tile-space region
+ * @return uv-space region
+ */
+inline LVecBase4 ShadowAtlas::region_to_uv(const LVecBase4i& region) {
+  LVecBase4 flt = LVecBase4(region.get_x(), region.get_y(), region.get_z(), region.get_w());
+  return flt * ((float)_tile_size / (float)_size);
+}
+
+/**
+ * @brief Returns the amount of used tiles
+ * @details Returns the amount of used tiles in the atlas
+ * @return Amount of used tiles
+ */
+inline int ShadowAtlas::get_num_used_tiles() const {
+  return _num_used_tiles;
+}
+
+/**
+ * @brief Returns the amount of used tiles in percentage
+ * @details This returns in percentage from 0 to 1 how much space of the atlas
+ *   is used right now. A value of 1 means the atlas is completely full, whereas
+ *   a value of 0 means the atlas is completely free.
+ * @return Atlas usage in percentage
+ */
+inline float ShadowAtlas::get_coverage() const {
+  return float(_num_used_tiles) / float(_num_tiles * _num_tiles);
+}

+ 186 - 0
contrib/src/rplight/shadowAtlas.cxx

@@ -0,0 +1,186 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "shadowAtlas.h"
+#include <string.h>
+
+NotifyCategoryDef(shadowatlas, "");
+
+/**
+ * @brief Constructs a new shadow atlas.
+ * @details This constructs a new shadow atlas with the given size and tile size.
+ *
+ *   The size determines the total size of the atlas in pixels. It should be a
+ *   power-of-two to favour the GPU.
+ *
+ *   The tile_size determines the smallest unit of tiles the atlas can store.
+ *   If, for example, a tile_size of 32 is used, then every entry stored must
+ *   have a resolution of 32 or greater, and the resolution must be a multiple
+ *   of 32. This is to optimize the search in the atlas, so the atlas does not
+ *   have to check every pixel, and instead can just check whole tiles.
+ *
+ *   If you want to disable the use of tiles, set the tile_size to 1, which
+ *   will make the shadow atlas use pixels instead of tiles.
+ *
+ * @param size Atlas-size in pixels
+ * @param tile_size tile-size in pixels, or 1 to use no tiles.
+ */
+ShadowAtlas::ShadowAtlas(size_t size, size_t tile_size) {
+  nassertv(size > 1 && tile_size >= 1);
+  nassertv(tile_size < size && size % tile_size == 0);
+  _size = size;
+  _tile_size = tile_size;
+  _num_used_tiles = 0;
+  init_tiles();
+}
+
+/**
+ * @brief Destructs the shadow atlas.
+ * @details This destructs the shadow atlas, freeing all used resources.
+ */
+ShadowAtlas::~ShadowAtlas() {
+  delete [] _flags;
+}
+
+/**
+ * @brief Internal method to init the storage.
+ * @details This method setups the storage used for storing the tile flags.
+ */
+void ShadowAtlas::init_tiles() {
+  _num_tiles = _size / _tile_size;
+  _flags = new bool[_num_tiles * _num_tiles];
+  memset(_flags, 0x0, sizeof(bool) * _num_tiles * _num_tiles);
+}
+
+/**
+ * @brief Internal method to reserve a region in the atlas.
+ * @details This reserves a given region in the shadow atlas. The region should
+ *   be in tile space.This is called by the ShadowAtlas::find_and_reserve_region.
+ *   It sets all flags in that region to true, indicating that those are used.
+ *   When an invalid region is passed, an assertion is triggered. If assertions
+ *   are optimized out, undefined behaviour occurs.
+ *
+ * @param x x- start positition of the region
+ * @param y y- start position of the region
+ * @param w width of the region
+ * @param h height of the region
+ */
+void ShadowAtlas::reserve_region(size_t x, size_t y, size_t w, size_t h) {
+  // Check if we are out of bounds, this should be disabled for performance
+  // reasons at some point.
+  nassertv(x >= 0 && y >= 0 && x + w <= _num_tiles && y + h <= _num_tiles);
+
+  _num_used_tiles += w * h;
+
+  // Iterate over every tile in the region and mark it as used
+  for (size_t cx = 0; cx < w; ++cx) {
+    for (size_t cy = 0; cy < h; ++cy) {
+      set_tile(cx + x, cy + y, true);
+    }
+  }
+}
+
+/**
+ * @brief Finds space for a map of the given size in the atlas.
+ * @details This methods searches for a space to store a region of the given
+ *   size in the atlas. tile_width and tile_height should be already in tile
+ *   space. They can be converted using ShadowAtlas::get_required_tiles.
+ *
+ *   If no region is found, or an invalid size is passed, an integer vector with
+ *   all components set to -1 is returned.
+ *
+ *  If a region is found, an integer vector with the given layout is returned:
+ *   x: x- Start of the region
+ *   y: y- Start of the region
+ *   z: width of the region
+ *   w: height of the region
+ *
+ *   The layout is in tile space, and can get converted to uv space using
+ *   ShadowAtlas::region_to_uv.
+ *
+ * @param tile_width Width of the region in tile space
+ * @param tile_height Height of the region in tile space
+ *
+ * @return Region, see description, or -1 when no region is found.
+ */
+LVecBase4i ShadowAtlas::find_and_reserve_region(size_t tile_width, size_t tile_height) {
+
+  // Check for empty region
+  if (tile_width < 1 || tile_height < 1) {
+    shadowatlas_cat.error() << "Called find_and_reserve_region with null-region!" << endl;
+    return LVecBase4i(-1);
+  }
+
+  // Check for region bigger than the shadow atlas
+  if (tile_width > _num_tiles || tile_height > _num_tiles) {
+    shadowatlas_cat.error() << "Requested region exceeds shadow atlas size!" << endl;
+    return LVecBase4i(-1);
+  }
+
+  // Iterate over every possible region and check if its still free
+  for (size_t x = 0; x <= _num_tiles - tile_width; ++x) {
+    for (size_t y = 0; y <= _num_tiles - tile_height; ++y) {
+      if (region_is_free(x, y, tile_width, tile_height)) {
+        // Found free region, now reserve it
+        reserve_region(x, y, tile_width, tile_height);
+        return LVecBase4i(x, y, tile_width, tile_height);
+      }
+    }
+  }
+
+  // When we reached this part, we couldn't find a free region, so the atlas
+  // seems to be full.
+  shadowatlas_cat.error() << "Failed to find a free region of size " << tile_width
+              << " x " << tile_height << "!"  << endl;
+  return LVecBase4i(-1);
+}
+
+/**
+ * @brief Frees a given region
+ * @details This frees a given region, marking it as free so that other shadow
+ *   maps can use the space again. The region should be the same as returned
+ *   by ShadowAtlas::find_and_reserve_region.
+ *
+ *   If an invalid region is passed, an assertion is triggered. If assertions
+ *   are compiled out, undefined behaviour will occur.
+ *
+ * @param region Region to free
+ */
+void ShadowAtlas::free_region(const LVecBase4i& region) {
+  // Out of bounds check, can't hurt
+  nassertv(region.get_x() >= 0 && region.get_y() >= 0);
+  nassertv(region.get_x() + region.get_z() <= _num_tiles && region.get_y() + region.get_w() <= _num_tiles);
+
+  _num_used_tiles -= region.get_z() * region.get_w();
+
+  for (size_t x = 0; x < region.get_z(); ++x) {
+    for (size_t y = 0; y < region.get_w(); ++y) {
+      // Could do an assert here, that the tile should have been used (=true) before
+      set_tile(region.get_x() + x, region.get_y() + y, false);
+    }
+  }
+}

+ 80 - 0
contrib/src/rplight/shadowAtlas.h

@@ -0,0 +1,80 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef SHADOWATLAS_H
+#define SHADOWATLAS_H
+
+#include "pandabase.h"
+#include "lvecBase4.h"
+
+NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
+
+
+/**
+ * @brief Class which manages distributing shadow maps in an atlas.
+ * @details This class manages the shadow atlas. It handles finding and reserving
+ *   space for new shadow maps.
+ */
+class ShadowAtlas {
+PUBLISHED:
+  ShadowAtlas(size_t size, size_t tile_size = 32);
+  ~ShadowAtlas();
+
+  inline int get_num_used_tiles() const;
+  inline float get_coverage() const;
+
+  MAKE_PROPERTY(num_used_tiles, get_num_used_tiles);
+  MAKE_PROPERTY(coverage, get_coverage);
+
+public:
+
+  LVecBase4i find_and_reserve_region(size_t tile_width, size_t tile_height);
+  void free_region(const LVecBase4i& region);
+  inline LVecBase4 region_to_uv(const LVecBase4i& region);
+
+  inline int get_tile_size() const;
+  inline int get_required_tiles(size_t resolution) const;
+
+protected:
+
+  void init_tiles();
+
+  inline void set_tile(size_t x, size_t y, bool flag);
+  inline bool get_tile(size_t x, size_t y) const;
+
+  inline bool region_is_free(size_t x, size_t y, size_t w, size_t h) const;
+  void reserve_region(size_t x, size_t y, size_t w, size_t h);
+
+  size_t _size;
+  size_t _num_tiles;
+  size_t _tile_size;
+  size_t _num_used_tiles;
+  bool* _flags;
+};
+
+#include "shadowAtlas.I"
+
+#endif // SHADOWATLAS_H

+ 192 - 0
contrib/src/rplight/shadowManager.I

@@ -0,0 +1,192 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/**
+ * @brief Sets the maximum amount of updates per frame.
+ * @details This controls the maximum amount of updated ShadowSources per frame.
+ *   The ShadowManager will take the first <max_updates> ShadowSources, and
+ *   generate shadow maps for them every frame. If there are more ShadowSources
+ *   waiting to get updated than available updates, the sources are sorted by
+ *   priority, and the update of the less important sources is delayed to the
+ *   next frame.
+ *
+ *   If the update count is set too low, and there are a lot of ShadowSources
+ *   waiting to get updated, artifacts will occur, and there might be ShadowSources
+ *   which never get updated, due to low priority.
+ *
+ *   If an update count of 0 is passed, no updates will happen. This also means
+ *   that there are no shadows. This is not recommended.
+ *
+ *   If an update count < 0 is passed, undefined behaviour occurs.
+ *
+ *   This method has to get called before ShadowManager::init, otherwise an
+ *   assertion will get triggered.
+ *
+ * @param max_updates Maximum amoumt of updates
+ */
+inline void ShadowManager::set_max_updates(size_t max_updates) {
+  nassertv(max_updates >= 0);
+  nassertv(_atlas == nullptr);  // ShadowManager was already initialized
+  if (max_updates == 0) {
+    shadowmanager_cat.warning() << "max_updates set to 0, no shadows will be updated." << endl;
+  }
+  _max_updates = max_updates;
+}
+
+/**
+ * @brief Sets the shadow atlas size
+ * @details This sets the desired shadow atlas size. It should be big enough
+ *   to store all important shadow sources, with some buffer, because the shadow
+ *   maps usually won't be fitted perfectly, so gaps can occur.
+ *
+ *   This has to get called before calling ShadowManager::init. When calling this
+ *   method after initialization, an assertion will get triggered.
+ *
+ * @param atlas_size Size of the shadow atlas in pixels
+ */
+inline void ShadowManager::set_atlas_size(size_t atlas_size) {
+  nassertv(atlas_size >= 16 && atlas_size <= 16384);
+  nassertv(_atlas == nullptr);  // ShadowManager was already initialized
+  _atlas_size = atlas_size;
+}
+
+/**
+ * @brief Returns the shadow atlas size.
+ * @details This returns the shadow atlas size previously set with
+ *   ShadowManager::set_atlas_size.
+ * @return Shadow atlas size in pixels
+ */
+inline size_t ShadowManager::get_atlas_size() const {
+  return _atlas_size;
+}
+
+
+/**
+ * @brief Returns a handle to the shadow atlas.
+ * @details This returns a handle to the internal shadow atlas instance. This
+ *   is only valid after calling ShadowManager::init. Calling this earlier will
+ *   trigger an assertion and undefined behaviour.
+ * @return The internal ShadowAtlas instance
+ */
+inline ShadowAtlas* ShadowManager::get_atlas() const {
+  nassertr(_atlas != nullptr, nullptr); // Can't hurt to check
+  return _atlas;
+}
+
+/**
+ * @brief Sets the target scene
+ * @details This sets the target scene for rendering shadows. All shadow cameras
+ *   will be parented to this scene to render shadows.
+ *
+ *   Usually the scene will be ShowBase.render. If the scene is an empty or
+ *   invalid NodePath, an assertion will be triggered.
+ *
+ *   This method has to get called before calling ShadowManager::init, or an
+ *   assertion will get triggered.
+ *
+ * @param scene_parent The target scene
+ */
+inline void ShadowManager::set_scene(NodePath scene_parent) {
+  nassertv(!scene_parent.is_empty());
+  nassertv(_atlas == nullptr);  // ShadowManager was already initialized
+  _scene_parent = scene_parent;
+}
+
+/**
+ * @brief Sets the handle to the TagStageManager.
+ * @details This sets the handle to the TagStateManager used by the pipeline.
+ *   Usually this is RenderPipeline.get_tag_mgr().
+ *
+ *   This has to get called before ShadowManager::init, otherwise an assertion
+ *   will get triggered.
+ *
+ * @param tag_mgr [description]
+ */
+inline void ShadowManager::set_tag_state_manager(TagStateManager* tag_mgr) {
+  nassertv(tag_mgr != nullptr);
+  nassertv(_atlas == nullptr);  // ShadowManager was already initialized
+  _tag_state_mgr = tag_mgr;
+}
+
+/**
+ * @brief Sets the handle to the Shadow targets output
+ * @details This sets the handle to the GraphicsOutput of the shadow atlas.
+ *   Usually this is RenderTarget.get_internal_buffer(), whereas the RenderTarget
+ *   is the target of the ShadowStage.
+ *
+ *   This is used for creating display regions and attaching cameras to them,
+ *   for performing shadow updates.
+ *
+ *   This has to get called before ShadowManager::init, otherwise an assertion
+ *   will be triggered.
+ *
+ * @param graphics_output [description]
+ */
+inline void ShadowManager::set_atlas_graphics_output(GraphicsOutput* graphics_output) {
+  nassertv(graphics_output != nullptr);
+  nassertv(_atlas == nullptr);  // ShadowManager was already initialized
+  _atlas_graphics_output = graphics_output;
+}
+
+
+/**
+ * @brief Adds a new shadow update
+ * @details This adds a new update to the update queue. When the queue is already
+ *   full, this method returns false, otherwise it returns true. The next time
+ *   the manager is updated, the shadow source will recieve an update of its
+ *   shadow map.
+ *
+ * @param source The shadow source to update
+ *
+ * @return Whether the shadow source udpate was sucessfully queued.
+ */
+inline bool ShadowManager::add_update(const ShadowSource* source) {
+  nassertr(_atlas != nullptr, false); // ShadowManager::init not called yet.
+  nassertr(source != nullptr, false); // nullptr-Pointer passed
+
+  if (_queued_updates.size() >= _max_updates) {
+    if (shadowmanager_cat.is_debug()) {
+      shadowmanager_cat.debug() << "cannot update source, out of update slots" << endl;
+    }
+    return false;
+  }
+
+  // Add the update to the queue
+  _queued_updates.push_back(source);
+  return true;
+}
+
+/**
+ * @brief Returns how many update slots are left.
+ * @details This returns how many update slots are left. You can assume the
+ *   next n calls to add_update will succeed, whereas n is the value returned
+ *   by this function.
+ * @return Number of update slots left.
+ */
+inline size_t ShadowManager::get_num_update_slots_left() const {
+  return _max_updates - _queued_updates.size();
+}

+ 157 - 0
contrib/src/rplight/shadowManager.cxx

@@ -0,0 +1,157 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "shadowManager.h"
+
+NotifyCategoryDef(shadowmanager, "");
+
+/**
+ * @brief Constructs a new shadow atlas
+ * @details This constructs a new shadow atlas. There are a set of properties
+ *   which should be set before calling ShadowManager::init, see the set-Methods.
+ *   After all properties are set, ShadowManager::init should get called.
+ *   ShadowManager::update should get called on a per frame basis.
+ */
+ShadowManager::ShadowManager() {
+  _max_updates = 10;
+  _atlas = nullptr;
+  _atlas_size = 4096;
+  _tag_state_mgr = nullptr;
+  _atlas_graphics_output = nullptr;
+}
+
+/**
+ * @brief Destructs the ShadowManager
+ * @details This destructs the shadow manager, clearing all resources used
+ */
+ShadowManager::~ShadowManager() {
+  delete _atlas;
+
+  // Todo: Could eventually unregister all shadow cameras. Since the tag state
+  // manager does this on cleanup already, and we get destructed at the same
+  // time (if at all), this is not really necessary
+}
+
+
+/**
+ * @brief Initializes the ShadowManager.
+ * @details This initializes the ShadowManager. All properties should have
+ *   been set before calling this, otherwise assertions will get triggered.
+ *
+ *   This setups everything required for rendering shadows, including the
+ *   shadow atlas and the various shadow cameras. After calling this method,
+ *   no properties can be changed anymore.
+ */
+void ShadowManager::init() {
+  nassertv(!_scene_parent.is_empty());    // Scene parent not set, call set_scene_parent before init!
+  nassertv(_tag_state_mgr != nullptr);     // TagStateManager not set, call set_tag_state_mgr before init!
+  nassertv(_atlas_graphics_output != nullptr); // AtlasGraphicsOutput not set, call set_atlas_graphics_output before init!
+
+  _cameras.resize(_max_updates);
+  _display_regions.resize(_max_updates);
+  _camera_nps.reserve(_max_updates);
+
+  // Create the cameras and regions
+  for(size_t i = 0; i < _max_updates; ++i) {
+
+    // Create the camera
+    PT(Camera) camera = new Camera("ShadowCam-" + format_string(i));
+    camera->set_lens(new MatrixLens());
+    camera->set_active(false);
+    camera->set_scene(_scene_parent);
+    _tag_state_mgr->register_camera("shadow", camera);
+    _camera_nps.push_back(_scene_parent.attach_new_node(camera));
+    _cameras[i] = camera;
+
+    // Create the display region
+    PT(DisplayRegion) region = _atlas_graphics_output->make_display_region();
+    region->set_sort(1000);
+    region->set_clear_depth_active(true);
+    region->set_clear_depth(1.0);
+    region->set_clear_color_active(false);
+    region->set_camera(_camera_nps[i]);
+    region->set_active(false);
+    _display_regions[i] = region;
+  }
+
+  // Create the atlas
+  _atlas = new ShadowAtlas(_atlas_size);
+
+  // Reserve enough space for the updates
+  _queued_updates.reserve(_max_updates);
+}
+
+
+/**
+ * @brief Updates the ShadowManager
+ * @details This updates the ShadowManager, processing all shadow sources which
+ *   need to get updated.
+ *
+ *   This first collects all sources which require an update, sorts them by priority,
+ *   and then processes the first <max_updates> ShadowSources.
+ *
+ *   This may not get called before ShadowManager::init, or an assertion will be
+ *   thrown.
+ */
+void ShadowManager::update() {
+  nassertv(_atlas != nullptr);             // ShadowManager::init not called yet
+  nassertv(_queued_updates.size() <= _max_updates); // Internal error, should not happen
+
+  // Disable all cameras and regions which will not be used
+  for (size_t i = _queued_updates.size(); i < _max_updates; ++i) {
+    _cameras[i]->set_active(false);
+    _display_regions[i]->set_active(false);
+  }
+
+  // Iterate over all queued updates
+  for (size_t i = 0; i < _queued_updates.size(); ++i) {
+    const ShadowSource* source = _queued_updates[i];
+
+    // Enable the camera and display region, so they perform a render
+    _cameras[i]->set_active(true);
+    _display_regions[i]->set_active(true);
+
+    // Set the view projection matrix
+    DCAST(MatrixLens, _cameras[i]->get_lens())->set_user_mat(source->get_mvp());
+
+    // Optional: Show the camera frustum for debugging
+    // _cameras[i]->show_frustum();
+
+    // Set the correct dimensions on the display region
+    const LVecBase4& uv = source->get_uv_region();
+    _display_regions[i]->set_dimensions(
+      uv.get_x(),        // left
+      uv.get_x() + uv.get_z(), // right
+      uv.get_y(),        // bottom
+      uv.get_y() + uv.get_w()  // top
+    );
+  }
+
+  // Clear the update list
+  _queued_updates.clear();
+  _queued_updates.reserve(_max_updates);
+}

+ 91 - 0
contrib/src/rplight/shadowManager.h

@@ -0,0 +1,91 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef SHADOWMANAGER_H
+#define SHADOWMANAGER_H
+
+#include "pandabase.h"
+#include "camera.h"
+#include "luse.h"
+#include "matrixLens.h"
+#include "referenceCount.h"
+#include "nodePath.h"
+#include "displayRegion.h"
+#include "graphicsOutput.h"
+
+#include "tagStateManager.h"
+#include "shadowSource.h"
+#include "shadowAtlas.h"
+
+NotifyCategoryDecl(shadowmanager, EXPORT_CLASS, EXPORT_TEMPL);
+
+
+class ShadowManager : public ReferenceCount {
+PUBLISHED:
+  ShadowManager();
+  ~ShadowManager();
+
+  inline void set_max_updates(size_t max_updates);
+  inline void set_scene(NodePath scene_parent);
+  inline void set_tag_state_manager(TagStateManager* tag_mgr);
+  inline void set_atlas_graphics_output(GraphicsOutput* graphics_output);
+
+  inline void set_atlas_size(size_t atlas_size);
+  inline size_t get_atlas_size() const;
+  MAKE_PROPERTY(atlas_size, get_atlas_size, set_atlas_size);
+
+  inline size_t get_num_update_slots_left() const;
+  MAKE_PROPERTY(num_update_slots_left, get_num_update_slots_left);
+
+  inline ShadowAtlas* get_atlas() const;
+  MAKE_PROPERTY(atlas, get_atlas);
+
+  void init();
+  void update();
+
+public:
+  inline bool add_update(const ShadowSource* source);
+
+private:
+  size_t _max_updates;
+  size_t _atlas_size;
+  NodePath _scene_parent;
+
+  pvector<PT(Camera)> _cameras;
+  pvector<NodePath> _camera_nps;
+  pvector<PT(DisplayRegion)> _display_regions;
+
+  ShadowAtlas* _atlas;
+  TagStateManager* _tag_state_mgr;
+  GraphicsOutput* _atlas_graphics_output;
+
+  typedef pvector<const ShadowSource*> UpdateQueue;
+  UpdateQueue _queued_updates;
+};
+
+#include "shadowManager.I"
+
+#endif // SHADOWMANAGER_H

+ 262 - 0
contrib/src/rplight/shadowSource.I

@@ -0,0 +1,262 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+
+/**
+ * @brief Returns whether the shadow source needs an update.
+ * @details This returns the update flag, which was previously set with
+ *   ShadowSource::set_needs_update. If the value is true, it means that the
+ *   ShadowSource is invalid and should be regenerated. This can either be the
+ *   case because the scene changed and affected the shadow source, or the light
+ *   moved.
+ * @return Update-Flag
+ */
+inline bool ShadowSource::get_needs_update() const {
+  return !has_region() || _needs_update;
+}
+
+/**
+ * @brief Returns the slot of the shadow source.
+ * @details This returns the assigned slot of the ShadowSource, or -1 if no slot
+ *   was assigned yet. You can check if a slot exists with ShadowSource::has_slot.
+ *   The slot is the index of the ShadowSource in the global source buffer.
+ * @return Slot, or -1 to indicate no slot.
+ */
+inline int ShadowSource::get_slot() const {
+  return _slot;
+}
+
+/**
+ * @brief Returns whether the source has a slot.
+ * @details This returns whether the ShadowSource currently has an assigned slot.
+ *   If the source has a slot assigned, this returns true, otherwise false. Cases
+ *   where the source has no slot might be when the source just got attached, but
+ *   never got rendered yet.
+ * @return [description]
+ */
+inline bool ShadowSource::has_slot() const {
+  return _slot >= 0;
+}
+
+/**
+ * @brief Assigns the source a slot
+ * @details This assigns a slot to the ShadowSource. This is called from the
+ *   ShadowManager, when the source gets attached first time. This should not
+ *   get called by the user.
+ *
+ * @param slot Slot of the source, or -1 to indicate no slot.
+ */
+inline void ShadowSource::set_slot(int slot) {
+  _slot = slot;
+}
+
+/**
+ * @brief Setups a perspective lens for the source.
+ * @details This makes the shadow source behave like a perspective lens. The
+ *   parameters are similar to the ones of a PerspectiveLens.
+ *
+ * @param fov FoV of the lens
+ * @param near_plane The near plane of the lens, to avoid artifacts at low distance
+ * @param far_plane The far plane of the lens
+ * @param pos Position of the lens, in world space
+ * @param direction Direction (Orientation) of the lens
+ */
+inline void ShadowSource::
+set_perspective_lens(PN_stdfloat fov, PN_stdfloat near_plane,
+                     PN_stdfloat far_plane, LVecBase3 pos,
+                     LVecBase3 direction) {
+  // Construct the transfo*rmation matrix
+  LMatrix4 transform_mat = LMatrix4::translate_mat(-pos);
+
+  // Construct a temporary lens to generate the lens matrix
+  PerspectiveLens temp_lens = PerspectiveLens(fov, fov);
+  temp_lens.set_film_offset(0, 0);
+  temp_lens.set_near_far(near_plane, far_plane);
+  temp_lens.set_view_vector(direction, LVector3::up());
+  set_matrix_lens(transform_mat * temp_lens.get_projection_mat());
+
+  // Set new bounds, approximate with sphere
+  CPT(BoundingHexahedron) hexahedron = DCAST(BoundingHexahedron, temp_lens.make_bounds());
+  LPoint3 center = (hexahedron->get_min() + hexahedron->get_max()) * 0.5f;
+  _bounds = BoundingSphere(pos + center, (hexahedron->get_max() - center).length());
+}
+
+/**
+ * @brief Sets a custom matrix for the source.
+ * @details This tells the source to use a custom matrix for rendering, just like
+ *   the matrix lens. The matrix should include all transformations, rotations and
+ *   scales. No other matrices will be used for rendering this shadow source (not
+ *   even a coordinate system conversion).
+ *
+ * @param mvp Custom View-Projection matrix
+ */
+inline void ShadowSource::set_matrix_lens(const LMatrix4& mvp) {
+  _mvp = mvp;
+  set_needs_update(true);
+}
+
+/**
+ * @brief Sets the update flag of the source.
+ * @details Sets whether the source is still valid, or needs to get regenerated.
+ *   Usually you only want to flag the shadow source as invalid, by passing
+ *   true as the flag. However, the ShadowManager will set the flag to false
+ *   after updating the source.
+ *
+ * @param flag The update flag
+ */
+inline void ShadowSource::set_needs_update(bool flag) {
+  _needs_update = flag;
+}
+
+/**
+ * @brief Returns whether the source has a valid region.
+ * @details This returns whether the ShadowSource has a valid shadow atlas region
+ *   assigned. This might be not the case when the source never was rendered yet,
+ *   or is about to get updated.
+ * @return true if the source has a valid region, else false.
+ */
+inline bool ShadowSource::has_region() const {
+  return _region.get_x() >= 0 && _region.get_y() >= 0 && _region.get_z() >= 0 && _region.get_w() >= 0;
+}
+
+/**
+ * @brief Returns the resolution of the source.
+ * @details Returns the shadow map resolution of source, in pixels. This is the
+ *   space the source takes in the shadow atlas, in pixels.
+ * @return Resolution in pixels
+ */
+inline size_t ShadowSource::get_resolution() const {
+  return _resolution;
+}
+
+/**
+ * @brief Returns the assigned region of the source in atlas space.
+ * @details This returns the region of the source, in atlas space. This is the
+ *  region set by ShadowSource::set_region. If no region was set yet, returns
+ *  a 4-component integer vector with all components set to -1. To check this,
+ *  you should call ShadowSource::has_region() first.
+ *
+ * @return [description]
+ */
+inline const LVecBase4i& ShadowSource::get_region() const {
+  return _region;
+}
+
+/**
+ * @brief Returns the assigned region of the source in UV space.
+ * @details This returns the region of the source, in UV space. This is the
+ *  region set by ShadowSource::set_region. If no region was set yet, returns
+ *  a 4-component integer vector with all components set to -1. To check this,
+ *  you should call ShadowSource::has_region() first.
+ *
+ * @return [description]
+ */
+inline const LVecBase4& ShadowSource::get_uv_region() const {
+  return _region_uv;
+}
+
+/**
+ * @brief Sets the assigned region of the source in atlas and uv space.
+ * @details This sets the assigned region of the ShadowSource. The region in
+ *   atlas space should be the region returned from the
+ *   ShadowAtlas::find_and_reserve_region. The uv-region should be the same region,
+ *   but in the 0 .. 1 range (can be converted with ShadowAtlas::region_to_uv).
+ *   This is required for the shaders, because they expect coordinates in the
+ *   0 .. 1 range for sampling.
+ *
+ * @param region Atlas-Space region
+ * @param region_uv UV-Space region
+ */
+inline void ShadowSource::set_region(const LVecBase4i& region, const LVecBase4& region_uv) {
+  _region = region;
+  _region_uv = region_uv;
+}
+
+/**
+ * @brief Returns the View-Projection matrix of the source.
+ * @details This returns the current view-projection matrix of the ShadowSource.
+ *   If no matrix was set yet, returns a matrix with all components zero.
+ *   If a matrix was set with ShadowSource::set_matrix_lens, returns the matrix
+ *   set by that function call.
+ *
+ *   If a matrix was set with ShadowSource::set_perspective_lens, returns a
+ *   perspective view-projection matrix setup by those parameters.
+ *
+ *   The matrix returned is the matrix used for rendering the shadow map, and
+ *   includes the camera transform as well as the projection matrix.
+ *
+ * @return View-Projection matrix.
+ */
+inline const LMatrix4& ShadowSource::get_mvp() const {
+  return _mvp;
+}
+
+/**
+ * @brief Writes the source to a GPUCommand.
+ * @details This writes the ShadowSource to a GPUCommand. This stores the
+ *   mvp and the uv-region in the command.
+ *
+ * @param cmd GPUCommand to write to.
+ */
+inline void ShadowSource::write_to_command(GPUCommand &cmd) const {
+  // When storing on the gpu, we should already have a valid slot
+  nassertv(_slot >= 0);
+  cmd.push_mat4(_mvp);
+  cmd.push_vec4(_region_uv);
+}
+
+/**
+ * @brief Sets the resolution of the source.
+ * @details This sets the resolution of the ShadowSource, in pixels. It should be
+ *   a multiple of the tile size of the ShadowAtlas, and greater than zero.
+ *
+ * @param resolution [description]
+ */
+inline void ShadowSource::set_resolution(size_t resolution) {
+  nassertv(resolution > 0);
+  _resolution = resolution;
+  set_needs_update(true);
+}
+
+/**
+ * @brief Returns the shadow sources bounds
+ * @details This returns the bounds of the shadow source, approximated as a sphere
+ * @return Bounds as a BoundingSphere
+ */
+inline const BoundingSphere& ShadowSource::get_bounds() const {
+  return _bounds;
+}
+
+/**
+ * @brief Clears the assigned region of the source
+ * @details This unassigns any shadow atlas region from the source, previously
+ *   set with set_region
+ */
+inline void ShadowSource::clear_region() {
+  _region.fill(-1);
+  _region_uv.fill(0);
+}

+ 41 - 0
contrib/src/rplight/shadowSource.cxx

@@ -0,0 +1,41 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "shadowSource.h"
+
+/**
+ * @brief Constructs a new shadow source
+ * @details This constructs a new shadow source, with no projection setup,
+ *   and no slot assigned.
+ */
+ShadowSource::ShadowSource() {
+  _slot = -1;
+  _needs_update = true;
+  _resolution = 512;
+  _mvp.fill(0.0);
+  _region.fill(-1);
+  _region_uv.fill(0);
+}

+ 93 - 0
contrib/src/rplight/shadowSource.h

@@ -0,0 +1,93 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#ifndef SHADOWSOURCE_H
+#define SHADOWSOURCE_H
+
+#include "pandabase.h"
+#include "luse.h"
+#include "transformState.h"
+#include "look_at.h"
+#include "compose_matrix.h"
+#include "perspectiveLens.h"
+#include "boundingVolume.h"
+#include "boundingSphere.h"
+#include "boundingHexahedron.h"
+#include "geometricBoundingVolume.h"
+
+#include "gpuCommand.h"
+
+/**
+ * @brief This class represents a single shadow source.
+ * @details The ShadowSource can be seen as a Camera. It is used by the Lights
+ *   to render their shadows. Each ShadowSource has a position in the atlas,
+ *   and a view-projection matrix. The shadow manager regenerates the shadow maps
+ *   using the data from the shadow sources.
+ */
+class ShadowSource {
+public:
+  ShadowSource();
+
+  inline void write_to_command(GPUCommand &cmd) const;
+
+  inline void set_needs_update(bool flag);
+  inline void set_slot(int slot);
+  inline void set_region(const LVecBase4i& region, const LVecBase4& region_uv);
+  inline void set_resolution(size_t resolution);
+  inline void set_perspective_lens(PN_stdfloat fov, PN_stdfloat near_plane,
+                                   PN_stdfloat far_plane, LVecBase3 pos,
+                                   LVecBase3 direction);
+  inline void set_matrix_lens(const LMatrix4& mvp);
+
+  inline bool has_region() const;
+  inline bool has_slot() const;
+
+  inline void clear_region();
+
+  inline int get_slot() const;
+  inline bool get_needs_update() const;
+  inline size_t get_resolution() const;
+  inline const LMatrix4& get_mvp() const;
+  inline const LVecBase4i& get_region() const;
+  inline const LVecBase4& get_uv_region() const;
+
+  inline const BoundingSphere& get_bounds() const;
+
+private:
+  int _slot;
+  bool _needs_update;
+  size_t _resolution;
+  LMatrix4 _mvp;
+  LVecBase4i _region;
+  LVecBase4 _region_uv;
+
+  BoundingSphere _bounds;
+};
+
+#include "shadowSource.I"
+
+#endif // SHADOWSOURCE_H

+ 93 - 0
contrib/src/rplight/tagStateManager.I

@@ -0,0 +1,93 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+/**
+ * @brief Registers a new camera which renders a certain pass
+ * @details This registers a new camera which will be used to render the given
+ *   pass. The TagStateManager will keep track of the camera and
+ *   applies all registered states onto the camera with Camera::set_tag_state.
+ *   It also applies the appropriate camera mask to the camera,
+ *   and sets an initial state to disable color write depending on the pass.
+ *
+ * @param source Camera which will be used to render shadows
+ */
+inline void TagStateManager::
+register_camera(const string& name, Camera* source) {
+  ContainerList::iterator entry = _containers.find(name);
+  nassertv(entry != _containers.end());
+  register_camera(entry->second, source);
+}
+
+/**
+ * @brief Unregisters a camera from the list of shadow cameras
+ * @details This unregisters a camera from the list of shadows cameras. It also
+ *   resets all tag states of the camera, and also its initial state.
+ *
+ * @param source Camera to unregister
+ */
+inline void TagStateManager::
+unregister_camera(const string& name, Camera* source) {
+  ContainerList::iterator entry = _containers.find(name);
+  nassertv(entry != _containers.end());
+  unregister_camera(entry->second, source);
+}
+
+/**
+ * @brief Applies a given state for a pass to a NodePath
+ * @details This applies a shader to the given NodePath which is used when the
+ *   NodePath is rendered by any registered camera for that pass.
+ *   It also disables color write depending on the pass.
+ *
+ * @param np The nodepath to apply the shader to
+ * @param shader A handle to the shader to apply
+ * @param name Name of the state, should be a unique identifier
+ * @param sort Determines the sort with which the shader will be applied.
+ */
+inline void TagStateManager::
+apply_state(const string& state, NodePath np, Shader* shader,
+            const string &name, int sort) {
+  ContainerList::iterator entry = _containers.find(state);
+  nassertv(entry != _containers.end());
+  apply_state(entry->second, np, shader, name, sort);
+}
+
+/**
+ * @brief Returns the render mask for the given state
+ * @details This returns the mask of a given render pass, which can be used
+ *   to either show or hide objects from this pass.
+ *
+ * @param container_name Name of the render-pass
+ * @return Bit mask of the render pass
+ */
+inline BitMask32 TagStateManager::
+get_mask(const string &container_name) {
+  if (container_name == "gbuffer") {
+    return BitMask32::bit(1);
+  }
+  ContainerList::iterator entry = _containers.find(container_name);
+  nassertr(entry != _containers.end(), BitMask32());
+  return entry->second.mask;
+}

+ 202 - 0
contrib/src/rplight/tagStateManager.cxx

@@ -0,0 +1,202 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include "tagStateManager.h"
+
+
+NotifyCategoryDef(tagstatemgr, "");
+
+/**
+ * @brief Constructs a new TagStateManager
+ * @details This constructs a new TagStateManager. The #main_cam_node should
+ *   refer to the main scene camera, and will most likely be base.cam.
+ *   It is necessary to pass the camera because the C++ code does not have
+ *   access to the showbase.
+ *
+ * @param main_cam_node The main scene camera
+ */
+TagStateManager::
+TagStateManager(NodePath main_cam_node) {
+  nassertv(!main_cam_node.is_empty());
+  nassertv(DCAST(Camera, main_cam_node.node()) != nullptr);
+  _main_cam_node = main_cam_node;
+
+  // Set default camera mask
+  DCAST(Camera, _main_cam_node.node())->set_camera_mask(BitMask32::bit(1));
+
+  // Init containers
+  _containers["shadow"]   = StateContainer("Shadows",  2, false);
+  _containers["voxelize"] = StateContainer("Voxelize", 3, false);
+  _containers["envmap"]   = StateContainer("Envmap",   4, true);
+  _containers["forward"]  = StateContainer("Forward",  5, true);
+}
+
+/**
+ * @brief Destructs the TagStateManager
+ * @details This destructs the TagStateManager, and cleans up all resources used.
+ */
+TagStateManager::
+~TagStateManager() {
+  cleanup_states();
+}
+
+/**
+ * @brief Applies a given state to a NodePath
+ * @details This applies a shader to the given NodePath which is used when the
+ *   NodePath is rendered by any registered camera of the container.
+ *
+ * @param container The container which is used to store the state
+ * @param np The nodepath to apply the shader to
+ * @param shader A handle to the shader to apply
+ * @param name Name of the state, should be a unique identifier
+ * @param sort Changes the sort with which the shader will be applied.
+ */
+void TagStateManager::
+apply_state(StateContainer& container, NodePath np, Shader* shader,
+            const string &name, int sort) {
+  if (tagstatemgr_cat.is_spam()) {
+    tagstatemgr_cat.spam() << "Constructing new state " << name
+                 << " with shader " << shader << endl;
+  }
+
+  // Construct the render state
+  CPT(RenderState) state = RenderState::make_empty();
+
+  // Disable color write for all stages except the environment container
+  if (!container.write_color) {
+    state = state->set_attrib(ColorWriteAttrib::make(ColorWriteAttrib::C_off), 10000);
+  }
+  state = state->set_attrib(ShaderAttrib::make(shader, sort), sort);
+
+  // Emit a warning if we override an existing state
+  if (container.tag_states.count(name) != 0) {
+    tagstatemgr_cat.warning() << "Overriding existing state " << name << endl;
+  }
+
+  // Store the state, this is required whenever we attach a new camera, so
+  // it can also track the existing states
+  container.tag_states[name] = state;
+
+  // Save the tag on the node path
+  np.set_tag(container.tag_name, name);
+
+  // Apply the state on all cameras which are attached so far
+  for (size_t i = 0; i < container.cameras.size(); ++i) {
+    container.cameras[i]->set_tag_state(name, state);
+  }
+}
+
+/**
+ * @brief Cleans up all registered states.
+ * @details This cleans up all states which were registered to the TagStateManager.
+ *   It also calls Camera::clear_tag_states() on the main_cam_node and all attached
+ *   cameras.
+ */
+void TagStateManager::
+cleanup_states() {
+  if (tagstatemgr_cat.is_info()) {
+    tagstatemgr_cat.info() << "cleaning up states" << endl;
+  }
+
+  // Clear all tag states of the main camera
+  DCAST(Camera, _main_cam_node.node())->clear_tag_states();
+
+  // Clear the containers
+  // XXX: Just iterate over the _container map
+  cleanup_container_states(_containers["shadow"]);
+  cleanup_container_states(_containers["voxelize"]);
+  cleanup_container_states(_containers["envmap"]);
+  cleanup_container_states(_containers["forward"]);
+}
+
+/**
+ * @brief Cleans up the states of a given container
+ * @details This cleans all tag states of the given container,
+ *   and also calls Camera::clear_tag_states on every assigned camera.
+ *
+ * @param container Container to clear
+ */
+void TagStateManager::
+cleanup_container_states(StateContainer& container) {
+  for (size_t i = 0; i < container.cameras.size(); ++i) {
+    container.cameras[i]->clear_tag_states();
+  }
+  container.tag_states.clear();
+}
+
+/**
+ * @brief Registers a new camera to a given container
+ * @details This registers a new camera to a container, and sets its initial
+ *   state as well as the camera mask.
+ *
+ * @param container The container to add the camera to
+ * @param source The camera to add
+ */
+void TagStateManager::
+register_camera(StateContainer& container, Camera* source) {
+  source->set_tag_state_key(container.tag_name);
+  source->set_camera_mask(container.mask);
+
+  // Construct an initial state which also disables color write, additionally
+  // to the ColorWriteAttrib on each unique state.
+  CPT(RenderState) state = RenderState::make_empty();
+
+  if (!container.write_color) {
+    state = state->set_attrib(ColorWriteAttrib::make(ColorWriteAttrib::C_off), 10000);
+  }
+  source->set_initial_state(state);
+
+  // Store the camera so we can keep track of it
+  container.cameras.push_back(source);
+}
+
+/**
+ * @brief Unregisters a camera from a container
+ * @details This unregisters a camera from the list of cameras of a given
+ *   container. It also resets all tag states of the camera, and also its initial
+ *   state.
+ *
+ * @param source Camera to unregister
+ */
+void TagStateManager::
+unregister_camera(StateContainer& container, Camera* source) {
+  CameraList& cameras = container.cameras;
+
+  // Make sure the camera was attached so far
+  if (std::find(cameras.begin(), cameras.end(), source) == cameras.end()) {
+    tagstatemgr_cat.error()
+      << "Called unregister_camera but camera was never registered!" << endl;
+    return;
+  }
+
+  // Remove the camera from the list of attached cameras
+  cameras.erase(std::remove(cameras.begin(), cameras.end(), source), cameras.end());
+
+  // Reset the camera
+  source->clear_tag_states();
+  source->set_initial_state(RenderState::make_empty());
+}

+ 93 - 0
contrib/src/rplight/tagStateManager.h

@@ -0,0 +1,93 @@
+/**
+ *
+ * RenderPipeline
+ *
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TAGSTATEMANAGER_H
+#define TAGSTATEMANAGER_H
+
+#include "pandabase.h"
+#include "bitMask.h"
+#include "camera.h"
+#include "nodePath.h"
+#include "shader.h"
+#include "renderState.h"
+#include "shaderAttrib.h"
+#include "colorWriteAttrib.h"
+
+NotifyCategoryDecl(tagstatemgr, EXPORT_CLASS, EXPORT_TEMPL);
+
+/**
+ * @brief This class handles all different tag states
+ * @details The TagStateManager stores a list of RenderStates assigned to different
+ *   steps in the pipeline. For example, there are a list of shadow states, which
+ *   are applied whenever objects are rendered from a shadow camera.
+ *
+ *   The Manager also stores a list of all cameras used in the different stages,
+ *   to keep track of the states used and to be able to attach new states.
+ */
+class TagStateManager {
+PUBLISHED:
+  TagStateManager(NodePath main_cam_node);
+  ~TagStateManager();
+
+  inline void apply_state(const string& state, NodePath np, Shader* shader, const string &name, int sort);
+  void cleanup_states();
+
+  inline void register_camera(const string& state, Camera* source);
+  inline void unregister_camera(const string& state, Camera* source);
+  inline BitMask32 get_mask(const string &container_name);
+
+private:
+  typedef vector<Camera*> CameraList;
+  typedef pmap<string, CPT(RenderState)> TagStateList;
+
+  struct StateContainer {
+    CameraList cameras;
+    TagStateList tag_states;
+    string tag_name;
+    BitMask32 mask;
+    bool write_color;
+
+    StateContainer() {};
+    StateContainer(const string &tag_name, size_t mask, bool write_color)
+      : tag_name(tag_name), mask(BitMask32::bit(mask)), write_color(write_color) {};
+  };
+
+  void apply_state(StateContainer& container, NodePath np, Shader* shader,
+                   const string& name, int sort);
+  void cleanup_container_states(StateContainer& container);
+  void register_camera(StateContainer &container, Camera* source);
+  void unregister_camera(StateContainer &container, Camera* source);
+
+  typedef pmap<string, StateContainer> ContainerList;
+  ContainerList _containers;
+
+  NodePath _main_cam_node;
+};
+
+
+#include "tagStateManager.I"
+
+#endif // TAGSTATEMANAGER_H

+ 4 - 4
contrib/src/sceneeditor/seFileSaver.py

@@ -488,7 +488,7 @@ class FileSaver:
                      out_file.write (i2+ "alight = AmbientLight(\'"+ light.getName() +"\')\n")
                      out_file.write (i2+ "alight = AmbientLight(\'"+ light.getName() +"\')\n")
                      out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
                      out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
-                     out_file.write (i2+ "self."+light.getName()+"= render.attachNewNode(alight.upcastToPandaNode())\n")
+                     out_file.write (i2+ "self."+light.getName()+"= render.attachNewNode(alight)\n")
                      out_file.write (i2+ "self."+light.getName()+".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
                      out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
                      out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
                      out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
@@ -503,7 +503,7 @@ class FileSaver:
                      #out_file.write (i2+ "alight.setPoint(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      #out_file.write (i2+ "alight.setPoint(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
                      out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
-                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToPandaNode())\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight)\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
@@ -521,7 +521,7 @@ class FileSaver:
                      out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
                      out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
                      out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
                      out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
-                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToPandaNode())\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight)\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
                      out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
@@ -539,7 +539,7 @@ class FileSaver:
                      out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
                      out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
                      out_file.write (i2+ "alight.setExponent(" +str(light.getExponent()) +")\n")
                      out_file.write (i2+ "alight.setExponent(" +str(light.getExponent()) +")\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
                      out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
-                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToLensNode())\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight)\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
                      out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")

+ 1 - 8
contrib/src/sceneeditor/seLights.py

@@ -63,13 +63,8 @@ class seLight(NodePath):
         self.lence = lence
         self.lence = lence
         self.active = True
         self.active = True
 
 
-        if isinstance(light, Spotlight):
-            node = light.upcastToLensNode()
-        else:
-            node = light.upcastToPandaNode()
-
         # Attach node to self
         # Attach node to self
-        self.LightNode=parent.attachNewNode(node)
+        self.LightNode=parent.attachNewNode(light)
         self.LightNode.setTag("Metadata",tag)
         self.LightNode.setTag("Metadata",tag)
         if(self.type=='spot'):
         if(self.type=='spot'):
             self.LightNode.setHpr(self.orientation)
             self.LightNode.setHpr(self.orientation)
@@ -418,8 +413,6 @@ class seLightManager(NodePath):
         #################################################################
         #################################################################
         type = lower(light.getType().getName())
         type = lower(light.getType().getName())
 
 
-        light.upcastToNamable()
-
         specularColor = VBase4(1)
         specularColor = VBase4(1)
         position = Point3(0,0,0)
         position = Point3(0,0,0)
         orientation = Vec3(1,0,0)
         orientation = Vec3(1,0,0)

+ 0 - 10
direct/metalibs/direct/direct.cxx

@@ -1,10 +0,0 @@
-/**
- * @file direct.cxx
- * @author drose
- * @date 2000-05-18
- */
-
-// This is a dummy file whose sole purpose is to give the compiler something
-// to compile when making libdirect.so in NO_DEFER mode, which generates an
-// empty library that itself links with all the other shared libraries that
-// make up libdirect.

+ 3 - 3
direct/src/actor/Actor.py

@@ -897,7 +897,7 @@ class Actor(DirectObject, NodePath):
         return ((toFrame+1)-fromFrame) / animControl.getFrameRate()
         return ((toFrame+1)-fromFrame) / animControl.getFrameRate()
 
 
     def getNumFrames(self, animName=None, partName=None):
     def getNumFrames(self, animName=None, partName=None):
-        lodName = next(iter(self.__animControlDict))
+        #lodName = next(iter(self.__animControlDict))
         controls = self.getAnimControls(animName, partName)
         controls = self.getAnimControls(animName, partName)
         if len(controls) == 0:
         if len(controls) == 0:
             return None
             return None
@@ -1103,8 +1103,8 @@ class Actor(DirectObject, NodePath):
         # Get a handle to the joint.
         # Get a handle to the joint.
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
 
 
-        if node == None:
-            node = self.attachNewNode(jointName)
+        if node is None:
+            node = partDef.partBundleNP.attachNewNode(jointName)
 
 
         if (joint):
         if (joint):
             if localTransform:
             if localTransform:

+ 6 - 85
direct/src/dcparser/dcClass.cxx

@@ -514,15 +514,9 @@ receive_update_broadcast_required_owner(PyObject *distobj,
   for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
   for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
     DCField *field = get_inherited_field(i);
     DCField *field = get_inherited_field(i);
     if (field->as_molecular_field() == (DCMolecularField *)NULL &&
     if (field->as_molecular_field() == (DCMolecularField *)NULL &&
-        field->is_required()) {
+        field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
       packer.begin_unpack(field);
       packer.begin_unpack(field);
-      if (field->is_ownrecv()) {
-        field->receive_update(packer, distobj);
-      } else {
-        // It's not an ownrecv field; skip over it.  It's difficult to filter
-        // this on the server, ask Roger for the reason.
-        packer.unpack_skip();
-      }
+      field->receive_update(packer, distobj);
       if (!packer.end_unpack()) {
       if (!packer.end_unpack()) {
         break;
         break;
       }
       }
@@ -946,19 +940,17 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
   bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
   bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
 
 
   if (has_optional_fields) {
   if (has_optional_fields) {
-    packer.raw_pack_uint16(STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER);
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
   } else {
   } else {
-    packer.raw_pack_uint16(STATESERVER_OBJECT_GENERATE_WITH_REQUIRED);
+    packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
   }
   }
 
 
+  packer.raw_pack_uint32(do_id);
   // Parent is a bit overloaded; this parent is not about inheritance, this
   // Parent is a bit overloaded; this parent is not about inheritance, this
   // one is about the visibility container parent, i.e.  the zone parent:
   // one is about the visibility container parent, i.e.  the zone parent:
-  if (parent_id) {
-    packer.raw_pack_uint32(parent_id);
-  }
+  packer.raw_pack_uint32(parent_id);
   packer.raw_pack_uint32(zone_id);
   packer.raw_pack_uint32(zone_id);
   packer.raw_pack_uint16(_number);
   packer.raw_pack_uint16(_number);
-  packer.raw_pack_uint32(do_id);
 
 
   // Specify all of the required fields.
   // Specify all of the required fields.
   int num_fields = get_num_inherited_fields();
   int num_fields = get_num_inherited_fields();
@@ -1009,77 +1001,6 @@ ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
   return Datagram(packer.get_data(), packer.get_length());
   return Datagram(packer.get_data(), packer.get_length());
 }
 }
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON
-#ifdef HAVE_PYTHON
-/**
- * Generates a datagram containing the message necessary to create a new
- * database distributed object from the AI.
- *
- * First Pass is to only include required values (with Defaults).
- */
-Datagram DCClass::
-ai_database_generate_context(
-    unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id,
-    CHANNEL_TYPE owner_channel,
-    CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const
-{
-  DCPacker packer;
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(database_server_id);
-  packer.RAW_PACK_CHANNEL(from_channel_id);
-  // packer.raw_pack_uint8('A');
-  packer.raw_pack_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT);
-  packer.raw_pack_uint32(parent_id);
-  packer.raw_pack_uint32(zone_id);
-  packer.RAW_PACK_CHANNEL(owner_channel);
-  packer.raw_pack_uint16(_number); // DCD class ID
-  packer.raw_pack_uint32(context_id);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == NULL) {
-      packer.begin_pack(field);
-      packer.pack_default_value();
-      packer.end_pack();
-    }
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
-
-#ifdef HAVE_PYTHON
-Datagram DCClass::
-ai_database_generate_context_old(
-    unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id,
-    CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const
-{
-  DCPacker packer;
-  packer.raw_pack_uint8(1);
-  packer.RAW_PACK_CHANNEL(database_server_id);
-  packer.RAW_PACK_CHANNEL(from_channel_id);
-  // packer.raw_pack_uint8('A');
-  packer.raw_pack_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT);
-  packer.raw_pack_uint32(parent_id);
-  packer.raw_pack_uint32(zone_id);
-  packer.raw_pack_uint16(_number); // DCD class ID
-  packer.raw_pack_uint32(context_id);
-
-  // Specify all of the required fields.
-  int num_fields = get_num_inherited_fields();
-  for (int i = 0; i < num_fields; ++i) {
-    DCField *field = get_inherited_field(i);
-    if (field->is_required() && field->as_molecular_field() == NULL) {
-      packer.begin_pack(field);
-      packer.pack_default_value();
-      packer.end_pack();
-    }
-  }
-
-  return Datagram(packer.get_data(), packer.get_length());
-}
-#endif  // HAVE_PYTHON
 
 
 /**
 /**
  * Write a string representation of this instance to <out>.
  * Write a string representation of this instance to <out>.

+ 0 - 5
direct/src/dcparser/dcClass.h

@@ -117,11 +117,6 @@ PUBLISHED:
   Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
   Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
                                       ZONEID_TYPE zone_id,                                                           PyObject *optional_fields) const;
                                       ZONEID_TYPE zone_id,                                                           PyObject *optional_fields) const;
 
 
-  Datagram ai_database_generate_context(unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id, CHANNEL_TYPE owner_channel,
-                                CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const;
-  Datagram ai_database_generate_context_old(unsigned int context_id, DOID_TYPE parent_id, ZONEID_TYPE zone_id,
-                                CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const;
-
 #endif
 #endif
 
 
 public:
 public:

+ 2 - 2
direct/src/dcparser/dcField.cxx

@@ -391,7 +391,7 @@ Datagram DCField::
 client_format_update(DOID_TYPE do_id, PyObject *args) const {
 client_format_update(DOID_TYPE do_id, PyObject *args) const {
   DCPacker packer;
   DCPacker packer;
 
 
-  packer.raw_pack_uint16(CLIENT_OBJECT_UPDATE_FIELD);
+  packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
   packer.raw_pack_uint32(do_id);
   packer.raw_pack_uint32(do_id);
   packer.raw_pack_uint16(_number);
   packer.raw_pack_uint16(_number);
 
 
@@ -417,7 +417,7 @@ ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyOb
   packer.raw_pack_uint8(1);
   packer.raw_pack_uint8(1);
   packer.RAW_PACK_CHANNEL(to_id);
   packer.RAW_PACK_CHANNEL(to_id);
   packer.RAW_PACK_CHANNEL(from_id);
   packer.RAW_PACK_CHANNEL(from_id);
-  packer.raw_pack_uint16(STATESERVER_OBJECT_UPDATE_FIELD);
+  packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
   packer.raw_pack_uint32(do_id);
   packer.raw_pack_uint32(do_id);
   packer.raw_pack_uint16(_number);
   packer.raw_pack_uint16(_number);
 
 

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

@@ -708,7 +708,7 @@ pack_object(PyObject *object) {
     pack_int64(PyLong_AsLongLong(object));
     pack_int64(PyLong_AsLongLong(object));
 #if PY_MAJOR_VERSION >= 3
 #if PY_MAJOR_VERSION >= 3
   } else if (PyUnicode_Check(object)) {
   } else if (PyUnicode_Check(object)) {
-    char *buffer;
+    const char *buffer;
     Py_ssize_t length;
     Py_ssize_t length;
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     buffer = PyUnicode_AsUTF8AndSize(object, &length);
     if (buffer) {
     if (buffer) {

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

@@ -2238,6 +2238,8 @@ output_instance(ostream &out, bool brief, const string &prename,
       }
       }
       break;
       break;
 
 
+    case ST_blob:
+    case ST_blob32:
     case ST_string:
     case ST_string:
       if (!_uint_range.is_empty()) {
       if (!_uint_range.is_empty()) {
         out << "(";
         out << "(";

+ 0 - 6
direct/src/dcparser/dcbase.h

@@ -80,12 +80,6 @@ using namespace std;
 #define nassertv(condition) assert(condition)
 #define nassertv(condition) assert(condition)
 #define nassertv_always(condition) assert(condition)
 #define nassertv_always(condition) assert(condition)
 
 
-// Panda defines these export symbols for building DLL's.  Outside of Panda,
-// we assume we're not putting this code in a DLL, so we define them to
-// nothing.
-#define EXPCL_DIRECT
-#define EXPTP_DIRECT
-
 // Panda defines a special Filename class.  We'll use an ordinary string
 // Panda defines a special Filename class.  We'll use an ordinary string
 // instead.
 // instead.
 typedef string Filename;
 typedef string Filename;

+ 6 - 9
direct/src/dcparser/dcmsgtypes.h

@@ -17,16 +17,13 @@
 // This file defines the server message types used within this module.  It
 // This file defines the server message types used within this module.  It
 // duplicates some symbols defined in MsgTypes.py and AIMsgTypes.py.
 // duplicates some symbols defined in MsgTypes.py and AIMsgTypes.py.
 
 
-#define CLIENT_OBJECT_UPDATE_FIELD                        24
-#define CLIENT_CREATE_OBJECT_REQUIRED                     34
-#define CLIENT_CREATE_OBJECT_REQUIRED_OTHER               35
+#define CLIENT_OBJECT_SET_FIELD                           120
+#define CLIENT_ENTER_OBJECT_REQUIRED                      142
+#define CLIENT_ENTER_OBJECT_REQUIRED_OTHER                143
 
 
-#define STATESERVER_OBJECT_GENERATE_WITH_REQUIRED         2001
-#define STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER   2003
-#define STATESERVER_OBJECT_UPDATE_FIELD                   2004
-#define STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT   2050
-#define STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT  2051
-#define STATESERVER_BOUNCE_MESSAGE                        2086
+#define STATESERVER_CREATE_OBJECT_WITH_REQUIRED           2000
+#define STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER     2001
+#define STATESERVER_OBJECT_SET_FIELD                      2020
 
 
 #define CLIENT_OBJECT_GENERATE_CMU                        9002
 #define CLIENT_OBJECT_GENERATE_CMU                        9002
 
 

+ 4 - 0
direct/src/deadrec/config_deadrec.cxx

@@ -15,6 +15,10 @@
 
 
 #include "dconfig.h"
 #include "dconfig.h"
 
 
+#if !defined(CPPPARSER) && !defined(BUILDING_DIRECT_DEADREC)
+  #error Buildsystem error: BUILDING_DIRECT_DEADREC not defined
+#endif
+
 Configure(config_deadrec);
 Configure(config_deadrec);
 NotifyCategoryDef(deadrec, "");
 NotifyCategoryDef(deadrec, "");
 
 

+ 2 - 2
direct/src/deadrec/config_deadrec.h

@@ -18,10 +18,10 @@
 #include "notifyCategoryProxy.h"
 #include "notifyCategoryProxy.h"
 #include "configVariableBool.h"
 #include "configVariableBool.h"
 
 
-NotifyCategoryDecl(deadrec, EXPCL_DIRECT, EXPTP_DIRECT);
+NotifyCategoryDecl(deadrec, EXPCL_DIRECT_DEADREC, EXPTP_DIRECT_DEADREC);
 
 
 extern ConfigVariableBool accept_clock_skew;
 extern ConfigVariableBool accept_clock_skew;
 
 
-extern EXPCL_DIRECT void init_libdeadrec();
+extern EXPCL_DIRECT_DEADREC void init_libdeadrec();
 
 
 #endif
 #endif

+ 1 - 1
direct/src/deadrec/smoothMover.h

@@ -38,7 +38,7 @@ static const int max_timestamp_delays = 10;
  * update.  The assumption is that all SmoothMovers in the world will be
  * update.  The assumption is that all SmoothMovers in the world will be
  * operating in the same mode together.
  * operating in the same mode together.
  */
  */
-class EXPCL_DIRECT SmoothMover {
+class EXPCL_DIRECT_DEADREC SmoothMover {
 PUBLISHED:
 PUBLISHED:
   SmoothMover();
   SmoothMover();
   ~SmoothMover();
   ~SmoothMover();

+ 54 - 4
direct/src/directbase/directsymbols.h

@@ -16,12 +16,62 @@
 
 
 /* See dtoolsymbols.h for a rant on the purpose of this file.  */
 /* See dtoolsymbols.h for a rant on the purpose of this file.  */
 
 
+/* BUILDING_DIRECT is just a buildsystem shortcut for all of these: */
 #ifdef BUILDING_DIRECT
 #ifdef BUILDING_DIRECT
-  #define EXPCL_DIRECT EXPORT_CLASS
-  #define EXPTP_DIRECT EXPORT_TEMPL
+  #define BUILDING_DIRECT_DEADREC
+  #define BUILDING_DIRECT_DIRECTD
+  #define BUILDING_DIRECT_INTERVAL
+  #define BUILDING_DIRECT_MOTIONTRAIL
+  #define BUILDING_DIRECT_SHOWBASE
+  #define BUILDING_DIRECT_DISTRIBUTED
+#endif
+
+#ifdef BUILDING_DIRECT_DEADREC
+  #define EXPCL_DIRECT_DEADREC EXPORT_CLASS
+  #define EXPTP_DIRECT_DEADREC EXPORT_TEMPL
+#else
+  #define EXPCL_DIRECT_DEADREC IMPORT_CLASS
+  #define EXPTP_DIRECT_DEADREC IMPORT_TEMPL
+#endif
+
+#ifdef BUILDING_DIRECT_DIRECTD
+  #define EXPCL_DIRECT_DIRECTD EXPORT_CLASS
+  #define EXPTP_DIRECT_DIRECTD EXPORT_TEMPL
+#else
+  #define EXPCL_DIRECT_DIRECTD IMPORT_CLASS
+  #define EXPTP_DIRECT_DIRECTD IMPORT_TEMPL
+#endif
+
+#ifdef BUILDING_DIRECT_INTERVAL
+  #define EXPCL_DIRECT_INTERVAL EXPORT_CLASS
+  #define EXPTP_DIRECT_INTERVAL EXPORT_TEMPL
+#else
+  #define EXPCL_DIRECT_INTERVAL IMPORT_CLASS
+  #define EXPTP_DIRECT_INTERVAL IMPORT_TEMPL
+#endif
+
+#ifdef BUILDING_DIRECT_MOTIONTRAIL
+  #define EXPCL_DIRECT_MOTIONTRAIL EXPORT_CLASS
+  #define EXPTP_DIRECT_MOTIONTRAIL EXPORT_TEMPL
+#else
+  #define EXPCL_DIRECT_MOTIONTRAIL IMPORT_CLASS
+  #define EXPTP_DIRECT_MOTIONTRAIL IMPORT_TEMPL
+#endif
+
+#ifdef BUILDING_DIRECT_SHOWBASE
+  #define EXPCL_DIRECT_SHOWBASE EXPORT_CLASS
+  #define EXPTP_DIRECT_SHOWBASE EXPORT_TEMPL
+#else
+  #define EXPCL_DIRECT_SHOWBASE IMPORT_CLASS
+  #define EXPTP_DIRECT_SHOWBASE IMPORT_TEMPL
+#endif
+
+#ifdef BUILDING_DIRECT_DISTRIBUTED
+  #define EXPCL_DIRECT_DISTRIBUTED EXPORT_CLASS
+  #define EXPTP_DIRECT_DISTRIBUTED EXPORT_TEMPL
 #else
 #else
-  #define EXPCL_DIRECT IMPORT_CLASS
-  #define EXPTP_DIRECT IMPORT_TEMPL
+  #define EXPCL_DIRECT_DISTRIBUTED IMPORT_CLASS
+  #define EXPTP_DIRECT_DISTRIBUTED IMPORT_TEMPL
 #endif
 #endif
 
 
 #endif
 #endif

+ 4 - 0
direct/src/directd/directd.cxx

@@ -30,6 +30,10 @@
 
 
 #include "pset.h"
 #include "pset.h"
 
 
+#if !defined(CPPPARSER) && !defined(BUILDING_DIRECT_DIRECTD)
+  #error Buildsystem error: BUILDING_DIRECT_DIRECTD not defined
+#endif
+
 namespace {
 namespace {
   // ...This section is part of the old stuff from the original
   // ...This section is part of the old stuff from the original
   // implementation.  The new stuff that uses job objects doesn't need this
   // implementation.  The new stuff that uses job objects doesn't need this

+ 1 - 1
direct/src/directd/directd.h

@@ -52,7 +52,7 @@ typedef int HANDLE;
  * presented in order chronologically by their intended usage.  The first
  * presented in order chronologically by their intended usage.  The first
  * group will probably provide everthing needed for DirectD.
  * group will probably provide everthing needed for DirectD.
  */
  */
-class EXPCL_DIRECT DirectD {
+class EXPCL_DIRECT_DIRECTD DirectD {
 PUBLISHED:
 PUBLISHED:
   DirectD();
   DirectD();
   ~DirectD();
   ~DirectD();

+ 2 - 2
direct/src/directtools/DirectSelection.py

@@ -623,8 +623,8 @@ class SelectionRay(SelectionQueue):
     def pickBitMask(self, bitMask = BitMask32.allOff(),
     def pickBitMask(self, bitMask = BitMask32.allOff(),
                     targetNodePath = None,
                     targetNodePath = None,
                     skipFlags = SKIP_ALL):
                     skipFlags = SKIP_ALL):
-        if parentNodePath is None:
-            parentNodePath = render
+        if targetNodePath is None:
+            targetNodePath = render
         self.collideWithBitMask(bitMask)
         self.collideWithBitMask(bitMask)
         self.pick(targetNodePath)
         self.pick(targetNodePath)
         # Determine collision entry
         # Determine collision entry

+ 2 - 0
direct/src/distributed/CRDataCache.py

@@ -2,6 +2,8 @@ from direct.distributed.CachedDOData import CachedDOData
 from panda3d.core import ConfigVariableInt
 from panda3d.core import ConfigVariableInt
 
 
 
 
+__all__ = ["CRDataCache"]
+
 class CRDataCache:
 class CRDataCache:
     # Stores cached data for DistributedObjects between instantiations on the client
     # Stores cached data for DistributedObjects between instantiations on the client
 
 

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

@@ -175,7 +175,7 @@ class ClientRepositoryBase(ConnectionRepository):
         "generate" messages when they are replayed().
         "generate" messages when they are replayed().
         """
         """
 
 
-        if msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
+        if msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
             # It's a generate message.
             # It's a generate message.
             doId = extra
             doId = extra
             if doId in self.deferredDoIds:
             if doId in self.deferredDoIds:
@@ -263,7 +263,7 @@ class ClientRepositoryBase(ConnectionRepository):
             distObj.setLocation(parentId, zoneId)
             distObj.setLocation(parentId, zoneId)
             distObj.updateRequiredFields(dclass, di)
             distObj.updateRequiredFields(dclass, di)
             # updateRequiredFields calls announceGenerate
             # updateRequiredFields calls announceGenerate
-            print("New DO:%s, dclass:%s"%(doId, dclass.getName()))
+            self.notify.debug("New DO:%s, dclass:%s" % (doId, dclass.getName()))
         return distObj
         return distObj
 
 
     def generateWithRequiredOtherFields(self, dclass, doId, di,
     def generateWithRequiredOtherFields(self, dclass, doId, di,
@@ -381,7 +381,7 @@ class ClientRepositoryBase(ConnectionRepository):
             # The object had been deferred.  Great; we don't even have
             # The object had been deferred.  Great; we don't even have
             # to generate it now.
             # to generate it now.
             del self.deferredDoIds[doId]
             del self.deferredDoIds[doId]
-            i = self.deferredGenerates.index((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId))
+            i = self.deferredGenerates.index((CLIENT_ENTER_OBJECT_REQUIRED_OTHER, doId))
             del self.deferredGenerates[i]
             del self.deferredGenerates[i]
             if len(self.deferredGenerates) == 0:
             if len(self.deferredGenerates) == 0:
                 taskMgr.remove('deferredGenerate')
                 taskMgr.remove('deferredGenerate')

+ 5 - 4
direct/src/distributed/ConnectionRepository.py

@@ -7,9 +7,10 @@ from direct.distributed.DoCollectionManager import DoCollectionManager
 from direct.showbase import GarbageReport
 from direct.showbase import GarbageReport
 from .PyDatagramIterator import PyDatagramIterator
 from .PyDatagramIterator import PyDatagramIterator
 
 
-import types
+import inspect
 import gc
 import gc
 
 
+__all__ = ["ConnectionRepository", "GCTrigger"]
 
 
 class ConnectionRepository(
 class ConnectionRepository(
         DoInterestManager, DoCollectionManager, CConnectionRepository):
         DoInterestManager, DoCollectionManager, CConnectionRepository):
@@ -326,13 +327,13 @@ class ConnectionRepository(
             if classDef is None:
             if classDef is None:
                 self.notify.debug("No class definition for %s." % (className))
                 self.notify.debug("No class definition for %s." % (className))
             else:
             else:
-                if type(classDef) == types.ModuleType:
+                if inspect.ismodule(classDef):
                     if not hasattr(classDef, className):
                     if not hasattr(classDef, className):
                         self.notify.warning("Module %s does not define class %s." % (className, className))
                         self.notify.warning("Module %s does not define class %s." % (className, className))
                         continue
                         continue
                     classDef = getattr(classDef, className)
                     classDef = getattr(classDef, className)
 
 
-                if type(classDef) != types.ClassType and type(classDef) != types.TypeType:
+                if not inspect.isclass(classDef):
                     self.notify.error("Symbol %s is not a class name." % (className))
                     self.notify.error("Symbol %s is not a class name." % (className))
                 else:
                 else:
                     dclass.setClassDef(classDef)
                     dclass.setClassDef(classDef)
@@ -387,7 +388,7 @@ class ConnectionRepository(
                     if classDef is None:
                     if classDef is None:
                         self.notify.error("No class definition for %s." % className)
                         self.notify.error("No class definition for %s." % className)
                     else:
                     else:
-                        if type(classDef) == types.ModuleType:
+                        if inspect.ismodule(classDef):
                             if not hasattr(classDef, className):
                             if not hasattr(classDef, className):
                                 self.notify.error("Module %s does not define class %s." % (className, className))
                                 self.notify.error("Module %s does not define class %s." % (className, className))
                             classDef = getattr(classDef, className)
                             classDef = getattr(classDef, className)

+ 2 - 0
direct/src/distributed/DistributedNode.py

@@ -15,6 +15,8 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
             self.DistributedNode_initialized = 1
             self.DistributedNode_initialized = 1
             self.gotStringParentToken = 0
             self.gotStringParentToken = 0
             DistributedObject.DistributedObject.__init__(self, cr)
             DistributedObject.DistributedObject.__init__(self, cr)
+            if not self.this:
+                NodePath.__init__(self, "DistributedNode")
 
 
             # initialize gridParent
             # initialize gridParent
             self.gridParent = None
             self.gridParent = None

+ 0 - 1
direct/src/distributed/DistributedNodeUD.py

@@ -1,4 +1,3 @@
-#from otp.ai.AIBaseGlobal import *
 from .DistributedObjectUD import DistributedObjectUD
 from .DistributedObjectUD import DistributedObjectUD
 
 
 class DistributedNodeUD(DistributedObjectUD):
 class DistributedNodeUD(DistributedObjectUD):

+ 7 - 15
direct/src/distributed/DistributedObjectAI.py

@@ -146,8 +146,6 @@ class DistributedObjectAI(DistributedObjectBase):
                     barrier.cleanup()
                     barrier.cleanup()
                 self.__barriers = {}
                 self.__barriers = {}
 
 
-                self.air.stopTrackRequestDeletedDO(self)
-
                 # DCR: I've re-enabled this block of code so that Toontown's
                 # DCR: I've re-enabled this block of code so that Toontown's
                 # AI won't leak channels.
                 # AI won't leak channels.
                 # Let me know if it causes trouble.
                 # Let me know if it causes trouble.
@@ -155,10 +153,9 @@ class DistributedObjectAI(DistributedObjectBase):
                 ### block until a solution is thought out of how to prevent
                 ### block until a solution is thought out of how to prevent
                 ### this delete message or to handle this message better
                 ### this delete message or to handle this message better
                 # TODO: do we still need this check?
                 # TODO: do we still need this check?
-                if not hasattr(self, "doNotDeallocateChannel"):
-                    if self.air and not hasattr(self.air, "doNotDeallocateChannel"):
-                        if self.air.minChannel <= self.doId <= self.air.maxChannel:
-                            self.air.deallocateChannel(self.doId)
+                if not getattr(self, "doNotDeallocateChannel", False):
+                    if self.air:
+                        self.air.deallocateChannel(self.doId)
                 self.air = None
                 self.air = None
 
 
                 self.parentId = None
                 self.parentId = None
@@ -200,9 +197,6 @@ class DistributedObjectAI(DistributedObjectBase):
         """
         """
         pass
         pass
 
 
-    def addInterest(self, zoneId, note="", event=None):
-        self.air.addInterest(self.doId, zoneId, note, event)
-
     def b_setLocation(self, parentId, zoneId):
     def b_setLocation(self, parentId, zoneId):
         self.d_setLocation(parentId, zoneId)
         self.d_setLocation(parentId, zoneId)
         self.setLocation(parentId, zoneId)
         self.setLocation(parentId, zoneId)
@@ -274,9 +268,6 @@ class DistributedObjectAI(DistributedObjectBase):
 
 
         dclass.receiveUpdateOther(self, di)
         dclass.receiveUpdateOther(self, di)
 
 
-    def sendSetZone(self, zoneId):
-        self.air.sendSetZone(self, zoneId)
-
     def startMessageBundle(self, name):
     def startMessageBundle(self, name):
         self.air.startMessageBundle(name)
         self.air.startMessageBundle(name)
     def sendMessageBundle(self):
     def sendMessageBundle(self):
@@ -349,10 +340,10 @@ class DistributedObjectAI(DistributedObjectBase):
             self.air.sendUpdate(self, fieldName, args)
             self.air.sendUpdate(self, fieldName, args)
 
 
     def GetPuppetConnectionChannel(self, doId):
     def GetPuppetConnectionChannel(self, doId):
-        return doId + (1 << 32)
+        return doId + (1001 << 32)
 
 
     def GetAccountConnectionChannel(self, doId):
     def GetAccountConnectionChannel(self, doId):
-        return doId + (3 << 32)
+        return doId + (1003 << 32)
 
 
     def GetAccountIDFromChannelCode(self, channel):
     def GetAccountIDFromChannelCode(self, channel):
         return channel >> 32
         return channel >> 32
@@ -482,7 +473,6 @@ class DistributedObjectAI(DistributedObjectBase):
                 (self.__class__, doId))
                 (self.__class__, doId))
             return
             return
         self.air.requestDelete(self)
         self.air.requestDelete(self)
-        self.air.startTrackRequestDeletedDO(self)
         self._DOAI_requestedDelete = True
         self._DOAI_requestedDelete = True
 
 
     def taskName(self, taskString):
     def taskName(self, taskString):
@@ -581,3 +571,5 @@ class DistributedObjectAI(DistributedObjectBase):
         """ This is a no-op on the AI. """
         """ This is a no-op on the AI. """
         pass
         pass
 
 
+    def setAI(self, aiChannel):
+        self.air.setAI(self.doId, aiChannel)

+ 8 - 1
direct/src/distributed/DistributedObjectBase.py

@@ -1,4 +1,3 @@
-
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 
 
@@ -93,3 +92,11 @@ class DistributedObjectBase(DirectObject):
 
 
     def hasParentingRules(self):
     def hasParentingRules(self):
         return self.dclass.getFieldByName('setParentingRules') != None
         return self.dclass.getFieldByName('setParentingRules') != None
+
+    def delete(self):
+        """
+        Override this to handle cleanup right before this object
+        gets deleted.
+        """
+
+        pass

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

@@ -270,10 +270,10 @@ class DistributedObjectUD(DistributedObjectBase):
             self.air.sendUpdate(self, fieldName, args)
             self.air.sendUpdate(self, fieldName, args)
 
 
     def GetPuppetConnectionChannel(self, doId):
     def GetPuppetConnectionChannel(self, doId):
-        return doId + (1 << 32)
+        return doId + (1001 << 32)
 
 
     def GetAccountConnectionChannel(self, doId):
     def GetAccountConnectionChannel(self, doId):
-        return doId + (3 << 32)
+        return doId + (1003 << 32)
 
 
     def GetAccountIDFromChannelCode(self, channel):
     def GetAccountIDFromChannelCode(self, channel):
         return channel >> 32
         return channel >> 32

+ 13 - 9
direct/src/distributed/DoInterestManager.py

@@ -111,7 +111,7 @@ class DoInterestManager(DirectObject.DirectObject):
         self._allInterestsCompleteCallbacks = []
         self._allInterestsCompleteCallbacks = []
 
 
     def __verbose(self):
     def __verbose(self):
-        return self.InterestDebug or self.getVerbose()
+        return self.InterestDebug.getValue() or self.getVerbose()
 
 
     def _getAnonymousEvent(self, desc):
     def _getAnonymousEvent(self, desc):
         return 'anonymous-%s-%s' % (desc, DoInterestManager._SerialGen.next())
         return 'anonymous-%s-%s' % (desc, DoInterestManager._SerialGen.next())
@@ -504,18 +504,23 @@ class DoInterestManager(DirectObject.DirectObject):
                 'trying to set interest to invalid parent: %s' % parentId)
                 'trying to set interest to invalid parent: %s' % parentId)
         datagram = PyDatagram()
         datagram = PyDatagram()
         # Add message type
         # Add message type
-        datagram.addUint16(CLIENT_ADD_INTEREST)
-        datagram.addUint16(handle)
-        datagram.addUint32(contextId)
-        datagram.addUint32(parentId)
         if isinstance(zoneIdList, list):
         if isinstance(zoneIdList, list):
             vzl = list(zoneIdList)
             vzl = list(zoneIdList)
             vzl.sort()
             vzl.sort()
             uniqueElements(vzl)
             uniqueElements(vzl)
+            datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
+            datagram.addUint32(contextId)
+            datagram.addUint16(handle)
+            datagram.addUint32(parentId)
+            datagram.addUint16(len(vzl))
             for zone in vzl:
             for zone in vzl:
                 datagram.addUint32(zone)
                 datagram.addUint32(zone)
         else:
         else:
-           datagram.addUint32(zoneIdList)
+            datagram.addUint16(CLIENT_ADD_INTEREST)
+            datagram.addUint32(contextId)
+            datagram.addUint16(handle)
+            datagram.addUint32(parentId)
+            datagram.addUint32(zoneIdList)
         self.send(datagram)
         self.send(datagram)
 
 
     def _sendRemoveInterest(self, handle, contextId):
     def _sendRemoveInterest(self, handle, contextId):
@@ -530,9 +535,8 @@ class DoInterestManager(DirectObject.DirectObject):
         datagram = PyDatagram()
         datagram = PyDatagram()
         # Add message type
         # Add message type
         datagram.addUint16(CLIENT_REMOVE_INTEREST)
         datagram.addUint16(CLIENT_REMOVE_INTEREST)
+        datagram.addUint32(contextId)
         datagram.addUint16(handle)
         datagram.addUint16(handle)
-        if contextId != 0:
-            datagram.addUint32(contextId)
         self.send(datagram)
         self.send(datagram)
         if __debug__:
         if __debug__:
             state = DoInterestManager._interests[handle]
             state = DoInterestManager._interests[handle]
@@ -583,8 +587,8 @@ class DoInterestManager(DirectObject.DirectObject):
         This handles the interest done messages and may dispatch an event
         This handles the interest done messages and may dispatch an event
         """
         """
         assert DoInterestManager.notify.debugCall()
         assert DoInterestManager.notify.debugCall()
-        handle = di.getUint16()
         contextId = di.getUint32()
         contextId = di.getUint32()
+        handle = di.getUint16()
         if self.__verbose():
         if self.__verbose():
             print('CR::INTEREST.interestDone(handle=%s)' % handle)
             print('CR::INTEREST.interestDone(handle=%s)' % handle)
         DoInterestManager.notify.debug(
         DoInterestManager.notify.debug(

+ 133 - 97
direct/src/distributed/MsgTypes.py

@@ -3,104 +3,140 @@
 from direct.showbase.PythonUtil import invertDictLossless
 from direct.showbase.PythonUtil import invertDictLossless
 
 
 MsgName2Id = {
 MsgName2Id = {
-    # 2 new params: passwd, char bool 0/1 1 = new account
-    # 2 new return values: 129 = not found, 12 = bad passwd,
-    'CLIENT_LOGIN':                                  1,
-    'CLIENT_LOGIN_RESP':                             2,
-    'CLIENT_GET_AVATARS':                            3,
+    'CLIENT_HELLO':                                  1,
+    'CLIENT_HELLO_RESP':                             2,
+
+    # Sent by the client when it's leaving.
+    'CLIENT_DISCONNECT':                             3,
+
     # Sent by the server when it is dropping the connection deliberately.
     # Sent by the server when it is dropping the connection deliberately.
-    'CLIENT_GO_GET_LOST':                            4,
-    'CLIENT_GET_AVATARS_RESP':                       5,
-    'CLIENT_CREATE_AVATAR':                          6,
-    'CLIENT_CREATE_AVATAR_RESP':                     7,
-    'CLIENT_GET_FRIEND_LIST':                        10,
-    'CLIENT_GET_FRIEND_LIST_RESP':                   11,
-    'CLIENT_GET_AVATAR_DETAILS':                     14,
-    'CLIENT_GET_AVATAR_DETAILS_RESP':                15,
-    'CLIENT_LOGIN_2':                                16,
-    'CLIENT_LOGIN_2_RESP':                           17,
-
-    'CLIENT_OBJECT_UPDATE_FIELD':                    24,
-    'CLIENT_OBJECT_UPDATE_FIELD_RESP':               24,
-    'CLIENT_OBJECT_DISABLE':                         25,
-    'CLIENT_OBJECT_DISABLE_RESP':                    25,
-    'CLIENT_OBJECT_DISABLE_OWNER':                   26,
-    'CLIENT_OBJECT_DISABLE_OWNER_RESP':              26,
-    'CLIENT_OBJECT_DELETE':                          27,
-    'CLIENT_OBJECT_DELETE_RESP':                     27,
-    'CLIENT_SET_ZONE_CMU':                           29,
-    'CLIENT_REMOVE_ZONE':                            30,
-    'CLIENT_SET_AVATAR':                             32,
-    'CLIENT_CREATE_OBJECT_REQUIRED':                 34,
-    'CLIENT_CREATE_OBJECT_REQUIRED_RESP':            34,
-    'CLIENT_CREATE_OBJECT_REQUIRED_OTHER':           35,
-    'CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP':      35,
-    'CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER':     36,
-    'CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER_RESP':36,
-
-    'CLIENT_REQUEST_GENERATES':                      36,
-
-    'CLIENT_DISCONNECT':                             37,
-
-    'CLIENT_GET_STATE_RESP':                         47,
-    'CLIENT_DONE_INTEREST_RESP':                     48,
-
-    'CLIENT_DELETE_AVATAR':                          49,
-
-    'CLIENT_DELETE_AVATAR_RESP':                     5,
-
-    'CLIENT_HEARTBEAT':                              52,
-    'CLIENT_FRIEND_ONLINE':                          53,
-    'CLIENT_FRIEND_OFFLINE':                         54,
-    'CLIENT_REMOVE_FRIEND':                          56,
-
-    'CLIENT_CHANGE_PASSWORD':                        65,
-
-    'CLIENT_SET_NAME_PATTERN':                       67,
-    'CLIENT_SET_NAME_PATTERN_ANSWER':                68,
-
-    'CLIENT_SET_WISHNAME':                           70,
-    'CLIENT_SET_WISHNAME_RESP':                      71,
-    'CLIENT_SET_WISHNAME_CLEAR':                     72,
-    'CLIENT_SET_SECURITY':                           73,
-
-    'CLIENT_SET_DOID_RANGE':                         74,
-
-    'CLIENT_GET_AVATARS_RESP2':                      75,
-    'CLIENT_CREATE_AVATAR2':                         76,
-    'CLIENT_SYSTEM_MESSAGE':                         78,
-    'CLIENT_SET_AVTYPE':                             80,
-
-    'CLIENT_GET_PET_DETAILS':                        81,
-    'CLIENT_GET_PET_DETAILS_RESP':                   82,
-
-    'CLIENT_ADD_INTEREST':                           97,
-    'CLIENT_REMOVE_INTEREST':                        99,
-    'CLIENT_OBJECT_LOCATION':                        102,
-
-    'CLIENT_LOGIN_3':                                111,
-    'CLIENT_LOGIN_3_RESP':                           110,
-
-    'CLIENT_GET_FRIEND_LIST_EXTENDED':               115,
-    'CLIENT_GET_FRIEND_LIST_EXTENDED_RESP':          116,
-
-    'CLIENT_SET_FIELD_SENDABLE':                     120,
-
-    'CLIENT_SYSTEMMESSAGE_AKNOWLEDGE':               123,
-    'CLIENT_CHANGE_GENERATE_ORDER':                  124,
-
-    # new toontown specific login message, adds last logged in, and if child account has parent acount
-    'CLIENT_LOGIN_TOONTOWN':                         125,
-    'CLIENT_LOGIN_TOONTOWN_RESP':                    126,
-
-
-
-    'STATESERVER_OBJECT_GENERATE_WITH_REQUIRED':           2001,
-    'STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER':     2003,
-    'STATESERVER_OBJECT_UPDATE_FIELD':                     2004,
-    'STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT':     2050,
-    'STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT': 2051,
-    'STATESERVER_BOUNCE_MESSAGE':                          2086,
+    'CLIENT_EJECT':                                  4,
+
+    'CLIENT_HEARTBEAT':                              5,
+
+    'CLIENT_OBJECT_SET_FIELD':                       120,
+    'CLIENT_OBJECT_SET_FIELDS':                      121,
+    'CLIENT_OBJECT_LEAVING':                         132,
+    'CLIENT_OBJECT_LEAVING_OWNER':                   161,
+    'CLIENT_ENTER_OBJECT_REQUIRED':                  142,
+    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER':            143,
+    'CLIENT_ENTER_OBJECT_REQUIRED_OWNER':            172,
+    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER':      173,
+
+    'CLIENT_DONE_INTEREST_RESP':                     204,
+
+    'CLIENT_ADD_INTEREST':                           200,
+    'CLIENT_ADD_INTEREST_MULTIPLE':                  201,
+    'CLIENT_REMOVE_INTEREST':                        203,
+    'CLIENT_OBJECT_LOCATION':                        140,
+
+
+    # These are sent internally inside the Astron cluster.
+
+    # Message Director control messages:
+    'CONTROL_CHANNEL':                                  1,
+    'CONTROL_ADD_CHANNEL':                              9000,
+    'CONTROL_REMOVE_CHANNEL':                           9001,
+    'CONTROL_ADD_RANGE':                                9002,
+    'CONTROL_REMOVE_RANGE':                             9003,
+    'CONTROL_ADD_POST_REMOVE':                          9010,
+    'CONTROL_CLEAR_POST_REMOVES':                       9011,
+
+    # State Server control messages:
+    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED':          2000,
+    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER':    2001,
+    'STATESERVER_DELETE_AI_OBJECTS':                    2009,
+    'STATESERVER_OBJECT_GET_FIELD':                     2010,
+    'STATESERVER_OBJECT_GET_FIELD_RESP':                2011,
+    'STATESERVER_OBJECT_GET_FIELDS':                    2012,
+    'STATESERVER_OBJECT_GET_FIELDS_RESP':               2013,
+    'STATESERVER_OBJECT_GET_ALL':                       2014,
+    'STATESERVER_OBJECT_GET_ALL_RESP':                  2015,
+    'STATESERVER_OBJECT_SET_FIELD':                     2020,
+    'STATESERVER_OBJECT_SET_FIELDS':                    2021,
+    'STATESERVER_OBJECT_DELETE_FIELD_RAM':              2030,
+    'STATESERVER_OBJECT_DELETE_FIELDS_RAM':             2031,
+    'STATESERVER_OBJECT_DELETE_RAM':                    2032,
+    'STATESERVER_OBJECT_SET_LOCATION':                          2040,
+    'STATESERVER_OBJECT_CHANGING_LOCATION':                     2041,
+    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED':          2042,
+    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER':    2043,
+    'STATESERVER_OBJECT_GET_LOCATION':                          2044,
+    'STATESERVER_OBJECT_GET_LOCATION_RESP':                     2045,
+    'STATESERVER_OBJECT_SET_AI':                                2050,
+    'STATESERVER_OBJECT_CHANGING_AI':                           2051,
+    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED':                2052,
+    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER':          2053,
+    'STATESERVER_OBJECT_GET_AI':                                2054,
+    'STATESERVER_OBJECT_GET_AI_RESP':                           2055,
+    'STATESERVER_OBJECT_SET_OWNER':                             2060,
+    'STATESERVER_OBJECT_CHANGING_OWNER':                        2061,
+    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED':             2062,
+    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER':       2063,
+    'STATESERVER_OBJECT_GET_OWNER':                             2064,
+    'STATESERVER_OBJECT_GET_OWNER_RESP':                        2065,
+    'STATESERVER_OBJECT_GET_ZONE_OBJECTS':              2100,
+    'STATESERVER_OBJECT_GET_ZONES_OBJECTS':             2102,
+    'STATESERVER_OBJECT_GET_CHILDREN':                  2104,
+    'STATESERVER_OBJECT_GET_ZONE_COUNT':                2110,
+    'STATESERVER_OBJECT_GET_ZONE_COUNT_RESP':           2111,
+    'STATESERVER_OBJECT_GET_ZONES_COUNT':               2112,
+    'STATESERVER_OBJECT_GET_ZONES_COUNT_RESP':          2113,
+    'STATESERVER_OBJECT_GET_CHILD_COUNT':               2114,
+    'STATESERVER_OBJECT_GET_CHILD_COUNT_RESP':          2115,
+    'STATESERVER_OBJECT_DELETE_ZONE':                   2120,
+    'STATESERVER_OBJECT_DELETE_ZONES':                  2122,
+    'STATESERVER_OBJECT_DELETE_CHILDREN':               2124,
+    # DBSS-backed-object messages:
+    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS':        2200,
+    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER':  2201,
+    'DBSS_OBJECT_GET_ACTIVATED':                 2207,
+    'DBSS_OBJECT_GET_ACTIVATED_RESP':            2208,
+    'DBSS_OBJECT_DELETE_FIELD_DISK':             2230,
+    'DBSS_OBJECT_DELETE_FIELDS_DISK':            2231,
+    'DBSS_OBJECT_DELETE_DISK':                   2232,
+
+    # Database Server control messages:
+    'DBSERVER_CREATE_OBJECT':                       3000,
+    'DBSERVER_CREATE_OBJECT_RESP':                  3001,
+    'DBSERVER_OBJECT_GET_FIELD':                    3010,
+    'DBSERVER_OBJECT_GET_FIELD_RESP':               3011,
+    'DBSERVER_OBJECT_GET_FIELDS':                   3012,
+    'DBSERVER_OBJECT_GET_FIELDS_RESP':              3013,
+    'DBSERVER_OBJECT_GET_ALL':                      3014,
+    'DBSERVER_OBJECT_GET_ALL_RESP':                 3015,
+    'DBSERVER_OBJECT_SET_FIELD':                    3020,
+    'DBSERVER_OBJECT_SET_FIELDS':                   3021,
+    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS':          3022,
+    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP':     3023,
+    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS':         3024,
+    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP':    3025,
+    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY':           3026,
+    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY_RESP':      3027,
+    'DBSERVER_OBJECT_DELETE_FIELD':                 3030,
+    'DBSERVER_OBJECT_DELETE_FIELDS':                3031,
+    'DBSERVER_OBJECT_DELETE':                       3032,
+
+    # Client Agent control messages:
+    'CLIENTAGENT_SET_STATE':                        1000,
+    'CLIENTAGENT_SET_CLIENT_ID':                    1001,
+    'CLIENTAGENT_SEND_DATAGRAM':                    1002,
+    'CLIENTAGENT_EJECT':                            1004,
+    'CLIENTAGENT_DROP':                             1005,
+    'CLIENTAGENT_GET_NETWORK_ADDRESS':              1006,
+    'CLIENTAGENT_GET_NETWORK_ADDRESS_RESP':         1007,
+    'CLIENTAGENT_DECLARE_OBJECT':                   1010,
+    'CLIENTAGENT_UNDECLARE_OBJECT':                 1011,
+    'CLIENTAGENT_ADD_SESSION_OBJECT':               1012,
+    'CLIENTAGENT_REMOVE_SESSION_OBJECT':            1013,
+    'CLIENTAGENT_SET_FIELDS_SENDABLE':              1014,
+    'CLIENTAGENT_OPEN_CHANNEL':                     1100,
+    'CLIENTAGENT_CLOSE_CHANNEL':                    1101,
+    'CLIENTAGENT_ADD_POST_REMOVE':                  1110,
+    'CLIENTAGENT_CLEAR_POST_REMOVES':               1111,
+    'CLIENTAGENT_ADD_INTEREST':                     1200,
+    'CLIENTAGENT_ADD_INTEREST_MULTIPLE':            1201,
+    'CLIENTAGENT_REMOVE_INTEREST':                  1203,
     }
     }
 
 
 # create id->name table for debugging
 # create id->name table for debugging

+ 1 - 1
direct/src/distributed/MsgTypesCMU.py

@@ -19,7 +19,7 @@ MsgName2Id = {
     'CLIENT_HEARTBEAT_CMU'                    : 9011,
     'CLIENT_HEARTBEAT_CMU'                    : 9011,
     'CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU'  : 9011,
     'CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU'  : 9011,
 
 
-    'CLIENT_OBJECT_UPDATE_FIELD' : 24,  # Matches MsgTypes.CLIENT_OBJECT_UPDATE_FIELD
+    'CLIENT_OBJECT_UPDATE_FIELD' : 120,  # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
     }
     }
 
 
 # create id->name table for debugging
 # create id->name table for debugging

+ 0 - 208
direct/src/distributed/OldClientRepository.py

@@ -1,208 +0,0 @@
-"""OldClientRepository module: contains the OldClientRepository class"""
-
-from .ClientRepositoryBase import *
-
-class OldClientRepository(ClientRepositoryBase):
-    """
-    This is the open-source ClientRepository as provided by CMU.  It
-    communicates with the ServerRepository in this same directory.
-
-    If you are looking for the VR Studio's implementation of the
-    client repository, look to OTPClientRepository (elsewhere).
-    """
-    notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
-
-    def __init__(self, dcFileNames = None):
-        ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames)
-
-        # The DOID allocator.  The CMU LAN server may choose to
-        # send us a block of DOIDs.  If it chooses to do so, then we
-        # may create objects, using those DOIDs.
-        self.DOIDbase = 0
-        self.DOIDnext = 0
-        self.DOIDlast = 0
-
-    def handleSetDOIDrange(self, di):
-        self.DOIDbase = di.getUint32()
-        self.DOIDlast = self.DOIDbase + di.getUint32()
-        self.DOIDnext = self.DOIDbase
-
-    def handleRequestGenerates(self, di):
-        # When new clients join the zone of an object, they need to hear
-        # about it, so we send out all of our information about objects in
-        # that particular zone.
-
-        assert self.DOIDnext < self.DOIDlast
-        zone = di.getUint32()
-        for obj in self.doId2do.values():
-            if obj.zone == zone:
-                id = obj.doId
-                if (self.isLocalId(id)):
-                    self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
-
-    def createWithRequired(self, className, zoneId = 0, optionalFields=None):
-        if self.DOIDnext >= self.DOIDlast:
-            self.notify.error(
-                "Cannot allocate a distributed object ID: all IDs used up.")
-            return None
-        id = self.DOIDnext
-        self.DOIDnext = self.DOIDnext + 1
-        dclass = self.dclassesByName[className]
-        classDef = dclass.getClassDef()
-        if classDef == None:
-            self.notify.error("Could not create an undefined %s object." % (
-                dclass.getName()))
-        obj = classDef(self)
-        obj.dclass = dclass
-        obj.zone = zoneId
-        obj.doId = id
-        self.doId2do[id] = obj
-        obj.generateInit()
-        obj._retrieveCachedData()
-        obj.generate()
-        obj.announceGenerate()
-        datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
-        self.send(datagram)
-        return obj
-
-    def sendDisableMsg(self, doId):
-        datagram = PyDatagram()
-        datagram.addUint16(CLIENT_OBJECT_DISABLE)
-        datagram.addUint32(doId)
-        self.send(datagram)
-
-    def sendDeleteMsg(self, doId):
-        datagram = PyDatagram()
-        datagram.addUint16(CLIENT_OBJECT_DELETE)
-        datagram.addUint32(doId)
-        self.send(datagram)
-
-    def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None):
-        datagram = PyDatagram()
-        datagram.addUint16(CLIENT_REMOVE_ZONE)
-        datagram.addUint32(zoneId)
-
-        # if we have an explicit list of visible zones, add them
-        if visibleZoneList is not None:
-            vzl = list(visibleZoneList)
-            vzl.sort()
-            assert PythonUtil.uniqueElements(vzl)
-            for zone in vzl:
-                datagram.addUint32(zone)
-
-        # send the message
-        self.send(datagram)
-
-    def sendUpdateZone(self, obj, zoneId):
-        id = obj.doId
-        assert self.isLocalId(id)
-        self.sendDeleteMsg(id, 1)
-        obj.zone = zoneId
-        self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, []))
-
-    def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
-        datagram = PyDatagram()
-        # Add message type
-        datagram.addUint16(CLIENT_SET_ZONE_CMU)
-        # Add zone id
-        datagram.addUint32(zoneId)
-
-        # if we have an explicit list of visible zones, add them
-        if visibleZoneList is not None:
-            vzl = list(visibleZoneList)
-            vzl.sort()
-            assert PythonUtil.uniqueElements(vzl)
-            for zone in vzl:
-                datagram.addUint32(zone)
-
-        # send the message
-        self.send(datagram)
-
-    def isLocalId(self, id):
-        return ((id >= self.DOIDbase) and (id < self.DOIDlast))
-
-    def haveCreateAuthority(self):
-        return (self.DOIDlast > self.DOIDnext)
-
-    def handleDatagram(self, di):
-        if self.notify.getDebug():
-            print("ClientRepository received datagram:")
-            di.getDatagram().dumpHex(ostream)
-
-        msgType = self.getMsgType()
-
-        # These are the sort of messages we may expect from the public
-        # Panda server.
-
-        if msgType == CLIENT_SET_DOID_RANGE:
-            self.handleSetDOIDrange(di)
-        elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_RESP:
-            self.handleGenerateWithRequired(di)
-        elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP:
-            self.handleGenerateWithRequiredOther(di)
-        elif msgType == CLIENT_OBJECT_UPDATE_FIELD_RESP:
-            self.handleUpdateField(di)
-        elif msgType == CLIENT_OBJECT_DELETE_RESP:
-            self.handleDelete(di)
-        elif msgType == CLIENT_OBJECT_DISABLE_RESP:
-            self.handleDisable(di)
-        elif msgType == CLIENT_REQUEST_GENERATES:
-            self.handleRequestGenerates(di)
-        else:
-            self.handleMessageType(msgType, di)
-
-        # If we're processing a lot of datagrams within one frame, we
-        # may forget to send heartbeats.  Keep them coming!
-        self.considerHeartbeat()
-
-    def handleGenerateWithRequired(self, di):
-        # Get the class Id
-        classId = di.getUint16()
-        # Get the DO Id
-        doId = di.getUint32()
-        # Look up the dclass
-        dclass = self.dclassesByNumber[classId]
-        dclass.startGenerate()
-        # Create a new distributed object, and put it in the dictionary
-        distObj = self.generateWithRequiredFields(dclass, doId, di)
-        dclass.stopGenerate()
-
-    def generateWithRequiredFields(self, dclass, doId, di):
-        if doId in self.doId2do:
-            # ...it is in our dictionary.
-            # Just update it.
-            distObj = self.doId2do[doId]
-            assert distObj.dclass == dclass
-            distObj.generate()
-            distObj.updateRequiredFields(dclass, di)
-            # updateRequiredFields calls announceGenerate
-        elif self.cache.contains(doId):
-            # ...it is in the cache.
-            # Pull it out of the cache:
-            distObj = self.cache.retrieve(doId)
-            assert distObj.dclass == dclass
-            # put it in the dictionary:
-            self.doId2do[doId] = distObj
-            # and update it.
-            distObj.generate()
-            distObj.updateRequiredFields(dclass, di)
-            # updateRequiredFields calls announceGenerate
-        else:
-            # ...it is not in the dictionary or the cache.
-            # Construct a new one
-            classDef = dclass.getClassDef()
-            if classDef == None:
-                self.notify.error("Could not create an undefined %s object." % (
-                    dclass.getName()))
-            distObj = classDef(self)
-            distObj.dclass = dclass
-            # Assign it an Id
-            distObj.doId = doId
-            # Put the new do in the dictionary
-            self.doId2do[doId] = distObj
-            # Update the required fields
-            distObj.generateInit()  # Only called when constructed
-            distObj.generate()
-            distObj.updateRequiredFields(dclass, di)
-            # updateRequiredFields calls announceGenerate
-        return distObj

+ 5 - 8
direct/src/distributed/PyDatagram.py

@@ -7,7 +7,7 @@ from panda3d.core import Datagram
 from panda3d.direct import *
 from panda3d.direct import *
 # Import the type numbers
 # Import the type numbers
 
 
-#from otp.ai.AIMsgTypes import *
+from direct.distributed.MsgTypes import *
 
 
 class PyDatagram(Datagram):
 class PyDatagram(Datagram):
 
 
@@ -47,13 +47,10 @@ class PyDatagram(Datagram):
         self.addUint16(code)
         self.addUint16(code)
 
 
 
 
-#    def addServerControlHeader(self,   code):
-#        self.addInt8(1)
-#        self.addChannel(CONTROL_MESSAGE)
-#        self.addUint16(code)
-#    def addOldServerControlHeader(self,   code):
-#        self.addChannel(CONTROL_MESSAGE)
-#        self.addUint16(code)
+    def addServerControlHeader(self, code):
+        self.addInt8(1)
+        self.addChannel(CONTROL_CHANNEL)
+        self.addUint16(code)
 
 
     def putArg(self, arg, subatomicType, divisor=1):
     def putArg(self, arg, subatomicType, divisor=1):
         if (divisor == 1):
         if (divisor == 1):

+ 1 - 5
direct/src/distributed/PyDatagramIterator.py

@@ -75,7 +75,7 @@ class PyDatagramIterator(DatagramIterator):
                     b = self.getUint8()
                     b = self.getUint8()
                     retVal.append((a, b))
                     retVal.append((a, b))
             else:
             else:
-                raise Exception("Error: No such type as: " + str(subAtomicType))
+                raise Exception("Error: No such type as: " + str(subatomicType))
         else:
         else:
             # See if it is in the handy dict
             # See if it is in the handy dict
             getFunc = self.FuncDict.get(subatomicType)
             getFunc = self.FuncDict.get(subatomicType)
@@ -121,8 +121,4 @@ class PyDatagramIterator(DatagramIterator):
             else:
             else:
                 raise Exception("Error: No such type as: " + str(subatomicType))
                 raise Exception("Error: No such type as: " + str(subatomicType))
 
 
-
-
         return retVal
         return retVal
-
-

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

@@ -7,6 +7,8 @@ from direct.task import Task
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.distributed.PyDatagram import PyDatagram
 from direct.distributed.PyDatagram import PyDatagram
 
 
+import inspect
+
 
 
 class ServerRepository:
 class ServerRepository:
 
 
@@ -273,12 +275,12 @@ class ServerRepository:
             if classDef == None:
             if classDef == None:
                 self.notify.debug("No class definition for %s." % (className))
                 self.notify.debug("No class definition for %s." % (className))
             else:
             else:
-                if type(classDef) == types.ModuleType:
+                if inspect.ismodule(classDef):
                     if not hasattr(classDef, className):
                     if not hasattr(classDef, className):
                         self.notify.error("Module %s does not define class %s." % (className, className))
                         self.notify.error("Module %s does not define class %s." % (className, className))
                     classDef = getattr(classDef, className)
                     classDef = getattr(classDef, className)
 
 
-                if type(classDef) != types.ClassType and type(classDef) != types.TypeType:
+                if not inspect.isclass(classDef):
                     self.notify.error("Symbol %s is not a class name." % (className))
                     self.notify.error("Symbol %s is not a class name." % (className))
                 else:
                 else:
                     dclass.setClassDef(classDef)
                     dclass.setClassDef(classDef)

+ 13 - 11
direct/src/distributed/cConnectionRepository.cxx

@@ -301,8 +301,8 @@ check_datagram() {
 
 
     switch (_msg_type) {
     switch (_msg_type) {
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
-    case CLIENT_OBJECT_UPDATE_FIELD:
-    case STATESERVER_OBJECT_UPDATE_FIELD:
+    case CLIENT_OBJECT_SET_FIELD:
+    case STATESERVER_OBJECT_SET_FIELD:
       if (_handle_c_updates) {
       if (_handle_c_updates) {
         if (_has_owner_view) {
         if (_has_owner_view) {
           if (!handle_update_field_owner()) {
           if (!handle_update_field_owner()) {
@@ -494,7 +494,7 @@ send_message_bundle(unsigned int channel, unsigned int sender_channel) {
     dg.add_int8(1);
     dg.add_int8(1);
     dg.add_uint64(channel);
     dg.add_uint64(channel);
     dg.add_uint64(sender_channel);
     dg.add_uint64(sender_channel);
-    dg.add_uint16(STATESERVER_BOUNCE_MESSAGE);
+    //dg.add_uint16(STATESERVER_BOUNCE_MESSAGE);
     // add each bundled message
     // add each bundled message
     BundledMsgVector::const_iterator bmi;
     BundledMsgVector::const_iterator bmi;
     for (bmi = _bundle_msgs.begin(); bmi != _bundle_msgs.end(); bmi++) {
     for (bmi = _bundle_msgs.begin(); bmi != _bundle_msgs.end(); bmi++) {
@@ -708,7 +708,7 @@ handle_update_field() {
       Py_DECREF(dclass_obj);
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
       nassertr(dclass_this != NULL, false);
 
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
       Py_DECREF(dclass_this);
 
 
       // If in quiet zone mode, throw update away unless distobj has
       // If in quiet zone mode, throw update away unless distobj has
@@ -799,12 +799,13 @@ handle_update_field_owner() {
       Py_DECREF(dclass_obj);
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
       nassertr(dclass_this != NULL, false);
 
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
       Py_DECREF(dclass_this);
 
 
       // check if we should forward this update to the owner view
       // check if we should forward this update to the owner view
+      vector_uchar data = _di.get_remaining_bytes();
       DCPacker packer;
       DCPacker packer;
-      packer.set_unpack_data(_di.get_remaining_bytes());
+      packer.set_unpack_data((const char *)data.data(), data.size(), false);
       int field_id = packer.raw_unpack_uint16();
       int field_id = packer.raw_unpack_uint16();
       DCField *field = dclass->get_field_by_index(field_id);
       DCField *field = dclass->get_field_by_index(field_id);
       if (field->is_ownrecv()) {
       if (field->is_ownrecv()) {
@@ -841,12 +842,13 @@ handle_update_field_owner() {
       Py_DECREF(dclass_obj);
       Py_DECREF(dclass_obj);
       nassertr(dclass_this != NULL, false);
       nassertr(dclass_this != NULL, false);
 
 
-      DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
+      DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
       Py_DECREF(dclass_this);
       Py_DECREF(dclass_this);
 
 
       // check if we should forward this update to the owner view
       // check if we should forward this update to the owner view
+      vector_uchar data = _di.get_remaining_bytes();
       DCPacker packer;
       DCPacker packer;
-      packer.set_unpack_data(_di.get_remaining_bytes());
+      packer.set_unpack_data((const char *)data.data(), data.size(), false);
       int field_id = packer.raw_unpack_uint16();
       int field_id = packer.raw_unpack_uint16();
       DCField *field = dclass->get_field_by_index(field_id);
       DCField *field = dclass->get_field_by_index(field_id);
       if (true) {//field->is_broadcast()) {
       if (true) {//field->is_broadcast()) {
@@ -899,11 +901,11 @@ describe_message(ostream &out, const string &prefix,
 
 
     packer.RAW_UNPACK_CHANNEL();  // msg_sender
     packer.RAW_UNPACK_CHANNEL();  // msg_sender
     msg_type = packer.raw_unpack_uint16();
     msg_type = packer.raw_unpack_uint16();
-    is_update = (msg_type == STATESERVER_OBJECT_UPDATE_FIELD);
+    is_update = (msg_type == STATESERVER_OBJECT_SET_FIELD);
 
 
   } else {
   } else {
     msg_type = packer.raw_unpack_uint16();
     msg_type = packer.raw_unpack_uint16();
-    is_update = (msg_type == CLIENT_OBJECT_UPDATE_FIELD);
+    is_update = (msg_type == CLIENT_OBJECT_SET_FIELD);
   }
   }
 
 
   if (!is_update) {
   if (!is_update) {
@@ -974,7 +976,7 @@ describe_message(ostream &out, const string &prefix,
         Py_DECREF(dclass_obj);
         Py_DECREF(dclass_obj);
         nassertv(dclass_this != NULL);
         nassertv(dclass_this != NULL);
 
 
-        dclass = (DCClass *)PyLong_AsLong(dclass_this);
+        dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
         Py_DECREF(dclass_this);
         Py_DECREF(dclass_this);
       }
       }
     }
     }

+ 3 - 3
direct/src/distributed/cConnectionRepository.h

@@ -53,10 +53,10 @@ class SocketStream;
  * the C++ layer, while server messages that are not understood by the C++
  * the C++ layer, while server messages that are not understood by the C++
  * layer are returned up to the Python layer for processing.
  * layer are returned up to the Python layer for processing.
  */
  */
-class EXPCL_DIRECT CConnectionRepository {
+class EXPCL_DIRECT_DISTRIBUTED CConnectionRepository {
 PUBLISHED:
 PUBLISHED:
-  CConnectionRepository(bool has_owner_view = false,
-                        bool threaded_net = false);
+  explicit CConnectionRepository(bool has_owner_view = false,
+                                 bool threaded_net = false);
   ~CConnectionRepository();
   ~CConnectionRepository();
 
 
 /*
 /*

+ 2 - 2
direct/src/distributed/cDistributedSmoothNodeBase.cxx

@@ -278,12 +278,12 @@ begin_send_update(DCPacker &packer, const string &field_name) {
     packer.RAW_PACK_CHANNEL(_do_id);
     packer.RAW_PACK_CHANNEL(_do_id);
     packer.RAW_PACK_CHANNEL(_ai_id);
     packer.RAW_PACK_CHANNEL(_ai_id);
     // packer.raw_pack_uint8('A');
     // packer.raw_pack_uint8('A');
-    packer.raw_pack_uint16(STATESERVER_OBJECT_UPDATE_FIELD);
+    packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
     packer.raw_pack_uint32(_do_id);
     packer.raw_pack_uint32(_do_id);
     packer.raw_pack_uint16(field->get_number());
     packer.raw_pack_uint16(field->get_number());
 
 
   } else {
   } else {
-    packer.raw_pack_uint16(CLIENT_OBJECT_UPDATE_FIELD);
+    packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
     packer.raw_pack_uint32(_do_id);
     packer.raw_pack_uint32(_do_id);
     packer.raw_pack_uint16(field->get_number());
     packer.raw_pack_uint16(field->get_number());
   }
   }

+ 1 - 1
direct/src/distributed/cDistributedSmoothNodeBase.h

@@ -28,7 +28,7 @@ class CConnectionRepository;
  * This class defines some basic methods of DistributedSmoothNodeBase which
  * This class defines some basic methods of DistributedSmoothNodeBase which
  * have been moved into C++ as a performance optimization.
  * have been moved into C++ as a performance optimization.
  */
  */
-class EXPCL_DIRECT CDistributedSmoothNodeBase {
+class EXPCL_DIRECT_DISTRIBUTED CDistributedSmoothNodeBase {
 PUBLISHED:
 PUBLISHED:
   CDistributedSmoothNodeBase();
   CDistributedSmoothNodeBase();
   ~CDistributedSmoothNodeBase();
   ~CDistributedSmoothNodeBase();

+ 4 - 0
direct/src/distributed/config_distributed.cxx

@@ -14,6 +14,10 @@
 #include "config_distributed.h"
 #include "config_distributed.h"
 #include "dconfig.h"
 #include "dconfig.h"
 
 
+#if !defined(CPPPARSER) && !defined(BUILDING_DIRECT_DISTRIBUTED)
+  #error Buildsystem error: BUILDING_DIRECT_DISTRIBUTED not defined
+#endif
+
 Configure(config_distributed);
 Configure(config_distributed);
 NotifyCategoryDef(distributed, "");
 NotifyCategoryDef(distributed, "");
 
 

+ 2 - 2
direct/src/distributed/config_distributed.h

@@ -21,13 +21,13 @@
 #include "configVariableDouble.h"
 #include "configVariableDouble.h"
 #include "configVariableBool.h"
 #include "configVariableBool.h"
 
 
-NotifyCategoryDecl(distributed, EXPCL_DIRECT, EXPTP_DIRECT);
+NotifyCategoryDecl(distributed, EXPCL_DIRECT_DISTRIBUTED, EXPTP_DIRECT_DISTRIBUTED);
 
 
 extern ConfigVariableInt game_server_timeout_ms;
 extern ConfigVariableInt game_server_timeout_ms;
 extern ConfigVariableDouble min_lag;
 extern ConfigVariableDouble min_lag;
 extern ConfigVariableDouble max_lag;
 extern ConfigVariableDouble max_lag;
 extern ConfigVariableBool handle_datagrams_internally;
 extern ConfigVariableBool handle_datagrams_internally;
 
 
-extern EXPCL_DIRECT void init_libdistributed();
+extern EXPCL_DIRECT_DISTRIBUTED void init_libdistributed();
 
 
 #endif
 #endif

+ 7 - 1
direct/src/filter/CommonFilters.py

@@ -131,6 +131,9 @@ class CommonFilters:
             if (len(configuration) == 0):
             if (len(configuration) == 0):
                 return
                 return
 
 
+            if not self.manager.win.gsg.getSupportsBasicShaders():
+                return False
+
             auxbits = 0
             auxbits = 0
             needtex = set(["color"])
             needtex = set(["color"])
             needtexcoord = set(["color"])
             needtexcoord = set(["color"])
@@ -338,7 +341,10 @@ class CommonFilters:
                 text += "  o_color = float4(1, 1, 1, 1) - o_color;\n"
                 text += "  o_color = float4(1, 1, 1, 1) - o_color;\n"
             text += "}\n"
             text += "}\n"
 
 
-            self.finalQuad.setShader(Shader.make(text, Shader.SL_Cg))
+            shader = Shader.make(text, Shader.SL_Cg)
+            if not shader:
+                return False
+            self.finalQuad.setShader(shader)
             for tex in self.textures:
             for tex in self.textures:
                 self.finalQuad.setShaderInput("tx"+tex, self.textures[tex])
                 self.finalQuad.setShaderInput("tx"+tex, self.textures[tex])
 
 

+ 11 - 0
direct/src/gui/DirectEntry.py

@@ -59,6 +59,8 @@ class DirectEntry(DirectFrame):
             # Text used for the PGEntry text node
             # Text used for the PGEntry text node
             # NOTE: This overrides the DirectFrame text option
             # NOTE: This overrides the DirectFrame text option
             ('initialText',     '',               DGG.INITOPT),
             ('initialText',     '',               DGG.INITOPT),
+            # Enable or disable text overflow scrolling
+            ('overflow',        0,                self.setOverflowMode),
             # Command to be called on hitting Enter
             # Command to be called on hitting Enter
             ('command',        None,              None),
             ('command',        None,              None),
             ('extraArgs',      [],                None),
             ('extraArgs',      [],                None),
@@ -159,6 +161,9 @@ class DirectEntry(DirectFrame):
     def setCursorKeysActive(self):
     def setCursorKeysActive(self):
         PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])
         PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])
 
 
+    def setOverflowMode(self):
+        PGEntry.set_overflow_mode(self.guiItem, self['overflow'])
+
     def setObscureMode(self):
     def setObscureMode(self):
         PGEntry.setObscureMode(self.guiItem, self['obscured'])
         PGEntry.setObscureMode(self.guiItem, self['obscured'])
 
 
@@ -297,12 +302,18 @@ class DirectEntry(DirectFrame):
             else:
             else:
                 return self.guiItem.getText()
                 return self.guiItem.getText()
 
 
+    def getCursorPosition(self):
+        return self.guiItem.getCursorPosition()
+
     def setCursorPosition(self, pos):
     def setCursorPosition(self, pos):
         if (pos < 0):
         if (pos < 0):
             self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
             self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
         else:
         else:
             self.guiItem.setCursorPosition(pos)
             self.guiItem.setCursorPosition(pos)
 
 
+    def getNumCharacters(self):
+        return self.guiItem.getNumCharacters()
+
     def enterText(self, text):
     def enterText(self, text):
         """ sets the entry's text, and moves the cursor to the end """
         """ sets the entry's text, and moves the cursor to the end """
         self.set(text)
         self.set(text)

+ 22 - 23
direct/src/gui/DirectGuiBase.py

@@ -80,7 +80,8 @@ __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
 
 
 from panda3d.core import *
 from panda3d.core import *
-from panda3d.direct import get_config_showbase
+from direct.showbase import ShowBaseGlobal
+from direct.showbase.ShowBase import ShowBase
 from . import DirectGuiGlobals as DGG
 from . import DirectGuiGlobals as DGG
 from .OnscreenText import *
 from .OnscreenText import *
 from .OnscreenGeom import *
 from .OnscreenGeom import *
@@ -633,7 +634,7 @@ class DirectGuiBase(DirectObject.DirectObject):
         """
         """
         # Need to tack on gui item specific id
         # Need to tack on gui item specific id
         gEvent = event + self.guiId
         gEvent = event + self.guiId
-        if get_config_showbase().GetBool('debug-directgui-msgs', False):
+        if ShowBase.config.GetBool('debug-directgui-msgs', False):
             from direct.showbase.PythonUtil import StackTrace
             from direct.showbase.PythonUtil import StackTrace
             print(gEvent)
             print(gEvent)
             print(StackTrace())
             print(StackTrace())
@@ -662,7 +663,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     # Determine the default initial state for inactive (or
     # Determine the default initial state for inactive (or
     # unclickable) components.  If we are in edit mode, these are
     # unclickable) components.  If we are in edit mode, these are
     # actually clickable by default.
     # actually clickable by default.
-    guiEdit = get_config_showbase().GetBool('direct-gui-edit', 0)
+    guiEdit = ShowBase.config.GetBool('direct-gui-edit', False)
     if guiEdit:
     if guiEdit:
         inactiveInitState = DGG.NORMAL
         inactiveInitState = DGG.NORMAL
     else:
     else:
@@ -723,21 +724,24 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
         if self['guiId']:
         if self['guiId']:
             self.guiItem.setId(self['guiId'])
             self.guiItem.setId(self['guiId'])
         self.guiId = self.guiItem.getId()
         self.guiId = self.guiItem.getId()
-        if __dev__:
+
+        if ShowBaseGlobal.__dev__:
             guiObjectCollector.addLevel(1)
             guiObjectCollector.addLevel(1)
             guiObjectCollector.flushLevel()
             guiObjectCollector.flushLevel()
             # track gui items by guiId for tracking down leaks
             # track gui items by guiId for tracking down leaks
-            if hasattr(base, 'guiItems'):
-                if self.guiId in base.guiItems:
-                    base.notify.warning('duplicate guiId: %s (%s stomping %s)' %
-                                        (self.guiId, self,
-                                         base.guiItems[self.guiId]))
-                base.guiItems[self.guiId] = self
-                if hasattr(base, 'printGuiCreates'):
-                    printStack()
+            if ShowBase.config.GetBool('track-gui-items', False):
+                if not hasattr(ShowBase, 'guiItems'):
+                    ShowBase.guiItems = {}
+                if self.guiId in ShowBase.guiItems:
+                    ShowBase.notify.warning('duplicate guiId: %s (%s stomping %s)' %
+                                            (self.guiId, self,
+                                             ShowBase.guiItems[self.guiId]))
+                ShowBase.guiItems[self.guiId] = self
+
         # Attach button to parent and make that self
         # Attach button to parent and make that self
-        if (parent == None):
-            parent = aspect2d
+        if parent is None:
+            parent = ShowBaseGlobal.aspect2d
+
         self.assign(parent.attachNewNode(self.guiItem, self['sortOrder']))
         self.assign(parent.attachNewNode(self.guiItem, self['sortOrder']))
         # Update pose to initial values
         # Update pose to initial values
         if self['pos']:
         if self['pos']:
@@ -1024,17 +1028,12 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
 
 
     def destroy(self):
     def destroy(self):
         if hasattr(self, "frameStyle"):
         if hasattr(self, "frameStyle"):
-            if __dev__:
+            if ShowBaseGlobal.__dev__:
                 guiObjectCollector.subLevel(1)
                 guiObjectCollector.subLevel(1)
                 guiObjectCollector.flushLevel()
                 guiObjectCollector.flushLevel()
-                if hasattr(base, 'guiItems'):
-                    if self.guiId in base.guiItems:
-                        del base.guiItems[self.guiId]
-                    else:
-                        base.notify.warning(
-                            'DirectGuiWidget.destroy(): '
-                            'gui item %s not in base.guiItems' %
-                            self.guiId)
+                if hasattr(ShowBase, 'guiItems'):
+                    ShowBase.guiItems.pop(self.guiId, None)
+
             # Destroy children
             # Destroy children
             for child in self.getChildren():
             for child in self.getChildren():
                 childGui = self.guiDict.get(child.getName())
                 childGui = self.guiDict.get(child.getName())

+ 1 - 1
direct/src/interval/ActorInterval.py

@@ -83,7 +83,7 @@ class ActorInterval(Interval.Interval):
                 if startTime == None:
                 if startTime == None:
                     startTime = float(self.startFrame) / float(self.frameRate)
                     startTime = float(self.startFrame) / float(self.frameRate)
                 endTime = startTime + duration
                 endTime = startTime + duration
-                self.endFrame = duration * self.frameRate
+                self.endFrame = endTime * self.frameRate
             else:
             else:
                 # No end frame specified.  Choose the maximum of all
                 # No end frame specified.  Choose the maximum of all
                 # of the controls' numbers of frames.
                 # of the controls' numbers of frames.

+ 3 - 1
direct/src/interval/Interval.py

@@ -116,8 +116,9 @@ class Interval(DirectObject):
         return self.currT
         return self.currT
 
 
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
     def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
+        """ Starts the interval.  Returns an awaitable. """
         self.setupPlay(startT, endT, playRate, 0)
         self.setupPlay(startT, endT, playRate, 0)
-        self.__spawnTask()
+        return self.__spawnTask()
 
 
     def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
     def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
         self.setupPlay(startT, endT, playRate, 1)
         self.setupPlay(startT, endT, playRate, 1)
@@ -427,6 +428,7 @@ class Interval(DirectObject):
         task = Task(self.__playTask)
         task = Task(self.__playTask)
         task.interval = self
         task.interval = self
         taskMgr.add(task, taskName)
         taskMgr.add(task, taskName)
+        return task
 
 
     def __removeTask(self):
     def __removeTask(self):
         # Kill old task(s), including those from a similarly-named but
         # Kill old task(s), including those from a similarly-named but

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

@@ -342,7 +342,6 @@ class MetaInterval(CMetaInterval):
     # with all of their associated Python callbacks:
     # with all of their associated Python callbacks:
 
 
     def setManager(self, manager):
     def setManager(self, manager):
-        rogerroger
         self.__manager = manager
         self.__manager = manager
         CMetaInterval.setManager(self, manager)
         CMetaInterval.setManager(self, manager)
 
 

+ 4 - 4
direct/src/interval/cConstrainHprInterval.h

@@ -24,11 +24,11 @@
  * A constraint interval that will constrain the orientation of one node to
  * A constraint interval that will constrain the orientation of one node to
  * the orientation of another.
  * the orientation of another.
  */
  */
-class EXPCL_DIRECT CConstrainHprInterval : public CConstraintInterval {
+class EXPCL_DIRECT_INTERVAL CConstrainHprInterval : public CConstraintInterval {
 PUBLISHED:
 PUBLISHED:
-  CConstrainHprInterval(const string &name, double duration,
-                        const NodePath &node, const NodePath &target,
-                        bool wrt, const LVecBase3 hprOffset=LVector3::zero());
+  explicit CConstrainHprInterval(const string &name, double duration,
+                                 const NodePath &node, const NodePath &target,
+                                 bool wrt, const LVecBase3 hprOffset=LVector3::zero());
 
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;
   INLINE const NodePath &get_target() const;

+ 5 - 5
direct/src/interval/cConstrainPosHprInterval.h

@@ -24,12 +24,12 @@
  * A constraint interval that will constrain the position and orientation of
  * A constraint interval that will constrain the position and orientation of
  * one node to the position and orientation of another.
  * one node to the position and orientation of another.
  */
  */
-class EXPCL_DIRECT CConstrainPosHprInterval : public CConstraintInterval {
+class EXPCL_DIRECT_INTERVAL CConstrainPosHprInterval : public CConstraintInterval {
 PUBLISHED:
 PUBLISHED:
-  CConstrainPosHprInterval(const string &name, double duration,
-                           const NodePath &node, const NodePath &target,
-                           bool wrt, const LVecBase3 posOffset=LVector3::zero(),
-                           const LVecBase3 hprOffset=LVector3::zero());
+  explicit CConstrainPosHprInterval(const string &name, double duration,
+                                    const NodePath &node, const NodePath &target,
+                                    bool wrt, const LVecBase3 posOffset=LVector3::zero(),
+                                    const LVecBase3 hprOffset=LVector3::zero());
 
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;
   INLINE const NodePath &get_target() const;

+ 4 - 4
direct/src/interval/cConstrainPosInterval.h

@@ -23,11 +23,11 @@
  * A constraint interval that will constrain the position of one node to the
  * A constraint interval that will constrain the position of one node to the
  * position of another.
  * position of another.
  */
  */
-class EXPCL_DIRECT CConstrainPosInterval : public CConstraintInterval {
+class EXPCL_DIRECT_INTERVAL CConstrainPosInterval : public CConstraintInterval {
 PUBLISHED:
 PUBLISHED:
-  CConstrainPosInterval(const string &name, double duration,
-                        const NodePath &node, const NodePath &target,
-                        bool wrt, const LVecBase3 posOffset=LVector3::zero());
+  explicit CConstrainPosInterval(const string &name, double duration,
+                                 const NodePath &node, const NodePath &target,
+                                 bool wrt, const LVecBase3 posOffset=LVector3::zero());
 
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;
   INLINE const NodePath &get_target() const;

+ 4 - 4
direct/src/interval/cConstrainTransformInterval.h

@@ -22,11 +22,11 @@
  * A constraint interval that will constrain the transform of one node to the
  * A constraint interval that will constrain the transform of one node to the
  * transform of another.
  * transform of another.
  */
  */
-class EXPCL_DIRECT CConstrainTransformInterval : public CConstraintInterval {
+class EXPCL_DIRECT_INTERVAL CConstrainTransformInterval : public CConstraintInterval {
 PUBLISHED:
 PUBLISHED:
-  CConstrainTransformInterval(const string &name, double duration,
-                              const NodePath &node, const NodePath &target,
-                              bool wrt);
+  explicit CConstrainTransformInterval(const string &name, double duration,
+                                       const NodePath &node,
+                                       const NodePath &target, bool wrt);
 
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_target() const;
   INLINE const NodePath &get_target() const;

+ 1 - 1
direct/src/interval/cConstraintInterval.h

@@ -21,7 +21,7 @@
  * The base class for a family of intervals that constrain some property to a
  * The base class for a family of intervals that constrain some property to a
  * value over time.
  * value over time.
  */
  */
-class EXPCL_DIRECT CConstraintInterval : public CInterval {
+class EXPCL_DIRECT_INTERVAL CConstraintInterval : public CInterval {
 PUBLISHED:
 PUBLISHED:
  bool bogus_variable;
  bool bogus_variable;
 
 

+ 2 - 2
direct/src/interval/cInterval.h

@@ -32,7 +32,7 @@ class CIntervalManager;
  * C++ will inherit from this class; Intervals that must be implemented in
  * C++ will inherit from this class; Intervals that must be implemented in
  * Python will inherit from the similar Python class.
  * Python will inherit from the similar Python class.
  */
  */
-class EXPCL_DIRECT CInterval : public TypedReferenceCount {
+class EXPCL_DIRECT_INTERVAL CInterval : public TypedReferenceCount {
 public:
 public:
   CInterval(const string &name, double duration, bool open_ended);
   CInterval(const string &name, double duration, bool open_ended);
   virtual ~CInterval();
   virtual ~CInterval();
@@ -202,7 +202,7 @@ private:
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const CInterval &ival);
 INLINE ostream &operator << (ostream &out, const CInterval &ival);
-EXPCL_DIRECT ostream &operator << (ostream &out, CInterval::State state);
+EXPCL_DIRECT_INTERVAL ostream &operator << (ostream &out, CInterval::State state);
 
 
 #include "cInterval.I"
 #include "cInterval.I"
 
 

+ 1 - 1
direct/src/interval/cIntervalManager.h

@@ -36,7 +36,7 @@ class EventQueue;
  * It is also possible to create multiple IntervalManager objects for special
  * It is also possible to create multiple IntervalManager objects for special
  * needs.
  * needs.
  */
  */
-class EXPCL_DIRECT CIntervalManager {
+class EXPCL_DIRECT_INTERVAL CIntervalManager {
 PUBLISHED:
 PUBLISHED:
   CIntervalManager();
   CIntervalManager();
   ~CIntervalManager();
   ~CIntervalManager();

+ 3 - 3
direct/src/interval/cLerpAnimEffectInterval.h

@@ -29,10 +29,10 @@
  * CLerpAnimEffectInterval to adjust the degree to which each animation
  * CLerpAnimEffectInterval to adjust the degree to which each animation
  * affects the actor.
  * affects the actor.
  */
  */
-class EXPCL_DIRECT CLerpAnimEffectInterval : public CLerpInterval {
+class EXPCL_DIRECT_INTERVAL CLerpAnimEffectInterval : public CLerpInterval {
 PUBLISHED:
 PUBLISHED:
-  INLINE CLerpAnimEffectInterval(const string &name, double duration,
-                                 BlendType blend_type);
+  INLINE explicit CLerpAnimEffectInterval(const string &name, double duration,
+                                          BlendType blend_type);
 
 
   INLINE void add_control(AnimControl *control, const string &name,
   INLINE void add_control(AnimControl *control, const string &name,
                           float begin_effect, float end_effect);
                           float begin_effect, float end_effect);

+ 1 - 1
direct/src/interval/cLerpInterval.h

@@ -21,7 +21,7 @@
  * The base class for a family of intervals that linearly interpolate one or
  * The base class for a family of intervals that linearly interpolate one or
  * more numeric values over time.
  * more numeric values over time.
  */
  */
-class EXPCL_DIRECT CLerpInterval : public CInterval {
+class EXPCL_DIRECT_INTERVAL CLerpInterval : public CInterval {
 PUBLISHED:
 PUBLISHED:
   enum BlendType {
   enum BlendType {
     BT_no_blend,
     BT_no_blend,

+ 5 - 5
direct/src/interval/cLerpNodePathInterval.h

@@ -23,12 +23,12 @@
  * An interval that lerps one or more properties (like pos, hpr, etc.) on a
  * An interval that lerps one or more properties (like pos, hpr, etc.) on a
  * NodePath over time.
  * NodePath over time.
  */
  */
-class EXPCL_DIRECT CLerpNodePathInterval : public CLerpInterval {
+class EXPCL_DIRECT_INTERVAL CLerpNodePathInterval : public CLerpInterval {
 PUBLISHED:
 PUBLISHED:
-  CLerpNodePathInterval(const string &name, double duration,
-                        BlendType blend_type, bool bake_in_start,
-                        bool fluid,
-                        const NodePath &node, const NodePath &other);
+  explicit CLerpNodePathInterval(const string &name, double duration,
+                                 BlendType blend_type, bool bake_in_start,
+                                 bool fluid,
+                                 const NodePath &node, const NodePath &other);
 
 
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_node() const;
   INLINE const NodePath &get_other() const;
   INLINE const NodePath &get_other() const;

+ 2 - 2
direct/src/interval/cMetaInterval.h

@@ -29,9 +29,9 @@
  * own begin and end times.  Some of them may overlap and some of them may
  * own begin and end times.  Some of them may overlap and some of them may
  * not.
  * not.
  */
  */
-class EXPCL_DIRECT CMetaInterval : public CInterval {
+class EXPCL_DIRECT_INTERVAL CMetaInterval : public CInterval {
 PUBLISHED:
 PUBLISHED:
-  CMetaInterval(const string &name);
+  explicit CMetaInterval(const string &name);
   virtual ~CMetaInterval();
   virtual ~CMetaInterval();
 
 
   enum RelativeStart {
   enum RelativeStart {

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