Browse Source

Merge branch 'master' into webgl-port

rdb 5 years ago
parent
commit
e72bd7f5c6
100 changed files with 5679 additions and 619 deletions
  1. 423 0
      .github/workflows/ci.yml
  2. 6 0
      .gitignore
  3. 0 1
      .travis.yml
  4. 2 0
      BACKERS.md
  5. 190 0
      CMakeLists.txt
  6. 22 18
      README.md
  7. 78 0
      cmake/README.md
  8. 157 0
      cmake/install/Panda3DConfig.cmake
  9. 75 0
      cmake/macros/AddBisonTarget.cmake
  10. 77 0
      cmake/macros/AddFlexTarget.cmake
  11. 426 0
      cmake/macros/BuildMetalib.cmake
  12. 167 0
      cmake/macros/CompositeSources.cmake
  13. 400 0
      cmake/macros/Interrogate.cmake
  14. 506 0
      cmake/macros/PackageConfig.cmake
  15. 163 0
      cmake/macros/Python.cmake
  16. 9 0
      cmake/macros/README.md
  17. 37 0
      cmake/macros/RunPzip.cmake
  18. 21 0
      cmake/macros/Versioning.cmake
  19. 33 0
      cmake/modules/FindARToolKit.cmake
  20. 33 0
      cmake/modules/FindAssimp.cmake
  21. 175 0
      cmake/modules/FindCg.cmake
  22. 104 0
      cmake/modules/FindDirect3D9.cmake
  23. 21 0
      cmake/modules/FindEGL.cmake
  24. 25 0
      cmake/modules/FindEigen3.cmake
  25. 88 0
      cmake/modules/FindFCollada.cmake
  26. 116 0
      cmake/modules/FindFFMPEG.cmake
  27. 107 0
      cmake/modules/FindFFTW3.cmake
  28. 83 0
      cmake/modules/FindFMODEx.cmake
  29. 23 0
      cmake/modules/FindHarfBuzz.cmake
  30. 78 0
      cmake/modules/FindLibSquish.cmake
  31. 157 0
      cmake/modules/FindODE.cmake
  32. 20 0
      cmake/modules/FindOgg.cmake
  33. 92 0
      cmake/modules/FindOpenCV.cmake
  34. 88 0
      cmake/modules/FindOpenEXR.cmake
  35. 21 0
      cmake/modules/FindOpenGLES1.cmake
  36. 21 0
      cmake/modules/FindOpenGLES2.cmake
  37. 34 0
      cmake/modules/FindOpusFile.cmake
  38. 50 0
      cmake/modules/FindSWResample.cmake
  39. 50 0
      cmake/modules/FindSWScale.cmake
  40. 40 0
      cmake/modules/FindVRPN.cmake
  41. 34 0
      cmake/modules/FindVorbisFile.cmake
  42. 7 0
      cmake/modules/README.md
  43. 70 0
      cmake/scripts/ConcatenateToCXX.cmake
  44. 27 0
      cmake/scripts/CopyPattern.cmake
  45. 78 0
      cmake/scripts/CopyPython.cmake
  46. 28 0
      cmake/scripts/MakeComposite.cmake
  47. 10 0
      cmake/scripts/README.md
  48. 9 0
      cmake/templates/metalib_init.cxx.in
  49. 9 0
      cmake/templates/metalib_init.h.in
  50. 23 0
      cmake/templates/win32_python/__init__.py
  51. 18 0
      contrib/CMakeLists.txt
  52. 54 0
      contrib/src/ai/CMakeLists.txt
  53. 14 0
      contrib/src/contribbase/CMakeLists.txt
  54. 52 0
      contrib/src/rplight/CMakeLists.txt
  55. 75 0
      direct/CMakeLists.txt
  56. 115 103
      direct/src/actor/Actor.py
  57. 2 1
      direct/src/cluster/ClusterClient.py
  58. 20 17
      direct/src/cluster/ClusterConfig.py
  59. 16 15
      direct/src/controls/ControlManager.py
  60. 8 6
      direct/src/controls/DevWalker.py
  61. 8 6
      direct/src/controls/GhostWalker.py
  62. 8 6
      direct/src/controls/GravityWalker.py
  63. 20 18
      direct/src/controls/InputState.py
  64. 8 6
      direct/src/controls/NonPhysicsWalker.py
  65. 8 6
      direct/src/controls/ObserverWalker.py
  66. 0 2
      direct/src/controls/PhysicsRoller.py
  67. 8 6
      direct/src/controls/PhysicsWalker.py
  68. 1 1
      direct/src/controls/TwoDWalker.py
  69. 3 0
      direct/src/dcparse/CMakeLists.txt
  70. 87 0
      direct/src/dcparser/CMakeLists.txt
  71. 3 3
      direct/src/dcparser/dcPackerInterface.I
  72. 0 1
      direct/src/dcparser/dcPackerInterface.h
  73. 0 1
      direct/src/dcparser/dcParserDefs.h
  74. 7 3
      direct/src/dcparser/dcbase.h
  75. 0 1
      direct/src/dcparser/hashGenerator.h
  76. 24 0
      direct/src/deadrec/CMakeLists.txt
  77. 14 0
      direct/src/directbase/CMakeLists.txt
  78. 1 1
      direct/src/directdevices/DirectDeviceManager.py
  79. 2 2
      direct/src/directdevices/DirectFastrak.py
  80. 5 1
      direct/src/directnotify/DirectNotifyGlobal.py
  81. 2 1
      direct/src/directnotify/LoggerGlobal.py
  82. 6 6
      direct/src/directnotify/Notifier.py
  83. 15 15
      direct/src/directnotify/RotatingLog.py
  84. 0 99
      direct/src/directscripts/DetectPanda3D.js
  85. 0 132
      direct/src/directscripts/RunPanda3D.js
  86. 3 1
      direct/src/directscripts/extract_docs.py
  87. 38 32
      direct/src/directutil/Verify.py
  88. 111 16
      direct/src/dist/FreezeTool.py
  89. 4 0
      direct/src/dist/__init__.py
  90. 77 67
      direct/src/dist/commands.py
  91. 269 0
      direct/src/dist/icon.py
  92. 1 1
      direct/src/distributed/AsyncRequest.py
  93. 35 0
      direct/src/distributed/CMakeLists.txt
  94. 2 1
      direct/src/distributed/DistributedObject.py
  95. 2 1
      direct/src/distributed/DistributedObjectBase.py
  96. 9 8
      direct/src/distributed/DoCollectionManager.py
  97. 8 9
      direct/src/distributed/DoHierarchy.py
  98. 1 4
      direct/src/distributed/PyDatagram.py
  99. 1 0
      direct/src/distributed/PyDatagramIterator.py
  100. 1 1
      direct/src/distributed/ServerRepository.py

+ 423 - 0
.github/workflows/ci.yml

@@ -0,0 +1,423 @@
+name: Continuous Integration
+on: [push, pull_request]
+
+jobs:
+  cmake:
+    name: CMake Buildsystem
+
+    strategy:
+      fail-fast: false
+
+      matrix:
+        profile:
+        - ubuntu-xenial-standard-unity-makefile
+        - ubuntu-bionic-coverage-ninja
+        - macos-eigen-coverage-unity-xcode
+        - macos-nometa-standard-makefile
+#       - windows-standard-unity-msvc # FIXME when GH Actions runners upgrade CMake to >=3.16.1
+        - windows-nopython-nometa-standard-msvc
+
+        include:
+        - profile: ubuntu-xenial-standard-unity-makefile
+          os: ubuntu-16.04
+          config: Standard
+          unity: YES
+          generator: Unix Makefiles
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: ubuntu-bionic-coverage-ninja
+          os: ubuntu-18.04
+          config: Coverage
+          unity: NO
+          generator: Ninja
+          compiler: Clang
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: macos-eigen-coverage-unity-xcode
+          os: macOS-latest
+          config: Coverage
+          unity: YES
+          generator: Xcode
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: YES
+
+        - profile: macos-nometa-standard-makefile
+          os: macOS-latest
+          config: Standard
+          unity: NO
+          generator: Unix Makefiles
+          compiler: Default
+          metalibs: NO
+          python: YES
+          eigen: NO
+
+        - profile: windows-standard-unity-msvc
+          os: windows-2019
+          config: Standard
+          unity: YES
+          generator: Visual Studio 16 2019
+          compiler: Default
+          metalibs: YES
+          python: YES
+          eigen: NO
+
+        - profile: windows-nopython-nometa-standard-msvc
+          os: windows-2019
+          config: Standard
+          unity: NO
+          generator: Visual Studio 16 2019
+          compiler: Default
+          metalibs: NO
+          python: NO
+          eigen: NO
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        fetch-depth: 10
+
+    - name: Self-destruct makepanda
+      run: python makepanda/selfdestruct.py --yes
+
+    - name: Install dependencies (macOS)
+      if: runner.os == 'macOS'
+      run: |
+        curl -O https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-mac.tar.gz
+        tar -xf panda3d-1.10.5-tools-mac.tar.gz
+
+        brew install ccache
+
+        echo "##[set-env name=thirdpartyOption;]-D THIRDPARTY_DIRECTORY=../panda3d-1.10.5/thirdparty" -DHAVE_CG=OFF
+
+    - name: Install dependencies (Ubuntu)
+      if: startsWith(matrix.os, 'ubuntu')
+      run: >
+        sudo apt-get update
+
+        sudo apt-get install
+        build-essential ninja-build clang llvm ccache
+        bison flex
+        libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
+        libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
+        libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
+
+        # Workaround for CMake 3.12 finding this first:
+
+        sudo rm /usr/bin/x86_64-linux-gnu-python2.7-config
+
+    - name: Cache dependencies (Windows)
+      if: runner.os == 'Windows'
+      uses: actions/cache@v1
+      with:
+        path: thirdparty-tools
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.5-r1
+    - name: Install dependencies (Windows)
+      if: runner.os == 'Windows'
+      shell: powershell
+      run: |
+        if (!(Test-Path thirdparty-tools/panda3d-1.10.5)) {
+          $wc = New-Object System.Net.WebClient
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-win64.zip", "thirdparty-tools.zip")
+          Expand-Archive -Path thirdparty-tools.zip
+        }
+
+        echo "##[set-env name=thirdpartyOption;]-D THIRDPARTY_DIRECTORY=../thirdparty-tools/panda3d-1.10.5/thirdparty"
+
+    - name: ccache (non-Windows)
+      if: runner.os != 'Windows'
+      uses: actions/cache@v1
+      with:
+        path: ccache
+        key: ci-cmake-ccache-${{ matrix.profile }}
+
+    - name: Configure
+      shell: bash
+      env:
+        CMAKE_GENERATOR: "${{ matrix.generator }}"
+      run: >
+        mkdir build
+
+        cd build
+
+        if ${{ matrix.compiler == 'Clang' }}; then
+          if [[ "$CMAKE_GENERATOR" == *Studio*2019* ]]; then
+            export CMAKE_GENERATOR_TOOLSET=ClangCL thirdpartyOption="$thirdpartyOption -DHAVE_HARFBUZZ=NO"
+          elif [[ "$CMAKE_GENERATOR" == *Studio* ]]; then
+            export CMAKE_GENERATOR_TOOLSET=LLVM thirdpartyOption="$thirdpartyOption -DHAVE_HARFBUZZ=NO"
+          else
+            export CC=clang CXX=clang++
+          fi
+        fi
+
+        if ${{ runner.os != 'Windows' }}; then
+          compilerLauncher=$(echo -DCMAKE_C{,XX}_COMPILER_LAUNCHER=ccache)
+          echo "##[set-env name=CCACHE_DIR;]$(dirname $PWD)/ccache"
+        fi
+
+        cmake
+        ${compilerLauncher:-}
+        -D CMAKE_UNITY_BUILD=${{ matrix.unity }}
+        -D CMAKE_BUILD_TYPE="${{ matrix.config }}"
+        -D BUILD_METALIBS=${{ matrix.metalibs }}
+        -D HAVE_PYTHON=${{ matrix.python }}
+        -D HAVE_EIGEN=${{ matrix.eigen }}
+        ${thirdpartyOption:-}
+        ..
+
+    - name: Build (no Python)
+      if: contains(matrix.python, 'NO')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+
+    - name: Setup Python (Python 2.7)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 2.7
+    - name: Configure (Python 2.7)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=2.7
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 2.7)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 2.7)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest
+        $PYTHON_EXECUTABLE -m pytest ../tests
+      # END B
+
+    - name: Setup Python (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.5
+    - name: Configure (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.5
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.5)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.5)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest
+        $PYTHON_EXECUTABLE -m pytest ../tests
+      # END B
+
+    - name: Setup Python (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.6
+    - name: Configure (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.6
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.6)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.6)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Setup Python (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.7
+    - name: Configure (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.7
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.7)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.7)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Setup Python (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+    - name: Configure (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.8
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT=$pythonLocation .
+    - name: Build (Python 3.8)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.8)
+      # BEGIN B
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      env:
+        PYTHONPATH: ${{ matrix.config }}
+      run: |
+        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
+        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
+        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
+      # END B
+
+    - name: Upload coverage reports
+      if: always() && matrix.config == 'Coverage'
+      working-directory: build
+      shell: bash
+      env:
+        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+      run: |
+        shopt -s expand_aliases
+        if ${{ runner.os == 'macOS' }}; then alias llvm-profdata='xcrun llvm-profdata' llvm-cov='xcrun llvm-cov'; fi
+
+        python -m pip install coverage
+        python -m coverage combine $(find . -name '.coverage.*')
+
+        llvm-profdata merge pid-*.profraw -o coverage.profdata
+        llvm-cov show $(grep -Rl LLVM_PROFILE_FILE . | sed 's/^/-object /') -instr-profile=coverage.profdata > coverage.txt
+        bash <(curl -s https://codecov.io/bash)
+
+  makepanda:
+    strategy:
+      matrix:
+        os: [ubuntu-16.04, windows-2016, macOS-latest]
+    runs-on: ${{ matrix.os }}
+    steps:
+    - uses: actions/checkout@v1
+    - name: Install dependencies (Ubuntu)
+      if: matrix.os == 'ubuntu-16.04'
+      run: |
+        sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
+    - name: Get thirdparty packages (Windows)
+      if: runner.os == 'Windows'
+      shell: powershell
+      run: |
+        $wc = New-Object System.Net.WebClient
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-win64.zip", "thirdparty-tools.zip")
+        Expand-Archive -Path thirdparty-tools.zip
+        Move-Item -Path thirdparty-tools/panda3d-1.10.5/thirdparty -Destination .
+    - name: Get thirdparty packages (macOS)
+      if: runner.os == 'macOS'
+      run: |
+        curl -O https://www.panda3d.org/download/panda3d-1.10.5/panda3d-1.10.5-tools-mac.tar.gz
+        tar -xf panda3d-1.10.5-tools-mac.tar.gz
+        mv panda3d-1.10.5/thirdparty thirdparty
+        rmdir panda3d-1.10.5
+        (cd thirdparty/darwin-libs-a && rm -rf rocket)
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+    - name: Build Python 3.8
+      run: |
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
+    - name: Test Python 3.8
+      shell: bash
+      run: |
+        python -m pip install pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+    - name: Set up Python 3.7
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.7
+    - name: Build Python 3.7
+      run: |
+        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
+    - name: Test Python 3.7
+      shell: bash
+      run: |
+        python -m pip install pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+    - name: Set up Python 2.7
+      uses: actions/setup-python@v1
+      with:
+        python-version: 2.7
+    - name: Build Python 2.7
+      run: |
+        python makepanda/makepanda.py --no-copy-python --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir=$pythonLocation/include --python-libdir=$pythonLocation/lib --verbose --threads=4
+    - name: Test Python 2.7
+      shell: bash
+      run: |
+        python -m pip install pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+    - name: Make installer
+      run: |
+        python makepanda/makepackage.py --verbose --lzma

+ 6 - 0
.gitignore

@@ -54,6 +54,9 @@ CTestTestfile.cmake
 Thumbs.db
 Thumbs.db
 ehthumbs.db
 ehthumbs.db
 
 
+# macOS
+.DS_Store
+
 # Python
 # Python
 __pycache__/
 __pycache__/
 *.pyc
 *.pyc
@@ -63,3 +66,6 @@ __pycache__/
 .tox/
 .tox/
 .cache/
 .cache/
 .pytest_cache/
 .pytest_cache/
+/.settings/
+/.cproject
+/.project

+ 0 - 1
.travis.yml

@@ -44,7 +44,6 @@ install:
     - $PYTHONV -m pip install pytest
     - $PYTHONV -m pip install pytest
 script:
 script:
     - $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
     - $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
-    - test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
     - test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest -v tests
     - test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest -v tests
 notifications:
 notifications:
   irc:
   irc:

+ 2 - 0
BACKERS.md

@@ -23,12 +23,14 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 
 
 * Sam Edwards
 * Sam Edwards
 * Max Voss
 * Max Voss
+* Will Nielsen
 
 
 ## Enthusiasts
 ## Enthusiasts
 
 
 ![Benefactors](https://opencollective.com/panda3d/tiers/enthusiast.svg?avatarHeight=48&width=600)
 ![Benefactors](https://opencollective.com/panda3d/tiers/enthusiast.svg?avatarHeight=48&width=600)
 
 
 * Eric Thomson
 * Eric Thomson
+* Kyle Roach
 
 
 ## Backers
 ## Backers
 
 

+ 190 - 0
CMakeLists.txt

@@ -0,0 +1,190 @@
+cmake_minimum_required(VERSION 3.0.2)
+set(CMAKE_DISABLE_SOURCE_CHANGES ON) # Must go before project() below
+set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) # Must go before project() below
+
+if(CMAKE_VERSION VERSION_GREATER "3.11" OR POLICY CMP0072)
+  # Prefer GLVND over libGL when available; this will be enabled by default
+  # once the minimum CMake version is at least 3.11.
+  cmake_policy(SET CMP0072 NEW)
+endif()
+
+if(CMAKE_VERSION VERSION_GREATER "3.12" OR POLICY CMP0074)
+  # Needed for THIRDPARTY_DIRECTORY support; this will be enabled by default
+  # once the minimum CMake version is at least 3.12.
+  cmake_policy(SET CMP0074 NEW)
+endif()
+
+if(POLICY CMP0091)
+  # Needed for CMake to pass /MD flag properly with non-VC generators.
+  cmake_policy(SET CMP0091 NEW)
+endif()
+
+# Determine whether we are using a multi-config generator.
+if(CMAKE_VERSION VERSION_GREATER "3.8")
+  get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+else()
+  message(WARNING "Multi-configuration builds may not work properly when using
+a CMake < 3.9. Making a guess if this is a multi-config generator.")
+  if(DEFINED CMAKE_CONFIGURATION_TYPES)
+    set(IS_MULTICONFIG ON)
+  else()
+    set(IS_MULTICONFIG OFF)
+  endif()
+endif()
+
+# Define the type of build we are setting up.
+set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
+if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
+  list(APPEND _configs Coverage)
+endif()
+
+if(IS_MULTICONFIG)
+  message(STATUS "Using multi-configuration generator")
+  set(CMAKE_CONFIGURATION_TYPES ${_configs})
+else()
+  # Set the default CMAKE_BUILD_TYPE before calling project().
+  if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE Standard CACHE STRING "Choose the type of build." FORCE)
+    message(STATUS "Using default build type ${CMAKE_BUILD_TYPE}")
+  else()
+    message(STATUS "Using build type ${CMAKE_BUILD_TYPE}")
+  endif()
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${_configs})
+endif()
+
+# Figure out the version
+set(_s "[\\t ]*") # CMake doesn't support \s*
+file(STRINGS "setup.cfg" _version REGEX "^version${_s}=${_s}")
+string(REGEX REPLACE "^.*=${_s}" "" _version "${_version}")
+project(Panda3D VERSION ${_version})
+unset(_version)
+unset(_s)
+
+enable_testing()
+
+string(REPLACE "$(EFFECTIVE_PLATFORM_NAME)" "" PANDA_CFG_INTDIR "${CMAKE_CFG_INTDIR}")
+
+# Add generic modules to cmake module path,
+# and add Panda3D specific modules to cmake module path
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/")
+
+if(CMAKE_VERSION VERSION_GREATER "3.8")
+  # When using the Xcode generator, don't append the platform name to the
+  # intermediate configuration directory.
+  set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME OFF)
+endif()
+
+# Include modules builtin to CMake
+include(GNUInstallDirs)     # Defines CMAKE_INSTALL_<dir> variables
+
+# Include global modules needed for configure scripts
+include(PackageConfig)      # Defines package_option
+
+# Configure Panda3D
+include(dtool/CompilerFlags.cmake)
+include(dtool/PandaVersion.cmake)
+include(dtool/Package.cmake)
+include(dtool/Config.cmake)
+
+# Include global modules
+include(AddBisonTarget)     # Defines add_bison_target function
+include(AddFlexTarget)      # Defines add_flex_target function
+include(BuildMetalib)       # Defines add_component_library AND add_metalib
+include(CompositeSources)   # Defines composite_sources function
+include(Python)             # Defines add_python_target AND install_python_package
+include(Interrogate)        # Defines target_interrogate AND add_python_module
+include(RunPzip)            # Defines run_pzip function
+include(Versioning)         # Hooks 'add_library' to apply VERSION/SOVERSION
+
+# Determine which trees to build.
+option(BUILD_DTOOL "Build the dtool source tree." ON)
+option(BUILD_PANDA "Build the panda source tree." ON)
+option(BUILD_DIRECT "Build the direct source tree." ON)
+option(BUILD_PANDATOOL "Build the pandatool source tree." ON)
+option(BUILD_CONTRIB "Build the contrib source tree." ON)
+option(BUILD_MODELS "Build/install the built-in models." ON)
+
+# Include Panda3D packages
+if(BUILD_DTOOL)
+  add_subdirectory(dtool "${CMAKE_BINARY_DIR}/dtool")
+endif()
+
+if(BUILD_PANDA)
+  add_subdirectory(panda "${CMAKE_BINARY_DIR}/panda")
+endif()
+
+if(BUILD_DIRECT)
+  add_subdirectory(direct "${CMAKE_BINARY_DIR}/direct")
+endif()
+
+if(BUILD_PANDATOOL)
+  add_subdirectory(pandatool "${CMAKE_BINARY_DIR}/pandatool")
+endif()
+
+if(BUILD_CONTRIB)
+  add_subdirectory(contrib "${CMAKE_BINARY_DIR}/contrib")
+endif()
+
+if(BUILD_MODELS)
+  run_pzip(models
+    "${CMAKE_CURRENT_SOURCE_DIR}/models/"
+    "${PROJECT_BINARY_DIR}/${PANDA_CFG_INTDIR}/models"
+    *.egg)
+  run_pzip(dmodels
+    "${CMAKE_CURRENT_SOURCE_DIR}/dmodels/src/"
+    "${PROJECT_BINARY_DIR}/${PANDA_CFG_INTDIR}/models"
+    *.egg)
+
+  add_custom_command(TARGET models
+                     POST_BUILD
+                     COMMAND ${CMAKE_COMMAND}
+                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/models/maps/"
+                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models/maps"
+                             -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
+                     COMMENT "Copying models/maps")
+  add_custom_command(TARGET dmodels
+                     POST_BUILD
+                     COMMAND ${CMAKE_COMMAND}
+                             -DSOURCE="${CMAKE_CURRENT_SOURCE_DIR}/dmodels/src/"
+                             -DDESTINATION="${PANDA_OUTPUT_DIR}/models"
+                             -DFILES_MATCHING="PATTERN;*.rgb;PATTERN;*.png;PATTERN;*.jpg;PATTERN;*.wav"
+                             -P ${PROJECT_SOURCE_DIR}/cmake/scripts/CopyPattern.cmake
+                     COMMENT "Copying dmodels' assets")
+
+  install(DIRECTORY "${PANDA_OUTPUT_DIR}/models"
+    COMPONENT Models DESTINATION ${CMAKE_INSTALL_DATADIR}/panda3d)
+endif()
+
+if(INTERROGATE_PYTHON_INTERFACE)
+  # If we built the Python interface, run the test suite.  Note, we do NOT test
+  # for pytest before adding this test.  If the user doesn't have pytest, we'd
+  # like for the tests to fail.
+
+  # In the Coverage configuration, we also require pytest-cov
+
+  add_test(NAME pytest
+    COMMAND "${PYTHON_EXECUTABLE}" -m pytest "${PROJECT_SOURCE_DIR}/tests"
+    $<$<CONFIG:Coverage>:--cov=.>
+    WORKING_DIRECTORY "${PANDA_OUTPUT_DIR}")
+endif()
+
+# Generate the Panda3DConfig.cmake file so find_package(Panda3D) works, and
+# also register the build directory with CMake's package registry.
+
+file(COPY "${PROJECT_SOURCE_DIR}/cmake/install/Panda3DConfig.cmake"
+  DESTINATION "${PROJECT_BINARY_DIR}")
+install(FILES "${PROJECT_SOURCE_DIR}/cmake/install/Panda3DConfig.cmake"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D")
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+  "${PROJECT_BINARY_DIR}/Panda3DConfigVersion.cmake"
+  VERSION "${PROJECT_VERSION}"
+  COMPATIBILITY AnyNewerVersion)
+install(FILES "${PROJECT_BINARY_DIR}/Panda3DConfigVersion.cmake"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D")
+
+if(NOT CMAKE_CROSSCOMPILING)
+  export(PACKAGE Panda3D)
+endif()

+ 22 - 18
README.md

@@ -24,9 +24,9 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-4-1/).
+[this page](https://www.panda3d.org/download/sdk-1-10-6/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
-the following comand:
+the following command:
 
 
 ```bash
 ```bash
 pip install panda3d
 pip install panda3d
@@ -52,11 +52,11 @@ Building Panda3D
 Windows
 Windows
 -------
 -------
 
 
-You can build Panda3D with the Microsoft Visual C++ 2015 or 2017 compiler,
+You can build Panda3D with the Microsoft Visual C++ 2015, 2017 or 2019 compiler,
 which can be downloaded for free from the [Visual Studio site](https://visualstudio.microsoft.com/downloads/).
 which can be downloaded for free from the [Visual Studio site](https://visualstudio.microsoft.com/downloads/).
 You will also need to install the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk),
 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).
+and if you intend to target Windows Vista, you will also need the
+[Windows 8.1 SDK](https://go.microsoft.com/fwlink/p/?LinkId=323507).
 
 
 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,
@@ -64,16 +64,17 @@ 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
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip
-https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-win32.zip
 
 
-After acquiring these dependencies, you may simply build Panda3D from the
-command prompt using the following command.  (Change `14.1` to `14` if you are
-using Visual C++ 2015 instead of 2017.  Add the `--windows-sdk=10` option if
-you don't need to support Windows XP and did not install the Windows 7.1 SDK.)
+After acquiring these dependencies, you can build Panda3D from the command
+prompt using the following command.  Change the `--msvc-version` option based
+on your version of Visual C++; 2019 is 14.2, 2017 is 14.1, and 2015 is 14.
+Remove the `--windows-sdk=10` option if you need to support Windows Vista,
+which requires the Windows 8.1 SDK.
 
 
 ```bash
 ```bash
-makepanda\makepanda.bat --everything --installer --msvc-version=14.1 --no-eigen --threads=2
+makepanda\makepanda.bat --everything --installer --msvc-version=14.2 --windows-sdk=10 --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
@@ -116,7 +117,7 @@ sudo apt-get install build-essential pkg-config fakeroot python-dev libpng-dev l
 ```
 ```
 
 
 Once Panda3D has built, you can either install the .deb or .rpm package that
 Once Panda3D has built, you can either install the .deb or .rpm package that
-it produced, depending on which Linux distribution you are using.  For example,
+is produced, depending on which Linux distribution you are using.  For example,
 to install the package on Debian or Ubuntu, use this:
 to install the package on Debian or Ubuntu, use this:
 
 
 ```bash
 ```bash
@@ -135,7 +136,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.6/panda3d-1.10.6-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:
@@ -185,16 +186,19 @@ from the Play Store.  Many of the dependencies can be installed by running the
 following command in the Termux shell:
 following command in the Termux shell:
 
 
 ```bash
 ```bash
-pkg install python-dev termux-tools ndk-stl ndk-sysroot clang libvorbis-dev libopus-dev opusfile-dev openal-soft-dev freetype-dev harfbuzz-dev libpng-dev ecj4.6 dx patchelf aapt apksigner libcrypt-dev
+pkg install python ndk-sysroot clang bison freetype harfbuzz libpng eigen openal-soft opusfile libvorbis assimp libopus ecj dx patchelf aapt apksigner libcrypt openssl pkg-config
 ```
 ```
 
 
-Then, you can build and install the .apk right away using these commands:
+Then, you can build the .apk using this command:
 
 
 ```bash
 ```bash
 python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
 python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
-xdg-open panda3d.apk
 ```
 ```
 
 
+You can install the generated panda3d.apk by browsing to the panda3d folder
+using a file manager.  You may need to copy it to `/sdcard` to be able to
+access it from other apps.
+
 To launch a Python program from Termux, you can use the `run_python.sh` script
 To launch a Python program from Termux, you can use the `run_python.sh` script
 inside the `panda/src/android` directory.  It will launch Python in a separate
 inside the `panda/src/android` directory.  It will launch Python in a separate
 activity, load it with the Python script you passed as argument, and use a
 activity, load it with the Python script you passed as argument, and use a
@@ -208,7 +212,7 @@ Running Tests
 
 
 Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
 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
 and run the `pytest` command.  If you have not installed Panda3D, you will
-need to configure your enviroment by pointing the `PYTHONPATH` variable at
+need to configure your environment by pointing the `PYTHONPATH` variable at
 the `built` directory.  On Linux, you will also need to point the
 the `built` directory.  On Linux, you will also need to point the
 `LD_LIBRARY_PATH` variable at the `built/lib` directory.
 `LD_LIBRARY_PATH` variable at the `built/lib` directory.
 
 

+ 78 - 0
cmake/README.md

@@ -0,0 +1,78 @@
+Building with CMake
+-------------------
+
+On Windows and macOS, please ensure that you have the very latest version of
+CMake installed; older versions may work, but if not, please upgrade to the
+latest available version of CMake before requesting help.
+
+On systems that package CMake themselves (e.g. Linux distributions), we most
+likely support the provided version of CMake as long as the system itself is
+supported.
+
+CMake will also require that you already have your system's developer tools
+installed.
+
+The quickest way to build and install Panda with CMake is to install any
+third-party dependencies and then run:
+```sh
+mkdir build && cd build
+cmake ..
+cmake --build . --config Standard --parallel 4
+[sudo] cmake --build . --config Standard --target install
+```
+
+Note that, if you are targeting 64-bit on Windows, it is necessary to supply
+the `-A x64` option when first invoking `cmake` (as `cmake -A x64 ..`).
+
+CMake itself does not build Panda; rather, it generates project files for an
+existing IDE or build tool. To select a build tool, pass the `-G` option when
+first invoking CMake, (`cmake -G Ninja ..` is highly recommended on Linux).
+Some of these (Xcode, Visual Studio) support targeting multiple configurations
+(the `--config Standard`, above, selects the `Standard` configuration in those
+cases). Other build tools (Ninja, Makefiles, ...) do not support multiple
+configurations, and the `--config` option is ignored. To change the
+configuration in these cases (from `Standard`, the default), it is necessary to
+change the `CMAKE_BUILD_TYPE` variable as explained below.
+
+The configurations are:
+
+| Configuration  | Explanation                                            |
+| -------------- | ------------------------------------------------------ |
+| Standard       | Default; build provided to users of SDK                |
+| Release        | Distribution for end-users                             |
+| MinSizeRel     | Like Release, but optimized for size                   |
+| RelWithDebInfo | Like Release, but include debug symbols                |
+| Debug          | Do not optimize, enable optional debugging features    |
+| Coverage       | Like Debug, but profile code coverage; developers only |
+
+To configure CMake, it is recommended to use cmake-gui (`cmake-gui .`),
+or ccmake (`ccmake .`), however it is also possible to configure it entirely
+through CMake's command-line interface; see `man cmake` for more details.
+
+In general, the config variable for a particular third party library is:
+```
+	HAVE_<LIBRARY>=YES/NO   # Example: USE_JPEG
+```
+Panda subpackage building is handled by:
+```
+	BUILD_<SUBPACKAGE>=YES/NO   # Example: BUILD_DTOOL, BUILD_PANDA
+```
+Other configuration settings use their historical names (same names as in-source):
+```
+	# Examples
+	PANDA_DISTRIBUTOR="MyDistributor"
+	LINMATH_ALIGN=YES
+
+	# ... etc ...
+
+```
+
+For example, `makepanda.py --distributor X` becomes `cmake -DPANDA_DISTRIBUTOR=X`
+
+All found third-party libraries are enabled by default, and makepanda-style
+tools packages are searched in the same path as makepanda (however this may be
+overridden with the `THIRDPARTY_DIRECTORY` option).
+
+Most config settings are set to a sensible default for a typical PC/desktop
+Panda3D distribution. Third-party libraries and other settings can be enabled
+or disabled through configuration with the cmake GUI or CLI.

+ 157 - 0
cmake/install/Panda3DConfig.cmake

@@ -0,0 +1,157 @@
+# PANDA 3D SOFTWARE
+# Copyright (c) Carnegie Mellon University.  All rights reserved.
+#
+# All use of this software is subject to the terms of the revised BSD
+# license.  You should have received a copy of this license along
+# with this source code in a file named "LICENSE."
+#
+# Author: CFSworks (Dec. 11, 2018)
+
+# This file is installed to CMake's package search path, and is invoked for
+# find_package(Panda3D [COMPONENTS ...])
+#
+# The following components are available for importing:
+#
+#   Core      - The core Panda3D libraries; this component is always included.
+#
+#               Panda3D::Core::panda
+#               Panda3D::Core::pandaexpress
+#               etc.
+#
+#
+#   Python    - Python targets, which can be used for linking against the Python
+#               extension modules directly.  Note that this also imports the
+#               Python bindings for other requested components that have them.
+#
+#               Panda3D::Python::panda3d.core
+#               Panda3D::Python::panda3d.physics
+#               etc.
+#
+#
+#   Tools     - Various tools used in asset manipulation and debugging.
+#
+#               Panda3D::Tools::egg2bam
+#               Panda3D::Tools::egg-optchar
+#               Panda3D::Tools::pview
+#               etc.
+#
+#
+#   Direct    - Panda's "direct" Python framework; C++ support library.
+#
+#               Panda3D::Direct::p3direct
+#
+#
+#   Contrib   - Extensions not part of the Panda3D core, but contributed by the
+#               community.
+#
+#               Panda3D::Contrib::p3ai
+#               Panda3D::Contrib::p3rplight
+#
+#
+#   Framework - Panda's "p3framework" C++ framework.
+#
+#               Panda3D::Framework::p3framework
+#
+#
+#   Egg       - Support for the Egg file format.
+#
+#               Panda3D::Egg::pandaegg
+#
+#
+#   Bullet    - Support for Bullet physics.
+#
+#               Panda3D::Bullet::p3bullet
+#
+#
+#   ODE       - Support for the ODE physics engine.
+#
+#               Panda3D::ODE::p3ode
+#
+#
+#   FFmpeg    - Support for FFmpeg media format loading.
+#
+#               Panda3D::FFmpeg::p3ffmpeg
+#
+#
+#   OpenAL    - Support for OpenAL audio output.
+#
+#               Panda3D::OpenAL::p3openal_audio
+#
+#
+#   FMOD      - Support for FMOD audio output.
+#
+#               Panda3D::FMOD::p3fmod_audio
+#
+#
+#   OpenGL    - Support for OpenGL rendering.
+#
+#               Panda3D::OpenGL::pandagl
+#
+#
+#   DX9       - Support for Direct3D 9 rendering.
+#
+#               Panda3D::DX9::pandadx9
+#
+#
+#   OpenGLES1 - Support for OpenGL ES 1.x rendering.
+#
+#               Panda3D::OpenGLES1::pandagles
+#
+#
+#   OpenGLES2 - Support for OpenGL ES 2.x+ rendering.
+#
+#               Panda3D::OpenGLES2::pandagles2
+#
+#
+#   Vision    - Support for vision processing.
+#
+#               Panda3D::Vision::p3vision
+#
+#
+#   VRPN      - Support for connecting to a VRPN virtual reality server.
+#
+#               Panda3D::VRPN::p3vrpn
+
+if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 3.0)
+  message(FATAL_ERROR "CMake >= 3.0.2 required")
+endif()
+
+get_filename_component(_panda_config_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
+
+include("${_panda_config_prefix}/Panda3DPackages.cmake")
+
+set(_panda_components
+  Core Python Tools
+  Direct Contrib Framework Egg
+  Bullet ODE
+  FFmpeg
+  OpenAL FMOD
+  OpenGL DX9 OpenGLES1 OpenGLES2
+  Vision VRPN
+)
+
+set(Panda3D_FIND_REQUIRED_Core ON)
+
+foreach(_comp Core ${Panda3D_FIND_COMPONENTS})
+  if(";${_panda_components};" MATCHES ";${_comp};" AND
+     EXISTS "${_panda_config_prefix}/Panda3D${_comp}Targets.cmake")
+
+    include("${_panda_config_prefix}/Panda3D${_comp}Targets.cmake")
+
+    if(";${Panda3D_FIND_COMPONENTS};" MATCHES ";Python;" AND
+       EXISTS "${_panda_config_prefix}/Panda3D${_comp}PythonTargets.cmake")
+
+      include("${_panda_config_prefix}/Panda3D${_comp}PythonTargets.cmake")
+
+    endif()
+
+  elseif(Panda3D_FIND_REQUIRED_${_comp})
+
+    message(FATAL_ERROR "Panda3D REQUIRED component ${_comp} not found")
+
+  endif()
+
+endforeach(_comp)
+unset(_comp)
+
+unset(_panda_components)

+ 75 - 0
cmake/macros/AddBisonTarget.cmake

@@ -0,0 +1,75 @@
+# Filename: AddBisonTarget.cmake
+# Description: This file defines the function add_bison_target which instructs
+#   cmake to use bison on an input .yxx file.  If bison is not available on
+#   the system, add_bison_target tries to use .prebuilt .cxx files instead.
+#
+# Usage:
+#   add_bison_target(output_cxx input_yxx [DEFINES output_h] [PREFIX prefix])
+#
+
+# Define add_bison_target()
+function(add_bison_target output_cxx input_yxx)
+  set(arguments "")
+  set(outputs "${output_cxx}")
+  set(keyword "")
+
+  # Parse the extra arguments to the function.
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "DEFINES")
+      set(keyword "DEFINES")
+
+    elseif(arg STREQUAL "PREFIX")
+      set(keyword "PREFIX")
+
+    elseif(keyword STREQUAL "PREFIX")
+      list(APPEND arguments -p "${arg}")
+
+    elseif(keyword STREQUAL "DEFINES")
+      list(APPEND arguments --defines="${arg}")
+      list(APPEND outputs "${arg}")
+
+    else()
+      message(SEND_ERROR "Unexpected argument ${arg} to add_bison_target")
+
+    endif()
+  endforeach()
+
+  if(keyword STREQUAL arg AND NOT keyword STREQUAL "")
+    message(SEND_ERROR "Expected argument after ${keyword}")
+  endif()
+
+  if(HAVE_BISON)
+    get_source_file_property(input_yxx "${input_yxx}" LOCATION)
+
+    # If we have bison, we can of course just run it.
+    add_custom_command(
+      OUTPUT ${outputs}
+      COMMAND ${BISON_EXECUTABLE}
+        -o "${output_cxx}" ${arguments}
+        "${input_yxx}"
+      MAIN_DEPENDENCY "${input_yxx}"
+    )
+
+  else()
+    # Look for prebuilt versions of the outputs.
+    set(commands "")
+    set(depends "")
+
+    foreach(output ${outputs})
+      set(prebuilt_file "${output}.prebuilt")
+      get_filename_component(prebuilt_file "${prebuilt_file}" ABSOLUTE)
+
+      if(NOT EXISTS "${prebuilt_file}")
+        message(SEND_ERROR "Bison was not found and ${prebuilt_file} does not exist!")
+      endif()
+
+      list(APPEND depends "${prebuilt_file}")
+      list(APPEND commands COMMAND ${CMAKE_COMMAND} -E copy ${prebuilt_file} ${output})
+    endforeach()
+
+    add_custom_command(
+      OUTPUT ${outputs}
+      ${commands}
+      DEPENDS ${depends})
+  endif()
+endfunction(add_bison_target)

+ 77 - 0
cmake/macros/AddFlexTarget.cmake

@@ -0,0 +1,77 @@
+# Filename: AddFlexTarget.cmake
+# Description: This file defines the function add_flex_target which instructs
+#   cmake to use flex on an input .lxx file.  If flex is not available on
+#   the system, add_flex_target tries to use .prebuilt .cxx files instead.
+#
+# Usage:
+#   add_flex_target(output_cxx input_lxx [DEFINES output_h] [PREFIX prefix])
+#
+
+# Define add_flex_target()
+function(add_flex_target output_cxx input_lxx)
+  set(arguments "")
+  set(outputs "${output_cxx}")
+  set(keyword "")
+
+  # Parse the extra arguments to the function.
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "DEFINES")
+      set(keyword "DEFINES")
+
+    elseif(arg STREQUAL "PREFIX")
+      set(keyword "PREFIX")
+
+    elseif(arg STREQUAL "CASE_INSENSITIVE")
+      list(APPEND arguments "-i")
+
+    elseif(keyword STREQUAL "PREFIX")
+      list(APPEND arguments "-P${arg}")
+
+    elseif(keyword STREQUAL "DEFINES")
+      list(APPEND arguments "--header-file=${arg}")
+      list(APPEND outputs "${arg}")
+
+    else()
+      message(SEND_ERROR "Unexpected argument ${arg} to add_flex_target")
+
+    endif()
+  endforeach()
+
+  if(keyword STREQUAL arg AND NOT keyword STREQUAL "")
+    message(SEND_ERROR "Expected argument after ${keyword}")
+  endif()
+
+  if(HAVE_FLEX)
+    get_source_file_property(input_lxx "${input_lxx}" LOCATION)
+
+    # If we have flex, we can of course just run it.
+    add_custom_command(
+      OUTPUT ${outputs}
+      COMMAND ${FLEX_EXECUTABLE}
+        "-o${output_cxx}" ${arguments}
+        "${input_lxx}"
+      MAIN_DEPENDENCY "${input_lxx}")
+
+  else()
+    # Look for prebuilt versions of the outputs.
+    set(commands "")
+    set(depends "")
+
+    foreach(output ${outputs})
+      set(prebuilt_file "${output}.prebuilt")
+      get_filename_component(prebuilt_file "${prebuilt_file}" ABSOLUTE)
+
+      if(NOT EXISTS "${prebuilt_file}")
+        message(SEND_ERROR "Flex was not found and ${prebuilt_file} does not exist!")
+      endif()
+
+      list(APPEND depends "${prebuilt_file}")
+      list(APPEND commands COMMAND ${CMAKE_COMMAND} -E copy ${prebuilt_file} ${output})
+    endforeach()
+
+    add_custom_command(
+      OUTPUT ${outputs}
+      ${commands}
+      DEPENDS ${depends})
+  endif()
+endfunction(add_flex_target)

+ 426 - 0
cmake/macros/BuildMetalib.cmake

@@ -0,0 +1,426 @@
+# Filename: BuildMetalib.cmake
+#
+# Description: This file contains macros to build Panda3D's "metalibs" - these
+#   are special libraries that contain no unique code themselves and are
+#   instead just an agglomeration of the various component libraries that get
+#   linked into them. A library of libraries - a "metalibrary."
+
+#
+# Function: target_link_libraries(...)
+#
+# Overrides CMake's target_link_libraries() to support "linking" object
+# libraries. This is a partial reimplementation of CMake commit dc38970f83,
+# which is only available in CMake 3.12+
+#
+if(CMAKE_VERSION VERSION_LESS "3.12")
+  function(target_link_libraries target)
+    get_target_property(target_type "${target}" TYPE)
+    if(NOT target_type STREQUAL "OBJECT_LIBRARY")
+      _target_link_libraries("${target}" ${ARGN})
+      return()
+    endif()
+
+    foreach(library ${ARGN})
+      # This is a quick and dirty regex to tell targets apart from other stuff.
+      # It just checks if it's alphanumeric and starts with p3/panda.
+      if(library MATCHES "^(PKG::|p3|panda)[A-Za-z0-9]*$")
+        # We need to add "library"'s include directories to "target"
+        # (and transitively to INTERFACE_INCLUDE_DIRECTORIES so further
+        # dependencies will work)
+        set(include_directories "$<TARGET_PROPERTY:${library},INTERFACE_INCLUDE_DIRECTORIES>")
+        set_property(TARGET "${target}" APPEND PROPERTY INCLUDE_DIRECTORIES "${include_directories}")
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${include_directories}")
+
+        # SYSTEM include directories should still be reported as SYSTEM, so
+        # that warnings from those includes are suppressed
+        set(sys_include_directories
+          "$<TARGET_PROPERTY:${library},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>")
+        target_include_directories("${target}" SYSTEM PUBLIC "${sys_include_directories}")
+
+        # And for INTERFACE_COMPILE_DEFINITIONS as well
+        set(compile_definitions "$<TARGET_PROPERTY:${library},INTERFACE_COMPILE_DEFINITIONS>")
+        set_property(TARGET "${target}" APPEND PROPERTY COMPILE_DEFINITIONS "${compile_definitions}")
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "${compile_definitions}")
+
+        # Build up some generator expressions for determining whether `library`
+        # is a component library or not.
+        if(library MATCHES ".*::.*")
+          # "::" messes up CMake's genex parser; fortunately, a library whose
+          # name contains that is either an interface library or alias, and
+          # definitely not a component
+          set(is_component 0)
+          set(name_of_component "")
+          set(name_of_non_component "${library}")
+
+        else()
+          set(is_component "$<TARGET_PROPERTY:${library},IS_COMPONENT>")
+
+          # CMake complains if we lookup IS_COMPONENT on an INTERFACE library :(
+          set(is_object "$<STREQUAL:$<TARGET_PROPERTY:${library},TYPE>,OBJECT_LIBRARY>")
+          set(is_component "$<BOOL:$<${is_object}:${is_component}>>")
+
+          set(name_of_component "$<${is_component}:$<TARGET_NAME:${library}>>")
+          set(name_of_non_component "$<$<NOT:${is_component}>:$<TARGET_NAME:${library}>>")
+
+        endif()
+
+        # Libraries are only linked transitively if they aren't components.
+        set_property(TARGET "${target}" APPEND PROPERTY
+          INTERFACE_LINK_LIBRARIES "${name_of_non_component}")
+
+      else()
+        # This is a file path to an out-of-tree library - this needs to be
+        # recorded so that the metalib can link them. (They aren't needed at
+        # all for the object libraries themselves, so they don't have to work
+        # transitively.)
+        set_property(TARGET "${target}" APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${library}")
+
+      endif()
+
+    endforeach(library)
+
+  endfunction(target_link_libraries)
+endif()
+
+#
+# Function: add_component_library(target [SYMBOL building_symbol]
+#                                 [SOURCES] [[NOINIT]/[INIT func [header]]])
+#
+# Used very similarly to add_library.  You can specify a symbol with SYMBOL,
+# which works like CMake's own DEFINE_SYMBOL property: it's defined when
+# building the library, but not when building something that links against the
+# library.
+#
+# INIT specifies the init function that should be called from a metalib's init
+# function when this is added to a metalib.  The header parameter can further
+# clarify what header declares this function.  By default, this is
+# init_libTARGET and config_TARGET.h, respectively, where TARGET is the
+# target name (with 'p3' stripped off, if applicable).  The NOINIT keyword
+# suppresses this default.
+#
+# Note that this function gets to decide whether the component library is
+# OBJECT or SHARED, and whether the library is installed or not.  Also, as
+# a rule, component libraries may only be linked by other component libraries
+# in the same metalib - outside of the metalib, you must link the metalib
+# itself.
+#
+function(add_component_library target_name)
+  set(sources)
+  unset(symbol)
+
+  if(target_name MATCHES "^p3.*")
+    string(SUBSTRING "${target_name}" 2 -1 name_without_prefix)
+
+  else()
+    set(name_without_prefix "${target_name}")
+
+  endif()
+
+  set(init_func "init_lib${name_without_prefix}")
+  set(init_header "config_${name_without_prefix}.h")
+
+  set(symbol_keyword OFF)
+  set(init_keyword 0)
+  foreach(source ${ARGN})
+    if(source STREQUAL "SYMBOL")
+      set(symbol_keyword ON)
+      set(init_keyword 0)
+
+    elseif(source STREQUAL "INIT")
+      set(symbol_keyword OFF)
+      set(init_keyword 2)
+
+    elseif(source STREQUAL "NOINIT")
+      set(init_func)
+      set(init_header)
+
+    elseif(symbol_keyword)
+      set(symbol_keyword OFF)
+      set(symbol "${source}")
+
+    elseif(init_keyword EQUAL 2)
+      set(init_func "${source}")
+      set(init_keyword 1)
+
+    elseif(init_keyword EQUAL 1)
+      set(init_header "${source}")
+      set(init_keyword 0)
+
+    else()
+      list(APPEND sources "${source}")
+
+    endif()
+  endforeach()
+
+  if(BUILD_METALIBS)
+    # CMake 3.0.2 doesn't like .I/.N/.T files!  We let it know that they're only
+    # headers.
+    foreach(source ${sources})
+      if(source MATCHES "\\.[INT]$")
+        set_source_files_properties(${source} PROPERTIES HEADER_FILE_ONLY ON)
+      endif()
+    endforeach(source)
+
+    add_library("${target_name}" OBJECT ${sources})
+
+  else()
+    add_library("${target_name}" ${sources})
+
+  endif()
+
+  set_target_properties("${target_name}" PROPERTIES 
+    IS_COMPONENT ON
+    INIT_FUNCTION "${init_func}"
+    INIT_HEADER "${init_header}")
+
+  if(symbol)
+    set_property(TARGET "${target_name}" PROPERTY DEFINE_SYMBOL "${symbol}")
+
+    if(BUILD_METALIBS)
+      # ... DEFINE_SYMBOL is apparently not respected for object libraries?
+      set_property(TARGET "${target_name}" APPEND PROPERTY COMPILE_DEFINITIONS "${symbol}")
+
+      # Make sure other component libraries relying on this one inherit the
+      # symbol
+      set_property(TARGET "${target_name}" APPEND PROPERTY
+        INTERFACE_COMPILE_DEFINITIONS "$<$<BOOL:$<TARGET_PROPERTY:IS_COMPONENT>>:${symbol}>")
+    endif()
+  endif()
+
+  if(BUILD_METALIBS)
+    # Apparently neither is CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE?
+    set_property(TARGET "${target_name}" PROPERTY
+      INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}")
+
+    # If we're building dynamic libraries, the object library needs to be -fPIC
+    if(BUILD_SHARED_LIBS)
+      set_property(TARGET "${target_name}" PROPERTY
+        POSITION_INDEPENDENT_CODE ON)
+    endif()
+  endif()
+
+endfunction(add_component_library)
+
+#
+# Function: add_metalib(target [source1 source2]
+#                       [INCLUDE header1.h ...]
+#                       [INIT initfunc [initheader.h] [EXPORT type name expr]]
+#                       [COMPONENTS component1 ...])
+#
+# This is add_library, but for metalibs.
+#
+# The INIT keyword can specify an initialization function/header (which will be
+# autogenerated by this function) that calls the underlying component libs'
+# init functions.
+#
+# The EXPORT keyword exports the expression `expr`, which yields a value of
+# type `type`, as the undecorated (extern "C") symbol `name`
+#
+# The INCLUDE keyword allows the init file to pull in additional headers, which
+# may be useful for EXPORT.
+#
+function(add_metalib target_name)
+  set(components_keyword OFF)
+  set(init_keyword 0)
+  set(init_func)
+  set(init_header "${target_name}.h")
+  set(export_keyword 0)
+  set(export_declarations)
+  set(export_definitions)
+  set(component_init_headers)
+  set(components)
+  set(sources)
+
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "COMPONENTS")
+      set(components_keyword ON)
+      set(include_keyword OFF)
+      set(init_keyword 0)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "INCLUDE")
+      set(include_keyword ON)
+      set(components_keyword OFF)
+      set(init_keyword 0)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "INIT")
+      set(init_keyword 2)
+      set(components_keyword OFF)
+      set(include_keyword OFF)
+      set(export_keyword 0)
+
+    elseif(arg STREQUAL "EXPORT")
+      if(NOT init_func)
+        message(FATAL_ERROR "EXPORT cannot be used before INIT")
+      endif()
+
+      set(export_keyword 3)
+      set(components_keyword OFF)
+      set(include_keyword OFF)
+      set(init_keyword 0)
+
+    elseif(components_keyword)
+      list(APPEND components "${arg}")
+
+    elseif(include_keyword)
+      set(component_init_headers
+        "${component_init_headers}#include \"${arg}\"\n")
+
+    elseif(init_keyword EQUAL 2)
+      set(init_func "${arg}")
+      set(init_keyword 1)
+
+    elseif(init_keyword EQUAL 1)
+      set(init_header "${arg}")
+      set(init_keyword 0)
+
+    elseif(export_keyword EQUAL 3)
+      set(_export_type "${arg}")
+      set(export_keyword 2)
+
+    elseif(export_keyword EQUAL 2)
+      set(_export_name "${arg}")
+      set(export_keyword 1)
+
+    elseif(export_keyword EQUAL 1)
+      set(export_declarations
+        "${export_declarations}\nextern \"C\" IMPORT_CLASS ${_export_type} ${_export_name}();")
+      set(export_definitions
+        "${export_definitions}\nextern \"C\" EXPORT_CLASS ${_export_type} ${_export_name}() { return ${arg}; }")
+      unset(_export_type)
+      unset(_export_name)
+      set(export_keyword 0)
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach()
+
+  string(REPLACE ";" "|" piped_components "${components}")
+  set(component_genex_regex ".*TARGET_PROPERTY:(${piped_components}),.*")
+
+  set(private_defines)
+  set(interface_defines)
+  set(includes)
+  set(libs)
+  set(component_init_funcs "")
+  foreach(component ${components})
+    if(NOT TARGET "${component}")
+      message(FATAL_ERROR
+        "Missing component library ${component} referenced by metalib ${target_name}!
+        (Component library targets must be created BEFORE add_metalib.)")
+    endif()
+
+    get_target_property(is_component "${component}" IS_COMPONENT)
+    if(NOT is_component)
+      message(FATAL_ERROR
+        "Attempted to metalink non-component ${component} into ${target_name}!")
+    endif()
+
+    get_target_property(component_init_header "${component}" INIT_HEADER)
+    get_target_property(component_init_func "${component}" INIT_FUNCTION)
+
+    if(component_init_header)
+      set(component_init_headers
+        "${component_init_headers}#include \"${component_init_header}\"\n")
+    endif()
+
+    if(component_init_func)
+      set(component_init_funcs
+        "${component_init_funcs}  ${component_init_func}();\n")
+    endif()
+
+    if(BUILD_METALIBS)
+      # This will be an object library we're pulling in; rather than link,
+      # let's try to "flatten" all of its properties into ours.
+
+      # Private defines: Just reference using a generator expression
+      list(APPEND private_defines "$<TARGET_PROPERTY:${component},COMPILE_DEFINITIONS>")
+
+      # Interface defines: Copy those, but filter out generator expressions
+      # referencing a component library
+      get_target_property(component_defines "${component}" INTERFACE_COMPILE_DEFINITIONS)
+      if(component_defines)
+        foreach(component_define ${component_defines})
+          if(component_define MATCHES "${component_genex_regex}")
+            # Filter, it's a genex referencing one of our components
+          elseif(component_define MATCHES ".*IS_COMPONENT.*")
+            # Filter, it's testing to see if the consumer is a component
+          else()
+            list(APPEND interface_defines ${component_define})
+          endif()
+        endforeach(component_define)
+      endif()
+
+      # Include directories: Filter out anything that references a component
+      # library or anything in the project path
+      get_target_property(component_includes "${component}" INTERFACE_INCLUDE_DIRECTORIES)
+      foreach(component_include ${component_includes})
+        if(component_include MATCHES "${component_genex_regex}")
+          # Ignore component references
+
+        elseif(component_include MATCHES "^${PROJECT_SOURCE_DIR}")
+          # Include path within project; should only be included when building
+          list(APPEND includes "$<BUILD_INTERFACE:${component_include}>")
+
+        else()
+          # Anything else gets included
+          list(APPEND includes "${component_include}")
+
+        endif()
+      endforeach(component_include)
+
+      # Link libraries: Filter out component libraries; we aren't linking
+      # against them, we're using their objects
+      get_target_property(component_libraries "${component}" INTERFACE_LINK_LIBRARIES)
+      foreach(component_library ${component_libraries})
+        if(NOT component_library)
+          # NOTFOUND - guess there are no INTERFACE_LINK_LIBRARIES
+
+        elseif(component_library MATCHES "${component_genex_regex}")
+          # Ignore component references
+
+        elseif(component_library MATCHES ".*(${piped_components}).*")
+          # Component library, ignore
+
+        else()
+          # Anything else gets included
+          list(APPEND libs "${component_library}")
+
+        endif()
+      endforeach(component_library)
+
+      # Consume this component's objects
+      list(APPEND sources "$<TARGET_OBJECTS:${component}>")
+
+    else() # NOT BUILD_METALIBS
+      list(APPEND libs "${component}")
+
+    endif()
+  endforeach()
+
+  if(init_func)
+    set(init_source_path "${CMAKE_CURRENT_BINARY_DIR}/init_${target_name}.cxx")
+    set(init_header_path "${CMAKE_CURRENT_BINARY_DIR}/${init_header}")
+
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/metalib_init.cxx.in"
+      "${init_source_path}")
+    list(APPEND sources "${init_source_path}")
+
+    configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/metalib_init.h.in"
+      "${init_header_path}")
+    install(FILES "${init_header_path}" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)
+  endif()
+
+  add_library("${target_name}" ${sources})
+  target_compile_definitions("${target_name}"
+    PRIVATE ${private_defines}
+    INTERFACE ${interface_defines})
+  target_link_libraries("${target_name}" ${libs})
+  target_include_directories("${target_name}"
+    PUBLIC ${includes}
+    INTERFACE "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/panda3d>")
+
+endfunction(add_metalib)

+ 167 - 0
cmake/macros/CompositeSources.cmake

@@ -0,0 +1,167 @@
+# Filename: CompositeSources.cmake
+# Description: This file defines the function composite_sources which looks at
+#   a provided list of sources, generates _compositeN.cxx, and appends the
+#   composites to the list. The original files in the list are marked as headers
+#   so that they will be available in an IDE, but not compiled at build time.
+#
+# Usage:
+#   composite_sources(target source_var)
+#
+# Example:
+#   set(MY_SOURCES a.cxx b.cxx c.cxx)
+#   composite_sources(my_lib MY_SOURCES)
+#   add_library(my_lib ${MY_SOURCES})
+#
+
+
+# Settings for composite builds.  Should be moved to Config.cmake?
+set(CMAKE_UNITY_BUILD "ON" CACHE BOOL
+  "Enable unity builds; Panda defaults this to on.")
+
+set(CMAKE_UNITY_BUILD_BATCH_SIZE "30" CACHE STRING
+  "How many source files to build at a time through the unity build mechanism.
+  A high value will speed up the build dramatically but will be more memory
+  intensive than a low value.")
+
+set(COMPOSITE_SOURCE_EXTENSIONS ".cxx;.mm;.c" CACHE STRING
+  "Only files of these extensions will be composited.")
+
+set(COMPOSITE_SOURCE_EXCLUSIONS "" CACHE STRING
+  "A list of targets to skip when compositing sources. This is mainly
+desirable for CI builds.")
+
+set(COMPOSITE_GENERATOR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/MakeComposite.cmake")
+
+
+# Define composite_sources()
+function(composite_sources target sources_var)
+  if(NOT CMAKE_VERSION VERSION_LESS "3.16")
+    # CMake 3.16+ implements CMAKE_UNITY_BUILD* natively; no need to continue!
+
+    # Actually - <=3.16.2 has difficulty with multi-language support, so only
+    # allow .cxx in. Hopefully this can be removed soon.
+    foreach(_source ${${sources_var}})
+      get_filename_component(_source_ext "${_source}" EXT)
+      if(NOT _source_ext STREQUAL ".cxx")
+        set_source_files_properties(${_source} PROPERTIES
+          SKIP_UNITY_BUILD_INCLUSION YES)
+      endif()
+    endforeach(_source)
+
+    return()
+  endif()
+
+  if(NOT CMAKE_UNITY_BUILD)
+    # We've been turned off
+    return()
+  endif()
+
+  # How many sources were specified?
+  set(orig_sources ${${sources_var}})
+  set(sources ${orig_sources})
+  list(LENGTH sources num_sources)
+
+  # Don't composite if in the list of exclusions, and don't bother compositing
+  # with too few sources
+  list (FIND COMPOSITE_SOURCE_EXCLUSIONS ${target} _index)
+  if(num_sources LESS 2 OR ${CMAKE_UNITY_BUILD_BATCH_SIZE} LESS 2 OR ${_index} GREATER -1)
+    return()
+  endif()
+
+  # Sort each source file into a list.
+  foreach(source ${sources})
+    get_filename_component(extension "${source}" EXT)
+    get_source_file_property(generated "${source}" GENERATED)
+    get_source_file_property(is_header "${source}" HEADER_FILE_ONLY)
+    get_source_file_property(skip_compositing "${source}" SKIP_UNITY_BUILD_INCLUSION)
+
+    # Check if we can safely add this to a composite file.
+    if(NOT generated AND NOT is_header AND NOT skip_compositing AND
+        ";${COMPOSITE_SOURCE_EXTENSIONS};" MATCHES ";${extension};")
+
+      if(NOT DEFINED sources_${extension})
+        set(sources_${extension})
+      endif()
+
+      # Append it to one of the lists.
+      list(APPEND sources_${extension} "${source}")
+    endif()
+  endforeach(source)
+
+  # Now, put it all into one big list!
+  set(sorted_sources)
+  foreach(extension ${COMPOSITE_SOURCE_EXTENSIONS})
+    if(DEFINED sources_${extension})
+      list(APPEND sorted_sources ${sources_${extension}})
+    endif()
+  endforeach(extension)
+
+  set(composite_files)
+  set(composite_sources)
+
+  # Fill in composite_ext so we can kick off the loop.
+  list(GET sorted_sources 0 first_source)
+  get_filename_component(first_source_ext "${first_source}" EXT)
+  set(composite_ext ${first_source_ext})
+
+  while(num_sources GREATER 0)
+    # Pop the first element and adjust the sorted_sources length accordingly.
+    list(GET sorted_sources 0 source)
+    list(REMOVE_AT sorted_sources 0)
+    list(LENGTH sorted_sources num_sources)
+
+    # Add this file to our composite_sources buffer.
+    list(APPEND composite_sources ${source})
+    list(LENGTH composite_sources num_composite_sources)
+
+    # Get the next source file's extension, so we can see if we're done with
+    # this set of source files.
+    if(num_sources GREATER 0)
+      list(GET sorted_sources 0 next_source)
+      get_filename_component(next_extension "${next_source}" EXT)
+    else()
+      set(next_extension "")
+    endif()
+
+    # Check if this is the point where we should cut the file.
+    if(num_sources EQUAL 0 OR NOT num_composite_sources LESS ${CMAKE_UNITY_BUILD_BATCH_SIZE}
+       OR NOT composite_ext STREQUAL next_extension)
+      # It's pointless to make a composite source from just one file.
+      if(num_composite_sources GREATER 1)
+
+        # Figure out the name of our composite file.
+        list(LENGTH composite_files index)
+        math(EXPR index "1+${index}")
+        set(composite_file "${CMAKE_CURRENT_BINARY_DIR}/${target}_composite${index}${composite_ext}")
+        list(APPEND composite_files "${composite_file}")
+
+        # Set HEADER_FILE_ONLY to prevent it from showing up in the
+        # compiler command, but still show up in the IDE environment.
+        set_source_files_properties(${composite_sources} PROPERTIES HEADER_FILE_ONLY ON)
+
+        # We'll interrogate the composite files, so exclude the original sources.
+        set_source_files_properties(${composite_sources} PROPERTIES WRAP_EXCLUDE YES)
+
+        # Finally, add the target that generates the composite file.
+        add_custom_command(
+          OUTPUT "${composite_file}"
+          COMMAND ${CMAKE_COMMAND}
+            -DCOMPOSITE_FILE="${composite_file}"
+            -DCOMPOSITE_SOURCES="${composite_sources}"
+            -P "${COMPOSITE_GENERATOR}"
+          DEPENDS ${composite_sources})
+      endif()
+
+      # Reset for the next composite file.
+      set(composite_sources "")
+      set(composite_ext ${next_extension})
+    endif()
+  endwhile()
+
+  set_source_files_properties(${composite_files} PROPERTIES GENERATED YES)
+
+  # The new files are added to the existing files, which means the old files
+  # are still there, but they won't be compiled due to the HEADER_FILE_ONLY setting.
+  set(${sources_var} ${orig_sources} ${composite_files} PARENT_SCOPE)
+
+endfunction(composite_sources)

+ 400 - 0
cmake/macros/Interrogate.cmake

@@ -0,0 +1,400 @@
+# Filename: Interrogate.cmake
+#
+# Description: This file contains macros and functions that are used to invoke
+#   interrogate, to generate wrappers for Python and/or other languages.
+#
+# Functions:
+#   target_interrogate(target [ALL] [source1 [source2 ...]])
+#   add_python_module(module [lib1 [lib2 ...]])
+#   add_python_target(target [source1 [source2 ...]])
+#
+
+set(IGATE_FLAGS -DCPPPARSER -D__cplusplus -Dvolatile -Dmutable)
+
+# In addition, Interrogate needs to know if this is a 64-bit build:
+include(CheckTypeSize)
+check_type_size(long CMAKE_SIZEOF_LONG)
+if(CMAKE_SIZEOF_LONG EQUAL 8)
+  list(APPEND IGATE_FLAGS "-D_LP64")
+endif()
+
+
+# This is a list of regexes that are applied to every filename. If one of the
+# regexes matches, that file will not be passed to Interrogate.
+set(INTERROGATE_EXCLUDE_REGEXES
+  ".*\\.I$"
+  ".*\\.N$"
+  ".*\\.c$"
+  ".*\\.lxx$"
+  ".*\\.yxx$"
+  ".*_src\\..*"
+)
+
+if(WIN32)
+  list(APPEND IGATE_FLAGS -D_X86_ -D__STDC__=1 -D "_declspec(param)=" -D "__declspec(param)=" -D_near -D_far -D__near -D__far -D_WIN32 -D__stdcall)
+endif()
+if(MSVC_VERSION)
+  list(APPEND IGATE_FLAGS "-D_MSC_VER=${MSVC_VERSION}")
+endif()
+if(INTERROGATE_VERBOSE)
+  list(APPEND IGATE_FLAGS "-v")
+endif()
+
+set(IMOD_FLAGS -python-native)
+
+# This stores the names of every module added to the Interrogate system:
+set(ALL_INTERROGATE_MODULES CACHE INTERNAL "Internal variable")
+
+#
+# Function: target_interrogate(target [ALL] [source1 [source2 ...]])
+# NB. This doesn't actually invoke interrogate, but merely adds the
+# sources to the list of scan sources associated with the target.
+# Interrogate will be invoked when add_python_module is called.
+# If ALL is specified, all of the sources from the associated
+# target are added.
+#
+function(target_interrogate target)
+  set(sources)
+  set(extensions)
+  set(want_all OFF)
+  set(extensions_keyword OFF)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "ALL")
+      set(want_all ON)
+
+    elseif(arg STREQUAL "EXTENSIONS")
+      set(extensions_keyword ON)
+
+    elseif(extensions_keyword)
+      list(APPEND extensions "${arg}")
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach()
+
+  # If ALL was specified, pull in all sources from the target.
+  if(want_all)
+    get_target_property(target_sources "${target}" SOURCES)
+    list(APPEND sources ${target_sources})
+  endif()
+
+  list(REMOVE_DUPLICATES sources)
+
+  # Now let's get everything's absolute path, so that it can be passed
+  # through a property while still preserving the reference.
+  set(absolute_sources)
+  foreach(source ${sources})
+    get_source_file_property(exclude "${source}" WRAP_EXCLUDE)
+    if(NOT exclude)
+      get_source_file_property(location "${source}" LOCATION)
+      list(APPEND absolute_sources ${location})
+    endif()
+  endforeach(source)
+
+  set(absolute_extensions)
+  foreach(extension ${extensions})
+    get_source_file_property(location "${extension}" LOCATION)
+    list(APPEND absolute_extensions ${location})
+  endforeach(extension)
+
+  set_target_properties("${target}" PROPERTIES
+    IGATE_SOURCES "${absolute_sources}")
+  set_target_properties("${target}" PROPERTIES
+    IGATE_EXTENSIONS "${absolute_extensions}")
+
+  # CMake has no property for determining the source directory where the
+  # target was originally added. interrogate_sources makes use of this
+  # property (if it is set) in order to make all paths on the command-line
+  # relative to it, thereby shortening the command-line even more.
+  # Since this is not an Interrogate-specific property, it is not named with
+  # an IGATE_ prefix.
+  set_target_properties("${target}" PROPERTIES
+    TARGET_SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}")
+
+  # Also store where the build files are kept, so the Interrogate output can go
+  # there as well.
+  set_target_properties("${target}" PROPERTIES
+    TARGET_BINDIR "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
+
+endfunction(target_interrogate)
+
+#
+# Function: interrogate_sources(target output database language_flags module)
+#
+# This function actually runs a component-level interrogation against 'target'.
+# It generates the outfile.cxx (output) and dbfile.in (database) files, which
+# can then be used during the interrogate_module step to produce language
+# bindings.
+#
+# The target must first have had sources selected with target_interrogate.
+# Failure to do so will result in an error.
+#
+function(interrogate_sources target output database language_flags)
+  get_target_property(sources "${target}" IGATE_SOURCES)
+  get_target_property(extensions "${target}" IGATE_EXTENSIONS)
+
+  if(NOT sources)
+    message(FATAL_ERROR
+      "Cannot interrogate ${target} unless it's run through target_interrogate first!")
+  endif()
+
+  get_target_property(srcdir "${target}" TARGET_SRCDIR)
+  if(NOT srcdir)
+    # No TARGET_SRCDIR was set, so we'll do everything relative to our
+    # current binary dir instead:
+    set(srcdir "${CMAKE_CURRENT_BINARY_DIR}")
+  endif()
+
+  set(scan_sources)
+  set(nfiles)
+  foreach(source ${sources})
+    get_filename_component(source_basename "${source}" NAME)
+
+    # Only certain sources should actually be scanned by Interrogate. The
+    # rest are merely dependencies. This uses the exclusion regex above in
+    # order to determine what files are okay:
+    set(exclude OFF)
+    foreach(regex ${INTERROGATE_EXCLUDE_REGEXES})
+      if("${source_basename}" MATCHES "${regex}")
+        set(exclude ON)
+      endif()
+    endforeach(regex)
+
+    if(NOT exclude)
+      # This file is to be scanned by Interrogate. In order to avoid
+      # cluttering up the command line, we should first make it relative:
+      file(RELATIVE_PATH rel_source "${srcdir}" "${source}")
+      list(APPEND scan_sources "${rel_source}")
+
+      # Also see if this file has a .N counterpart, which has directives
+      # specific for Interrogate. If there is a .N file, we add it as a dep,
+      # so that CMake will rerun Interrogate if the .N files are modified:
+      get_filename_component(source_path "${source}" PATH)
+      get_filename_component(source_name_we "${source}" NAME_WE)
+      set(nfile "${source_path}/${source_name_we}.N")
+      if(EXISTS "${nfile}")
+        list(APPEND nfiles "${nfile}")
+      endif()
+    endif()
+  endforeach(source)
+
+  # Also add extensions, in relative-path form
+  foreach(extension ${extensions})
+    file(RELATIVE_PATH rel_extension "${srcdir}" "${extension}")
+    list(APPEND scan_sources "${rel_extension}")
+  endforeach(extension)
+
+  # Interrogate also needs the include paths, so we'll extract them from the
+  # target. These are available via a generator expression.
+
+  # When we read the INTERFACE_INCLUDE_DIRECTORIES property, we need to read it
+  # from a target that has the IS_INTERROGATE=1 property.
+  # (See PackageConfig.cmake for an explanation why.)
+  # The problem is, custom commands are not targets, so we can't put target
+  # properties on them. And if you try to use the $<TARGET_PROPERTY:prop>
+  # generator expression from the context of a custom command, it'll instead
+  # read the property from the most recent actual target. As a workaround for
+  # this, we create a fake target with the IS_INTERROGATE property set and pull
+  # the INTERFACE_INCLUDE_DIRECTORIES property out through that.
+  # I hate it, but such is CMake.
+  add_custom_target(${target}_igate_internal)
+  set_target_properties(${target}_igate_internal PROPERTIES
+    IS_INTERROGATE 1
+    INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>")
+
+  # Note, the \t is a workaround for a CMake bug where using a plain space in
+  # a JOIN will cause it to be escaped. Tabs are not escaped and will
+  # separate correctly.
+  set(include_flags "-I$<JOIN:$<TARGET_PROPERTY:${target}_igate_internal,INTERFACE_INCLUDE_DIRECTORIES>,\t-I>")
+
+  # Get the compiler definition flags. These must be passed to Interrogate
+  # in the same way that they are passed to the compiler so that Interrogate
+  # will preprocess each file in the same way.
+  set(_compile_defs "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
+  if(NOT CMAKE_HOST_WIN32)
+    # Win32's command-line parser doesn't understand "'"
+    # that's fine, it also ignores '"'
+    set(_q "'")
+  endif()
+  set(_compile_defs_flags "-D${_q}$<JOIN:${_compile_defs},${_q}\t-D${_q}>${_q}")
+  # We may have just ended up with -D'' if there are no flags; filter that
+  set(define_flags
+    "$<$<NOT:$<STREQUAL:${_compile_defs_flags},-D${_q}${_q}>>:${_compile_defs_flags}>")
+
+  # Some of the definitions may be specified using -D flags in the global
+  # CXX_FLAGS variables; parse those out (this also picks up NDEBUG)
+  set(_configs ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE} "<ALL>")
+  list(REMOVE_DUPLICATES _configs)
+  foreach(_config ${_configs})
+    if(_config STREQUAL "<ALL>")
+      set(flags "${CMAKE_CXX_FLAGS}")
+    else()
+      string(TOUPPER "${_config}" _CONFIG)
+      set(flags "${CMAKE_CXX_FLAGS_${_CONFIG}}")
+    endif()
+
+    # Convert "/D define1" and "-Ddefine2" flags, interspersed with other
+    # compiler nonsense, into a basic "-Ddefine1 -Ddefine2" string
+    string(REGEX MATCHALL "[/-]D[ \t]*[A-Za-z0-9_]+" igate_flags "${flags}")
+    string(REPLACE ";" " " igate_flags "${igate_flags}")
+    string(REPLACE "/D" "-D" igate_flags "${igate_flags}")
+
+    if(_config STREQUAL "<ALL>")
+      list(APPEND define_flags "${igate_flags}")
+    else()
+      list(APPEND define_flags "$<$<CONFIG:${_config}>:${igate_flags}>")
+    endif()
+  endforeach(_config)
+
+  get_filename_component(output_directory "${output}" DIRECTORY)
+  get_filename_component(database_directory "${database}" DIRECTORY)
+
+  add_custom_command(
+    OUTPUT "${output}" "${database}"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${output_directory}"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${database_directory}"
+    COMMAND host_interrogate
+      -oc "${output}"
+      -od "${database}"
+      -srcdir "${srcdir}"
+      -library ${target}
+      ${INTERROGATE_OPTIONS}
+      ${IGATE_FLAGS}
+      ${language_flags}
+      ${define_flags}
+      -S "${PROJECT_SOURCE_DIR}/dtool/src/interrogatedb"
+      -S "${PROJECT_SOURCE_DIR}/dtool/src/parser-inc"
+      -S "${PYTHON_INCLUDE_DIRS}"
+      ${include_flags}
+      ${scan_sources}
+
+    DEPENDS host_interrogate ${sources} ${extensions} ${nfiles}
+    COMMENT "Interrogating ${target}")
+
+  # Propagate the target's compile definitions to the output file
+  set_source_files_properties("${output}" PROPERTIES
+    COMPILE_DEFINITIONS "$<TARGET_PROPERTY:${target},INTERFACE_COMPILE_DEFINITIONS>")
+
+endfunction(interrogate_sources)
+
+#
+# Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...]
+#    [IMPORT mod1 ...])
+# Uses interrogate to create a Python module. If the LINK keyword is specified,
+# the Python module is linked against the specified libraries instead of those
+# listed before. The IMPORT keyword makes the output module import another
+# Python module when it's initialized.
+#
+function(add_python_module module)
+  if(NOT INTERROGATE_PYTHON_INTERFACE)
+    return()
+  endif()
+
+  set(targets)
+  set(component "Python")
+  set(link_targets)
+  set(import_flags)
+  set(infiles_rel)
+  set(infiles_abs)
+  set(sources_abs)
+  set(extensions)
+
+  set(keyword)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "COMPONENT")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "LINK")
+      list(APPEND link_targets "${arg}")
+      set(keyword)
+
+    elseif(keyword STREQUAL "IMPORT")
+      list(APPEND import_flags "-import" "${arg}")
+      set(keyword)
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+      set(keyword)
+
+    else()
+      list(APPEND targets "${arg}")
+
+    endif()
+  endforeach(arg)
+
+  if(NOT link_targets)
+    set(link_targets ${targets})
+  endif()
+
+  string(REGEX REPLACE "^.*\\." "" modname "${module}")
+
+  foreach(target ${targets})
+    get_target_property(workdir_abs "${target}" TARGET_BINDIR)
+    if(NOT workdir_abs)
+      # No TARGET_BINDIR was set, so we'll just use our current directory:
+      set(workdir_abs "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
+    endif()
+    # Keep command lines short
+    file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
+
+    interrogate_sources(${target}
+      "${workdir_abs}/${target}_igate.cxx"
+      "${workdir_abs}/${target}.in"
+      "-python-native;-module;${module}")
+
+    get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
+    list(APPEND infiles_rel "${workdir_rel}/${target}.in")
+    list(APPEND infiles_abs "${workdir_abs}/${target}.in")
+    list(APPEND sources_abs "${workdir_abs}/${target}_igate.cxx")
+    list(APPEND extensions ${target_extensions})
+  endforeach(target)
+
+  add_custom_command(
+    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    COMMAND ${CMAKE_COMMAND} -E
+      make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
+    COMMAND host_interrogate_module
+      -oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+      -module ${modname} -library ${modname}
+      ${import_flags}
+      ${INTERROGATE_MODULE_OPTIONS}
+      ${IMOD_FLAGS} ${infiles_rel}
+    DEPENDS host_interrogate_module ${infiles_abs}
+    COMMENT "Generating module ${module}")
+
+  # CMake chokes on ${CMAKE_CFG_INTDIR} in source paths when unity builds are
+  # enabled. The easiest way out of this is to skip unity for those paths.
+  # Since generated Interrogate .cxx files are pretty big already, this doesn't
+  # really inconvenience us at all.
+  set_source_files_properties(
+    "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    ${sources_abs} PROPERTIES
+    SKIP_UNITY_BUILD_INCLUSION YES)
+
+  add_python_target(${module} COMPONENT "${component}" EXPORT "${component}"
+    "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
+    ${sources_abs} ${extensions})
+  target_link_libraries(${module} ${link_targets})
+
+  if(CMAKE_VERSION VERSION_LESS "3.11")
+    # CMake <3.11 doesn't allow generator expressions on source files, so we
+    # need to copy them to our target, which does allow them.
+
+    foreach(source ${sources_abs})
+      get_source_file_property(compile_definitions "${source}" COMPILE_DEFINITIONS)
+      if(compile_definitions)
+        set_property(TARGET ${module} APPEND PROPERTY
+          COMPILE_DEFINITIONS ${compile_definitions})
+
+        set_source_files_properties("${source}" PROPERTIES COMPILE_DEFINITIONS "")
+      endif()
+    endforeach(source)
+  endif()
+
+  list(APPEND ALL_INTERROGATE_MODULES "${module}")
+  set(ALL_INTERROGATE_MODULES "${ALL_INTERROGATE_MODULES}" CACHE INTERNAL "Internal variable")
+endfunction(add_python_module)

+ 506 - 0
cmake/macros/PackageConfig.cmake

@@ -0,0 +1,506 @@
+# Filename: PackageConfig.cmake
+#
+# This module defines functions which find and configure libraries
+# and packages for Panda3D.
+#
+# Assumes an attempt to find the package has already been made with
+# find_package(). (i.e. relies on packagename_FOUND variable)
+#
+# The packages are added as imported/interface libraries in the PKG::
+# namespace.  If the package is not found (or disabled by the user),
+# a dummy package will be created instead.  Therefore, it is safe
+# to link against the PKG::PACKAGENAME target unconditionally.
+#
+# Function: package_option
+# Usage:
+#   package_option(package_name package_doc_string
+#                  [DEFAULT ON | OFF]
+#                  [IMPORTED_AS CMake::Imported::Target [...]]
+#                  [FOUND_AS find_name]
+#                  [LICENSE license])
+#
+# Examples:
+#   package_option(LIBNAME "Enables LIBNAME support." DEFAULT OFF)
+#
+#       If no default is given, the default in normal
+#       builds is to enable all found third-party packages.
+#       In builds for redistribution, there is the additional requirement that
+#       the package be suitably-licensed.
+#
+#       FOUND_AS indicates the name of the CMake find_package() module, which
+#       may differ from Panda3D's internal name for that package.
+#
+#       IMPORTED_AS is used to indicate that the find_package() may have
+#       provided one or more IMPORTED targets, and that if at least one is
+#       found, the IMPORTED target(s) should be used instead of the
+#       variables provided by find_package()
+#
+#
+# Function: package_status
+# Usage:
+#   package_status(package_name "Package description" ["Config summary"])
+#
+# Examples:
+#   package_status(OpenAL "OpenAL Audio Output")
+#   package_status(ROCKET "Rocket" "without Python bindings")
+#
+#
+# Function: show_packages
+# Usage:
+#   show_packages()
+#
+#   This prints the package usage report using the information provided in
+#   calls to package_status above.
+#
+
+set(_ALL_PACKAGE_OPTIONS CACHE INTERNAL "Internal variable")
+
+#
+# package_option
+#
+# In order to make sure no third-party licenses are inadvertently violated,
+# this imposes a few rules regarding license:
+# 1) If there is no license, no special restrictions.
+# 2) If there is a license, but the build is not flagged for redistribution,
+#    no special restrictions.
+# 3) If there is a license, and this is for redistribution, the package is
+#    forcibly defaulted off and must be explicitly enabled, unless the license
+#    matches a list of licenses suitable for redistribution.
+#
+function(package_option name)
+  # Parse the arguments.
+  set(command)
+  set(default)
+  set(found_as "${name}")
+  set(imported_as)
+  set(license "")
+  set(cache_string)
+
+  string(TOUPPER "${name}" name)
+
+  foreach(arg ${ARGN})
+    if(command STREQUAL "DEFAULT")
+      set(default "${arg}")
+      set(command)
+
+    elseif(command STREQUAL "FOUND_AS")
+      set(found_as "${arg}")
+      set(command)
+
+    elseif(command STREQUAL "LICENSE")
+      set(license "${arg}")
+      set(command)
+
+    elseif(arg STREQUAL "DEFAULT")
+      set(command "DEFAULT")
+
+    elseif(arg STREQUAL "FOUND_AS")
+      set(command "FOUND_AS")
+
+    elseif(arg STREQUAL "LICENSE")
+      set(command "LICENSE")
+
+    elseif(arg STREQUAL "IMPORTED_AS")
+      set(command "IMPORTED_AS")
+
+    elseif(command STREQUAL "IMPORTED_AS")
+      list(APPEND imported_as "${arg}")
+
+    else()
+      # Yes, a list, because semicolons can be in there, and
+      # that gets split up into multiple args, so we have to
+      # join it back together here.
+      list(APPEND cache_string "${arg}")
+
+    endif()
+  endforeach()
+
+  if(command AND NOT command STREQUAL "IMPORTED_AS")
+    message(SEND_ERROR "${command} in package_option takes an argument")
+  endif()
+
+  # If the default is not set, we set it.
+  if(NOT DEFINED default)
+    if(IS_DIST_BUILD)
+      # Accept things that don't have a configured license
+      if(license STREQUAL "")
+        set(default "${${found_as}_FOUND}")
+
+      else()
+        list(FIND PANDA_DIST_USE_LICENSES ${license} license_index)
+        # If the license isn't in the accept listed, don't use the package
+        if(${license_index} EQUAL "-1")
+          set(default OFF)
+
+        else()
+          set(default "${${found_as}_FOUND}")
+
+        endif()
+
+      endif()
+
+    else()
+      set(default "${${found_as}_FOUND}")
+
+    endif()
+  endif()
+
+  # If it was set by the user but not found, display an error.
+  string(TOUPPER "${found_as}" FOUND_AS)
+  if(HAVE_${name} AND NOT ${found_as}_FOUND AND NOT ${FOUND_AS}_FOUND)
+    message(SEND_ERROR "NOT FOUND: ${name}.  Disable HAVE_${name} to continue.")
+  endif()
+
+  # Prevent the function from being called twice.
+  #   This would indicate a cmake error.
+  if(";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
+    message(SEND_ERROR "package_option(${name}) was called twice.
+                        This is a bug in the cmake build scripts.")
+
+  else()
+    list(APPEND _ALL_PACKAGE_OPTIONS "${name}")
+    set(_ALL_PACKAGE_OPTIONS "${_ALL_PACKAGE_OPTIONS}" CACHE INTERNAL "Internal variable")
+
+  endif()
+
+  set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
+
+  # Create the INTERFACE library used to depend on this package.
+  add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
+
+  # Explicitly record the package's include directories as system include
+  # directories.  CMake does do this automatically for INTERFACE libraries, but
+  # it does it by discovering all transitive links first, then reading
+  # INTERFACE_INCLUDE_DIRECTORIES for those which are INTERFACE libraries.  So,
+  # this would be broken for the metalib system (pre CMake 3.12) which doesn't
+  # "link" the object libraries.
+  if(CMAKE_VERSION VERSION_LESS "3.12")
+    set_target_properties(PKG::${name} PROPERTIES
+      INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
+      "$<TARGET_PROPERTY:PKG::${name},INTERFACE_INCLUDE_DIRECTORIES>")
+  endif()
+
+  # Create the option, and if it actually is enabled, populate the INTERFACE
+  # library created above
+  option("HAVE_${name}" "${cache_string}" "${default}")
+  if(HAVE_${name})
+    set(use_variables ON)
+
+    # This is gross, but we actually want to hide package include directories
+    # from Interrogate to make sure it relies on parser-inc instead, so we'll
+    # use some generator expressions to do that.
+    set(_is_not_interface_lib
+      "$<NOT:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,INTERFACE_LIBRARY>>")
+    set(_is_not_interrogate
+      "$<NOT:$<BOOL:$<${_is_not_interface_lib}:$<TARGET_PROPERTY:IS_INTERROGATE>>>>")
+
+    foreach(implib ${imported_as})
+      if(TARGET ${implib})
+        # We found one of the implibs, so we don't need to use variables
+        # (below) anymore
+        set(use_variables OFF)
+
+        # Hide it from Interrogate
+        target_link_libraries(PKG::${name} INTERFACE
+          "$<${_is_not_interrogate}:$<TARGET_NAME:${implib}>>")
+      endif()
+    endforeach(implib)
+
+    if(use_variables)
+      if(DEFINED ${found_as}_INCLUDE_DIRS)
+        set(includes ${${found_as}_INCLUDE_DIRS})
+      elseif(DEFINED ${found_as}_INCLUDE_DIR)
+        set(includes "${${found_as}_INCLUDE_DIR}")
+      elseif(DEFINED ${FOUND_AS}_INCLUDE_DIRS)
+        set(includes ${${FOUND_AS}_INCLUDE_DIRS})
+      else()
+        set(includes "${${FOUND_AS}_INCLUDE_DIR}")
+      endif()
+
+      if(DEFINED ${found_as}_LIBRARIES)
+        set(libs ${${found_as}_LIBRARIES})
+      elseif(DEFINED ${found_as}_LIBRARY)
+        set(libs "${${found_as}_LIBRARY}")
+      elseif(DEFINED ${FOUND_AS}_LIBRARIES)
+        set(libs ${${FOUND_AS}_LIBRARIES})
+      else()
+        set(libs "${${FOUND_AS}_LIBRARY}")
+      endif()
+
+      target_link_libraries(PKG::${name} INTERFACE ${libs})
+
+      # Hide it from Interrogate
+      set_target_properties(PKG::${name} PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "$<${_is_not_interrogate}:${includes}>")
+    endif()
+  endif()
+endfunction(package_option)
+
+set(_ALL_CONFIG_PACKAGES CACHE INTERNAL "Internal variable")
+
+#
+# package_status
+#
+function(package_status name desc)
+  set(note "")
+  foreach(arg ${ARGN})
+    set(note "${arg}")
+  endforeach()
+
+  string(TOUPPER "${name}" name)
+
+  if(NOT ";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
+    message(SEND_ERROR "package_status(${name}) was called before package_option(${name}).
+                        This is a bug in the cmake build scripts.")
+    return()
+  endif()
+
+  if(";${_ALL_CONFIG_PACKAGES};" MATCHES ";${name};")
+    message(SEND_ERROR "package_status(${name}) was called twice.
+                        This is a bug in the cmake build scripts.")
+
+  else()
+    list(APPEND _ALL_CONFIG_PACKAGES "${name}")
+    set(_ALL_CONFIG_PACKAGES "${_ALL_CONFIG_PACKAGES}" CACHE INTERNAL "Internal variable")
+
+  endif()
+
+  set(PANDA_PACKAGE_DESC_${name} "${desc}" PARENT_SCOPE)
+  set(PANDA_PACKAGE_NOTE_${name} "${note}" PARENT_SCOPE)
+endfunction()
+
+#
+# show_packages
+#
+function(show_packages)
+  message("")
+  message("Configuring support for the following optional third-party packages:")
+
+  foreach(package ${_ALL_CONFIG_PACKAGES})
+    set(desc "${PANDA_PACKAGE_DESC_${package}}")
+    set(note "${PANDA_PACKAGE_NOTE_${package}}")
+    if(HAVE_${package})
+      if(NOT note STREQUAL "")
+        message("+ ${desc} (${note})")
+      else()
+        message("+ ${desc}")
+      endif()
+
+    else()
+      if(NOT ${package}_FOUND)
+        set(reason "not found")
+      elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
+        set(reason "not requested")
+      else()
+        set(reason "disabled")
+      endif()
+
+      message("- ${desc} (${reason})")
+
+    endif()
+  endforeach()
+endfunction()
+
+#
+# export_packages(filename)
+#
+# Generates an includable CMake file that contains definitions for every PKG::
+# package defined.
+#
+function(export_packages filename)
+  set(exports "# Exports for Panda3D PKG:: packages\n")
+
+  foreach(pkg ${_ALL_PACKAGE_OPTIONS})
+    set(exports "${exports}\n# Create imported target PKG::${pkg}\n")
+    set(exports "${exports}add_library(PKG::${pkg} INTERFACE IMPORTED)\n\n")
+
+    set(exports "${exports}set_target_properties(PKG::${pkg} PROPERTIES\n")
+    foreach(prop
+        INTERFACE_COMPILE_DEFINITIONS
+        INTERFACE_COMPILE_FEATURES
+        INTERFACE_COMPILE_OPTIONS
+        INTERFACE_INCLUDE_DIRECTORIES
+        INTERFACE_LINK_DEPENDS
+        INTERFACE_LINK_DIRECTORIES
+        INTERFACE_LINK_OPTIONS
+        INTERFACE_POSITION_INDEPENDENT_CODE
+       #INTERFACE_SYSTEM_INCLUDE_DIRECTORIES  # Let the consumer dictate this
+        INTERFACE_SOURCES)
+
+      set(prop_ex "$<TARGET_PROPERTY:PKG::${pkg},${prop}>")
+      set(exports "${exports}$<$<BOOL:${prop_ex}>:  ${prop} \"${prop_ex}\"\n>")
+
+    endforeach(prop)
+
+    # Ugh, INTERFACE_LINK_LIBRARIES isn't transitive.  Fine.  Take care of it
+    # by hand:
+    set(libraries)
+    set(stack "PKG::${pkg}")
+    set(history)
+    while(stack)
+      # Remove head item from stack
+      unset(head)
+      while(NOT DEFINED head)
+        if(NOT stack)
+          break()
+        endif()
+
+        list(GET stack 0 head)
+        list(REMOVE_AT stack 0)
+
+        # Don't visit anything twice
+        list(FIND history "${head}" _index)
+        if(_index GREATER -1)
+          unset(head)
+        endif()
+      endwhile()
+
+      if(head)
+        list(APPEND history "${head}")
+      else()
+        break()
+      endif()
+
+      # If head isn't a target, add it to `libraries`, else recurse
+      if(TARGET "${head}")
+        get_target_property(link_libs "${head}" INTERFACE_LINK_LIBRARIES)
+        if(link_libs)
+          list(APPEND stack ${link_libs})
+        endif()
+
+        get_target_property(type "${head}" TYPE)
+        if(NOT type STREQUAL "INTERFACE_LIBRARY")
+          get_target_property(imported_location "${head}" IMPORTED_LOCATION)
+          get_target_property(imported_implib "${head}" IMPORTED_IMPLIB)
+          if(imported_implib)
+            list(APPEND libraries ${imported_implib})
+          elseif(imported_location)
+            list(APPEND libraries ${imported_location})
+          endif()
+
+          get_target_property(configs "${head}" IMPORTED_CONFIGURATIONS)
+          if(configs AND NOT imported_location)
+            foreach(config ${configs})
+              get_target_property(imported_location "${head}" IMPORTED_LOCATION_${config})
+
+              # Prefer IMPORTED_IMPLIB where present
+              get_target_property(imported_implib "${head}" IMPORTED_IMPLIB_${config})
+              if(imported_implib)
+                set(imported_location "${imported_implib}")
+              endif()
+
+              if(imported_location)
+                if(configs MATCHES ".*;.*")
+                  set(_bling "$<1:$>") # genex-escaped $
+                  list(APPEND libraries "${_bling}<${_bling}<CONFIG:${config}>:${imported_location}>")
+
+                else()
+                  list(APPEND libraries ${imported_location})
+
+                endif()
+              endif()
+            endforeach(config)
+          endif()
+
+        elseif(CMAKE_VERSION VERSION_GREATER "3.8")
+          # This is an INTERFACE_LIBRARY, and CMake is new enough to support
+          # IMPORTED_IMPLIB
+          get_target_property(imported_libname "${head}" IMPORTED_LIBNAME)
+          if(imported_libname)
+            list(APPEND libraries ${imported_libname})
+          endif()
+
+        endif()
+
+      elseif("${head}" MATCHES "\\$<TARGET_NAME:\([^>]+\)>")
+        string(REGEX REPLACE ".*\\$<TARGET_NAME:\([^>]+\)>.*" "\\1" match "${head}")
+        list(APPEND stack "${match}")
+
+      else()
+        list(APPEND libraries "${head}")
+
+      endif()
+    endwhile(stack)
+
+    set(exports "${exports}  INTERFACE_LINK_LIBRARIES \"${libraries}\"\n")
+
+    set(exports "${exports})\n")
+  endforeach(pkg)
+
+  # file(GENERATE) does not like $<LINK_ONLY:...> (and it's meant to be
+  # consumed by our importer) so we escape it
+  set(_bling "$<1:$>") # genex-escaped $
+  string(REPLACE "$<LINK_ONLY:" "${_bling}<LINK_ONLY:" exports "${exports}")
+
+  file(GENERATE OUTPUT "${filename}" CONTENT "${exports}")
+endfunction(export_packages)
+
+#
+# export_targets(set [NAMESPACE namespace] [COMPONENT component])
+#
+# Export targets in the export set named by "set"
+#
+# NAMESPACE overrides the namespace prefixed to the exported targets; it
+# defaults to "Panda3D::[set]::" if no explicit override is given.
+#
+# COMPONENT overrides the install component for the generated .cmake file; it
+# defaults to "[set]" if no explicit override is given.
+#
+function(export_targets set)
+  set(namespace "Panda3D::${set}::")
+  set(component "${set}")
+  set(keyword)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "NAMESPACE" OR
+       arg STREQUAL "COMPONENT")
+
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "NAMESPACE")
+      set(namespace "${arg}")
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+
+    else()
+      message(FATAL_ERROR "export_targets() given unexpected arg: ${arg}")
+
+    endif()
+  endforeach(arg)
+
+  export(EXPORT "${set}" NAMESPACE "${namespace}"
+    FILE "${PROJECT_BINARY_DIR}/Panda3D${set}Targets.cmake")
+  install(EXPORT "${set}" NAMESPACE "${namespace}"
+    FILE "Panda3D${set}Targets.cmake"
+    COMPONENT "${component}" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D)
+
+endfunction(export_targets)
+
+#
+# find_package
+#
+# This override implements CMAKE_FIND_PACKAGE_PREFER_CONFIG on versions of
+# CMake too old to include it.
+#
+if(CMAKE_VERSION VERSION_LESS "3.15")
+  macro(find_package name)
+    if(";${ARGN};" MATCHES ";(CONFIG|MODULE|NO_MODULE);")
+      # Caller explicitly asking for a certain mode; so be it.
+      _find_package(${ARGV})
+
+    elseif(CMAKE_FIND_PACKAGE_PREFER_CONFIG)
+      # Try CONFIG
+      _find_package("${name}" CONFIG ${ARGN})
+
+      if(NOT ${name}_FOUND)
+        # CONFIG didn't work, fall back to MODULE
+        _find_package("${name}" MODULE ${ARGN})
+      endif()
+
+    else()
+      # Default behavior
+      _find_package(${ARGV})
+
+    endif()
+  endmacro(find_package)
+endif()

+ 163 - 0
cmake/macros/Python.cmake

@@ -0,0 +1,163 @@
+# Filename: Python.cmake
+#
+# Description: This file provides support functions for building/installing
+#   Python extension modules and/or pure-Python packages.
+#
+# Functions:
+#   add_python_target(target [source1 [source2 ...]])
+#   install_python_package(path [ARCH/LIB])
+#
+
+#
+# Function: add_python_target(target [EXPORT exp] [COMPONENT comp]
+#                                    [source1 [source2 ...]])
+# Build the provided source(s) as a Python extension module, linked against the
+# Python runtime library.
+#
+# Note that this also takes care of installation, unlike other target creation
+# commands in CMake.  The EXPORT and COMPONENT keywords allow passing the
+# corresponding options to install(), but default to "Python" otherwise.
+#
+function(add_python_target target)
+  if(NOT HAVE_PYTHON)
+    return()
+  endif()
+
+  string(REGEX REPLACE "^.*\\." "" basename "${target}")
+  set(sources)
+  set(component "Python")
+  set(export "Python")
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "COMPONENT")
+      set(keyword "component")
+
+    elseif(arg STREQUAL "EXPORT")
+      set(keyword "export")
+
+    elseif(keyword)
+      set(${keyword} "${arg}")
+      unset(keyword)
+
+    else()
+      list(APPEND sources "${arg}")
+
+    endif()
+  endforeach(arg)
+
+  string(REGEX REPLACE "\\.[^.]+$" "" namespace "${target}")
+  string(REPLACE "." "/" slash_namespace "${namespace}")
+
+  add_library(${target} ${MODULE_TYPE} ${sources})
+  target_link_libraries(${target} PKG::PYTHON)
+
+  if(BUILD_SHARED_LIBS)
+    set(_outdir "${PANDA_OUTPUT_DIR}/${slash_namespace}")
+
+    set_target_properties(${target} PROPERTIES
+      LIBRARY_OUTPUT_DIRECTORY "${_outdir}"
+      OUTPUT_NAME "${basename}"
+      PREFIX ""
+      SUFFIX "${PYTHON_EXTENSION_SUFFIX}")
+
+    # This is explained over in CompilerFlags.cmake
+    foreach(_config ${CMAKE_CONFIGURATION_TYPES})
+      string(TOUPPER "${_config}" _config)
+      set_target_properties(${target} PROPERTIES
+        LIBRARY_OUTPUT_DIRECTORY_${_config} "${_outdir}")
+    endforeach(_config)
+
+    if(PYTHON_ARCH_INSTALL_DIR)
+      install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION "${PYTHON_ARCH_INSTALL_DIR}/${slash_namespace}")
+    endif()
+
+  else()
+    set_target_properties(${target} PROPERTIES
+      OUTPUT_NAME "${basename}"
+      PREFIX "libpy.${namespace}.")
+
+    install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+  endif()
+
+endfunction(add_python_target)
+
+#
+# Function: install_python_package(name [SOURCE path] [ARCH/LIB] [COMPONENT component])
+#
+# Installs the Python package `name` (which may have its source at `path`).
+#
+# The package is copied to (or created in) the build directory so that the user
+# may import it before the install step.
+#
+# Note that this handles more than just installation; it will also invoke
+# Python's compileall utility to pregenerate .pyc/.pyo files.  This will only
+# happen if the Python interpreter is found.
+#
+# The ARCH or LIB keyword may be used to specify whether this package should be
+# installed into Python's architecture-dependent or architecture-independent
+# package path.  The default, if unspecified, is LIB.
+#
+# The COMPONENT keyword overrides the install component (see CMake's
+# documentation for more information on what this does).  The default is
+# "Python".
+#
+function(install_python_package package_name)
+  set(type "LIB")
+  unset(keyword)
+  set(component "Python")
+  unset(src_path)
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "ARCH")
+      set(type "ARCH")
+
+    elseif(arg STREQUAL "LIB")
+      set(type "LIB")
+
+    elseif(arg STREQUAL "COMPONENT")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "COMPONENT")
+      set(component "${arg}")
+      unset(keyword)
+
+    elseif(arg STREQUAL "SOURCE")
+      set(keyword "${arg}")
+
+    elseif(keyword STREQUAL "SOURCE")
+      set(src_path "${arg}")
+      unset(keyword)
+
+    else()
+      message(FATAL_ERROR "install_python_package got unexpected argument: ${ARGN}")
+
+    endif()
+  endforeach(arg)
+
+  if(NOT DEFINED src_path AND type STREQUAL "ARCH" AND WIN32 AND NOT CYGWIN)
+    # Win32 needs a special fixup so the DLLs in "bin" can be on the path;
+    # let's set src_path to the directory containing our fixup __init__.py
+    set(src_path "${CMAKE_SOURCE_DIR}/cmake/templates/win32_python")
+  endif()
+
+  set(path "${PANDA_OUTPUT_DIR}/${package_name}")
+
+  set(args -D "OUTPUT_DIR=${path}")
+  if(src_path)
+    list(APPEND args -D "SOURCE_DIR=${src_path}")
+  endif()
+  if(PYTHON_EXECUTABLE)
+    list(APPEND args -D "PYTHON_EXECUTABLES=${PYTHON_EXECUTABLE}")
+  endif()
+  add_custom_target(${package_name} ALL
+    COMMAND ${CMAKE_COMMAND}
+      ${args}
+      -P "${CMAKE_SOURCE_DIR}/cmake/scripts/CopyPython.cmake")
+
+  set(dir "${PYTHON_${type}_INSTALL_DIR}")
+  if(dir)
+    install(DIRECTORY "${path}" DESTINATION "${dir}"
+      COMPONENT "${component}"
+      FILES_MATCHING REGEX "\\.py[co]?$")
+  endif()
+
+endfunction(install_python_package)

+ 9 - 0
cmake/macros/README.md

@@ -0,0 +1,9 @@
+Directory Info
+--------------
+**Directory:** /cmake/macros  
+**License:** Unlicense  
+**Description:** This directory is used for CMake modules which may be _unsafe_
+to use outside of a Panda3D project.  These modules may rely on Panda3D specific
+cmake variables, Panda3D's directory structure, or some other dependency.
+They are not intended to be included in other projects directly, though you
+are free to copy and adapt them to your own needs.

+ 37 - 0
cmake/macros/RunPzip.cmake

@@ -0,0 +1,37 @@
+function(run_pzip target_name source destination glob)
+  file(GLOB_RECURSE files RELATIVE "${source}" "${source}/${glob}")
+
+  set(dstfiles "")
+  foreach(filename ${files})
+    string(REGEX REPLACE "^/" "" filename "${filename}")
+
+    get_filename_component(dstdir "${destination}/${filename}" DIRECTORY)
+
+    if(TARGET host_pzip)
+      set(dstfile "${filename}.pz")
+      list(APPEND dstfiles "${destination}/${dstfile}")
+
+      add_custom_command(OUTPUT "${destination}/${dstfile}"
+        COMMAND ${CMAKE_COMMAND} -E make_directory "${dstdir}"
+        COMMAND host_pzip -o "${destination}/${dstfile}" "${source}/${filename}"
+        DEPENDS host_pzip
+        COMMENT "")
+
+    else()
+      # If pzip isn't built, we just copy instead.
+      list(APPEND dstfiles "${destination}/${filename}")
+
+      add_custom_command(OUTPUT "${destination}/${filename}"
+        COMMAND ${CMAKE_COMMAND} -E
+          copy_if_different "${source}/${filename}" "${destination}/${filename}"
+        COMMENT "")
+
+    endif()
+
+  endforeach(filename)
+
+  add_custom_target(${target_name} ALL
+    DEPENDS ${dstfiles}
+    WORKING_DIRECTORY "${destination}")
+
+endfunction(run_pzip)

+ 21 - 0
cmake/macros/Versioning.cmake

@@ -0,0 +1,21 @@
+# Filename: Versioning.cmake
+#
+# Description: Contains an override for add_library to set the
+#   VERSION and SOVERSION properties on all shared libraries, automatically, to
+#   the project version.
+#
+# Functions:
+#   add_library(...)
+#
+
+function(add_library target_name)
+  _add_library("${target_name}" ${ARGN})
+
+  get_target_property(type "${target_name}" TYPE)
+  if(type STREQUAL "SHARED_LIBRARY")
+    set_target_properties("${target_name}" PROPERTIES
+      VERSION "${PROJECT_VERSION}"
+      SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
+  endif()
+
+endfunction(add_library)

+ 33 - 0
cmake/modules/FindARToolKit.cmake

@@ -0,0 +1,33 @@
+# Filename: FindARToolKit.cmake
+# Authors: CFSworks (3 Nov, 2018)
+#
+# Usage:
+#   find_package(ARToolKit [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ARTOOLKIT_FOUND       - system has ARToolKit
+#   ARTOOLKIT_INCLUDE_DIR - the include directory containing ARToolKit header files
+#   ARTOOLKIT_LIBRARIES   - the paths to the ARToolKit client libraries
+#
+
+find_path(ARTOOLKIT_INCLUDE_DIR "AR/ar.h")
+
+find_library(ARTOOLKIT_AR_LIBRARY
+  NAMES "AR" "libAR")
+
+find_library(ARTOOLKIT_ARMulti_LIBRARY
+  NAMES "ARMulti" "libARMulti")
+
+mark_as_advanced(ARTOOLKIT_INCLUDE_DIR ARTOOLKIT_AR_LIBRARY ARTOOLKIT_ARMulti_LIBRARY)
+
+set(ARTOOLKIT_LIBRARIES)
+if(ARTOOLKIT_AR_LIBRARY)
+  list(APPEND ARTOOLKIT_LIBRARIES "${ARTOOLKIT_AR_LIBRARY}")
+endif()
+if(ARTOOLKIT_ARMulti_LIBRARY)
+  list(APPEND ARTOOLKIT_LIBRARIES "${ARTOOLKIT_ARMulti_LIBRARY}")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(ARToolKit DEFAULT_MSG
+  ARTOOLKIT_INCLUDE_DIR ARTOOLKIT_LIBRARIES)

+ 33 - 0
cmake/modules/FindAssimp.cmake

@@ -0,0 +1,33 @@
+# Filename: FindAssimp.cmake
+# Authors: CFSworks (9 Nov, 2018)
+#
+# Usage:
+#   find_package(Assimp [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ASSIMP_FOUND        - system has Assimp
+#   ASSIMP_INCLUDE_DIR  - the path to the location of the assimp/ directory
+#   ASSIMP_LIBRARIES    - the libraries to link against for Assimp
+#
+
+find_path(ASSIMP_INCLUDE_DIR
+  NAMES "assimp/Importer.hpp")
+
+find_library(ASSIMP_ASSIMP_LIBRARY
+  NAMES "assimp")
+
+find_library(ASSIMP_IRRXML_LIBRARY
+  NAMES "IrrXML")
+
+if(ASSIMP_ASSIMP_LIBRARY)
+  set(ASSIMP_LIBRARIES "${ASSIMP_ASSIMP_LIBRARY}")
+
+  if(ASSIMP_IRRXML_LIBRARY)
+    list(APPEND ASSIMP_LIBRARIES "${ASSIMP_IRRXML_LIBRARY}")
+  endif()
+endif()
+
+mark_as_advanced(ASSIMP_INCLUDE_DIR ASSIMP_LIBRARIES)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Assimp DEFAULT_MSG ASSIMP_INCLUDE_DIR ASSIMP_LIBRARIES)

+ 175 - 0
cmake/modules/FindCg.cmake

@@ -0,0 +1,175 @@
+# Filename: FindCg.cmake
+# Author: kestred (8 Dec, 2013)
+#
+# Usage:
+#   find_package(Cg [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   CG_FOUND         - system has NvidiaCg
+#   CG_INCLUDE_DIR   - the NvidiaCg include directory
+#   CG_INCLUDE_DIRS  - directories for all NvidiaCg components
+#   CG_LIBRARY_DIR   - the NvidiaCg library directory
+#   CG_LIBRARY       - the path to the library binary
+#   CG_LIBRARIES     - the paths to the Cg library and each library below.
+#
+#   CGGL_FOUND       - system has CgGL
+#   CGGL_INCLUDE_DIR - the CgGL include directory
+#   CGGL_LIBRARY_DIR - the CgGL library directory
+#   CGGL_LIBRARY     - the path to the library binary
+#
+
+
+### Define macros to find each sublibrary ###
+
+# Find Cg for OpenGL
+macro(find_cggl)
+  if(APPLE)
+    # GL support is built-in on Apple
+    set(CGGL_LIBRARY "${CG_LIBRARY}")
+    set(CGGL_LIBRARY_DIR "${CG_LIBRARY_DIR}")
+    set(CGGL_INCLUDE_DIR "${CG_INCLUDE_DIR}")
+  endif()
+
+  if(Cg_FIND_QUIETLY)
+    set(CgGL_FIND_QUIETLY TRUE)
+  endif()
+  if(NOT CGGL_LIBRARY_DIR OR NOT CGGL_INCLUDE_DIR)
+    # Find the include directory
+    find_path(CGGL_INCLUDE_DIR
+      NAMES "cgGL.h"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg/include"
+            "/usr/include"
+            "/usr/local/include"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit/include" # Gentoo
+      PATH_SUFFIXES "" "Cg" "cg"
+      DOC "The path to NvidiaCgGL's include directory."
+    )
+
+    # Find the library directory
+    find_library(CGGL_LIBRARY
+      NAMES "CgGL" "libCgGL"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg"
+            "/usr"
+            "/usr/lib/x86_64-linux-gnu"
+            "/usr/local"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit" # Gentoo
+      PATH_SUFFIXES "" "lib" "lib32" "lib64"
+      DOC "The filepath to NvidiaCgGL's libary binary."
+    )
+    get_filename_component(CGGL_LIBRARY_DIR "${CGGL_LIBRARY}" PATH)
+    set(CGGL_LIBRARY_DIR "${CGGL_LIBRARY_DIR}" CACHE PATH "The path to the CgGL library directory.") # Library path
+
+    mark_as_advanced(CGGL_INCLUDE_DIR)
+    mark_as_advanced(CGGL_LIBRARY_DIR)
+    mark_as_advanced(CGGL_LIBRARY)
+  endif()
+
+  find_package_handle_standard_args(CgGL DEFAULT_MSG CGGL_LIBRARY CGGL_INCLUDE_DIR CGGL_LIBRARY_DIR)
+
+endmacro()
+
+
+# Find Cg for Direct3D 9
+macro(find_cgd3d9)
+  if(Cg_FIND_QUIETLY)
+    set(CgD3D9_FIND_QUIETLY TRUE)
+  endif()
+  if(NOT CGD3D9_LIBRARY_DIR OR NOT CGD3D9_INCLUDE_DIR)
+    # Find the include directory
+    find_path(CGD3D9_INCLUDE_DIR
+      NAMES "cgD3D9.h"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg/include"
+            "/usr/include"
+            "/usr/local/include"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit/include" # Gentoo
+      PATH_SUFFIXES "" "Cg" "cg"
+      DOC "The path to NvidiaCgD3D9's include directory."
+    )
+
+    # Find the library directory
+    find_library(CGD3D9_LIBRARY
+      NAMES "CgD3D9" "libCgD3D9"
+      PATHS "C:/Program Files/Cg"
+            "C:/Program Files/NVIDIA Corporation/Cg"
+            "/usr"
+            "/usr/lib/x86_64-linux-gnu"
+            "/usr/local"
+            "/opt/Cg"
+            "/opt/nvidia-cg-toolkit" # Gentoo
+      PATH_SUFFIXES "" "lib" "lib32" "lib64"
+      DOC "The filepath to NvidiaCgD3D9's libary binary."
+    )
+    get_filename_component(CGD3D9_LIBRARY_DIR "${CGD3D9_LIBRARY}" PATH)
+    set(CGD3D9_LIBRARY_DIR "${CGD3D9_LIBRARY_DIR}" CACHE PATH "The path to the CgD3D9 library directory.") # Library path
+
+    mark_as_advanced(CGD3D9_INCLUDE_DIR)
+    mark_as_advanced(CGD3D9_LIBRARY_DIR)
+    mark_as_advanced(CGD3D9_LIBRARY)
+  endif()
+
+  find_package_handle_standard_args(CgD3D9 DEFAULT_MSG CGD3D9_LIBRARY CGD3D9_INCLUDE_DIR CGD3D9_LIBRARY_DIR)
+
+endmacro()
+
+
+
+# Find base Nvidia Cg
+if(NOT CG_LIBRARY_DIR OR NOT CG_INCLUDE_DIRS)
+  # Find the include directory
+  find_path(CG_INCLUDE_DIR
+    NAMES "Cg/cg.h"
+    PATHS "C:/Program Files/Cg"
+          "C:/Program Files/NVIDIA Corporation/Cg/include"
+          "/usr/include"
+          "/usr/local/include"
+          "/opt/Cg"
+          "/opt/nvidia-cg-toolkit/include" # Gentoo
+    PATH_SUFFIXES "" "Cg" "cg"
+    DOC "The path to NvidiaCg's include directory."
+  )
+
+  # Find the library directory
+  find_library(CG_LIBRARY
+    NAMES "Cg" "libCg"
+    PATHS "C:/Program Files/Cg"
+          "C:/Program Files/NVIDIA Corporation/Cg"
+          "/usr"
+          "/usr/lib/x86_64-linux-gnu"
+          "/usr/local"
+          "/opt/Cg"
+          "/opt/nvidia-cg-toolkit" # Gentoo
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+  get_filename_component(CG_LIBRARY_DIR "${CG_LIBRARY}" PATH)
+  set(CG_LIBRARY_DIR "${CG_LIBRARY_DIR}" CACHE PATH "The path to NvidiaCG's library directory.") # Library path
+
+  string(REGEX REPLACE "/Cg$" "" CG_BASE_INCLUDE_DIR "${CG_INCLUDE_DIR}")
+  set(CG_INCLUDE_DIRS ${CG_BASE_INCLUDE_DIR} ${CG_INCLUDE_DIR})
+
+  mark_as_advanced(CG_INCLUDE_DIRS)
+  mark_as_advanced(CG_INCLUDE_DIR)
+  mark_as_advanced(CG_LIBRARY_DIR)
+  mark_as_advanced(CG_LIBRARY)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Cg DEFAULT_MSG CG_LIBRARY CG_INCLUDE_DIRS CG_LIBRARY_DIR)
+
+if(CG_INCLUDE_DIR AND CG_LIBRARY_DIR)
+  find_cggl()
+  find_cgd3d9()
+
+  set(CG_LIBRARIES ${CG_LIBRARY})
+  if(CGGL_LIBRARY)
+    list(APPEND CG_LIBRARIES "${CGGL_LIBRARY}")
+  endif()
+  if(CGD3D9_LIBRARY)
+    list(APPEND CG_LIBRARIES "${CGD3D9_LIBRARY}")
+  endif()
+endif()

+ 104 - 0
cmake/modules/FindDirect3D9.cmake

@@ -0,0 +1,104 @@
+# Filename: FindDirect3D9.cmake
+# Authors: CFSworks (26 Oct, 2018)
+#
+# Usage:
+#   find_package(Direct3D9 [REQUIRED] [QUIET])
+#
+# This supports the following components:
+#   d3dx9
+#   dxerr
+#   dxguid
+#
+# Once done this will define:
+#   DIRECT3D9_FOUND       - system has Direct3D 9.x
+#   DIRECT3D9_INCLUDE_DIR - the include directory containing d3d9.h - note that
+#                           this will be empty if it's part of the Windows SDK.
+#   DIRECT3D9_LIBRARY     - the path to d3d9.lib
+#   DIRECT3D9_LIBRARIES   - the path to d3d9.lib and all extra component
+#                           libraries
+#
+
+include(CheckIncludeFile)
+
+if(Direct3D9_FIND_QUIETLY)
+  if(DEFINED CMAKE_REQUIRED_QUIET)
+    set(_OLD_CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET})
+  endif()
+  # Suppress check_include_file messages
+  set(CMAKE_REQUIRED_QUIET ON)
+endif()
+
+check_include_file("d3d9.h" SYSTEM_INCLUDE_D3D9_H)
+mark_as_advanced(SYSTEM_INCLUDE_D3D9_H)
+
+if(Direct3D9_FIND_QUIETLY)
+  if(DEFINED _OLD_CMAKE_REQUIRED_QUIET)
+    set(CMAKE_REQUIRED_QUIET ${_OLD_CMAKE_REQUIRED_QUIET})
+    unset(_OLD_CMAKE_REQUIRED_QUIET)
+  else()
+    unset(CMAKE_REQUIRED_QUIET)
+  endif()
+endif()
+
+if(SYSTEM_INCLUDE_D3D9_H
+    AND NOT Direct3D9_FIND_REQUIRED_d3dx9
+    AND NOT Direct3D9_FIND_REQUIRED_dxerr)
+  # It's available as #include <d3d9.h> - easy enough.  We'll use "." as a way
+  # of saying "We found it, but please erase this variable later."
+  set(DIRECT3D9_INCLUDE_DIR ".")
+
+  # Since d3d9.h is on the search path, we can pretty much assume d3d9.lib is
+  # as well.
+  set(DIRECT3D9_LIBRARY "d3d9.lib")
+
+  # And dxguid.lib, why not
+  set(DIRECT3D9_dxguid_LIBRARY "dxguid.lib")
+
+else()
+  # We could not find it easily - maybe it's installed separately as part of
+  # the DirectX SDK?
+
+  find_path(DIRECT3D9_INCLUDE_DIR
+    NAMES d3d9.h
+    PATHS "$ENV{DXSDK_DIR}/Include")
+
+  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+    set(dx_lib_path "$ENV{DXSDK_DIR}/Lib/x64/")
+  else()
+    set(dx_lib_path "$ENV{DXSDK_DIR}/Lib/x86/")
+  endif()
+
+  find_library(DIRECT3D9_LIBRARY d3d9 "${dx_lib_path}" NO_DEFAULT_PATH)
+
+  find_library(DIRECT3D9_d3dx9_LIBRARY d3dx9 "${dx_lib_path}" NO_DEFAULT_PATH)
+  find_library(DIRECT3D9_dxerr_LIBRARY dxerr "${dx_lib_path}" NO_DEFAULT_PATH)
+  find_library(DIRECT3D9_dxguid_LIBRARY dxguid "${dx_lib_path}" NO_DEFAULT_PATH)
+
+  unset(dx_lib_path)
+endif()
+
+mark_as_advanced(DIRECT3D9_INCLUDE_DIR DIRECT3D9_LIBRARY)
+set(DIRECT3D9_LIBRARIES "${DIRECT3D9_LIBRARY}")
+
+foreach(_component d3dx9 dxerr dxguid)
+  if(DIRECT3D9_${_component}_LIBRARY)
+    set(Direct3D9_${_component}_FOUND ON)
+    list(FIND Direct3D9_FIND_COMPONENTS "${_component}" _index)
+    if(${_index} GREATER -1)
+      list(APPEND DIRECT3D9_LIBRARIES "${DIRECT3D9_${_component}_LIBRARY}")
+    endif()
+    unset(_index)
+  endif()
+endforeach(_component)
+unset(_component)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Direct3D9 HANDLE_COMPONENTS
+  REQUIRED_VARS DIRECT3D9_INCLUDE_DIR DIRECT3D9_LIBRARY DIRECT3D9_LIBRARIES)
+
+# See above - if we found the include as part of the system path, we don't want
+# to actually modify the include search path, but we need a non-empty string to
+# satisfy find_package_handle_standard_args()
+if(DIRECT3D9_INCLUDE_DIR STREQUAL ".")
+  set(DIRECT3D9_INCLUDE_DIR "")
+endif()

+ 21 - 0
cmake/modules/FindEGL.cmake

@@ -0,0 +1,21 @@
+# Filename: FindEGL.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(EGL [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   EGL_FOUND        - system has EGL
+#   EGL_INCLUDE_DIR  - the include directory containing EGL/egl.h
+#   EGL_LIBRARY      - the library to link against for EGL
+#
+
+find_path(EGL_INCLUDE_DIR "EGL/egl.h")
+
+find_library(EGL_LIBRARY
+  NAMES "EGL")
+
+mark_as_advanced(EGL_INCLUDE_DIR EGL_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(EGL DEFAULT_MSG EGL_INCLUDE_DIR EGL_LIBRARY)

+ 25 - 0
cmake/modules/FindEigen3.cmake

@@ -0,0 +1,25 @@
+# Filename: FindEigen3.cmake
+# Authors: kestred (13 Dec, 2013)
+#
+# Usage:
+#   find_package(Eigen3 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   EIGEN_FOUND        - system has any version of Eigen
+#   EIGEN3_FOUND       - system has Eigen3
+#   EIGEN3_INCLUDE_DIR - the Eigen3 include directory
+#
+
+find_path(EIGEN3_INCLUDE_DIR
+  NAMES signature_of_eigen3_matrix_library
+  PATH_SUFFIXES eigen3 eigen)
+
+mark_as_advanced(EIGEN3_INCLUDE_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR)
+
+if(EIGEN3_FOUND)
+  set(EIGEN_FOUND TRUE)
+  set(EIGEN_INCLUDE_DIR ${EIGEN3_INCLUDE_DIR})
+endif()

+ 88 - 0
cmake/modules/FindFCollada.cmake

@@ -0,0 +1,88 @@
+# Filename: FindFCollada.cmake
+# Author: CFSworks (17 Mar, 2019)
+#
+# Usage:
+#   find_package(FCollada [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FCOLLADA_FOUND       - system has FCollada
+#   FCOLLADA_INCLUDE_DIR - the FCollada include directory
+#
+#   FCOLLADA_RELEASE_LIBRARY - the filepath of the FCollada release library
+#   FCOLLADA_DEBUG_LIBRARY   - the filepath of the FCollada debug library
+#
+#   FCollada::FCollada - The recommended FCollada library to link against
+#
+
+# Find the FCollada include files
+find_path(FCOLLADA_INCLUDE_DIR "FCollada.h" PATH_SUFFIXES "FCollada")
+
+# Find the library built for release
+find_library(FCOLLADA_RELEASE_LIBRARY
+  NAMES "FCollada" "libFCollada"
+  "FColladaS" "libFColladaS"
+  "FColladaU" "libFColladaU"
+  "FColladaSU" "libFColladaSU"
+)
+
+# Find the library built for debug
+find_library(FCOLLADA_DEBUG_LIBRARY
+  NAMES "FColladaD" "libFColladaD"
+  "FColladaSD" "libFColladaSD"
+  "FColladaUD" "libFColladaUD"
+  "FColladaSUD" "libFColladaSUD"
+)
+
+mark_as_advanced(FCOLLADA_INCLUDE_DIR)
+mark_as_advanced(FCOLLADA_RELEASE_LIBRARY)
+mark_as_advanced(FCOLLADA_DEBUG_LIBRARY)
+
+set(_defines)
+if(FCOLLADA_RELEASE_LIBRARY MATCHES "FCollada[^/]*U" OR
+    FCOLLADA_DEBUG_LIBRARY MATCHES "FCollada[^/]*U")
+  list(APPEND _defines "UNICODE")
+endif()
+if(NOT MSVC AND
+    NOT FCOLLADA_RELEASE_LIBRARY MATCHES "FCollada[^/]*S" AND
+    NOT FCOLLADA_DEBUG_LIBRARY MATCHES "FCollada[^/]*S")
+  list(APPEND _defines "FCOLLADA_DLL")
+endif()
+
+# Identify the configs which we have available
+set(_configs)
+if(FCOLLADA_INCLUDE_DIR)
+  if(FCOLLADA_RELEASE_LIBRARY)
+    list(APPEND _configs RELEASE)
+  endif()
+  if(FCOLLADA_DEBUG_LIBRARY)
+    list(APPEND _configs DEBUG)
+  endif()
+
+  if(_configs)
+    set(_HAS_FCOLLADA_LIBRARY ON)
+    add_library(FCollada::FCollada UNKNOWN IMPORTED GLOBAL)
+
+    set_target_properties(FCollada::FCollada PROPERTIES
+      INTERFACE_COMPILE_DEFINITIONS "${_defines}"
+      INTERFACE_INCLUDE_DIRECTORIES "${FCOLLADA_INCLUDE_DIR}")
+
+  endif()
+
+endif()
+
+foreach(_config ${_configs})
+  set_property(TARGET FCollada::FCollada
+    APPEND PROPERTY IMPORTED_CONFIGURATIONS "${_config}")
+
+  set_target_properties(FCollada::FCollada PROPERTIES
+    IMPORTED_LOCATION_${_config} "${FCOLLADA_${_config}_LIBRARY}")
+
+endforeach(_config)
+unset(_config)
+unset(_configs)
+unset(_defines)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FCollada DEFAULT_MSG FCOLLADA_INCLUDE_DIR _HAS_FCOLLADA_LIBRARY)
+
+unset(_HAS_FCOLLADA_LIBRARY)

+ 116 - 0
cmake/modules/FindFFMPEG.cmake

@@ -0,0 +1,116 @@
+# Filename: FindFFMPEG.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(FFMPEG [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FFMPEG_FOUND       - system has ffmpeg
+#   FFMPEG_INCLUDE_DIR - the ffmpeg include directory
+#   FFMPEG_LIBRARIES   - the path to the library binary
+#
+#   FFMPEG_LIBAVCODEC  - the path to the libavcodec library binary
+#   FFMPEG_LIBAVFORMAT - the path to the libavformat library binary
+#   FFMPEG_LIBAVUTIL   - the path to the libavutil library binary
+#
+
+# Find the libffmpeg include files
+find_path(FFMPEG_INCLUDE_DIR
+  NAMES "libavcodec/avcodec.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libavcodec library
+find_library(FFMPEG_LIBAVCODEC
+  NAMES "avcodec"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libavformat library
+find_library(FFMPEG_LIBAVFORMAT
+  NAMES "avformat"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libavutil library
+find_library(FFMPEG_LIBAVUTIL
+  NAMES "avutil"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(FFMPEG_INCLUDE_DIR)
+mark_as_advanced(FFMPEG_LIBAVCODEC)
+mark_as_advanced(FFMPEG_LIBAVFORMAT)
+mark_as_advanced(FFMPEG_LIBAVUTIL)
+
+# Translate library into library directory
+if(FFMPEG_LIBAVCODEC)
+  unset(FFMPEG_LIBRARY_DIR CACHE)
+  get_filename_component(FFMPEG_LIBRARY_DIR "${FFMPEG_LIBAVCODEC}" PATH)
+  set(FFMPEG_LIBRARY_DIR "${FFMPEG_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+set(FFMPEG_LIBRARIES)
+if(FFMPEG_LIBAVCODEC)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVCODEC}")
+endif()
+if(FFMPEG_LIBAVFORMAT)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVFORMAT}")
+endif()
+if(FFMPEG_LIBAVUTIL)
+  list(APPEND FFMPEG_LIBRARIES "${FFMPEG_LIBAVUTIL}")
+endif()
+
+if(APPLE)
+  # When statically built for Apple, FFMPEG may have dependencies on these
+  # additional frameworks and libraries.
+
+  find_library(APPLE_COREVIDEO_LIBRARY CoreVideo)
+  if(APPLE_COREVIDEO_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_COREVIDEO_LIBRARY}")
+  endif()
+
+  find_library(APPLE_VDA_LIBRARY VideoDecodeAcceleration)
+  if(APPLE_VDA_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_VDA_LIBRARY}")
+  endif()
+
+  find_library(APPLE_ICONV_LIBRARY iconv)
+  if(APPLE_ICONV_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_ICONV_LIBRARY}")
+  endif()
+
+  find_library(APPLE_BZ2_LIBRARY bz2)
+  if(APPLE_BZ2_LIBRARY)
+    list(APPEND FFMPEG_LIBRARIES "${APPLE_BZ2_LIBRARY}")
+  endif()
+endif()
+
+mark_as_advanced(FFMPEG_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFMPEG DEFAULT_MSG FFMPEG_LIBRARIES FFMPEG_LIBAVCODEC FFMPEG_LIBAVFORMAT FFMPEG_LIBAVUTIL FFMPEG_INCLUDE_DIR FFMPEG_LIBRARY_DIR)

+ 107 - 0
cmake/modules/FindFFTW3.cmake

@@ -0,0 +1,107 @@
+# Filename: FindFFTW.cmake
+# Author: Unknown (???), kestred (29 Nov, 2013)
+#
+# Usage:
+#   find_package(FFTW [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FFTW3_FOUND       - true if fftw is found on the system
+#   FFTW3_INCLUDE_DIR - the fftw include directory
+#   FFTW3_LIBRARY_DIR - the fftw library directory
+#   FFTW3_LIBRARY     - the path to the library binary
+#
+# The following variables will be checked by the function
+#   FFTW3_ROOT - if set, the libraries are exclusively searched under this path
+#
+
+# Check if we can use PkgConfig
+find_package(PkgConfig QUIET)
+
+#Determine from PKG
+if(PKG_CONFIG_FOUND AND NOT FFTW3_ROOT)
+  pkg_check_modules(PKG_FFTW QUIET "fftw3")
+endif()
+
+if(FFTW3_ROOT)
+  # Try to find headers under root
+  find_path(FFTW3_INCLUDE_DIR
+    NAMES "fftw3.h"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "include"
+    NO_DEFAULT_PATH
+  )
+
+  # Try to find library under root
+  find_library(FFTW3_LIBRARY
+    NAMES "fftw3"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+
+  find_library(FFTW3_FFTWF_LIBRARY
+    NAMES "fftw3f"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+
+  find_library(FFTW3_FFTWL_LIBRARY
+    NAMES "fftw3l"
+    PATHS ${FFTW3_ROOT}
+    PATH_SUFFIXES "lib" "lib64"
+    NO_DEFAULT_PATH
+  )
+else()
+  # Find headers the normal way
+  find_path(FFTW3_INCLUDE_DIR
+    NAMES "fftw3.h"
+    PATHS ${PKG_FFTW3_INCLUDE_DIRS}
+          ${INCLUDE_INSTALL_DIR}
+          "/usr/include"
+          "/usr/local/include"
+    PATH_SUFFIXES "" "fftw"
+  )
+
+  # Find library the normal way
+  find_library(FFTW3_LIBRARY
+    NAMES "fftw3"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+
+  find_library(FFTW3_FFTWF_LIBRARY
+    NAMES "fftw3f"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+
+
+  find_library(FFTW3_FFTWL_LIBRARY
+    NAMES "fftw3l"
+    PATHS ${PKG_FFTW3_LIBRARY_DIRS}
+          ${LIB_INSTALL_DIR}
+          "/usr"
+          "/usr/local"
+    PATH_SUFFIXES "" "lib" "lib32" "lib64"
+  )
+endif()
+
+# Get the directory conaining FFTW3_LIBRARY
+get_filename_component(FFTW3_LIBRARY_DIR "${FFTW3_LIBRARY}" PATH)
+set(FFTW3_LIBRARY_DIR "${FFTW3_LIBRARY_DIR}" CACHE PATH "The path to fftw's library directory.") # Library path
+
+mark_as_advanced(FFTW3_INCLUDE_DIR)
+mark_as_advanced(FFTW3_LIBRARY_DIR)
+mark_as_advanced(FFTW3_LIBRARY)
+mark_as_advanced(FFTW3_FFTWF_LIBRARY)
+mark_as_advanced(FFTW3_FFTWL_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFTW3 DEFAULT_MSG FFTW3_LIBRARY FFTW3_INCLUDE_DIR FFTW3_LIBRARY_DIR)

+ 83 - 0
cmake/modules/FindFMODEx.cmake

@@ -0,0 +1,83 @@
+# Filename: FindFMODEx.cmake
+# Author: kestred (8 Dec, 2013)
+#
+# Usage:
+#   find_pcackage(FMODEx [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   FMODEX_FOUND       - system has FMOD Ex
+#   FMODEX_INCLUDE_DIR - the FMOD Ex include directory
+#   FMODEX_LIBRARY_DIR - the FMOD Ex library directory
+#   FMODEX_LIBRARY     - the path to the library binary
+#
+#   FMODEX_32_LIBRARY - the filepath of the FMOD Ex SDK 32-bit library
+#   FMOXEX_64_LIBRARY - the filepath of the FMOD Ex SDK 64-bit library
+#
+
+# Find the include directory
+find_path(FMODEX_INCLUDE_DIR
+  NAMES "fmod.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+        "/opt/fmodex/include"
+        "/opt/fmodex/api/inc"
+        "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API Win32/api/inc"
+  PATH_SUFFIXES "" "fmodex/fmod" "fmodex/fmod3" "fmod" "fmod3"
+  DOC "The path to FMOD Ex's include directory."
+)
+
+# Find the 32-bit library
+find_library(FMODEX_32_LIBRARY
+  NAMES "fmodex_vc" "fmodex_bc" "fmodex" "fmodexL" "libfmodex" "libfmodexL" "fmodex_vc" "fmodexL_vc"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/X11R6"
+        "/usr/local/X11R6"
+        "/sw"
+        "/opt"
+        "/opt/local"
+        "/opt/csw"
+        "/opt/fmodex"
+        "/opt/fmodex/api"
+        "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API Win32/api/lib"
+  PATH_SUFFIXES "" "lib" "lib32"
+)
+
+# Find the 64-bit library
+find_library(FMODEX_64_LIBRARY
+  NAMES "fmodex64" "libfmodex64" "fmodexL64" "libfmodexL64" "fmodex64_vc" "fmodexL64_vc"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/X11R6"
+        "/usr/local/X11R6"
+        "/sw"
+        "/opt"
+        "/opt/local"
+        "/opt/csw"
+        "/opt/fmodex"
+        "/opt/fmodex/api"
+        "/usr/freeware"
+  PATH_SUFFIXES "" "lib" "lib64"
+)
+
+if(FMODEX_32_LIBRARY)
+  set(FMODEX_LIBRARY ${FMODEX_32_LIBRARY} CACHE FILEPATH "The filepath to FMOD Ex's library binary.")
+elseif(FMODEX_64_LIBRARY)
+  set(FMODEX_LIBRARY ${FMODEX_64_LIBRARY} CACHE FILEPATH "The filepath to FMOD Ex's library binary.")
+endif()
+
+get_filename_component(FMODEX_LIBRARY_DIR "${FMODEX_LIBRARY}" PATH)
+set(FMODEX_LIBRARY_DIR "${FMODEX_LIBRARY_DIR}" CACHE PATH "The path to FMOD Ex's library directory.")
+
+mark_as_advanced(FMODEX_INCLUDE_DIR)
+mark_as_advanced(FMODEX_LIBRARY_DIR)
+mark_as_advanced(FMODEX_LIBRARY)
+mark_as_advanced(FMODEX_32_LIBRARY)
+mark_as_advanced(FMODEX_64_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FMODEx DEFAULT_MSG FMODEX_LIBRARY FMODEX_INCLUDE_DIR FMODEX_LIBRARY_DIR)

+ 23 - 0
cmake/modules/FindHarfBuzz.cmake

@@ -0,0 +1,23 @@
+# Filename: FindHarfBuzz.cmake
+# Authors: CFSworks (2 Nov, 2018)
+#
+# Usage:
+#   find_package(HarfBuzz [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   HARFBUZZ_FOUND       - system has HarfBuzz
+#   HARFBUZZ_INCLUDE_DIR - the include directory containing hb.h
+#   HARFBUZZ_LIBRARY     - the path to the HarfBuzz library
+#
+
+find_path(HARFBUZZ_INCLUDE_DIR
+  NAMES "hb.h"
+  PATH_SUFFIXES "harfbuzz")
+
+find_library(HARFBUZZ_LIBRARY
+  NAMES "harfbuzz")
+
+mark_as_advanced(HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(HarfBuzz DEFAULT_MSG HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY)

+ 78 - 0
cmake/modules/FindLibSquish.cmake

@@ -0,0 +1,78 @@
+# Filename: FindLibSquish.cmake
+# Author: kestred (7 Dec, 2013)
+#
+# Usage:
+#   find_package(LibSquish [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   LIBSQUISH_FOUND       - system has libsquish
+#   LIBSQUISH_INCLUDE_DIR - the libsquish include directory
+#   LIBSQUISH_LIBRARY_DIR - the libsquish library directory
+#   LIBSQUISH_LIBRARY     - the path to the library binary
+#
+#   LIBSQUISH_RELEASE_LIBRARY - the filepath of the libsquish release library
+#   LIBSQUISH_DEBUG_LIBRARY   - the filepath of the libsquish debug library
+#
+
+# Find the libsquish include files
+find_path(LIBSQUISH_INCLUDE_DIR
+  NAMES "squish.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "" "cppunit"
+)
+
+# Find the libsquish library built for release
+find_library(LIBSQUISH_RELEASE_LIBRARY
+  NAMES "squish" "libsquish"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+# Find the libsquish library built for debug
+find_library(LIBSQUISH_DEBUG_LIBRARY
+  NAMES "squishd" "libsquishd"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+
+mark_as_advanced(LIBSQUISH_INCLUDE_DIR)
+mark_as_advanced(LIBSQUISH_RELEASE_LIBRARY)
+mark_as_advanced(LIBSQUISH_DEBUG_LIBRARY)
+
+# Choose library
+if(CMAKE_BUILD_TYPE MATCHES "Debug" AND LIBSQUISH_DEBUG_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_DEBUG_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+elseif(LIBSQUISH_RELEASE_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_RELEASE_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+elseif(LIBSQUISH_DEBUG_LIBRARY)
+	set(LIBSQUISH_LIBRARY ${LIBSQUISH_DEBUG_LIBRARY} CACHE FILEPATH "The filepath to libsquish's library binary.")
+endif()
+
+# Translate library into library directory
+if(LIBSQUISH_LIBRARY)
+	unset(LIBSQUISH_LIBRARY_DIR CACHE)
+	get_filename_component(LIBSQUISH_LIBRARY_DIR "${LIBSQUISH_LIBRARY}" PATH)
+	set(LIBSQUISH_LIBRARY_DIR "${LIBSQUISH_LIBRARY_DIR}" CACHE PATH "The path to libsquish's library directory.") # Library path
+endif()
+
+mark_as_advanced(LIBSQUISH_LIBRARY)
+mark_as_advanced(LIBSQUISH_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(LibSquish DEFAULT_MSG LIBSQUISH_LIBRARY LIBSQUISH_INCLUDE_DIR LIBSQUISH_LIBRARY_DIR)

+ 157 - 0
cmake/modules/FindODE.cmake

@@ -0,0 +1,157 @@
+# Filename: FindODE.cmake
+# Author: CFSworks (7 Feb, 2014)
+#
+# Usage:
+#   find_package(ODE [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   ODE_FOUND       - system has ode
+#   ODE_INCLUDE_DIR - the ode include directory
+#
+#   ODE_RELEASE_LIBRARY - the filepath of the ode release library
+#   ODE_DEBUG_LIBRARY   - the filepath of the ode debug library
+#
+#   ODE_SINGLE_DEBUG_LIBRARY   - the filepath of the single-precision ode debug library
+#   ODE_SINGLE_RELEASE_LIBRARY   - the filepath of the single-precision ode release library
+#   ODE_DOUBLE_DEBUG_LIBRARY   - the filepath of the double-precision ode debug library
+#   ODE_DOUBLE_RELEASE_LIBRARY   - the filepath of the double-precision ode release library
+#
+#   ODE::ODE        - The recommended ODE library to link against
+#   ODE::ODE_single - If available, this links against single-precision ODE
+#   ODE::ODE_double - If available, this links against double-precision ODE
+#
+
+# Find the libode include files
+find_path(ODE_INCLUDE_DIR "ode/ode.h")
+
+# Find the libode library built for release
+find_library(ODE_RELEASE_LIBRARY
+  NAMES "ode" "libode")
+
+# Find the libode library built for debug
+find_library(ODE_DEBUG_LIBRARY
+  NAMES "oded" "liboded")
+
+# Find the single-precision library built for release
+find_library(ODE_SINGLE_RELEASE_LIBRARY
+  NAMES "ode_single" "libode_single")
+
+# Find the single-precision library built for debug
+find_library(ODE_SINGLE_DEBUG_LIBRARY
+  NAMES "ode_singled" "libode_singled")
+
+# Find the double-precision library built for release
+find_library(ODE_DOUBLE_RELEASE_LIBRARY
+  NAMES "ode_double" "libode_double")
+
+# Find the double-precision library built for debug
+find_library(ODE_DOUBLE_DEBUG_LIBRARY
+  NAMES "ode_doubled" "libode_doubled")
+
+# Find libccd, which ODE sometimes links against, so we want to let the linker
+# know about it if it's present.
+find_library(ODE_LIBCCD_LIBRARY
+  NAMES "ccd" "libccd")
+
+mark_as_advanced(ODE_INCLUDE_DIR)
+mark_as_advanced(ODE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_SINGLE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_SINGLE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_DOUBLE_RELEASE_LIBRARY)
+mark_as_advanced(ODE_DOUBLE_DEBUG_LIBRARY)
+mark_as_advanced(ODE_LIBCCD_LIBRARY)
+
+# Define targets for both precisions (and unspecified)
+foreach(_precision _single _double "")
+  string(TOUPPER "${_precision}" _PRECISION)
+
+  if(EXISTS "${ODE${_PRECISION}_RELEASE_LIBRARY}" OR
+      EXISTS "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+    if(NOT TARGET ODE::ODE${_precision})
+      add_library(ODE::ODE${_precision} UNKNOWN IMPORTED GLOBAL)
+
+      set_target_properties(ODE::ODE${_precision} PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES "${ODE_INCLUDE_DIR}")
+
+      if(ODE_LIBCCD_LIBRARY)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          INTERFACE_LINK_LIBRARIES "${ODE_LIBCCD_LIBRARY}")
+      endif()
+
+      if(EXISTS "${ODE${_PRECISION}_RELEASE_LIBRARY}")
+        set_property(TARGET ODE::ODE${_precision} APPEND PROPERTY
+          IMPORTED_CONFIGURATIONS RELEASE)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
+          IMPORTED_LOCATION_RELEASE "${ODE${_PRECISION}_RELEASE_LIBRARY}")
+      endif()
+
+      if(EXISTS "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+        set_property(TARGET ODE::ODE${_precision} APPEND PROPERTY
+          IMPORTED_CONFIGURATIONS DEBUG)
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
+          IMPORTED_LOCATION_DEBUG "${ODE${_PRECISION}_DEBUG_LIBRARY}")
+      endif()
+
+      # If this has a precision, we should be sure to define
+      # dIDESINGLE/dIDEDOUBLE to keep it consistent
+      if(_precision)
+        string(REPLACE "_" "dIDE" _precision_symbol "${_PRECISION}")
+
+        set_target_properties(ODE::ODE${_precision} PROPERTIES
+          INTERFACE_COMPILE_DEFINITIONS "${_precision_symbol}")
+
+        unset(_precision_symbol)
+      endif()
+
+    endif()
+  endif()
+endforeach(_precision)
+unset(_precision)
+unset(_PRECISION)
+
+# OKAY.  If everything went well, we have ODE::ODE_single and/or
+# ODE::ODE_double.  We might even have an ODE::ODE, but if not, we need to
+# alias one of the other two to it.
+if(NOT TARGET ODE::ODE)
+  if(TARGET ODE::ODE_single)
+    set(_copy_from "ODE::ODE_single")
+  elseif(TARGET ODE::ODE_double)
+    set(_copy_from "ODE::ODE_double")
+  endif()
+
+  if(DEFINED _copy_from)
+    add_library(ODE::ODE UNKNOWN IMPORTED GLOBAL)
+
+    foreach(_prop
+        INTERFACE_INCLUDE_DIRECTORIES
+        INTERFACE_COMPILE_DEFINITIONS
+        INTERFACE_LINK_LIBRARIES
+        IMPORTED_CONFIGURATIONS
+        IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE
+        IMPORTED_LOCATION_RELEASE
+        IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG
+        IMPORTED_LOCATION_DEBUG)
+
+      get_target_property(_value "${_copy_from}" "${_prop}")
+      if(_value)
+        set_target_properties(ODE::ODE PROPERTIES "${_prop}" "${_value}")
+      endif()
+      unset(_value)
+    endforeach(_prop)
+    unset(_prop)
+  endif()
+
+  unset(_copy_from)
+endif()
+
+if(TARGET ODE::ODE)
+  set(_HAS_ODE_LIBRARY ON)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(ODE DEFAULT_MSG ODE_INCLUDE_DIR _HAS_ODE_LIBRARY)
+
+unset(_HAS_ODE_LIBRARY)

+ 20 - 0
cmake/modules/FindOgg.cmake

@@ -0,0 +1,20 @@
+# Filename: FindOgg.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(Ogg [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OGG_FOUND       - system has Ogg
+#   OGG_INCLUDE_DIR - the include directory containing ogg/
+#   OGG_LIBRARY     - the path to the ogg library
+#
+
+find_path(OGG_INCLUDE_DIR NAMES "ogg/ogg.h")
+
+find_library(OGG_LIBRARY NAMES "ogg" "libogg_static")
+
+mark_as_advanced(OGG_INCLUDE_DIR OGG_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Ogg DEFAULT_MSG OGG_INCLUDE_DIR OGG_LIBRARY)

+ 92 - 0
cmake/modules/FindOpenCV.cmake

@@ -0,0 +1,92 @@
+# Filename: FindOpenCV.cmake
+# Authors: CFSworks (3 Nov, 2018)
+#
+# Usage:
+#   find_package(OpenCV [REQUIRED] [QUIET])
+#
+# This supports the following components:
+#   calib3d
+#   contrib
+#   core
+#   features2d
+#   flann
+#   gpu
+#   highgui
+#   imgproc
+#   legacy
+#   ml
+#   nonfree
+#   objdetect
+#   photo
+#   stitching
+#   superres
+#   video
+#   videoio
+#   videostab
+#
+# Once done this will define:
+#   OPENCV_FOUND        - system has OpenCV
+#   OpenCV_INCLUDE_DIRS - the include dir(s) containing OpenCV header files
+#   OpenCV_comp_LIBRARY - the path to the OpenCV library for the particular
+#                         component
+#   OpenCV_LIBS         - the paths to the OpenCV libraries for the requested
+#                         component(s)
+#   OpenCV_VERSION_MAJOR- a "best guess" of the major version (X.x)
+#   OpenCV_VERSION_MINOR- a "best guess" of the minor version (x.X)
+#
+
+set(OpenCV_INCLUDE_DIRS)
+
+find_path(OpenCV_V1_INCLUDE_DIR
+  NAMES "cv.h"
+  PATH_SUFFIXES "opencv")
+mark_as_advanced(OpenCV_V1_INCLUDE_DIR)
+if(OpenCV_V1_INCLUDE_DIR)
+  list(APPEND OpenCV_INCLUDE_DIRS "${OpenCV_V1_INCLUDE_DIR}")
+
+  # This is a wild guess:
+  set(OpenCV_VERSION_MAJOR 1)
+  set(OpenCV_VERSION_MINOR 0)
+endif()
+
+find_path(OpenCV_V2_INCLUDE_DIR "opencv2/core/version.hpp")
+mark_as_advanced(OpenCV_V2_INCLUDE_DIR)
+if(OpenCV_V2_INCLUDE_DIR)
+  list(APPEND OpenCV_INCLUDE_DIRS "${OpenCV_V2_INCLUDE_DIR}")
+
+  file(STRINGS "${OpenCV_V2_INCLUDE_DIR}/opencv2/core/version.hpp"
+    _version_major REGEX "#define CV_VERSION_EPOCH")
+  file(STRINGS "${OpenCV_V2_INCLUDE_DIR}/opencv2/core/version.hpp"
+    _version_minor REGEX "#define CV_VERSION_MAJOR")
+
+  string(REGEX REPLACE "[^0-9]" "" OpenCV_VERSION_MAJOR "${_version_major}")
+  string(REGEX REPLACE "[^0-9]" "" OpenCV_VERSION_MINOR "${_version_minor}")
+  unset(_version_major)
+  unset(_version_minor)
+endif()
+
+set(OpenCV_LIBS)
+foreach(_component calib3d contrib core features2d flann gpu highgui imgproc
+                  legacy ml nonfree objdetect photo stitching superres video
+                  videoio videostab)
+
+  list(FIND OpenCV_FIND_COMPONENTS "${_component}" _index)
+  if(_index GREATER -1 OR _component STREQUAL "core")
+    if(NOT OpenCV_${_component}_LIBRARY)
+      find_library(OpenCV_${_component}_LIBRARY
+        NAMES "opencv_${_component}")
+    endif()
+
+    if(OpenCV_${_component}_LIBRARY)
+      list(APPEND OpenCV_LIBS "${OpenCV_${_component}_LIBRARY}")
+      set(OpenCV_${_component}_FOUND ON)
+    endif()
+  endif()
+  unset(_index)
+endforeach(_component)
+unset(_component)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenCV HANDLE_COMPONENTS
+  REQUIRED_VARS OpenCV_INCLUDE_DIRS OpenCV_LIBS
+  OpenCV_VERSION_MAJOR OpenCV_VERSION_MINOR)

+ 88 - 0
cmake/modules/FindOpenEXR.cmake

@@ -0,0 +1,88 @@
+# Filename: FindOpenEXR.cmake
+# Authors: CFSworks (5 Nov, 2018)
+#
+# Usage:
+#   find_package(OpenEXR [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENEXR_FOUND       - system has OpenEXR
+#   OPENEXR_INCLUDE_DIR - the include directory containing OpenEXR header files
+#   OPENEXR_LIBRARIES   - the path to the OpenEXR libraries
+#
+
+find_path(OPENEXR_INCLUDE_DIR
+  "ImfVersion.h"
+  PATH_SUFFIXES "OpenEXR")
+
+mark_as_advanced(OPENEXR_INCLUDE_DIR)
+
+find_library(OPENEXR_imf_LIBRARY
+  NAMES "IlmImf")
+
+if(OPENEXR_imf_LIBRARY)
+  get_filename_component(_imf_dir "${OPENEXR_imf_LIBRARY}" DIRECTORY)
+  find_library(OPENEXR_imfutil_LIBRARY
+    NAMES "IlmImfUtil"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_ilmthread_LIBRARY
+    NAMES "IlmThread"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_iex_LIBRARY
+    NAMES "Iex"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_iexmath_LIBRARY
+    NAMES "IexMath"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_imath_LIBRARY
+    NAMES "Imath"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  find_library(OPENEXR_half_LIBRARY
+    NAMES "Half"
+    PATHS "${_imf_dir}"
+    NO_DEFAULT_PATH)
+
+  unset(_imf_dir)
+endif()
+
+mark_as_advanced(
+  OPENEXR_imf_LIBRARY
+  OPENEXR_imfutil_LIBRARY
+  OPENEXR_ilmthread_LIBRARY
+  OPENEXR_iex_LIBRARY
+  OPENEXR_iexmath_LIBRARY
+  OPENEXR_imath_LIBRARY
+  OPENEXR_half_LIBRARY
+)
+
+set(OPENEXR_LIBRARIES
+  ${OPENEXR_imf_LIBRARY}
+  ${OPENEXR_imfutil_LIBRARY}
+  ${OPENEXR_ilmthread_LIBRARY}
+  ${OPENEXR_iex_LIBRARY}
+  ${OPENEXR_iexmath_LIBRARY}
+  ${OPENEXR_imath_LIBRARY}
+  ${OPENEXR_half_LIBRARY}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenEXR DEFAULT_MSG
+  OPENEXR_INCLUDE_DIR OPENEXR_LIBRARIES
+
+  OPENEXR_imf_LIBRARY
+  OPENEXR_imfutil_LIBRARY
+  OPENEXR_ilmthread_LIBRARY
+  OPENEXR_iex_LIBRARY
+  OPENEXR_iexmath_LIBRARY
+  OPENEXR_imath_LIBRARY
+  OPENEXR_half_LIBRARY
+)

+ 21 - 0
cmake/modules/FindOpenGLES1.cmake

@@ -0,0 +1,21 @@
+# Filename: FindOpenGLES1.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(OpenGLES1 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENGLES1_FOUND       - system has OpenGL ES 1.x
+#   OPENGLES1_INCLUDE_DIR - the include directory containing GLES/gl.h
+#   OPENGLES1_LIBRARY     - the library to link against for OpenGL ES 1.x
+#
+
+find_path(OPENGLES1_INCLUDE_DIR "GLES/gl.h")
+
+find_library(OPENGLES1_LIBRARY
+  NAMES "GLESv1" "GLESv1_CM" "GLES_CM")
+
+mark_as_advanced(OPENGLES1_INCLUDE_DIR OPENGLES1_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenGLES1 DEFAULT_MSG OPENGLES1_INCLUDE_DIR OPENGLES1_LIBRARY)

+ 21 - 0
cmake/modules/FindOpenGLES2.cmake

@@ -0,0 +1,21 @@
+# Filename: FindOpenGLES2.cmake
+# Authors: CFSworks (21 Oct, 2018)
+#
+# Usage:
+#   find_package(OpenGLES2 [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPENGLES2_FOUND        - system has OpenGL ES 2.x
+#   OPENGLES2_INCLUDE_DIR  - the include directory containing GLES2/gl2.h
+#   OPENGLES2_LIBRARY      - the library to link against for OpenGL ES 2.x
+#
+
+find_path(OPENGLES2_INCLUDE_DIR "GLES2/gl2.h")
+
+find_library(OPENGLES2_LIBRARY
+  NAMES "GLESv2")
+
+mark_as_advanced(OPENGLES2_INCLUDE_DIR OPENGLES2_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG OPENGLES2_INCLUDE_DIR OPENGLES2_LIBRARY)

+ 34 - 0
cmake/modules/FindOpusFile.cmake

@@ -0,0 +1,34 @@
+# Filename: FindOpusFile.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(OpusFile [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   OPUSFILE_FOUND        - system has Ogg and opusfile
+#   OPUSFILE_INCLUDE_DIRS - the include directory/ies containing opus/ and ogg/
+#   OPUSFILE_LIBRARIES    - the paths to the opus and opusfile libraries
+#
+
+# Find Ogg
+find_package(Ogg QUIET)
+
+# Find Opus
+find_path(OPUS_INCLUDE_DIR NAMES "opus/opusfile.h")
+
+find_library(OPUS_opus_LIBRARY NAMES "opus")
+find_library(OPUS_opusfile_LIBRARY NAMES "opusfile")
+
+mark_as_advanced(OPUS_INCLUDE_DIR OPUS_opus_LIBRARY OPUS_opusfile_LIBRARY)
+
+# Define output variables
+set(OPUSFILE_INCLUDE_DIRS ${OPUS_INCLUDE_DIR} "${OPUS_INCLUDE_DIR}/opus")
+if(NOT OGG_INCLUDE_DIR STREQUAL OPUS_INCLUDE_DIR)
+  list(APPEND OPUSFILE_INCLUDE_DIRS ${OGG_INCLUDE_DIR})
+endif()
+set(OPUSFILE_LIBRARIES ${OPUS_opusfile_LIBRARY} ${OPUS_opus_LIBRARY} ${OGG_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OpusFile DEFAULT_MSG
+  Ogg_FOUND
+  OPUS_INCLUDE_DIR OPUS_opus_LIBRARY OPUS_opusfile_LIBRARY)

+ 50 - 0
cmake/modules/FindSWResample.cmake

@@ -0,0 +1,50 @@
+# Filename: FindSWResample.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(SWResample [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   SWRESAMPLE_FOUND       - system has ffmpeg's libswresample
+#   SWRESAMPLE_INCLUDE_DIR - the libswresample include directory
+#   SWRESAMPLE_LIBRARY     - the path to the library binary
+#
+
+# Find the libswresample include files
+find_path(SWRESAMPLE_INCLUDE_DIR
+  NAMES "libswresample/swresample.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libswresample library
+find_library(SWRESAMPLE_LIBRARY
+  NAMES "swresample"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(SWRESAMPLE_INCLUDE_DIR)
+mark_as_advanced(SWRESAMPLE_LIBRARY)
+
+# Translate library into library directory
+if(SWRESAMPLE_LIBRARY)
+  unset(SWRESAMPLE_LIBRARY_DIR CACHE)
+  get_filename_component(SWRESAMPLE_LIBRARY_DIR "${SWRESAMPLE_LIBRARY}" PATH)
+  set(SWRESAMPLE_LIBRARY_DIR "${SWRESAMPLE_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+mark_as_advanced(SWRESAMPLE_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SWResample DEFAULT_MSG SWRESAMPLE_LIBRARY SWRESAMPLE_INCLUDE_DIR SWRESAMPLE_LIBRARY_DIR)

+ 50 - 0
cmake/modules/FindSWScale.cmake

@@ -0,0 +1,50 @@
+# Filename: FindSWScale.cmake
+# Author: CFSworks (10 Apr, 2014)
+#
+# Usage:
+#   find_package(SWScale [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   SWSCALE_FOUND       - system has ffmpeg's libswscale
+#   SWSCALE_INCLUDE_DIR - the libswscale include directory
+#   SWSCALE_LIBRARY     - the path to the library binary
+#
+
+# Find the libswscale include files
+find_path(SWSCALE_INCLUDE_DIR
+  NAMES "libswscale/swscale.h"
+  PATHS "/usr/include"
+        "/usr/local/include"
+        "/sw/include"
+        "/opt/include"
+        "/opt/local/include"
+        "/opt/csw/include"
+  PATH_SUFFIXES "libav" "ffmpeg"
+)
+
+# Find the libswscale library
+find_library(SWSCALE_LIBRARY
+  NAMES "swscale"
+  PATHS "/usr"
+        "/usr/local"
+        "/usr/freeware"
+        "/sw"
+        "/opt"
+        "/opt/csw"
+  PATH_SUFFIXES "lib" "lib32" "lib64"
+)
+
+mark_as_advanced(SWSCALE_INCLUDE_DIR)
+mark_as_advanced(SWSCALE_LIBRARY)
+
+# Translate library into library directory
+if(SWSCALE_LIBRARY)
+  unset(SWSCALE_LIBRARY_DIR CACHE)
+  get_filename_component(SWSCALE_LIBRARY_DIR "${SWSCALE_LIBRARY}" PATH)
+  set(SWSCALE_LIBRARY_DIR "${SWSCALE_LIBRARY_DIR}" CACHE PATH "The path to libffmpeg's library directory.") # Library path
+endif()
+
+mark_as_advanced(SWSCALE_LIBRARY_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SWScale DEFAULT_MSG SWSCALE_LIBRARY SWSCALE_INCLUDE_DIR SWSCALE_LIBRARY_DIR)

+ 40 - 0
cmake/modules/FindVRPN.cmake

@@ -0,0 +1,40 @@
+# Filename: FindVRPN.cmake
+# Authors: CFSworks (2 Nov, 2018)
+#
+# Usage:
+#   find_package(VRPN [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   VRPN_FOUND       - system has VRPN
+#   VRPN_INCLUDE_DIR - the include directory containing VRPN header files
+#   VRPN_LIBRARIES   - the path to the VRPN client libraries
+#
+
+find_path(VRPN_INCLUDE_DIR "vrpn_Connection.h")
+
+find_library(VRPN_vrpn_LIBRARY
+  NAMES "vrpn")
+
+mark_as_advanced(VRPN_INCLUDE_DIR VRPN_vrpn_LIBRARY)
+
+if(VRPN_vrpn_LIBRARY)
+  get_filename_component(_vrpn_dir "${VRPN_vrpn_LIBRARY}" DIRECTORY)
+  find_library(VRPN_quat_LIBRARY
+    NAMES "quat"
+    PATHS "${_vrpn_dir}"
+    NO_DEFAULT_PATH)
+
+  unset(_vrpn_dir)
+  mark_as_advanced(VRPN_quat_LIBRARY)
+endif()
+
+set(VRPN_LIBRARIES)
+if(VRPN_vrpn_LIBRARY)
+  list(APPEND VRPN_LIBRARIES "${VRPN_vrpn_LIBRARY}")
+endif()
+if(VRPN_quat_LIBRARY)
+  list(APPEND VRPN_LIBRARIES "${VRPN_quat_LIBRARY}")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(VRPN DEFAULT_MSG VRPN_INCLUDE_DIR VRPN_LIBRARIES)

+ 34 - 0
cmake/modules/FindVorbisFile.cmake

@@ -0,0 +1,34 @@
+# Filename: FindVorbisFile.cmake
+# Authors: CFSworks (13 Jan, 2019)
+#
+# Usage:
+#   find_package(VorbisFile [REQUIRED] [QUIET])
+#
+# Once done this will define:
+#   VORBISFILE_FOUND        - system has Ogg and vorbisfile
+#   VORBISFILE_INCLUDE_DIRS - the include directory/ies containing vorbis/ and ogg/
+#   VORBISFILE_LIBRARIES    - the paths to the vorbis and vorbisfile libraries
+#
+
+# Find Ogg
+find_package(Ogg QUIET)
+
+# Find Vorbis
+find_path(VORBIS_INCLUDE_DIR NAMES "vorbis/vorbisfile.h")
+
+find_library(VORBIS_vorbis_LIBRARY NAMES "vorbis" "libvorbis_static")
+find_library(VORBIS_vorbisfile_LIBRARY NAMES "vorbisfile" "libvorbisfile_static")
+
+mark_as_advanced(VORBIS_INCLUDE_DIR VORBIS_vorbis_LIBRARY VORBIS_vorbisfile_LIBRARY)
+
+# Define output variables
+set(VORBISFILE_INCLUDE_DIRS ${VORBIS_INCLUDE_DIR})
+if(NOT OGG_INCLUDE_DIR STREQUAL VORBIS_INCLUDE_DIR)
+  list(APPEND VORBISFILE_INCLUDE_DIRS ${OGG_INCLUDE_DIR})
+endif()
+set(VORBISFILE_LIBRARIES ${VORBIS_vorbisfile_LIBRARY} ${VORBIS_vorbis_LIBRARY} ${OGG_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(VorbisFile DEFAULT_MSG
+  Ogg_FOUND
+  VORBIS_INCLUDE_DIR VORBIS_vorbis_LIBRARY VORBIS_vorbisfile_LIBRARY)

+ 7 - 0
cmake/modules/README.md

@@ -0,0 +1,7 @@
+Directory Info
+--------------
+**Directory:** /cmake/modules
+**License:** Unlicense  
+**Description:** This directory is used for standard CMake modules which are safe
+to use outside of a Panda3D project.  This should be the normal location for
+most FindXYZZY packages that are written for Panda3D.

+ 70 - 0
cmake/scripts/ConcatenateToCXX.cmake

@@ -0,0 +1,70 @@
+# Filename: ConcatenateToCXX.cmake
+#
+# Description: When run, creates a single C++ file which includes a const char[]
+#   containing the bytes from one or more files.
+#
+#   There is a {SYMBOL_NAME}_size symbol defined as well, storing the total
+#   number of bytes in the concatenated input files.
+#
+#   A single null terminator byte is added for the benefit of programs that
+#   simply treat the data array as a string.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D OUTPUT_FILE="out.cxx" -D SYMBOL_NAME=data -D INPUT_FILES="a.bin b.bin" -P ConcatenateToCXX.cmake
+#
+
+if(NOT CMAKE_SCRIPT_MODE_FILE)
+  message(FATAL_ERROR "ConcatenateToCXX.cmake should not be included but run in script mode.")
+  return()
+endif()
+
+if(NOT DEFINED OUTPUT_FILE)
+  message(FATAL_ERROR "OUTPUT_FILE should be defined when running ConcatenateToCXX.cmake!")
+  return()
+endif()
+
+if(NOT DEFINED SYMBOL_NAME)
+  set(SYMBOL_NAME "data")
+endif()
+
+file(WRITE "${OUTPUT_FILE}" "/* Generated by CMake.  DO NOT EDIT. */
+
+extern const char ${SYMBOL_NAME}[];
+extern const int ${SYMBOL_NAME}_size;
+
+const char ${SYMBOL_NAME}[] = {\n")
+
+set(byte_count 0)
+separate_arguments(INPUT_FILES)
+foreach(infile ${INPUT_FILES})
+  file(APPEND "${OUTPUT_FILE}" "  /* ${infile} */\n")
+
+  set(offset 0)
+  while(1)
+    # Read up to 1024 bytes from the input file
+    file(READ "${infile}" data LIMIT 1024 OFFSET ${offset} HEX)
+    math(EXPR offset "${offset} + 1024")
+
+    # If 'data' is empty, we're done
+    if(NOT data)
+      break()
+    endif()
+
+    # Count the bytes we're adding
+    string(LENGTH "${data}" strlen)
+    math(EXPR byte_count "${byte_count} + (${strlen} / 2)")
+
+    # Format runs of up to 32 hex chars by indenting and giving a newline
+    string(REGEX REPLACE
+      "(...?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?)" " \\1\n"
+      data "${data}")
+    # Format each byte (2 hex chars) in each line with 0x prefix and comma suffix
+    string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" " 0x\\1," data "${data}")
+    file(APPEND "${OUTPUT_FILE}" "${data}")
+  endwhile()
+endforeach(infile)
+
+file(APPEND "${OUTPUT_FILE}" "  0\n};
+
+extern const int ${SYMBOL_NAME}_size = ${byte_count};\n")

+ 27 - 0
cmake/scripts/CopyPattern.cmake

@@ -0,0 +1,27 @@
+# Filename: CopyPattern.cmake
+#
+# Description: This is a standalone version of CMake's file(COPY) command so we
+#              can use all of its features during build-time instead of
+#              config-time.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D SOURCE=[source directory]
+#         -D DESTINATION=[destination directory]
+#         -D FILES_MATCHING="[globbing patterns passed to file(COPY)]"
+#         -P CopyPattern.cmake
+if(NOT DEFINED SOURCE OR NOT DEFINED DESTINATION)
+    message(SEND_ERROR "CopyPattern.cmake requires SOURCE and DESTINATION to be
+defined.")
+endif()
+
+if(DEFINED FILES_MATCHING)
+  separate_arguments(FILES_MATCHING UNIX_COMMAND ${FILES_MATCHING})
+
+  file(COPY "${SOURCE}"
+       DESTINATION "${DESTINATION}"
+       FILES_MATCHING ${FILES_MATCHING})
+else()
+  file(COPY "${SOURCE}"
+       DESTINATION "${DESTINATION}")
+endif()

+ 78 - 0
cmake/scripts/CopyPython.cmake

@@ -0,0 +1,78 @@
+# Filename: CopyPython.cmake
+#
+# Description: When run, copies Python files from a source directory to a
+#   build directory located somewhere in the project's binary path. If
+#   PYTHON_EXECUTABLES is provided, it will also invoke compileall in the
+#   destination dir(s) to precache .pyc/.pyo files.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -D OUTPUT_DIR="/out/dir/Release"
+#         -D SOURCE_DIR="/panda3d/direct/src/"
+#         -D PYTHON_EXECUTABLES="/usr/bin/python3.6"
+#         -P CopyPython.cmake
+#
+
+if(NOT CMAKE_SCRIPT_MODE_FILE)
+  message(FATAL_ERROR "CopyPython.cmake should not be included but run in script mode.")
+  return()
+endif()
+
+if(NOT DEFINED OUTPUT_DIR)
+  message(FATAL_ERROR "OUTPUT_DIR should be defined when running CopyPython.cmake!")
+  return()
+endif()
+
+# Ensure OUTPUT_DIR exists
+file(MAKE_DIRECTORY ${OUTPUT_DIR})
+
+# If there's a SOURCE_DIR, glob for .py files and copy
+# (this is done by hand to avoid the CMake bug where it creates empty dirs)
+if(DEFINED SOURCE_DIR)
+  file(GLOB_RECURSE py_files RELATIVE "${SOURCE_DIR}" "${SOURCE_DIR}/*.py")
+  foreach(py_file ${py_files})
+    get_filename_component(py_file_parent "${py_file}" DIRECTORY)
+    file(TIMESTAMP "${SOURCE_DIR}/${py_file}" src_stamp)
+    file(TIMESTAMP "${OUTPUT_DIR}/${py_file}" dst_stamp)
+
+    # The file is only copied if:
+    # - there's an __init__.py in its dir (or file is in the root) (i.e. file belongs to a package), and
+    # - the modification timestamp differs (i.e. file changed or never copied)
+    if((py_file_parent STREQUAL "." OR NOT py_file_parent
+        OR EXISTS "${SOURCE_DIR}/${py_file_parent}/__init__.py")
+       AND NOT src_stamp STREQUAL dst_stamp)
+
+      file(COPY "${SOURCE_DIR}/${py_file}" DESTINATION "${OUTPUT_DIR}/${py_file_parent}")
+      set(changed YES)
+    endif()
+
+  endforeach(py_file)
+
+else()
+  # No SOURCE_DIR; assume we're outdated since our caller might be populating
+  # the OUTPUT_DIR themselves
+  set(changed YES)
+
+endif()
+
+# Make sure Python recognizes OUTPUT_DIR as a package
+if(NOT EXISTS "${OUTPUT_DIR}/__init__.py")
+  file(WRITE "${OUTPUT_DIR}/__init__.py" "")
+  set(changed YES)
+endif()
+
+# Generate .pyc files for each Python version, if our caller wants
+if(changed AND DEFINED PYTHON_EXECUTABLES)
+  foreach(interp ${PYTHON_EXECUTABLES})
+    execute_process(
+      COMMAND "${interp}" -m compileall .
+      OUTPUT_QUIET
+      WORKING_DIRECTORY "${OUTPUT_DIR}")
+
+    execute_process(
+      COMMAND "${interp}" -O -m compileall .
+      OUTPUT_QUIET
+      WORKING_DIRECTORY "${OUTPUT_DIR}")
+
+  endforeach(interp)
+endif()

+ 28 - 0
cmake/scripts/MakeComposite.cmake

@@ -0,0 +1,28 @@
+# Filename: MakeComposite.cmake
+#
+# Description: When run, creates a single C++ file which includes multiple
+#   other C++ files, to help facilitate unity builds.
+#   Unity builds provide significantly increased compile speed.
+#
+# Usage:
+#   This script is invoked via add_custom_target, like this:
+#   cmake -P MakeComposite.cmake -D COMPOSITE_FILE="x_composite1.cxx" -D COMPOSITE_SOURCES="a.cxx b.cxx"
+#
+
+if(CMAKE_SCRIPT_MODE_FILE)
+  if(NOT DEFINED COMPOSITE_FILE)
+    message(FATAL_ERROR "COMPOSITE_FILE should be defined when running MakeComposite.cmake!")
+    return()
+  endif()
+
+  file(WRITE "${COMPOSITE_FILE}" "/* Generated by CMake.  DO NOT EDIT. */\n")
+
+  separate_arguments(COMPOSITE_SOURCES)
+  foreach(source ${COMPOSITE_SOURCES})
+    file(APPEND "${COMPOSITE_FILE}" "#include \"${source}\"\n")
+  endforeach()
+
+else()
+  message(SEND_ERROR "MakeComposite.cmake should not be included but run in script mode.")
+
+endif()

+ 10 - 0
cmake/scripts/README.md

@@ -0,0 +1,10 @@
+Directory Info
+--------------
+**Directory:** /cmake/scripts  
+**License:** Unlicense  
+**Description:** This directory is used for cmake files which are not meant to
+be included using CMake's normal include() directive.  Typically, files in this
+directory will be invoked as a custom command/target in the form of:
+```
+ cmake -P <CustomScriptName.cmake> [... other options ...]
+```

+ 9 - 0
cmake/templates/metalib_init.cxx.in

@@ -0,0 +1,9 @@
+#include "dtoolbase.h"
+
+@component_init_headers@
+
+EXPORT_CLASS void
+@init_func@() {
+@component_init_funcs@
+}
+@export_definitions@

+ 9 - 0
cmake/templates/metalib_init.h.in

@@ -0,0 +1,9 @@
+#ifndef _METALIB_INIT_@target_name@
+#define _METALIB_INIT_@target_name@
+
+#include "dtoolbase.h"
+
+IMPORT_CLASS void @init_func@();
+@export_declarations@
+
+#endif

+ 23 - 0
cmake/templates/win32_python/__init__.py

@@ -0,0 +1,23 @@
+def _fixup_dlls():
+    try:
+        path = __path__[0]
+    except (NameError, IndexError):
+        return # Not a package, or not on filesystem
+
+    import os
+
+    relpath = os.path.relpath(path, __path__[-1])
+    dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath))
+    if not os.path.isdir(dll_path):
+        return
+
+    if hasattr(os, 'add_dll_directory'):
+        os.add_dll_directory(dll_path)
+    else:
+        os_path = os.environ.get('PATH', '')
+        os_path = os_path.split(os.pathsep) if os_path else []
+        os_path.insert(0, dll_path)
+        os.environ['PATH'] = os.pathsep.join(os_path)
+
+_fixup_dlls()
+del _fixup_dlls

+ 18 - 0
contrib/CMakeLists.txt

@@ -0,0 +1,18 @@
+if(NOT BUILD_PANDA)
+  message(FATAL_ERROR "Cannot build contrib without panda!  Please enable the BUILD_PANDA option.")
+endif()
+
+# Include source directories
+add_subdirectory(src/ai)
+add_subdirectory(src/contribbase)
+add_subdirectory(src/rplight)
+
+if(HAVE_PYTHON)
+  add_python_module(panda3d.ai p3ai
+    IMPORT panda3d.core COMPONENT ContribPython)
+
+  add_python_module(panda3d._rplight p3rplight
+    IMPORT panda3d.core COMPONENT ContribPython)
+endif()
+
+export_targets(Contrib COMPONENT ContribDevel)

+ 54 - 0
contrib/src/ai/CMakeLists.txt

@@ -0,0 +1,54 @@
+set(P3AI_HEADERS
+  aiBehaviors.h
+  aiCharacter.h
+  aiGlobals.h
+  aiNode.h
+  aiPathFinder.h
+  aiWorld.h
+  arrival.h
+  config_ai.h
+  evade.h
+  flee.h
+  flock.h
+  meshNode.h
+  obstacleAvoidance.h
+  pathFind.h
+  pathFollow.h
+  pursue.h
+  seek.h
+  wander.h
+)
+
+set(P3AI_SOURCES
+  aiBehaviors.cxx
+  aiCharacter.cxx
+  aiNode.cxx
+  aiPathFinder.cxx
+  aiWorld.cxx
+  arrival.cxx
+  config_ai.cxx
+  evade.cxx
+  flee.cxx
+  flock.cxx
+  meshNode.cxx
+  obstacleAvoidance.cxx
+  pathFind.cxx
+  pathFollow.cxx
+  pursue.cxx
+  seek.cxx
+  wander.cxx
+)
+
+composite_sources(p3ai P3AI_SOURCES)
+add_library(p3ai ${P3AI_HEADERS} ${P3AI_SOURCES})
+set_target_properties(p3ai PROPERTIES DEFINE_SYMBOL BUILDING_PANDAAI)
+target_link_libraries(p3ai p3contribbase)
+target_interrogate(p3ai ALL)
+
+install(TARGETS p3ai
+  EXPORT Contrib COMPONENT Contrib
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+  ARCHIVE COMPONENT ContribDevel)
+install(FILES ${P3AI_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 14 - 0
contrib/src/contribbase/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(P3CONTRIBBASE_SOURCES
+  contribbase.cxx
+)
+
+set(P3CONTRIBBASE_HEADERS
+  contribbase.h contribsymbols.h
+)
+
+# Yes, INTERFACE: don't build it, there's no code!
+add_library(p3contribbase INTERFACE)
+target_link_libraries(p3contribbase INTERFACE panda)
+
+install(TARGETS p3contribbase EXPORT Contrib COMPONENT Contrib)
+install(FILES ${P3CONTRIBBASE_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 52 - 0
contrib/src/rplight/CMakeLists.txt

@@ -0,0 +1,52 @@
+set(P3RPLIGHT_HEADERS
+  config_rplight.h
+  gpuCommand.h gpuCommand.I
+  gpuCommandList.h
+  iesDataset.h
+  internalLightManager.h internalLightManager.I
+  pointerSlotStorage.h
+  pssmCameraRig.h pssmCameraRig.I
+  rpLight.h rpLight.I
+  rpPointLight.h rpPointLight.I
+  rpSpotLight.h rpSpotLight.I
+  shadowAtlas.h shadowAtlas.I
+  shadowManager.h shadowManager.I
+  shadowSource.h shadowSource.I
+  tagStateManager.h tagStateManager.I
+)
+
+set(P3RPLIGHT_SOURCES
+  config_rplight.cxx
+  gpuCommand.cxx
+  gpuCommandList.cxx
+  iesDataset.cxx
+  internalLightManager.cxx
+  pssmCameraRig.cxx
+  rpLight.cxx
+  rpPointLight.cxx
+  rpSpotLight.cxx
+  shadowAtlas.cxx
+  shadowManager.cxx
+  shadowSource.cxx
+  tagStateManager.cxx
+)
+
+composite_sources(p3rplight P3RPLIGHT_SOURCES)
+# STATIC, because it doesn't contain any EXPCL_ stuff
+add_library(p3rplight STATIC ${P3RPLIGHT_HEADERS} ${P3RPLIGHT_SOURCES})
+target_link_libraries(p3rplight p3contribbase)
+target_interrogate(p3rplight ALL)
+
+if(MODULE_TYPE STREQUAL "MODULE")
+  # Because it's STATIC (see above), we need to make it PIC so it can link into
+  # a Python module
+  set_target_properties(p3rplight PROPERTIES POSITION_INDEPENDENT_CODE ON)
+endif()
+
+install(TARGETS p3rplight
+  EXPORT Contrib COMPONENT Contrib
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+  ARCHIVE COMPONENT ContribDevel)
+install(FILES ${P3RPLIGHT_HEADERS} COMPONENT ContribDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 75 - 0
direct/CMakeLists.txt

@@ -0,0 +1,75 @@
+if(NOT BUILD_PANDA)
+  message(FATAL_ERROR "Cannot build direct without panda!  Please enable the BUILD_PANDA option.")
+endif()
+
+# Include source directories which have C++ components:
+add_subdirectory(src/dcparse)
+add_subdirectory(src/dcparser)
+add_subdirectory(src/deadrec)
+add_subdirectory(src/directbase)
+#add_subdirectory(src/directd)
+#add_subdirectory(src/directdServer)
+add_subdirectory(src/distributed)
+add_subdirectory(src/interval)
+add_subdirectory(src/motiontrail)
+add_subdirectory(src/showbase)
+
+set(P3DIRECT_COMPONENTS
+  p3dcparser p3deadrec
+  p3interval p3motiontrail p3showbase
+)
+if(HAVE_PYTHON)
+  list(APPEND P3DIRECT_COMPONENTS p3distributed)
+endif()
+
+set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "DirectDevel")
+add_metalib(p3direct INIT init_libdirect direct.h COMPONENTS ${P3DIRECT_COMPONENTS})
+unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
+set_property(TARGET p3direct PROPERTY LINKER_LANGUAGE "CXX")
+
+# Installation:
+install(TARGETS p3direct
+  EXPORT Direct COMPONENT Direct
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+  ARCHIVE COMPONENT DirectDevel)
+
+if(HAVE_PYTHON)
+  # Now for the Python side of everything
+
+  add_python_module(panda3d.direct
+    p3dcparser p3deadrec p3distributed p3interval
+    p3motiontrail p3showbase LINK p3direct IMPORT panda3d.core COMPONENT Direct)
+
+  # Copy Python source files into the build directory
+  install_python_package(direct SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/src" LIB COMPONENT Direct)
+
+  # This bit is to generate the 'pandac' compatibility shim. It's deprecated now,
+  # but in older versions of Panda3D, one would use
+  # from pandac.PandaModules import *
+  # instead of
+  # from panda3d.FOO import *
+  # Generate PandaModules:
+  file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py"
+    "\"This module is deprecated.  Import from panda3d.core and other panda3d.* modules instead.\"
+
+print(\"Warning: pandac.PandaModules is deprecated, import from panda3d.core instead\")\n")
+
+  foreach(module ${ALL_INTERROGATE_MODULES})
+    string(REGEX REPLACE "^.*\\." "" module_name "${module}")
+    file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py" "
+try:
+    from ${module} import *
+except ImportError as err:
+    if not (\"No module named\" in str(err) and \"${module_name}\" in str(err)):
+        raise
+")
+  endforeach()
+
+  # Now install ourselves:
+  install_python_package(pandac SOURCE "${CMAKE_CURRENT_BINARY_DIR}/pandac" LIB COMPONENT Direct)
+endif()
+
+# "Direct" component contains both the Python stuff and the non-Python stuff,
+# because direct has a pretty solid dependency on Python.
+export_targets(Direct NAMESPACE "Panda3D::Direct::" COMPONENT DirectDevel)

+ 115 - 103
direct/src/actor/Actor.py

@@ -5,6 +5,7 @@ __all__ = ['Actor']
 from panda3d.core import *
 from panda3d.core import *
 from panda3d.core import Loader as PandaLoader
 from panda3d.core import Loader as PandaLoader
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
+from direct.showbase.Loader import Loader
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 
 
 
 
@@ -104,45 +105,43 @@ class Actor(DirectObject, NodePath):
                  lodNode = None, flattenable = True, setFinal = False,
                  lodNode = None, flattenable = True, setFinal = False,
                  mergeLODBundles = None, allowAsyncBind = None,
                  mergeLODBundles = None, allowAsyncBind = None,
                  okMissing = None):
                  okMissing = None):
-        """__init__(self, string | string:string{}, string:string{} |
-        string:(string:string{}){}, Actor=None)
-        Actor constructor: can be used to create single or multipart
+        """Actor constructor: can be used to create single or multipart
         actors. If another Actor is supplied as an argument this
         actors. If another Actor is supplied as an argument this
         method acts like a copy constructor. Single part actors are
         method acts like a copy constructor. Single part actors are
         created by calling with a model and animation dictionary
         created by calling with a model and animation dictionary
-        (animName:animPath{}) as follows:
+        ``(animName:animPath{})`` as follows::
 
 
-           a = Actor("panda-3k.egg", {"walk":"panda-walk.egg" \
+           a = Actor("panda-3k.egg", {"walk":"panda-walk.egg",
                                       "run":"panda-run.egg"})
                                       "run":"panda-run.egg"})
 
 
-        This could be displayed and animated as such:
+        This could be displayed and animated as such::
 
 
            a.reparentTo(render)
            a.reparentTo(render)
            a.loop("walk")
            a.loop("walk")
            a.stop()
            a.stop()
 
 
         Multipart actors expect a dictionary of parts and a dictionary
         Multipart actors expect a dictionary of parts and a dictionary
-        of animation dictionaries (partName:(animName:animPath{}){}) as
-        below:
+        of animation dictionaries ``(partName:(animName:animPath{}){})``
+        as below::
 
 
             a = Actor(
             a = Actor(
 
 
                 # part dictionary
                 # part dictionary
-                {"head":"char/dogMM/dogMM_Shorts-head-mod", \
-                 "torso":"char/dogMM/dogMM_Shorts-torso-mod", \
-                 "legs":"char/dogMM/dogMM_Shorts-legs-mod"}, \
+                {"head": "char/dogMM/dogMM_Shorts-head-mod",
+                 "torso": "char/dogMM/dogMM_Shorts-torso-mod",
+                 "legs": "char/dogMM/dogMM_Shorts-legs-mod"},
 
 
                 # dictionary of anim dictionaries
                 # dictionary of anim dictionaries
-                {"head":{"walk":"char/dogMM/dogMM_Shorts-head-walk", \
-                         "run":"char/dogMM/dogMM_Shorts-head-run"}, \
-                 "torso":{"walk":"char/dogMM/dogMM_Shorts-torso-walk", \
-                          "run":"char/dogMM/dogMM_Shorts-torso-run"}, \
-                 "legs":{"walk":"char/dogMM/dogMM_Shorts-legs-walk", \
-                         "run":"char/dogMM/dogMM_Shorts-legs-run"} \
+                {"head":{"walk": "char/dogMM/dogMM_Shorts-head-walk",
+                         "run": "char/dogMM/dogMM_Shorts-head-run"},
+                 "torso":{"walk": "char/dogMM/dogMM_Shorts-torso-walk",
+                          "run": "char/dogMM/dogMM_Shorts-torso-run"},
+                 "legs":{"walk": "char/dogMM/dogMM_Shorts-legs-walk",
+                         "run": "char/dogMM/dogMM_Shorts-legs-run"}
                  })
                  })
 
 
         In addition multipart actor parts need to be connected together
         In addition multipart actor parts need to be connected together
-        in a meaningful fashion:
+        in a meaningful fashion::
 
 
             a.attach("head", "torso", "joint-head")
             a.attach("head", "torso", "joint-head")
             a.attach("torso", "legs", "joint-hips")
             a.attach("torso", "legs", "joint-hips")
@@ -151,7 +150,7 @@ class Actor(DirectObject, NodePath):
         # ADD LOD COMMENT HERE!
         # ADD LOD COMMENT HERE!
         #
         #
 
 
-        Other useful Actor class functions:
+        Other useful Actor class functions::
 
 
             #fix actor eye rendering
             #fix actor eye rendering
             a.drawInFront("joint-pupil?", "eyes*")
             a.drawInFront("joint-pupil?", "eyes*")
@@ -213,7 +212,7 @@ class Actor(DirectObject, NodePath):
         self.__LODCenter = Point3(0, 0, 0)
         self.__LODCenter = Point3(0, 0, 0)
         self.switches = None
         self.switches = None
 
 
-        if (other == None):
+        if other is None:
             # act like a normal constructor
             # act like a normal constructor
 
 
             # create base hierarchy
             # create base hierarchy
@@ -353,7 +352,7 @@ class Actor(DirectObject, NodePath):
             self.gotName = other.gotName
             self.gotName = other.gotName
 
 
             # copy the scene graph elements of other
             # copy the scene graph elements of other
-            if (overwrite):
+            if overwrite:
                 otherCopy = other.copyTo(NodePath())
                 otherCopy = other.copyTo(NodePath())
                 otherCopy.detachNode()
                 otherCopy.detachNode()
                 # assign these elements to ourselve (overwrite)
                 # assign these elements to ourselve (overwrite)
@@ -373,7 +372,7 @@ class Actor(DirectObject, NodePath):
             self.switches = other.switches
             self.switches = other.switches
             self.__LODNode = self.find('**/+LODNode')
             self.__LODNode = self.find('**/+LODNode')
             self.__hasLOD = 0
             self.__hasLOD = 0
-            if (not self.__LODNode.isEmpty()):
+            if not self.__LODNode.isEmpty():
                 self.__hasLOD = 1
                 self.__hasLOD = 1
 
 
 
 
@@ -419,7 +418,7 @@ class Actor(DirectObject, NodePath):
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
 
 
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef == None:
+        if partDef is None:
             Actor.notify.error("no part named: %s" % (partName))
             Actor.notify.error("no part named: %s" % (partName))
 
 
         self.__doListJoints(0, partDef.getBundle(),
         self.__doListJoints(0, partDef.getBundle(),
@@ -492,7 +491,7 @@ class Actor(DirectObject, NodePath):
                 for animName, file, animControl in animInfo:
                 for animName, file, animControl in animInfo:
                     print('    Anim: %s' % animName)
                     print('    Anim: %s' % animName)
                     print('      File: %s' % file)
                     print('      File: %s' % file)
-                    if animControl == None:
+                    if animControl is None:
                         print(' (not loaded)')
                         print(' (not loaded)')
                     else:
                     else:
                         print('      NumFrames: %d PlayRate: %0.2f' %
                         print('      NumFrames: %d PlayRate: %0.2f' %
@@ -501,7 +500,12 @@ class Actor(DirectObject, NodePath):
 
 
     def cleanup(self):
     def cleanup(self):
         """
         """
-        Actor cleanup function
+        This method should be called when intending to destroy the Actor, and
+        cleans up any additional resources stored on the Actor class before
+        removing the underlying node using `removeNode()`.
+
+        Note that `removeNode()` itself is not sufficient to destroy actors,
+        which is why this method exists.
         """
         """
         self.stop(None)
         self.stop(None)
         self.clearPythonData()
         self.clearPythonData()
@@ -513,6 +517,11 @@ class Actor(DirectObject, NodePath):
             self.removeNode()
             self.removeNode()
 
 
     def removeNode(self):
     def removeNode(self):
+        """
+        You should call `cleanup()` for Actor objects instead, since
+        :meth:`~panda3d.core.NodePath.removeNode()` is not sufficient for
+        completely destroying Actor objects.
+        """
         if self.__geomNode and (self.__geomNode.getNumChildren() > 0):
         if self.__geomNode and (self.__geomNode.getNumChildren() > 0):
             assert self.notify.warning("called actor.removeNode() on %s without calling cleanup()" % self.getName())
             assert self.notify.warning("called actor.removeNode() on %s without calling cleanup()" % self.getName())
         NodePath.removeNode(self)
         NodePath.removeNode(self)
@@ -526,7 +535,7 @@ class Actor(DirectObject, NodePath):
 
 
     def flush(self):
     def flush(self):
         """
         """
-        Actor flush function
+        Actor flush function.  Used by `cleanup()`.
         """
         """
         self.clearPythonData()
         self.clearPythonData()
 
 
@@ -558,14 +567,14 @@ class Actor(DirectObject, NodePath):
         bundles = []
         bundles = []
 
 
         for lodName, partBundleDict in self.__partBundleDict.items():
         for lodName, partBundleDict in self.__partBundleDict.items():
-            if partName == None:
+            if partName is None:
                 for partDef in partBundleDict.values():
                 for partDef in partBundleDict.values():
                     bundles.append(partDef.getBundle())
                     bundles.append(partDef.getBundle())
 
 
             else:
             else:
                 subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
                 subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
                 partDef = partBundleDict.get(subpartDef.truePartName)
                 partDef = partBundleDict.get(subpartDef.truePartName)
-                if partDef != None:
+                if partDef is not None:
                     bundles.append(partDef.getBundle())
                     bundles.append(partDef.getBundle())
                 else:
                 else:
                     Actor.notify.warning("Couldn't find part: %s" % (partName))
                     Actor.notify.warning("Couldn't find part: %s" % (partName))
@@ -636,7 +645,7 @@ class Actor(DirectObject, NodePath):
         Set the node that switches actor geometry in and out.
         Set the node that switches actor geometry in and out.
         If one is not supplied as an argument, make one
         If one is not supplied as an argument, make one
         """
         """
-        if (node == None):
+        if node is None:
             node = LODNode.makeDefaultLod("lod")
             node = LODNode.makeDefaultLod("lod")
 
 
         if self.__LODNode:
         if self.__LODNode:
@@ -686,7 +695,7 @@ class Actor(DirectObject, NodePath):
         self.switches[lodName] = [inDist, outDist]
         self.switches[lodName] = [inDist, outDist]
         # add the switch distance info
         # add the switch distance info
         self.__LODNode.node().addSwitch(inDist, outDist)
         self.__LODNode.node().addSwitch(inDist, outDist)
-        if center != None:
+        if center is not None:
             self.setCenter(center)
             self.setCenter(center)
 
 
     def setLOD(self, lodName, inDist=0, outDist=0):
     def setLOD(self, lodName, inDist=0, outDist=0):
@@ -725,7 +734,7 @@ class Actor(DirectObject, NodePath):
         return self.__hasLOD
         return self.__hasLOD
 
 
     def setCenter(self, center):
     def setCenter(self, center):
-        if center == None:
+        if center is None:
             center = Point3(0, 0, 0)
             center = Point3(0, 0, 0)
         self.__LODCenter = center
         self.__LODCenter = center
         if self.__LODNode:
         if self.__LODNode:
@@ -783,7 +792,7 @@ class Actor(DirectObject, NodePath):
         Returns True if any joint has changed as a result of this,
         Returns True if any joint has changed as a result of this,
         False otherwise. """
         False otherwise. """
 
 
-        if lodName == None:
+        if lodName is None:
             lodNames = self.getLODNames()
             lodNames = self.getLODNames()
         else:
         else:
             lodNames = [lodName]
             lodNames = [lodName]
@@ -791,7 +800,7 @@ class Actor(DirectObject, NodePath):
         anyChanged = False
         anyChanged = False
         if lod < len(lodNames):
         if lod < len(lodNames):
             lodName = lodNames[lod]
             lodName = lodNames[lod]
-            if partName == None:
+            if partName is None:
                 partBundleDict = self.__partBundleDict[lodName]
                 partBundleDict = self.__partBundleDict[lodName]
                 partNames = list(partBundleDict.keys())
                 partNames = list(partBundleDict.keys())
             else:
             else:
@@ -907,11 +916,11 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
-        if partName == None:
+        if partName is None:
             partName, animDict = next(iter(animControlDict.items()))
             partName, animDict = next(iter(animControlDict.items()))
         else:
         else:
             animDict = animControlDict.get(partName)
             animDict = animControlDict.get(partName)
-            if animDict == None:
+            if animDict is None:
                 # part was not present
                 # part was not present
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 return None
                 return None
@@ -932,11 +941,11 @@ class Actor(DirectObject, NodePath):
         in dictionary.  NOTE: only returns info for an arbitrary LOD
         in dictionary.  NOTE: only returns info for an arbitrary LOD
         """
         """
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
         lodName, animControlDict = next(iter(self.__animControlDict.items()))
-        if partName == None:
+        if partName is None:
             partName, animDict = next(iter(animControlDict.items()))
             partName, animDict = next(iter(animControlDict.items()))
         else:
         else:
             animDict = animControlDict.get(partName)
             animDict = animControlDict.get(partName)
-            if animDict == None:
+            if animDict is None:
                 # part was not present
                 # part was not present
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 Actor.notify.warning("couldn't find part: %s" % (partName))
                 return None
                 return None
@@ -970,7 +979,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef != None:
+        if partDef is not None:
             return partDef.partBundleNP
             return partDef.partBundleNP
         return None
         return None
 
 
@@ -985,7 +994,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
         partDef = partBundleDict.get(subpartDef.truePartName)
-        if partDef != None:
+        if partDef is not None:
             return partDef.getBundle()
             return partDef.getBundle()
         return None
         return None
 
 
@@ -1002,9 +1011,9 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         # remove the part
         # remove the part
-        if (partName in partBundleDict):
+        if partName in partBundleDict:
             partBundleDict[partName].partBundleNP.removeNode()
             partBundleDict[partName].partBundleNP.removeNode()
-            del(partBundleDict[partName])
+            del partBundleDict[partName]
 
 
         # find the corresponding anim control dict
         # find the corresponding anim control dict
         if self.mergeLODBundles:
         if self.mergeLODBundles:
@@ -1015,8 +1024,8 @@ class Actor(DirectObject, NodePath):
             return
             return
 
 
         # remove the animations
         # remove the animations
-        if (partName in partDict):
-            del(partDict[partName])
+        if partName in partDict:
+            del partDict[partName]
 
 
     def hidePart(self, partName, lodName="lodRoot"):
     def hidePart(self, partName, lodName="lodRoot"):
         """
         """
@@ -1094,7 +1103,7 @@ class Actor(DirectObject, NodePath):
         if node is None:
         if node is None:
             node = partDef.partBundleNP.attachNewNode(jointName)
             node = partDef.partBundleNP.attachNewNode(jointName)
 
 
-        if (joint):
+        if joint:
             if localTransform:
             if localTransform:
                 joint.addLocalTransform(node.node())
                 joint.addLocalTransform(node.node())
             else:
             else:
@@ -1126,7 +1135,7 @@ 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 (joint):
+        if joint:
             joint.clearNetTransforms()
             joint.clearNetTransforms()
             joint.clearLocalTransforms()
             joint.clearLocalTransforms()
         else:
         else:
@@ -1135,16 +1144,16 @@ class Actor(DirectObject, NodePath):
     def getJoints(self, partName = None, jointName = '*', lodName = None):
     def getJoints(self, partName = None, jointName = '*', lodName = None):
         """ Returns the list of all joints, from the named part or
         """ Returns the list of all joints, from the named part or
         from all parts, that match the indicated jointName.  The
         from all parts, that match the indicated jointName.  The
-        jointName may include pattern characters like *. """
+        jointName may include pattern characters like \\*. """
 
 
         joints=[]
         joints=[]
         pattern = GlobPattern(jointName)
         pattern = GlobPattern(jointName)
 
 
-        if lodName == None and self.mergeLODBundles:
+        if lodName is None and self.mergeLODBundles:
             # Get the common bundle.
             # Get the common bundle.
             partBundleDicts = [self.__commonBundleHandles]
             partBundleDicts = [self.__commonBundleHandles]
 
 
-        elif lodName == None:
+        elif lodName is None:
             # Get all LOD's.
             # Get all LOD's.
             partBundleDicts = self.__partBundleDict.values()
             partBundleDicts = self.__partBundleDict.values()
         else:
         else:
@@ -1233,7 +1242,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
 
 
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
-        if joint == None:
+        if joint is None:
             Actor.notify.warning("no joint named %s!" % (jointName))
             Actor.notify.warning("no joint named %s!" % (jointName))
             return None
             return None
         return joint.getDefaultValue()
         return joint.getDefaultValue()
@@ -1253,7 +1262,7 @@ class Actor(DirectObject, NodePath):
             return None
             return None
 
 
         joint = bundle.findChild(jointName)
         joint = bundle.findChild(jointName)
-        if joint == None:
+        if joint is None:
             Actor.notify.warning("no joint named %s!" % (jointName))
             Actor.notify.warning("no joint named %s!" % (jointName))
             return None
             return None
         return joint.getTransformState()
         return joint.getTransformState()
@@ -1278,7 +1287,7 @@ class Actor(DirectObject, NodePath):
         anyGood = False
         anyGood = False
         for bundleDict in self.__partBundleDict.values():
         for bundleDict in self.__partBundleDict.values():
             bundle = bundleDict[trueName].getBundle()
             bundle = bundleDict[trueName].getBundle()
-            if node == None:
+            if node is None:
                 node = self.attachNewNode(ModelNode(jointName))
                 node = self.attachNewNode(ModelNode(jointName))
                 joint = bundle.findChild(jointName)
                 joint = bundle.findChild(jointName)
                 if joint and isinstance(joint, MovingPartMatrix):
                 if joint and isinstance(joint, MovingPartMatrix):
@@ -1300,7 +1309,7 @@ class Actor(DirectObject, NodePath):
         optimal than controlJoint() for cases in which the transform
         optimal than controlJoint() for cases in which the transform
         is not intended to be animated during the lifetime of the
         is not intended to be animated during the lifetime of the
         Actor. """
         Actor. """
-        if transform == None:
+        if transform is None:
             transform = TransformState.makePosHprScale(pos, hpr, scale)
             transform = TransformState.makePosHprScale(pos, hpr, scale)
 
 
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
@@ -1331,7 +1340,7 @@ class Actor(DirectObject, NodePath):
             partDef = partBundleDict.get(subpartDef.truePartName)
             partDef = partBundleDict.get(subpartDef.truePartName)
             if partDef:
             if partDef:
                 joint = partDef.partBundleNP.find("**/" + jointName)
                 joint = partDef.partBundleNP.find("**/" + jointName)
-                if (joint.isEmpty()):
+                if joint.isEmpty():
                     Actor.notify.warning("%s not found!" % (jointName))
                     Actor.notify.warning("%s not found!" % (jointName))
                 else:
                 else:
                     return path.instanceTo(joint)
                     return path.instanceTo(joint)
@@ -1351,7 +1360,7 @@ class Actor(DirectObject, NodePath):
                 anotherPartDef = partBundleDict.get(anotherPartName)
                 anotherPartDef = partBundleDict.get(anotherPartName)
                 if anotherPartDef:
                 if anotherPartDef:
                     joint = anotherPartDef.partBundleNP.find("**/" + jointName)
                     joint = anotherPartDef.partBundleNP.find("**/" + jointName)
-                    if (joint.isEmpty()):
+                    if joint.isEmpty():
                         Actor.notify.warning("%s not found!" % (jointName))
                         Actor.notify.warning("%s not found!" % (jointName))
                     else:
                     else:
                         partDef.partBundleNP.reparentTo(joint)
                         partDef.partBundleNP.reparentTo(joint)
@@ -1396,10 +1405,10 @@ class Actor(DirectObject, NodePath):
         root under the given lod.
         root under the given lod.
         """
         """
         # check to see if we are working within an lod
         # check to see if we are working within an lod
-        if lodName != None:
+        if lodName is not None:
             # find the named lod node
             # find the named lod node
             lodRoot = self.__LODNode.find(str(lodName))
             lodRoot = self.__LODNode.find(str(lodName))
-            if root == None:
+            if root is None:
                 # no need to look further
                 # no need to look further
                 root = lodRoot
                 root = lodRoot
             else:
             else:
@@ -1407,7 +1416,7 @@ class Actor(DirectObject, NodePath):
                 root = lodRoot.find("**/" + root)
                 root = lodRoot.find("**/" + root)
         else:
         else:
             # start search from self if no root and no lod given
             # start search from self if no root and no lod given
-            if root == None:
+            if root is None:
                 root = self
                 root = self
 
 
         frontParts = root.findAllMatches("**/" + frontPartName)
         frontParts = root.findAllMatches("**/" + frontPartName)
@@ -1427,7 +1436,7 @@ class Actor(DirectObject, NodePath):
 
 
         # Find the back part.
         # Find the back part.
         backPart = root.find("**/" + backPartName)
         backPart = root.find("**/" + backPartName)
-        if (backPart.isEmpty()):
+        if backPart.isEmpty():
             Actor.notify.warning("no part named %s!" % (backPartName))
             Actor.notify.warning("no part named %s!" % (backPartName))
             return
             return
 
 
@@ -1443,7 +1452,7 @@ class Actor(DirectObject, NodePath):
 
 
 
 
     def fixBounds(self, partName = None):
     def fixBounds(self, partName = None):
-        if(partName == None):
+        if partName is None:
             #iterate through everything
             #iterate through everything
             for lodData in self.__partBundleDict.values():
             for lodData in self.__partBundleDict.values():
                 for partData in lodData.values():
                 for partData in lodData.values():
@@ -1474,7 +1483,7 @@ class Actor(DirectObject, NodePath):
         in this actor
         in this actor
         """
         """
         # if no part name specified fix all parts
         # if no part name specified fix all parts
-        if (part==None):
+        if part is None:
             part = self
             part = self
 
 
         # update all characters first
         # update all characters first
@@ -1532,12 +1541,12 @@ class Actor(DirectObject, NodePath):
         Play the given animation on the given part of the actor.
         Play the given animation on the given part of the actor.
         If no part is specified, try to play on all parts. NOTE:
         If no part is specified, try to play on all parts. NOTE:
         plays over ALL LODs"""
         plays over ALL LODs"""
-        if fromFrame == None:
+        if fromFrame is None:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
                 control.play()
                 control.play()
         else:
         else:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
-                if toFrame == None:
+                if toFrame is None:
                     control.play(fromFrame, control.getNumFrames() - 1)
                     control.play(fromFrame, control.getNumFrames() - 1)
                 else:
                 else:
                     control.play(fromFrame, toFrame)
                     control.play(fromFrame, toFrame)
@@ -1551,12 +1560,12 @@ class Actor(DirectObject, NodePath):
         all LOD's
         all LOD's
         """
         """
 
 
-        if fromFrame == None:
+        if fromFrame is None:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
                 control.loop(restart)
                 control.loop(restart)
         else:
         else:
             for control in self.getAnimControls(animName, partName):
             for control in self.getAnimControls(animName, partName):
-                if toFrame == None:
+                if toFrame is None:
                     control.loop(restart, fromFrame, control.getNumFrames() - 1)
                     control.loop(restart, fromFrame, control.getNumFrames() - 1)
                 else:
                 else:
                     control.loop(restart, fromFrame, toFrame)
                     control.loop(restart, fromFrame, toFrame)
@@ -1568,11 +1577,11 @@ class Actor(DirectObject, NodePath):
         restarting at zero frame if requested. If no part name
         restarting at zero frame if requested. If no part name
         is given then try to loop on all parts. NOTE: loops on
         is given then try to loop on all parts. NOTE: loops on
         all LOD's"""
         all LOD's"""
-        if fromFrame == None:
+        if fromFrame is None:
             fromFrame = 0
             fromFrame = 0
 
 
         for control in self.getAnimControls(animName, partName):
         for control in self.getAnimControls(animName, partName):
-            if toFrame == None:
+            if toFrame is None:
                 control.pingpong(restart, fromFrame, control.getNumFrames() - 1)
                 control.pingpong(restart, fromFrame, control.getNumFrames() - 1)
             else:
             else:
                 control.pingpong(restart, fromFrame, toFrame)
                 control.pingpong(restart, fromFrame, toFrame)
@@ -1624,11 +1633,11 @@ class Actor(DirectObject, NodePath):
         Config.prc variable.
         Config.prc variable.
         """
         """
         for bundle in self.getPartBundles(partName = partName):
         for bundle in self.getPartBundles(partName = partName):
-            if blendType != None:
+            if blendType is not None:
                 bundle.setBlendType(blendType)
                 bundle.setBlendType(blendType)
-            if animBlend != None:
+            if animBlend is not None:
                 bundle.setAnimBlendFlag(animBlend)
                 bundle.setAnimBlendFlag(animBlend)
-            if frameBlend != None:
+            if frameBlend is not None:
                 bundle.setFrameBlendFlag(frameBlend)
                 bundle.setFrameBlendFlag(frameBlend)
 
 
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
@@ -1707,15 +1716,15 @@ class Actor(DirectObject, NodePath):
 
 
         partDict = self.__animControlDict.get(lodName)
         partDict = self.__animControlDict.get(lodName)
         # if this assertion fails, named lod was not present
         # if this assertion fails, named lod was not present
-        assert partDict != None
+        assert partDict is not None
 
 
         animDict = partDict.get(partName)
         animDict = partDict.get(partName)
-        if animDict == None:
+        if animDict is None:
             # part was not present
             # part was not present
             Actor.notify.warning("couldn't find part: %s" % (partName))
             Actor.notify.warning("couldn't find part: %s" % (partName))
         else:
         else:
             anim = animDict.get(animName)
             anim = animDict.get(animName)
-            if anim == None:
+            if anim is None:
                 # anim was not present
                 # anim was not present
                 assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                 assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                 pass
                 pass
@@ -1751,7 +1760,7 @@ class Actor(DirectObject, NodePath):
         If lodName is None or omitted, all LOD's are returned.
         If lodName is None or omitted, all LOD's are returned.
         """
         """
 
 
-        if partName == None and self.__subpartsComplete:
+        if partName is None and self.__subpartsComplete:
             # If we have the __subpartsComplete flag, and no partName
             # If we have the __subpartsComplete flag, and no partName
             # is specified, it really means to play the animation on
             # is specified, it really means to play the animation on
             # all subparts, not on the overall Actor.
             # all subparts, not on the overall Actor.
@@ -1760,12 +1769,12 @@ class Actor(DirectObject, NodePath):
         controls = []
         controls = []
         # build list of lodNames and corresponding animControlDicts
         # build list of lodNames and corresponding animControlDicts
         # requested.
         # requested.
-        if lodName == None or self.mergeLODBundles:
+        if lodName is None or self.mergeLODBundles:
             # Get all LOD's
             # Get all LOD's
             animControlDictItems = self.__animControlDict.items()
             animControlDictItems = self.__animControlDict.items()
         else:
         else:
             partDict = self.__animControlDict.get(lodName)
             partDict = self.__animControlDict.get(lodName)
-            if partDict == None:
+            if partDict is None:
                 Actor.notify.warning("couldn't find lod: %s" % (lodName))
                 Actor.notify.warning("couldn't find lod: %s" % (lodName))
                 animControlDictItems = []
                 animControlDictItems = []
             else:
             else:
@@ -1774,7 +1783,7 @@ class Actor(DirectObject, NodePath):
         for lodName, partDict in animControlDictItems:
         for lodName, partDict in animControlDictItems:
             # Now, build the list of partNames and the corresponding
             # Now, build the list of partNames and the corresponding
             # animDicts.
             # animDicts.
-            if partName == None:
+            if partName is None:
                 # Get all main parts, but not sub-parts.
                 # Get all main parts, but not sub-parts.
                 animDictItems = []
                 animDictItems = []
                 for thisPart, animDict in partDict.items():
                 for thisPart, animDict in partDict.items():
@@ -1792,14 +1801,14 @@ class Actor(DirectObject, NodePath):
 
 
                 for pName in partNameList:
                 for pName in partNameList:
                     animDict = partDict.get(pName)
                     animDict = partDict.get(pName)
-                    if animDict == None:
+                    if animDict is None:
                         # Maybe it's a subpart that hasn't been bound yet.
                         # Maybe it's a subpart that hasn't been bound yet.
                         subpartDef = self.__subpartDict.get(pName)
                         subpartDef = self.__subpartDict.get(pName)
                         if subpartDef:
                         if subpartDef:
                             animDict = {}
                             animDict = {}
                             partDict[pName] = animDict
                             partDict[pName] = animDict
 
 
-                    if animDict == None:
+                    if animDict is None:
                         # part was not present
                         # part was not present
                         Actor.notify.warning("couldn't find part: %s" % (pName))
                         Actor.notify.warning("couldn't find part: %s" % (pName))
                     else:
                     else:
@@ -1825,7 +1834,7 @@ class Actor(DirectObject, NodePath):
                         names = animDict.keys()
                         names = animDict.keys()
                     for animName in names:
                     for animName in names:
                         anim = animDict.get(animName)
                         anim = animDict.get(animName)
-                        if anim == None and partName != None:
+                        if anim is None and partName is not None:
                             for pName in partNameList:
                             for pName in partNameList:
                                 # Maybe it's a subpart that hasn't been bound yet.
                                 # Maybe it's a subpart that hasn't been bound yet.
                                 subpartDef = self.__subpartDict.get(pName)
                                 subpartDef = self.__subpartDict.get(pName)
@@ -1836,14 +1845,14 @@ class Actor(DirectObject, NodePath):
                                         anim = anim.makeCopy()
                                         anim = anim.makeCopy()
                                         animDict[animName] = anim
                                         animDict[animName] = anim
 
 
-                        if anim == None:
+                        if anim is None:
                             # anim was not present
                             # anim was not present
                             assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                             assert Actor.notify.debug("couldn't find anim: %s" % (animName))
                             pass
                             pass
                         else:
                         else:
                             # bind the animation first if we need to
                             # bind the animation first if we need to
                             animControl = anim.animControl
                             animControl = anim.animControl
-                            if animControl == None:
+                            if animControl is None:
                                 animControl = self.__bindAnimToPart(
                                 animControl = self.__bindAnimToPart(
                                     animName, thisPart, lodName,
                                     animName, thisPart, lodName,
                                     allowAsyncBind = allowAsyncBind)
                                     allowAsyncBind = allowAsyncBind)
@@ -1870,7 +1879,7 @@ class Actor(DirectObject, NodePath):
         if isinstance(modelPath, NodePath):
         if isinstance(modelPath, NodePath):
             # If we got a NodePath instead of a string, use *that* as
             # If we got a NodePath instead of a string, use *that* as
             # the model directly.
             # the model directly.
-            if (copy):
+            if copy:
                 model = modelPath.copyTo(NodePath())
                 model = modelPath.copyTo(NodePath())
             else:
             else:
                 model = modelPath
                 model = modelPath
@@ -1888,6 +1897,9 @@ class Actor(DirectObject, NodePath):
                 else:
                 else:
                     loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFReportErrors)
                     loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFReportErrors)
 
 
+            # Ensure that custom Python loader hooks are initialized.
+            Loader._loadPythonFileTypes()
+
             # Pass loaderOptions to specify that we want to
             # Pass loaderOptions to specify that we want to
             # get the skeleton model.  This only matters to model
             # get the skeleton model.  This only matters to model
             # files (like .mb) for which we can choose to extract
             # files (like .mb) for which we can choose to extract
@@ -1896,15 +1908,15 @@ class Actor(DirectObject, NodePath):
             if model is not None:
             if model is not None:
                 model = NodePath(model)
                 model = NodePath(model)
 
 
-        if (model == None):
+        if model is None:
             raise IOError("Could not load Actor model %s" % (modelPath))
             raise IOError("Could not load Actor model %s" % (modelPath))
 
 
-        if (model.node().isOfType(Character.getClassType())):
+        if model.node().isOfType(Character.getClassType()):
             bundleNP = model
             bundleNP = model
         else:
         else:
             bundleNP = model.find("**/+Character")
             bundleNP = model.find("**/+Character")
 
 
-        if (bundleNP.isEmpty()):
+        if bundleNP.isEmpty():
             Actor.notify.warning("%s is not a character!" % (modelPath))
             Actor.notify.warning("%s is not a character!" % (modelPath))
             model.reparentTo(self.__geomNode)
             model.reparentTo(self.__geomNode)
         else:
         else:
@@ -1921,7 +1933,7 @@ class Actor(DirectObject, NodePath):
             # Now extract out the Character and integrate it with
             # Now extract out the Character and integrate it with
             # the Actor.
             # the Actor.
 
 
-            if (lodName!="lodRoot"):
+            if lodName != "lodRoot":
                 # parent to appropriate node under LOD switch
                 # parent to appropriate node under LOD switch
                 bundleNP.reparentTo(self.__LODNode.find(str(lodName)))
                 bundleNP.reparentTo(self.__LODNode.find(str(lodName)))
             else:
             else:
@@ -1963,7 +1975,7 @@ class Actor(DirectObject, NodePath):
             self.gotName = 1
             self.gotName = 1
 
 
         bundleDict = self.__partBundleDict.get(lodName, None)
         bundleDict = self.__partBundleDict.get(lodName, None)
-        if bundleDict == None:
+        if bundleDict is None:
             # make a dictionary to store these parts in
             # make a dictionary to store these parts in
             bundleDict = {}
             bundleDict = {}
             self.__partBundleDict[lodName] = bundleDict
             self.__partBundleDict[lodName] = bundleDict
@@ -2252,7 +2264,7 @@ class Actor(DirectObject, NodePath):
             for lodName in lodNames:
             for lodName in lodNames:
                 for partName in partNames:
                 for partName in partNames:
                     for animDef in self.__animControlDict[lodName][partName].values():
                     for animDef in self.__animControlDict[lodName][partName].values():
-                        if animDef.animControl != None:
+                        if animDef.animControl is not None:
                             # Try to clear any control effects before we let
                             # Try to clear any control effects before we let
                             # our handle on them go. This is especially
                             # our handle on them go. This is especially
                             # important if the anim control was blending
                             # important if the anim control was blending
@@ -2264,12 +2276,12 @@ class Actor(DirectObject, NodePath):
                 for partName in partNames:
                 for partName in partNames:
                     for anim in anims:
                     for anim in anims:
                         animDef = self.__animControlDict[lodName][partName].get(anim)
                         animDef = self.__animControlDict[lodName][partName].get(anim)
-                        if animDef and animDef.animControl != None:
+                        if animDef and animDef.animControl is not None:
                             # Try to clear any control effects before we let
                             # Try to clear any control effects before we let
                             # our handle on them go. This is especially
                             # our handle on them go. This is especially
                             # important if the anim control was blending
                             # important if the anim control was blending
                             # animations.
                             # animations.
-                            animDef.animControl.getPart().clearControlEffects()
+                            animDef.animControl.getPart().setControlEffect(animDef.animControl, 0.0)
                             animDef.animControl = None
                             animDef.animControl = None
 
 
 
 
@@ -2327,19 +2339,19 @@ class Actor(DirectObject, NodePath):
 
 
         partDict = self.__animControlDict[lodName]
         partDict = self.__animControlDict[lodName]
         animDict = partDict.get(partName)
         animDict = partDict.get(partName)
-        if animDict == None:
+        if animDict is None:
             # It must be a subpart that hasn't been bound yet.
             # It must be a subpart that hasn't been bound yet.
             animDict = {}
             animDict = {}
             partDict[partName] = animDict
             partDict[partName] = animDict
 
 
         anim = animDict.get(animName)
         anim = animDict.get(animName)
-        if anim == None:
+        if anim is None:
             # It must be a subpart that hasn't been bound yet.
             # It must be a subpart that hasn't been bound yet.
             anim = partDict[subpartDef.truePartName].get(animName)
             anim = partDict[subpartDef.truePartName].get(animName)
             anim = anim.makeCopy()
             anim = anim.makeCopy()
             animDict[animName] = anim
             animDict[animName] = anim
 
 
-        if anim == None:
+        if anim is None:
             Actor.notify.error("actor has no animation %s", animName)
             Actor.notify.error("actor has no animation %s", animName)
 
 
         # only bind if not already bound!
         # only bind if not already bound!
@@ -2394,7 +2406,7 @@ class Actor(DirectObject, NodePath):
 
 
                 # find the part in our tree
                 # find the part in our tree
                 bundleNP = partLod.find("**/%s%s"%(Actor.partPrefix,partName))
                 bundleNP = partLod.find("**/%s%s"%(Actor.partPrefix,partName))
-                if (bundleNP != None):
+                if bundleNP is not None:
                     # store the part bundle
                     # store the part bundle
                     self.__prepareBundle(bundleNP, partDef.partModel,
                     self.__prepareBundle(bundleNP, partDef.partModel,
                                          partName, lodName)
                                          partName, lodName)
@@ -2439,15 +2451,15 @@ class Actor(DirectObject, NodePath):
         return ActorInterval.ActorInterval(self, *args, **kw)
         return ActorInterval.ActorInterval(self, *args, **kw)
 
 
     def getAnimBlends(self, animName=None, partName=None, lodName=None):
     def getAnimBlends(self, animName=None, partName=None, lodName=None):
-        """ Returns a list of the form:
+        """Returns a list of the form::
 
 
-        [ (lodName, [(animName, [(partName, effect), (partName, effect), ...]),
-                     (animName, [(partName, effect), (partName, effect), ...]),
-                     ...]),
-          (lodName, [(animName, [(partName, effect), (partName, effect), ...]),
-                     (animName, [(partName, effect), (partName, effect), ...]),
-                     ...]),
-           ... ]
+           [ (lodName, [(animName, [(partName, effect), (partName, effect), ...]),
+                        (animName, [(partName, effect), (partName, effect), ...]),
+                        ...]),
+             (lodName, [(animName, [(partName, effect), (partName, effect), ...]),
+                        (animName, [(partName, effect), (partName, effect), ...]),
+                        ...]),
+              ... ]
 
 
         This list reports the non-zero control effects for each
         This list reports the non-zero control effects for each
         partName within a particular animation and LOD. """
         partName within a particular animation and LOD. """
@@ -2466,7 +2478,7 @@ class Actor(DirectObject, NodePath):
         else:
         else:
             lodNames = [lodName]
             lodNames = [lodName]
 
 
-        if partName == None and self.__subpartsComplete:
+        if partName is None and self.__subpartsComplete:
             partNames = self.__subpartDict.keys()
             partNames = self.__subpartDict.keys()
         else:
         else:
             partNames = [partName]
             partNames = [partName]

+ 2 - 1
direct/src/cluster/ClusterClient.py

@@ -1,4 +1,4 @@
-"""ClusterClient: Master for mutli-piping or PC clusters.  """
+"""ClusterClient: Master for multi-piping or PC clusters."""
 
 
 from panda3d.core import *
 from panda3d.core import *
 from .ClusterMsgs import *
 from .ClusterMsgs import *
@@ -8,6 +8,7 @@ from direct.showbase import DirectObject
 from direct.task import Task
 from direct.task import Task
 import os
 import os
 
 
+
 class ClusterClient(DirectObject.DirectObject):
 class ClusterClient(DirectObject.DirectObject):
     notify = DirectNotifyGlobal.directNotify.newCategory("ClusterClient")
     notify = DirectNotifyGlobal.directNotify.newCategory("ClusterClient")
     MGR_NUM = 1000000
     MGR_NUM = 1000000

+ 20 - 17
direct/src/cluster/ClusterConfig.py

@@ -1,23 +1,26 @@
 
 
 from .ClusterClient import *
 from .ClusterClient import *
 
 
-# A dictionary of information for various cluster configurations.
-# Dictionary is keyed on cluster-config string
-# Each dictionary contains a list of display configurations, one for
-# each display in the cluster
-# Information that can be specified for each display:
-#      display name: Name of display (used in Configrc to specify server)
-#      display type: Used to flag client vs. server
-#      pos:   positional offset of display's camera from main cluster group
-#      hpr:   orientation offset of display's camera from main cluster group
-#      focal length: display's focal length (in mm)
-#      film size: display's film size (in inches)
-#      film offset: offset of film back (in inches)
-# Note: Note, this overrides offsets specified in DirectCamConfig.py
-# For now we only specify frustum for first display region of configuration
-# TODO: Need to handle multiple display regions per cluster node and to
-# generalize to non cluster situations
-
+#: A dictionary of information for various cluster configurations.
+#: Dictionary is keyed on cluster-config string
+#: Each dictionary contains a list of display configurations, one for
+#: each display in the cluster
+#:
+#: Information that can be specified for each display:
+#:
+#: - display name: Name of display (used in Configrc to specify server)
+#: - display type: Used to flag client vs. server
+#: - pos: positional offset of display's camera from main cluster group
+#: - hpr: orientation offset of display's camera from main cluster group
+#: - focal length: display's focal length (in mm)
+#: - film size: display's film size (in inches)
+#: - film offset: offset of film back (in inches)
+#:
+#: Note: this overrides offsets specified in DirectCamConfig.py
+#: For now we only specify frustum for first display region of configuration
+#:
+#: TODO: Need to handle multiple display regions per cluster node and to
+#: generalize to non cluster situations
 ClientConfigs = {
 ClientConfigs = {
     'single-server':       [{'display name': 'display0',
     'single-server':       [{'display name': 'display0',
                               'display mode': 'client',
                               'display mode': 'client',

+ 16 - 15
direct/src/controls/ControlManager.py

@@ -15,7 +15,9 @@ from direct.directnotify import DirectNotifyGlobal
 from direct.task import Task
 from direct.task import Task
 from panda3d.core import ConfigVariableBool
 from panda3d.core import ConfigVariableBool
 
 
-CollisionHandlerRayStart = 4000.0 # This is a hack, it may be better to use a line instead of a ray.
+# This is a hack, it may be better to use a line instead of a ray.
+CollisionHandlerRayStart = 4000.0
+
 
 
 class ControlManager:
 class ControlManager:
     notify = DirectNotifyGlobal.directNotify.newCategory("ControlManager")
     notify = DirectNotifyGlobal.directNotify.newCategory("ControlManager")
@@ -52,14 +54,14 @@ class ControlManager:
         return 'ControlManager: using \'%s\'' % self.currentControlsName
         return 'ControlManager: using \'%s\'' % self.currentControlsName
 
 
     def add(self, controls, name="basic"):
     def add(self, controls, name="basic"):
-        """
-        controls is an avatar control system.
-        name is any key that you want to use to refer to the
-            the controls later (e.g. using the use(<name>) call).
+        """Add a control instance to the list of available control systems.
 
 
-        Add a control instance to the list of available control systems.
+        Args:
+            controls: an avatar control system.
+            name (str): any key that you want to use to refer to the controls
+                later (e.g. using the use(<name>) call).
 
 
-        See also: use().
+        See also: :meth:`use()`.
         """
         """
         assert self.notify.debugCall(id(self))
         assert self.notify.debugCall(id(self))
         assert controls is not None
         assert controls is not None
@@ -77,15 +79,14 @@ class ControlManager:
         return self.controls.get(name)
         return self.controls.get(name)
 
 
     def remove(self, name):
     def remove(self, name):
-        """
-        name is any key that was used to refer to the
-            the controls when they were added (e.g.
-            using the add(<controls>, <name>) call).
+        """Remove a control instance from the list of available control
+        systems.
 
 
-        Remove a control instance from the list of
-        available control systems.
+        Args:
+            name: any key that was used to refer to the controls when they were
+                added (e.g. using the add(<controls>, <name>) call).
 
 
-        See also: add().
+        See also: :meth:`add()`.
         """
         """
         assert self.notify.debugCall(id(self))
         assert self.notify.debugCall(id(self))
         oldControls = self.controls.pop(name,None)
         oldControls = self.controls.pop(name,None)
@@ -108,7 +109,7 @@ class ControlManager:
 
 
         Use a previously added control system.
         Use a previously added control system.
 
 
-        See also: add().
+        See also: :meth:`add()`.
         """
         """
         assert self.notify.debugCall(id(self))
         assert self.notify.debugCall(id(self))
         if __debug__ and hasattr(self, "ignoreUse"):
         if __debug__ and hasattr(self, "ignoreUse"):

+ 8 - 6
direct/src/controls/DevWalker.py

@@ -2,15 +2,17 @@
 DevWalker.py is for avatars.
 DevWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 
 

+ 8 - 6
direct/src/controls/GhostWalker.py

@@ -2,15 +2,17 @@
 GhostWalker.py is for avatars.
 GhostWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 
 

+ 8 - 6
direct/src/controls/GravityWalker.py

@@ -2,15 +2,17 @@
 GravityWalker.py is for avatars.
 GravityWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify

+ 20 - 18
direct/src/controls/InputState.py

@@ -1,7 +1,6 @@
-
-
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
+from direct.showbase.PythonUtil import SerialNumGen
 
 
 # internal class, don't create these on your own
 # internal class, don't create these on your own
 class InputStateToken:
 class InputStateToken:
@@ -136,14 +135,16 @@ class InputState(DirectObject.DirectObject):
 
 
     def watch(self, name, eventOn, eventOff, startState=False, inputSource=None):
     def watch(self, name, eventOn, eventOff, startState=False, inputSource=None):
         """
         """
-        This returns a token; hold onto the token and call token.release() when you
-        no longer want to watch for these events.
-
-        # set up
-        token = inputState.watch('forward', 'w', 'w-up', inputSource=inputState.WASD)
-         ...
-        # tear down
-        token.release()
+        This returns a token; hold onto the token and call token.release() when
+        you no longer want to watch for these events.
+
+        Example::
+
+            # set up
+            token = inputState.watch('forward', 'w', 'w-up', inputSource=inputState.WASD)
+            ...
+            # tear down
+            token.release()
         """
         """
         assert self.debugPrint(
         assert self.debugPrint(
             "watch(name=%s, eventOn=%s, eventOff=%s, startState=%s)"%(
             "watch(name=%s, eventOn=%s, eventOff=%s, startState=%s)"%(
@@ -192,15 +193,16 @@ class InputState(DirectObject.DirectObject):
         """
         """
         Force isSet(name) to return 'value'.
         Force isSet(name) to return 'value'.
 
 
-        This returns a token; hold onto the token and call token.release() when you
-        no longer want to force the state.
+        This returns a token; hold onto the token and call token.release() when
+        you no longer want to force the state.
+
+        Example::
 
 
-        example:
-        # set up
-        token=inputState.force('forward', True, inputSource='myForwardForcer')
-         ...
-        # tear down
-        token.release()
+            # set up
+            token = inputState.force('forward', True, inputSource='myForwardForcer')
+            ...
+            # tear down
+            token.release()
         """
         """
         token = InputStateForceToken(self)
         token = InputStateForceToken(self)
         self._token2forceInfo[token] = (name, inputSource)
         self._token2forceInfo[token] = (name, inputSource)

+ 8 - 6
direct/src/controls/NonPhysicsWalker.py

@@ -2,15 +2,17 @@
 NonPhysicsWalker.py is for avatars.
 NonPhysicsWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 
 

+ 8 - 6
direct/src/controls/ObserverWalker.py

@@ -2,15 +2,17 @@
 ObserverWalker.py is for avatars.
 ObserverWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 
 

+ 0 - 2
direct/src/controls/PhysicsRoller.py

@@ -1,2 +0,0 @@
-"""PhysicsRoller is for wheels, soccer balls, billiard balls, and other things that roll."""
-

+ 8 - 6
direct/src/controls/PhysicsWalker.py

@@ -2,15 +2,17 @@
 PhysicsWalker.py is for avatars.
 PhysicsWalker.py is for avatars.
 
 
 A walker control such as this one provides:
 A walker control such as this one provides:
-    - creation of the collision nodes
-    - handling the keyboard and mouse input for avatar movement
-    - moving the avatar
+
+- creation of the collision nodes
+- handling the keyboard and mouse input for avatar movement
+- moving the avatar
 
 
 it does not:
 it does not:
-    - play sounds
-    - play animations
 
 
-although it does send messeges that allow a listener to play sounds or
+- play sounds
+- play animations
+
+although it does send messages that allow a listener to play sounds or
 animations based on walker events.
 animations based on walker events.
 """
 """
 
 

+ 1 - 1
direct/src/controls/TwoDWalker.py

@@ -1,5 +1,5 @@
 """
 """
-TwoDWalker.py is for controling the avatars in a 2D Scroller game environment.
+TwoDWalker.py is for controlling the avatars in a 2D scroller game environment.
 """
 """
 
 
 from .GravityWalker import *
 from .GravityWalker import *

+ 3 - 0
direct/src/dcparse/CMakeLists.txt

@@ -0,0 +1,3 @@
+add_executable(p3dcparse dcparse.cxx)
+target_link_libraries(p3dcparse p3direct)
+install(TARGETS p3dcparse EXPORT Direct COMPONENT Direct DESTINATION ${CMAKE_INSTALL_BINDIR})

+ 87 - 0
direct/src/dcparser/CMakeLists.txt

@@ -0,0 +1,87 @@
+set(P3DCPARSER_HEADERS
+  dcAtomicField.h dcAtomicField.I
+  dcClass.h dcClass.I
+  dcDeclaration.h
+  dcField.h dcField.I
+  dcFile.h dcFile.I
+  dcKeyword.h dcKeywordList.h
+  dcLexer.lxx dcLexerDefs.h
+  dcMolecularField.h
+  dcParser.yxx dcParserDefs.h
+  dcSubatomicType.h
+  dcPackData.h dcPackData.I
+  dcPacker.h dcPacker.I
+  dcPackerCatalog.h dcPackerCatalog.I
+  dcPackerInterface.h dcPackerInterface.I
+  dcParameter.h
+  dcClassParameter.h
+  dcArrayParameter.h
+  dcSimpleParameter.h
+  dcSwitchParameter.h
+  dcNumericRange.h dcNumericRange.I
+  dcSwitch.h
+  dcTypedef.h
+  dcbase.h
+  dcindent.h
+  dcmsgtypes.h
+  hashGenerator.h
+  primeNumberGenerator.h
+)
+
+set(P3DCPARSER_SOURCES
+  dcAtomicField.cxx
+  dcClass.cxx
+  dcDeclaration.cxx
+  dcField.cxx
+  dcFile.cxx
+  dcKeyword.cxx
+  dcKeywordList.cxx
+  dcMolecularField.cxx
+  dcSubatomicType.cxx
+  dcPackData.cxx
+  dcPacker.cxx
+  dcPackerCatalog.cxx
+  dcPackerInterface.cxx
+  dcParameter.cxx
+  dcClassParameter.cxx
+  dcArrayParameter.cxx
+  dcSimpleParameter.cxx
+  dcSwitchParameter.cxx
+  dcSwitch.cxx
+  dcTypedef.cxx
+  dcindent.cxx
+  hashGenerator.cxx
+  primeNumberGenerator.cxx
+)
+
+set(P3DCPARSER_IGATEEXT
+  dcClass_ext.cxx dcClass_ext.h
+  dcField_ext.cxx dcField_ext.h
+  dcPacker_ext.cxx dcPacker_ext.h
+)
+
+add_bison_target(dcParser.cxx dcParser.yxx DEFINES dcParser.h PREFIX dcyy)
+add_flex_target(dcLexer.cxx dcLexer.lxx CASE_INSENSITIVE PREFIX dcyy)
+
+# These cannot be interrogated, and are excluded from the composites.
+set(P3DCPARSER_PARSER_SOURCES
+    dcParser.cxx
+    dcLexer.cxx)
+
+composite_sources(p3dcparser P3DCPARSER_SOURCES)
+add_component_library(p3dcparser NOINIT SYMBOL BUILDING_DIRECT_DCPARSER
+  ${P3DCPARSER_HEADERS} ${P3DCPARSER_SOURCES} ${P3DCPARSER_PARSER_SOURCES})
+target_compile_definitions(p3dcparser PUBLIC WITHIN_PANDA)
+target_link_libraries(p3dcparser p3directbase panda)
+target_interrogate(p3dcparser ${P3DCPARSER_HEADERS} ${P3DCPARSER_SOURCES}
+  EXTENSIONS ${P3DCPARSER_IGATEEXT})
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3dcparser
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DCPARSER_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 3 - 3
direct/src/dcparser/dcPackerInterface.I

@@ -253,8 +253,9 @@ do_unpack_int64(const char *buffer) {
                     ((uint64_t)(unsigned char)buffer[4] << 32) |
                     ((uint64_t)(unsigned char)buffer[4] << 32) |
                     ((uint64_t)(unsigned char)buffer[5] << 40) |
                     ((uint64_t)(unsigned char)buffer[5] << 40) |
                     ((uint64_t)(unsigned char)buffer[6] << 48) |
                     ((uint64_t)(unsigned char)buffer[6] << 48) |
-                    ((int64_t)(signed char)buffer[7] << 54));
+                    ((int64_t)(signed char)buffer[7] << 56));
 }
 }
+
 /**
 /**
  *
  *
  */
  */
@@ -295,10 +296,9 @@ do_unpack_uint64(const char *buffer) {
           ((uint64_t)(unsigned char)buffer[4] << 32) |
           ((uint64_t)(unsigned char)buffer[4] << 32) |
           ((uint64_t)(unsigned char)buffer[5] << 40) |
           ((uint64_t)(unsigned char)buffer[5] << 40) |
           ((uint64_t)(unsigned char)buffer[6] << 48) |
           ((uint64_t)(unsigned char)buffer[6] << 48) |
-          ((int64_t)(signed char)buffer[7] << 54));
+          ((uint64_t)(unsigned char)buffer[7] << 56));
 }
 }
 
 
-
 /**
 /**
  *
  *
  */
  */

+ 0 - 1
direct/src/dcparser/dcPackerInterface.h

@@ -16,7 +16,6 @@
 
 
 #include "dcbase.h"
 #include "dcbase.h"
 #include "dcSubatomicType.h"
 #include "dcSubatomicType.h"
-#include "vector_uchar.h"
 
 
 class DCFile;
 class DCFile;
 class DCField;
 class DCField;

+ 0 - 1
direct/src/dcparser/dcParserDefs.h

@@ -16,7 +16,6 @@
 
 
 #include "dcbase.h"
 #include "dcbase.h"
 #include "dcSubatomicType.h"
 #include "dcSubatomicType.h"
-#include "vector_uchar.h"
 
 
 class DCFile;
 class DCFile;
 class DCClass;
 class DCClass;

+ 7 - 3
direct/src/dcparser/dcbase.h

@@ -32,15 +32,16 @@
 #include "pvector.h"
 #include "pvector.h"
 #include "pmap.h"
 #include "pmap.h"
 #include "pset.h"
 #include "pset.h"
+#include "vector_uchar.h"
 
 
 #else  // WITHIN_PANDA
 #else  // WITHIN_PANDA
 
 
-#ifdef WIN32
+#ifdef _MSC_VER
 /* C4786: 255 char debug symbols */
 /* C4786: 255 char debug symbols */
 #pragma warning (disable : 4786)
 #pragma warning (disable : 4786)
 /* C4503: decorated name length exceeded */
 /* C4503: decorated name length exceeded */
 #pragma warning (disable : 4503)
 #pragma warning (disable : 4503)
-#endif  /* WIN32_VC */
+#endif  /* _MSC_VER */
 
 
 #include <iostream>
 #include <iostream>
 #include <fstream>
 #include <fstream>
@@ -53,7 +54,7 @@
 // These header files are needed to compile dcLexer.cxx, the output from flex.
 // These header files are needed to compile dcLexer.cxx, the output from flex.
 // flex doesn't create a perfectly windows-friendly source file right out of
 // flex doesn't create a perfectly windows-friendly source file right out of
 // the box.
 // the box.
-#ifdef WIN32
+#ifdef _WIN32
 #include <io.h>
 #include <io.h>
 #include <malloc.h>
 #include <malloc.h>
 #else
 #else
@@ -81,6 +82,7 @@
 #define nassertr_always(condition, return_value) assert(condition)
 #define nassertr_always(condition, return_value) assert(condition)
 #define nassertv(condition) assert(condition)
 #define nassertv(condition) assert(condition)
 #define nassertv_always(condition) assert(condition)
 #define nassertv_always(condition) assert(condition)
+#define nassert_raise(message) {std::cerr << message << std::endl; abort();}
 
 
 // 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.
@@ -97,8 +99,10 @@ typedef std::string Filename;
 #define pvector std::vector
 #define pvector std::vector
 #define pmap std::map
 #define pmap std::map
 #define pset std::set
 #define pset std::set
+#define vector_uchar std::vector<unsigned char>
 
 
 #include <stdint.h>
 #include <stdint.h>
+#include <string.h>
 
 
 typedef std::ifstream pifstream;
 typedef std::ifstream pifstream;
 typedef std::ofstream pofstream;
 typedef std::ofstream pofstream;

+ 0 - 1
direct/src/dcparser/hashGenerator.h

@@ -16,7 +16,6 @@
 
 
 #include "dcbase.h"
 #include "dcbase.h"
 #include "primeNumberGenerator.h"
 #include "primeNumberGenerator.h"
-#include "vector_uchar.h"
 
 
 /**
 /**
  * This class generates an arbitrary hash number from a sequence of ints.
  * This class generates an arbitrary hash number from a sequence of ints.

+ 24 - 0
direct/src/deadrec/CMakeLists.txt

@@ -0,0 +1,24 @@
+set(P3DEADREC_HEADERS
+  config_deadrec.h
+  smoothMover.h smoothMover.I
+)
+
+set(P3DEADREC_SOURCES
+  config_deadrec.cxx
+  smoothMover.cxx
+)
+
+add_component_library(p3deadrec SYMBOL BUILDING_DIRECT_DEADREC
+  ${P3DEADREC_HEADERS} ${P3DEADREC_SOURCES})
+target_link_libraries(p3deadrec p3directbase panda)
+target_interrogate(p3deadrec ALL)
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3deadrec
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DEADREC_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 14 - 0
direct/src/directbase/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(P3DIRECTBASE_SOURCES
+  directbase.cxx
+)
+
+set(P3DIRECTBASE_HEADERS
+  directbase.h directsymbols.h
+)
+
+# Yes, INTERFACE: don't build it, there's no code!
+add_library(p3directbase INTERFACE)
+target_link_libraries(p3directbase INTERFACE panda)
+
+install(TARGETS p3directbase EXPORT Direct COMPONENT Direct)
+install(FILES ${P3DIRECTBASE_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 1 - 1
direct/src/directdevices/DirectDeviceManager.py

@@ -1,4 +1,4 @@
-""" Class used to create and control vrpn devices """
+"""Class used to create and control VRPN devices."""
 
 
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from panda3d.core import *
 from panda3d.core import *

+ 2 - 2
direct/src/directdevices/DirectFastrak.py

@@ -20,9 +20,9 @@ class DirectFastrak(DirectObject):
     fastrakCount = 0
     fastrakCount = 0
     notify = DirectNotifyGlobal.directNotify.newCategory('DirectFastrak')
     notify = DirectNotifyGlobal.directNotify.newCategory('DirectFastrak')
 
 
-    def __init__(self, device = 'Tracker0', nodePath = base.direct.camera):
+    def __init__(self, device = 'Tracker0', nodePath = None):
         # See if device manager has been initialized
         # See if device manager has been initialized
-        if base.direct.deviceManager == None:
+        if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()
             base.direct.deviceManager = DirectDeviceManager()
 
 
         # Set name
         # Set name

+ 5 - 1
direct/src/directnotify/DirectNotifyGlobal.py

@@ -1,8 +1,12 @@
-"""instantiate global DirectNotify used in Direct"""
+"""Instantiates global DirectNotify used in Direct."""
 
 
 __all__ = ['directNotify', 'giveNotify']
 __all__ = ['directNotify', 'giveNotify']
 
 
 from . import DirectNotify
 from . import DirectNotify
 
 
+#: The global :class:`~.DirectNotify.DirectNotify` object.
 directNotify = DirectNotify.DirectNotify()
 directNotify = DirectNotify.DirectNotify()
+
+#: Shorthand function for adding a DirectNotify category to a given class
+#: object.  Alias of `.DirectNotify.DirectNotify.giveNotify`.
 giveNotify = directNotify.giveNotify
 giveNotify = directNotify.giveNotify

+ 2 - 1
direct/src/directnotify/LoggerGlobal.py

@@ -1,5 +1,6 @@
-"""instantiate global Logger object"""
+"""Instantiates a global :class:`~.Logger.Logger` object."""
 
 
 from . import Logger
 from . import Logger
 
 
+#: Contains a global :class:`~.Logger.Logger` object.
 defaultLogger = Logger.Logger()
 defaultLogger = Logger.Logger()

+ 6 - 6
direct/src/directnotify/Notifier.py

@@ -8,6 +8,7 @@ from panda3d.core import ConfigVariableBool, NotifyCategory, StreamWriter, Notif
 import time
 import time
 import sys
 import sys
 
 
+
 class Notifier:
 class Notifier:
     serverDelta = 0
     serverDelta = 0
 
 
@@ -23,12 +24,11 @@ class Notifier:
 
 
     def __init__(self, name, logger=None):
     def __init__(self, name, logger=None):
         """
         """
-        name is a string
-        logger is a Logger
-
-        Create a new instance of the Notifier class with a given name
-        and an optional Logger class for piping output to. If no logger
-        specified, use the global default
+        Parameters:
+            name (str): a string name given to this Notifier instance.
+            logger (Logger, optional): an optional Logger object for
+                piping output to.  If none is specified, the global
+                :data:`~.LoggerGlobal.defaultLogger` is used.
         """
         """
         self.__name = name
         self.__name = name
 
 

+ 15 - 15
direct/src/directnotify/RotatingLog.py

@@ -1,8 +1,7 @@
-
-
 import os
 import os
 import time
 import time
 
 
+
 class RotatingLog:
 class RotatingLog:
     """
     """
     A file() (or open()) replacement that will automatically open and write
     A file() (or open()) replacement that will automatically open and write
@@ -11,22 +10,23 @@ class RotatingLog:
 
 
     def __init__(self, path="./log_file", hourInterval=24, megabyteLimit=1024):
     def __init__(self, path="./log_file", hourInterval=24, megabyteLimit=1024):
         """
         """
-        path is a full or partial path with file name.
-        hourInterval is the number of hours at which to rotate the file.
-        megabyteLimit is the number of megabytes of file size the log
-            may grow to, after which the log is rotated.  Note: The log
-            file may get a bit larger than limit do to writing out whole
-            lines (last line may exceed megabyteLimit or "megabyteGuidline").
+        Args:
+            path: a full or partial path with file name.
+            hourInterval: the number of hours at which to rotate the file.
+            megabyteLimit: the number of megabytes of file size the log may
+                grow to, after which the log is rotated.  Note: The log file
+                may get a bit larger than limit do to writing out whole lines
+                (last line may exceed megabyteLimit or "megabyteGuidline").
         """
         """
-        self.path=path
-        self.timeInterval=None
-        self.timeLimit=None
-        self.sizeLimit=None
+        self.path = path
+        self.timeInterval = None
+        self.timeLimit = None
+        self.sizeLimit = None
         if hourInterval is not None:
         if hourInterval is not None:
-            self.timeInterval=hourInterval*60*60
-            self.timeLimit=time.time()+self.timeInterval
+            self.timeInterval = hourInterval*60*60
+            self.timeLimit = time.time()+self.timeInterval
         if megabyteLimit is not None:
         if megabyteLimit is not None:
-            self.sizeLimit=megabyteLimit*1024*1024
+            self.sizeLimit = megabyteLimit*1024*1024
 
 
     def __del__(self):
     def __del__(self):
         self.close()
         self.close()

+ 0 - 99
direct/src/directscripts/DetectPanda3D.js

@@ -1,99 +0,0 @@
-// Based on Apple sample code at
-// http://developer.apple.com/internet/webcontent/examples/detectplugins_source.html
-
-
-// initialize global variables
-var detectableWithVB = false;
-var pluginFound = false;
-
-function goURL(daURL) {
-    // Assume we have Javascript 1.1 functionality.
-    window.location.replace(daURL);
-    return;
-}
-
-function redirectCheck(pluginFound, redirectURL, redirectIfFound) {
-    // check for redirection
-    if( redirectURL && ((pluginFound && redirectIfFound) || 
-	(!pluginFound && !redirectIfFound)) ) {
-	// go away
-	goURL(redirectURL);
-	return pluginFound;
-    } else {
-	// stay here and return result of plugin detection
-	return pluginFound;
-    }	
-}
-
-function canDetectPlugins() {
-    if( detectableWithVB || (navigator.plugins && navigator.plugins.length > 0) ) {
-	return true;
-    } else {
-	return false;
-    }
-}
-
-function detectPanda3D(redirectURL, redirectIfFound) {
-    pluginFound = detectPlugin('Panda3D'); 
-    // if not found, try to detect with VisualBasic
-    if(!pluginFound && detectableWithVB) {
-	pluginFound = detectActiveXControl('P3DACTIVEX.P3DActiveXCtrl.1');
-    }
-    // check for redirection
-    return redirectCheck(pluginFound, redirectURL, redirectIfFound);
-}
-
-function detectPlugin() {
-    // allow for multiple checks in a single pass
-    var daPlugins = detectPlugin.arguments;
-    // consider pluginFound to be false until proven true
-    var pluginFound = false;
-    // if plugins array is there and not fake
-    if (navigator.plugins && navigator.plugins.length > 0) {
-	var pluginsArrayLength = navigator.plugins.length;
-	// for each plugin...
-	for (pluginsArrayCounter=0; pluginsArrayCounter < pluginsArrayLength; pluginsArrayCounter++ ) {
-	    // loop through all desired names and check each against the current plugin name
-	    var numFound = 0;
-	    for(namesCounter=0; namesCounter < daPlugins.length; namesCounter++) {
-		// if desired plugin name is found in either plugin name or description
-		if( (navigator.plugins[pluginsArrayCounter].name.indexOf(daPlugins[namesCounter]) >= 0) || 
-		    (navigator.plugins[pluginsArrayCounter].description.indexOf(daPlugins[namesCounter]) >= 0) ) {
-		    // this name was found
-		    numFound++;
-		}   
-	    }
-	    // now that we have checked all the required names against this one plugin,
-	    // if the number we found matches the total number provided then we were successful
-	    if(numFound == daPlugins.length) {
-		pluginFound = true;
-		// if we've found the plugin, we can stop looking through at the rest of the plugins
-		break;
-	    }
-	}
-    }
-    return pluginFound;
-} // detectPlugin
-
-
-// Here we write out the VBScript block for MSIE Windows
-if ((navigator.userAgent.indexOf('MSIE') != -1) && (navigator.userAgent.indexOf('Win') != -1)) {
-    document.writeln('<script language="VBscript">');
-
-    document.writeln('\'do a one-time test for a version of VBScript that can handle this code');
-    document.writeln('detectableWithVB = False');
-    document.writeln('If ScriptEngineMajorVersion >= 2 then');
-    document.writeln('  detectableWithVB = True');
-    document.writeln('End If');
-
-    document.writeln('\'this next function will detect most plugins');
-    document.writeln('Function detectActiveXControl(activeXControlName)');
-    document.writeln('  on error resume next');
-    document.writeln('  detectActiveXControl = False');
-    document.writeln('  If detectableWithVB Then');
-    document.writeln('     detectActiveXControl = IsObject(CreateObject(activeXControlName))');
-    document.writeln('  End If');
-    document.writeln('End Function');
-
-    document.writeln('</script>');
-}

+ 0 - 132
direct/src/directscripts/RunPanda3D.js

@@ -1,132 +0,0 @@
-// This script injects the appropriate syntax into the document to
-// embed Panda3D, either for the ActiveX or Mozilla-based plugin.
-
-// It is also possible to write browser-independent code by nesting
-// <object> tags, but this causes problems when you need to reference
-// the particular object that is actually running (which object is
-// it?) for scripting purposes.
-
-// This script writes only a single <object> tag, and it can be
-// assigned the id you specify, avoiding this ambiguity.
-
-var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
-var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
-var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
-
-
-function P3D_Generateobj(objAttrs, params, embedAttrs, imageAttrs) 
-{ 
-  var str = '';
-
-  if (isIE && isWin && !isOpera)
-  {
-    str += '<object ';
-    for (var i in objAttrs)
-    {
-      str += i + '="' + objAttrs[i] + '" ';
-    }
-    str += '>';
-    for (var i in params)
-    {
-      str += '<param name="' + i + '" value="' + params[i] + '" /> ';
-    }
-  }
-  else
-  {
-    str += '<object ';
-    for (var i in embedAttrs)
-    {
-      str += i + '="' + embedAttrs[i] + '" ';
-    }
-    str += '> ';
-  }
-  if (imageAttrs["src"]) {
-    if (imageAttrs["href"]) {
-      str += '<a href="' + imageAttrs["href"] + '">';
-    }
-    str += '<img ';
-    for (var i in imageAttrs) {
-      if (i != "href") {
-        str += i + '="' + imageAttrs[i] + '" ';
-      }
-    }
-    str += '>';
-    if (imageAttrs["href"]) {
-      str += '</a>';
-    }
-  }
-  str += '</object>';
-
-  document.write(str);
-}
-
-function P3D_RunContent() {
-  var ret = 
-    P3D_GetArgs
-      (arguments, "clsid:924b4927-d3ba-41ea-9f7e-8a89194ab3ac",
-       "application/x-panda3d");
-  P3D_Generateobj(ret.objAttrs, ret.params, ret.embedAttrs, ret.imageAttrs);
-}
-
-function P3D_GetArgs(args, classid, mimeType){
-  var ret = new Object();
-  ret.embedAttrs = new Object();
-  ret.params = new Object();
-  ret.objAttrs = new Object();
-  ret.imageAttrs = new Object();
-
-  for (var i = 0; i < args.length; i = i + 2){
-    var currArg = args[i].toLowerCase();    
-
-    switch (currArg){	
-    case "src":
-    case "data":
-        ret.embedAttrs['data'] = args[i+1];
-        ret.params['data'] = args[i+1];
-        break;
-
-    case "codebase":
-        ret.objAttrs['codebase'] = args[i+1];
-        break;
-
-    case "noplugin_img":
-        ret.imageAttrs["src"] = args[i+1];
-        ret.imageAttrs["border"] = '0';
-        break;
-
-    case "noplugin_href":
-        ret.imageAttrs["href"] = args[i+1];
-        break;
-
-    case "splash_img":
-        ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
-        if (!ret.imageAttrs["src"]) {
-          ret.imageAttrs["src"] = args[i+1];
-        }
-        break;
-
-    case "width":
-    case "height":
-        ret.imageAttrs[args[i]] = ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
-        break;
-
-    case "id":
-    case "align":
-    case "vspace": 
-    case "hspace":
-    case "class":
-    case "title":
-    case "accesskey":
-    case "name":
-    case "tabindex":
-        ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
-        break;
-
-    default:
-        ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
-    }
-  }
-  ret.objAttrs["classid"] = classid;
-  if (mimeType) ret.embedAttrs["type"] = mimeType;
-  return ret;
-}

+ 3 - 1
direct/src/directscripts/extract_docs.py

@@ -273,6 +273,7 @@ def processModule(handle, package):
             if "panda3d." + package == module_name:
             if "panda3d." + package == module_name:
                 processType(handle, type)
                 processType(handle, type)
         else:
         else:
+            typename = interrogate_type_name(type)
             print("Type %s has no module name" % typename)
             print("Type %s has no module name" % typename)
 
 
     for i_func in range(interrogate_number_of_global_functions()):
     for i_func in range(interrogate_number_of_global_functions()):
@@ -283,7 +284,8 @@ def processModule(handle, package):
             if "panda3d." + package == module_name:
             if "panda3d." + package == module_name:
                 processFunction(handle, func)
                 processFunction(handle, func)
         else:
         else:
-            print("Type %s has no module name" % typename)
+            funcname = interrogate_function_name(func)
+            print("Function %s has no module name" % funcname)
 
 
     print("}", file=handle)
     print("}", file=handle)
 
 

+ 38 - 32
direct/src/directutil/Verify.py

@@ -1,27 +1,31 @@
 """
 """
-You can use verify() just like assert, with these small differences:
-    - you may need to "import Verify", if someone hasn't done it
-      for you.
-    - unlike assert where using parenthises are optional, verify()
-      requires them.
-      e.g.:
-        assert foo  # OK
-        verify foo  # Error
-        assert foo  # Not Recomended (may be interpreted as a tuple)
-        verify(foo) # OK
-    - verify() will print something like the following before raising
-      an exception:
-        verify failed:
-            File "direct/src/showbase/ShowBase.py", line 60
-    - verify() will optionally start pdb for you (this is currently
-      false by default).  You can either edit Verify.py to set
-      wantVerifyPdb = 1 or if you are using ShowBase you can set
-      want-verify-pdb 1 in your Configrc to start pdb automatically.
-    - verify() will still function in the release build.  It will
-      not be removed by -O like assert will.
-
-verify() will also throw an AssertionError, but you can ignore that if you
-like (I don't suggest trying to catch it, it's just doing it so that it can
+You can use :func:`verify()` just like assert, with these small differences:
+
+- you may need to ``import Verify``, if someone hasn't done it for you.
+
+- unlike assert where using parentheses are optional, :func:`verify()`
+  requires them, e.g.::
+
+    assert foo  # OK
+    verify foo  # Error
+    assert foo  # Not Recomended (may be interpreted as a tuple)
+    verify(foo) # OK
+
+- :func:`verify()` will print something like this before raising an exception::
+
+    verify failed:
+        File "direct/src/showbase/ShowBase.py", line 60
+
+- :func:`verify()` will optionally start pdb for you (this is currently false
+  by default).  You can either edit Verify.py to set ``wantVerifyPdb = 1`` or
+  if you are using ShowBase you can set ``want-verify-pdb 1`` in your
+  Config.prc file to start pdb automatically.
+
+- :func:`verify()` will still function in the release build.  It will not be
+  removed by -O like assert will.
+
+:func:`verify()` will also throw an AssertionError, but you can ignore that if
+you like (I don't suggest trying to catch it, it's just doing it so that it can
 replace assert more fully).
 replace assert more fully).
 
 
 Please do not use assert for things that you want run on release builds.
 Please do not use assert for things that you want run on release builds.
@@ -31,19 +35,20 @@ an exception can get it mistaken for an error handler.  If your code
 needs to handle an error or throw an exception, you should do that
 needs to handle an error or throw an exception, you should do that
 (and not just assert for it).
 (and not just assert for it).
 
 
-If you want to be a super keen software engineer then avoid using verify().
-If you want to be, or already are, a super keen software engineer, but
-you don't always have the time to write proper error handling, go ahead
-and use verify() -- that's what it's for.
+If you want to be a super keen software engineer then avoid using
+:func:`verify()`.  If you want to be, or already are, a super keen software
+engineer, but you don't always have the time to write proper error handling,
+go ahead and use :func:`verify()` -- that's what it's for.
 
 
-Please use assert (properly) and do proper error handling; and use verify()
-only when debugging (i.e. when it won't be checked-in) or where it helps
-you resist using assert for error handling.
+Please use assert (properly) and do proper error handling; and use
+:func:`verify()` only when debugging (i.e. when it won't be checked-in) or
+where it helps you resist using assert for error handling.
 """
 """
 
 
 from panda3d.core import ConfigVariableBool
 from panda3d.core import ConfigVariableBool
 
 
-wantVerifyPdb = ConfigVariableBool('want-verify-pdb', False) # Set to true to load pdb on failure.
+# Set to true to load pdb on failure.
+wantVerifyPdb = ConfigVariableBool('want-verify-pdb', False)
 
 
 
 
 def verify(assertion):
 def verify(assertion):
@@ -54,7 +59,7 @@ def verify(assertion):
     if not assertion:
     if not assertion:
         print("\n\nverify failed:")
         print("\n\nverify failed:")
         import sys
         import sys
-        print("    File \"%s\", line %d"%(
+        print("    File \"%s\", line %d" % (
                 sys._getframe(1).f_code.co_filename,
                 sys._getframe(1).f_code.co_filename,
                 sys._getframe(1).f_lineno))
                 sys._getframe(1).f_lineno))
         if wantVerifyPdb:
         if wantVerifyPdb:
@@ -62,5 +67,6 @@ def verify(assertion):
             pdb.set_trace()
             pdb.set_trace()
         raise AssertionError
         raise AssertionError
 
 
+
 if not hasattr(__builtins__, "verify"):
 if not hasattr(__builtins__, "verify"):
     __builtins__["verify"] = verify
     __builtins__["verify"] = verify

+ 111 - 16
direct/src/dist/FreezeTool.py

@@ -11,6 +11,7 @@ import struct
 import io
 import io
 import distutils.sysconfig as sysconf
 import distutils.sysconfig as sysconf
 import zipfile
 import zipfile
+import importlib
 
 
 from . import pefile
 from . import pefile
 
 
@@ -785,7 +786,7 @@ class Freezer:
         # already-imported modules.  (Some of them might do their own
         # already-imported modules.  (Some of them might do their own
         # special path mangling.)
         # special path mangling.)
         for moduleName, module in list(sys.modules.items()):
         for moduleName, module in list(sys.modules.items()):
-            if module and hasattr(module, '__path__'):
+            if module and getattr(module, '__path__', None) is not None:
                 path = list(getattr(module, '__path__'))
                 path = list(getattr(module, '__path__'))
                 if path:
                 if path:
                     modulefinder.AddPackagePath(moduleName, path[0])
                     modulefinder.AddPackagePath(moduleName, path[0])
@@ -852,7 +853,7 @@ class Freezer:
         allowChildren is true, the children of the indicated module
         allowChildren is true, the children of the indicated module
         may still be included."""
         may still be included."""
 
 
-        assert self.mf == None
+        assert self.mf is None
 
 
         self.modules[moduleName] = self.ModuleDef(
         self.modules[moduleName] = self.ModuleDef(
             moduleName, exclude = True,
             moduleName, exclude = True,
@@ -886,7 +887,7 @@ class Freezer:
             print("couldn't import %s" % (moduleName))
             print("couldn't import %s" % (moduleName))
             module = None
             module = None
 
 
-        if module != None:
+        if module is not None:
             for symbol in moduleName.split('.')[1:]:
             for symbol in moduleName.split('.')[1:]:
                 module = getattr(module, symbol)
                 module = getattr(module, symbol)
             if hasattr(module, '__path__'):
             if hasattr(module, '__path__'):
@@ -901,7 +902,7 @@ class Freezer:
         if '.' in baseName:
         if '.' in baseName:
             parentName, baseName = moduleName.rsplit('.', 1)
             parentName, baseName = moduleName.rsplit('.', 1)
             path = self.getModulePath(parentName)
             path = self.getModulePath(parentName)
-            if path == None:
+            if path is None:
                 return None
                 return None
 
 
         try:
         try:
@@ -925,7 +926,7 @@ class Freezer:
             print("couldn't import %s" % (moduleName))
             print("couldn't import %s" % (moduleName))
             module = None
             module = None
 
 
-        if module != None:
+        if module is not None:
             for symbol in moduleName.split('.')[1:]:
             for symbol in moduleName.split('.')[1:]:
                 module = getattr(module, symbol)
                 module = getattr(module, symbol)
             if hasattr(module, '__all__'):
             if hasattr(module, '__all__'):
@@ -938,7 +939,7 @@ class Freezer:
         if '.' in baseName:
         if '.' in baseName:
             parentName, baseName = moduleName.rsplit('.', 1)
             parentName, baseName = moduleName.rsplit('.', 1)
             path = self.getModulePath(parentName)
             path = self.getModulePath(parentName)
-            if path == None:
+            if path is None:
                 return None
                 return None
 
 
         try:
         try:
@@ -995,9 +996,9 @@ class Freezer:
         for parentName, newParentName in parentNames:
         for parentName, newParentName in parentNames:
             modules = self.getModuleStar(parentName)
             modules = self.getModuleStar(parentName)
 
 
-            if modules == None:
+            if modules is None:
                 # It's actually a regular module.
                 # It's actually a regular module.
-                mdef[newParentName] = self.ModuleDef(
+                mdefs[newParentName] = self.ModuleDef(
                     parentName, implicit = implicit, guess = guess,
                     parentName, implicit = implicit, guess = guess,
                     fromSource = fromSource, text = text)
                     fromSource = fromSource, text = text)
 
 
@@ -1031,7 +1032,7 @@ class Freezer:
         directories within a particular directory.
         directories within a particular directory.
         """
         """
 
 
-        assert self.mf == None
+        assert self.mf is None
 
 
         if not newName:
         if not newName:
             newName = moduleName
             newName = moduleName
@@ -1053,7 +1054,7 @@ class Freezer:
         to done(), you may not add any more modules until you call
         to done(), you may not add any more modules until you call
         reset(). """
         reset(). """
 
 
-        assert self.mf == None
+        assert self.mf is None
 
 
         # If we are building an exe, we also need to implicitly
         # If we are building an exe, we also need to implicitly
         # bring in Python's startup modules.
         # bring in Python's startup modules.
@@ -2221,6 +2222,10 @@ class Freezer:
 
 
         return True
         return True
 
 
+
+_PKG_NAMESPACE_DIRECTORY = object()
+
+
 class PandaModuleFinder(modulefinder.ModuleFinder):
 class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
@@ -2279,6 +2284,44 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
         return None
         return None
 
 
+    def _dir_exists(self, path):
+        """Returns True if the given directory exists, either on disk or inside
+        a wheel."""
+
+        if os.path.isdir(path):
+            return True
+
+        # Is there a zip file along the path?
+        dir, dirname = os.path.split(path.rstrip(os.path.sep + '/'))
+        fn = dirname
+        while dirname:
+            if os.path.isfile(dir):
+                # Okay, this is actually a file.  Is it a zip file?
+                if dir in self._zip_files:
+                    # Yes, and we've previously opened this.
+                    zip = self._zip_files[dir]
+                elif zipfile.is_zipfile(dir):
+                    zip = zipfile.ZipFile(dir)
+                    self._zip_files[dir] = zip
+                else:
+                    # It's a different kind of file.  Stop looking.
+                    return None
+
+                # (Most) zip files do not store directories; check instead for a
+                # file whose path starts with this directory name.
+                prefix = fn.replace(os.path.sep, '/') + '/'
+                for name in zip.namelist():
+                    if name.startswith(prefix):
+                        return True
+
+                return False
+
+            # Look at the parent directory.
+            dir, dirname = os.path.split(dir)
+            fn = os.path.join(dirname, fn)
+
+        return False
+
     def load_module(self, fqname, fp, pathname, file_info):
     def load_module(self, fqname, fp, pathname, file_info):
         """Copied from ModuleFinder.load_module with fixes to handle sending bytes
         """Copied from ModuleFinder.load_module with fixes to handle sending bytes
         to compile() for PY_SOURCE types. Sending bytes to compile allows it to
         to compile() for PY_SOURCE types. Sending bytes to compile allows it to
@@ -2291,6 +2334,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             self.msgout(2, "load_module ->", m)
             self.msgout(2, "load_module ->", m)
             return m
             return m
 
 
+        if type is _PKG_NAMESPACE_DIRECTORY:
+            m = self.add_module(fqname)
+            m.__code__ = compile('', '', 'exec')
+            m.__path__ = pathname
+            return m
+
         if type == imp.PY_SOURCE:
         if type == imp.PY_SOURCE:
             if fqname in overrideModules:
             if fqname in overrideModules:
                 # This module has a custom override.
                 # This module has a custom override.
@@ -2301,12 +2350,36 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             code += b'\n' if isinstance(code, bytes) else '\n'
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec')
             co = compile(code, pathname, 'exec')
         elif type == imp.PY_COMPILED:
         elif type == imp.PY_COMPILED:
-            try:
-                marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read())
-            except ImportError as exc:
-                self.msgout(2, "raise ImportError: " + str(exc), pathname)
-                raise
-            co = marshal.loads(marshal_data)
+            if sys.version_info >= (3, 7):
+                try:
+                    data = fp.read()
+                    importlib._bootstrap_external._classify_pyc(data, fqname, {})
+                except ImportError as exc:
+                    self.msgout(2, "raise ImportError: " + str(exc), pathname)
+                    raise
+
+                co = marshal.loads(memoryview(data)[16:])
+            elif sys.version_info >= (3, 4):
+                try:
+                    if sys.version_info >= (3, 5):
+                        marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read())
+                    else:
+                        marshal_data = importlib._bootstrap._validate_bytecode_header(fp.read())
+                except ImportError as exc:
+                    self.msgout(2, "raise ImportError: " + str(exc), pathname)
+                    raise
+
+                co = marshal.loads(marshal_data)
+            else:
+                if fp.read(4) != imp.get_magic():
+                    self.msgout(2, "raise ImportError: Bad magic number", pathname)
+                    raise ImportError("Bad magic number in %s" % pathname)
+
+                fp.read(4)
+                if sys.version_info >= (3, 3):
+                    fp.read(4)
+
+                co = marshal.load(fp)
         else:
         else:
             co = None
             co = None
 
 
@@ -2372,7 +2445,20 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
             path = self.path
             path = self.path
 
 
+            if fullname == 'distutils' and hasattr(sys, 'real_prefix'):
+                # The PyPI version of virtualenv inserts a special version of
+                # distutils that does some bizarre stuff that won't work in our
+                # deployed application.  Force it to find the regular one.
+                try:
+                    fp, fn, stuff = self.find_module('opcode')
+                    if fn:
+                        path = [os.path.dirname(fn)] + path
+                except ImportError:
+                    pass
+
         # Look for the module on the search path.
         # Look for the module on the search path.
+        ns_dirs = []
+
         for dir_path in path:
         for dir_path in path:
             basename = os.path.join(dir_path, name.split('.')[-1])
             basename = os.path.join(dir_path, name.split('.')[-1])
 
 
@@ -2389,6 +2475,10 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                 if self._open_file(init, mode):
                 if self._open_file(init, mode):
                     return (None, basename, ('', '', imp.PKG_DIRECTORY))
                     return (None, basename, ('', '', imp.PKG_DIRECTORY))
 
 
+            # This may be a namespace package.
+            if self._dir_exists(basename):
+                ns_dirs.append(basename)
+
         # It wasn't found through the normal channels.  Maybe it's one of
         # It wasn't found through the normal channels.  Maybe it's one of
         # ours, or maybe it's frozen?
         # ours, or maybe it's frozen?
         if not path:
         if not path:
@@ -2397,6 +2487,11 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                 # It's a frozen module.
                 # It's a frozen module.
                 return (None, name, ('', '', imp.PY_FROZEN))
                 return (None, name, ('', '', imp.PY_FROZEN))
 
 
+        # If we found folders on the path with this module name without an
+        # __init__.py file, we should consider this a namespace package.
+        if ns_dirs and sys.version_info >= (3, 3):
+            return (None, ns_dirs, ('', '', _PKG_NAMESPACE_DIRECTORY))
+
         raise ImportError(name)
         raise ImportError(name)
 
 
     def find_all_submodules(self, m):
     def find_all_submodules(self, m):

+ 4 - 0
direct/src/dist/__init__.py

@@ -0,0 +1,4 @@
+"""This package contains tools to help with distributing Panda3D
+applications.  See the :ref:`distribution` section in the programming
+manual for further details.
+"""

+ 77 - 67
direct/src/dist/commands.py

@@ -1,8 +1,13 @@
+"""Extends setuptools with the ``build_apps`` and ``bdist_apps`` commands.
+
+See the :ref:`distribution` section of the programming manual for information
+on how to use these commands.
+"""
+
 from __future__ import print_function
 from __future__ import print_function
 
 
 import collections
 import collections
 import os
 import os
-import pip
 import plistlib
 import plistlib
 import sys
 import sys
 import subprocess
 import subprocess
@@ -21,7 +26,7 @@ import distutils.log
 
 
 from . import FreezeTool
 from . import FreezeTool
 from . import pefile
 from . import pefile
-from direct.p3d.DeploymentTools import Icon
+from .icon import Icon
 import panda3d.core as p3d
 import panda3d.core as p3d
 
 
 
 
@@ -35,9 +40,8 @@ if sys.version_info < (3, 0):
 
 
     # Warn the user.  They might be using Python 2 by accident.
     # Warn the user.  They might be using Python 2 by accident.
     print("=================================================================")
     print("=================================================================")
-    print("WARNING: You are using Python 2, which will soon be discontinued.")
-    print("WARNING: Please use Python 3 for best results and continued")
-    print("WARNING: support after the EOL date of December 31st, 2019.")
+    print("WARNING: You are using Python 2, which has reached the end of its")
+    print("WARNING: life as of January 1, 2020.  Please upgrade to Python 3.")
     print("=================================================================")
     print("=================================================================")
     sys.stdout.flush()
     sys.stdout.flush()
     time.sleep(4.0)
     time.sleep(4.0)
@@ -66,6 +70,8 @@ def _parse_dict(input):
 
 
 
 
 def egg2bam(_build_cmd, srcpath, dstpath):
 def egg2bam(_build_cmd, srcpath, dstpath):
+    if dstpath.endswith('.gz') or dstpath.endswith('.pz'):
+        dstpath = dstpath[:-3]
     dstpath = dstpath + '.bam'
     dstpath = dstpath + '.bam'
     try:
     try:
         subprocess.check_call([
         subprocess.check_call([
@@ -105,6 +111,7 @@ PACKAGE_DATA_DIRS = {
         ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
         ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
         ('cefpython3/locals/*', 'locals', {}),
         ('cefpython3/locals/*', 'locals', {}),
         ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
         ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
+        ('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
     ],
     ],
 }
 }
 
 
@@ -229,7 +236,7 @@ class build_apps(setuptools.Command):
         self.icons = {}
         self.icons = {}
         self.platforms = [
         self.platforms = [
             'manylinux1_x86_64',
             'manylinux1_x86_64',
-            'macosx_10_6_x86_64',
+            'macosx_10_9_x86_64',
             'win_amd64',
             'win_amd64',
         ]
         ]
         self.plugins = []
         self.plugins = []
@@ -263,6 +270,7 @@ class build_apps(setuptools.Command):
             'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
             'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
 
 
             # macOS
             # macOS
+            '/usr/lib/libc++.1.dylib',
             '/usr/lib/libstdc++.*.dylib',
             '/usr/lib/libstdc++.*.dylib',
             '/usr/lib/libz.*.dylib',
             '/usr/lib/libz.*.dylib',
             '/usr/lib/libobjc.*.dylib',
             '/usr/lib/libobjc.*.dylib',
@@ -357,7 +365,7 @@ class build_apps(setuptools.Command):
         tmp.update(self.file_handlers)
         tmp.update(self.file_handlers)
         self.file_handlers = tmp
         self.file_handlers = tmp
 
 
-        tmp = self.package_data_dirs.copy()
+        tmp = PACKAGE_DATA_DIRS.copy()
         tmp.update(self.package_data_dirs)
         tmp.update(self.package_data_dirs)
         self.package_data_dirs = tmp
         self.package_data_dirs = tmp
 
 
@@ -385,29 +393,26 @@ class build_apps(setuptools.Command):
         directory containing the Python runtime libraries, which will be added
         directory containing the Python runtime libraries, which will be added
         to sys.path."""
         to sys.path."""
 
 
-        self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO)
+        import pip
 
 
-        whldir = os.path.join(self.build_base, '__whl_cache__')
+        self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO)
 
 
-        #TODO find a better way to get abi tag than from internal/private pip APIs
-        if hasattr(pip, 'pep425tags'):
-            pep425tags = pip.pep425tags
-            wheel = pip.wheel
-        else:
-            from pip._internal import pep425tags, wheel
+        whlcache = os.path.join(self.build_base, '__whl_cache__')
 
 
-        abi_tag = pep425tags.get_abi_tag()
+        pip_version = int(pip.__version__.split('.')[0])
+        if pip_version < 9:
+            raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__))
 
 
-        if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')):
-            abi_tag = abi_tag.replace('u', '')
+        abi_tag = 'cp%d%d' % (sys.version_info[:2])
+        if sys.version_info < (3, 8):
+            abi_tag += 'm'
 
 
         # For these distributions, we need to append 'u' on Linux
         # For these distributions, we need to append 'u' on Linux
         if abi_tag in ('cp26m', 'cp27m', 'cp32m') and not platform.startswith('win') and not platform.startswith('macosx'):
         if abi_tag in ('cp26m', 'cp27m', 'cp32m') and not platform.startswith('win') and not platform.startswith('macosx'):
             abi_tag += 'u'
             abi_tag += 'u'
 
 
-        pip_version = pip.__version__.split('.')
-        if int(pip_version[0]) < 9:
-            raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__))
+        whldir = os.path.join(whlcache, '_'.join((platform, abi_tag)))
+        os.makedirs(whldir, exist_ok=True)
 
 
         # Remove any .zip files. These are built from a VCS and block for an
         # Remove any .zip files. These are built from a VCS and block for an
         # interactive prompt on subsequent downloads.
         # interactive prompt on subsequent downloads.
@@ -435,19 +440,12 @@ class build_apps(setuptools.Command):
 
 
         subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
         subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
 
 
-        # Now figure out which of the downloaded wheels are relevant to us.
-        tags = pep425tags.get_supported(platform=platform, abi=abi_tag)
-        wheelpaths = []
-        for filename in os.listdir(whldir):
-            try:
-                whl = wheel.Wheel(filename)
-            except wheel.InvalidWheelFilename:
-                continue
-
-            if whl.supported(tags):
-                wheelpaths.append(os.path.join(whldir, filename))
-
-        return wheelpaths
+        # Return a list of paths to the downloaded whls
+        return [
+            os.path.join(whldir, filename)
+            for filename in os.listdir(whldir)
+            if filename.endswith('.whl')
+        ]
 
 
     def update_pe_resources(self, appname, runtime):
     def update_pe_resources(self, appname, runtime):
         """Update resources (e.g., icons) in windows PE file"""
         """Update resources (e.g., icons) in windows PE file"""
@@ -631,6 +629,28 @@ class build_apps(setuptools.Command):
         freezer_modules = set()
         freezer_modules = set()
         freezer_modpaths = set()
         freezer_modpaths = set()
         ext_suffixes = set()
         ext_suffixes = set()
+
+        def get_search_path_for(source_path):
+            search_path = [os.path.dirname(source_path)]
+            if use_wheels:
+                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
+
+                # If the .whl containing this file has a .libs directory, add
+                # it to the path.  This is an auditwheel/numpy convention.
+                if '.whl' + os.sep in source_path:
+                    whl, wf = source_path.split('.whl' + os.path.sep)
+                    whl += '.whl'
+                    rootdir = wf.split(os.path.sep, 1)[0]
+                    search_path.append(os.path.join(whl, rootdir, '.libs'))
+
+                    # Also look for more specific per-package cases, defined in
+                    # PACKAGE_LIB_DIRS at the top of this file.
+                    whl_name = os.path.basename(whl).split('-', 1)[0]
+                    extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, [])
+                    for extra_dir in extra_dirs:
+                        search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+            return search_path
+
         def create_runtime(appname, mainscript, use_console):
         def create_runtime(appname, mainscript, use_console):
             freezer = FreezeTool.Freezer(platform=platform, path=path)
             freezer = FreezeTool.Freezer(platform=platform, path=path)
             freezer.addModule('__main__', filename=mainscript)
             freezer.addModule('__main__', filename=mainscript)
@@ -773,26 +793,8 @@ class build_apps(setuptools.Command):
                     continue
                     continue
 
 
             # If this is a dynamic library, search for dependencies.
             # If this is a dynamic library, search for dependencies.
-            search_path = [os.path.dirname(source_path)]
-            if use_wheels:
-                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
-
-                # If the .whl containing this file has a .libs directory, add
-                # it to the path.  This is an auditwheel/numpy convention.
-                if '.whl' + os.sep in source_path:
-                    whl, wf = source_path.split('.whl' + os.path.sep)
-                    whl += '.whl'
-                    rootdir = wf.split(os.path.sep, 1)[0]
-                    search_path.append(os.path.join(whl, rootdir, '.libs'))
-
-                    # Also look for more specific per-package cases, defined in
-                    # PACKAGE_LIB_DIRS at the top of this file.
-                    whl_name = os.path.basename(whl).split('-', 1)[0]
-                    extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, [])
-                    for extra_dir in extra_dirs:
-                        search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
-
             target_path = os.path.join(builddir, basename)
             target_path = os.path.join(builddir, basename)
+            search_path = get_search_path_for(source_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
             self.copy_with_dependencies(source_path, target_path, search_path)
 
 
         # Copy over the tcl directory.
         # Copy over the tcl directory.
@@ -824,7 +826,7 @@ class build_apps(setuptools.Command):
                 whlfile = self._get_zip_file(whl)
                 whlfile = self._get_zip_file(whl)
                 filenames = whlfile.namelist()
                 filenames = whlfile.namelist()
                 for source_pattern, target_dir, flags in datadesc:
                 for source_pattern, target_dir, flags in datadesc:
-                    srcglob = p3d.GlobPattern(source_pattern)
+                    srcglob = p3d.GlobPattern(source_pattern.lower())
                     source_dir = os.path.dirname(source_pattern)
                     source_dir = os.path.dirname(source_pattern)
                     # Relocate the target dir to the build directory.
                     # Relocate the target dir to the build directory.
                     target_dir = target_dir.replace('/', os.sep)
                     target_dir = target_dir.replace('/', os.sep)
@@ -838,12 +840,15 @@ class build_apps(setuptools.Command):
                             relpath = wf[len(source_dir) + 1:]
                             relpath = wf[len(source_dir) + 1:]
                             source_path = os.path.join(whl, wf)
                             source_path = os.path.join(whl, wf)
                             target_path = os.path.join(target_dir, relpath)
                             target_path = os.path.join(target_dir, relpath)
-                            self.copy(source_path, target_path)
 
 
                             if 'PKG_DATA_MAKE_EXECUTABLE' in flags:
                             if 'PKG_DATA_MAKE_EXECUTABLE' in flags:
+                                search_path = get_search_path_for(source_path)
+                                self.copy_with_dependencies(source_path, target_path, search_path)
                                 mode = os.stat(target_path).st_mode
                                 mode = os.stat(target_path).st_mode
                                 mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
                                 mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
                                 os.chmod(target_path, mode)
                                 os.chmod(target_path, mode)
+                            else:
+                                self.copy(source_path, target_path)
 
 
         # Copy Game Files
         # Copy Game Files
         self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
         self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
@@ -895,6 +900,9 @@ class build_apps(setuptools.Command):
                 os.makedirs(dst_dir)
                 os.makedirs(dst_dir)
 
 
             ext = os.path.splitext(src)[1]
             ext = os.path.splitext(src)[1]
+            # If the file ends with .gz/.pz, we strip this off.
+            if ext in ('.gz', '.pz'):
+                ext = os.path.splitext(src[:-3])[1]
             if not ext:
             if not ext:
                 ext = os.path.basename(src)
                 ext = os.path.basename(src)
 
 
@@ -1168,18 +1176,20 @@ class build_apps(setuptools.Command):
                     dylib = dylib.replace('@loader_path/../Frameworks/', '')
                     dylib = dylib.replace('@loader_path/../Frameworks/', '')
                 elif dylib.startswith('@executable_path/../Frameworks/'):
                 elif dylib.startswith('@executable_path/../Frameworks/'):
                     dylib = dylib.replace('@executable_path/../Frameworks/', '')
                     dylib = dylib.replace('@executable_path/../Frameworks/', '')
-                elif dylib.startswith('@loader_path/'):
-                    dylib = dylib.replace('@loader_path/', '')
-
-                    # Do we need to flatten the relative reference?
-                    if '/' in dylib and flatten:
-                        new_dylib = '@loader_path/' + os.path.basename(dylib)
-                        str_size = len(cmd_data) - 16
-                        if len(new_dylib) < str_size:
-                            fp.seek(-str_size, os.SEEK_CUR)
-                            fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0'))
-                        else:
-                            self.warn('Unable to rewrite dependency {}'.format(orig))
+                else:
+                    for prefix in ('@loader_path/', '@rpath/'):
+                        if dylib.startswith(prefix):
+                            dylib = dylib.replace(prefix, '')
+
+                            # Do we need to flatten the relative reference?
+                            if '/' in dylib and flatten:
+                                new_dylib = prefix + os.path.basename(dylib)
+                                str_size = len(cmd_data) - 16
+                                if len(new_dylib) < str_size:
+                                    fp.seek(-str_size, os.SEEK_CUR)
+                                    fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0'))
+                                else:
+                                    self.warn('Unable to rewrite dependency {}'.format(orig))
 
 
                 load_dylibs.append(dylib)
                 load_dylibs.append(dylib)
 
 

+ 269 - 0
direct/src/dist/icon.py

@@ -0,0 +1,269 @@
+from direct.directnotify.DirectNotifyGlobal import *
+from panda3d.core import PNMImage, Filename, PNMFileTypeRegistry, StringStream
+import struct
+
+
+class Icon:
+    """ This class is used to create an icon for various platforms. """
+    notify = directNotify.newCategory("Icon")
+
+    def __init__(self):
+        self.images = {}
+
+    def addImage(self, image):
+        """ Adds an image to the icon.  Returns False on failure, True on success.
+        Only one image per size can be loaded, and the image size must be square. """
+
+        if not isinstance(image, PNMImage):
+            fn = image
+            if not isinstance(fn, Filename):
+                fn = Filename.fromOsSpecific(fn)
+
+            image = PNMImage()
+            if not image.read(fn):
+                Icon.notify.warning("Image '%s' could not be read" % fn.getBasename())
+                return False
+
+        if image.getXSize() != image.getYSize():
+            Icon.notify.warning("Ignoring image without square size")
+            return False
+
+        self.images[image.getXSize()] = image
+
+        return True
+
+    def generateMissingImages(self):
+        """ Generates image sizes that should be present but aren't by scaling
+        from the next higher size. """
+
+        for required_size in (256, 128, 48, 32, 16):
+            if required_size in self.images:
+                continue
+
+            sizes = sorted(self.images.keys())
+            if required_size * 2 in sizes:
+                from_size = required_size * 2
+            else:
+                from_size = 0
+                for from_size in sizes:
+                    if from_size > required_size:
+                        break
+
+            if from_size > required_size:
+                Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size))
+
+                image = PNMImage(required_size, required_size)
+                if self.images[from_size].hasAlpha():
+                    image.addAlpha()
+                image.quickFilterFrom(self.images[from_size])
+                self.images[required_size] = image
+            else:
+                Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size))
+
+    def _write_bitmap(self, fp, image, size, bpp):
+        """ Writes the bitmap header and data of an .ico file. """
+
+        fp.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
+
+        # XOR mask
+        if bpp == 24:
+            # Align rows to 4-byte boundary
+            rowalign = b'\0' * (-(size * 3) & 3)
+            for y in range(size):
+                for x in range(size):
+                    r, g, b = image.getXel(x, size - y - 1)
+                    fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
+                fp.write(rowalign)
+
+        elif bpp == 32:
+            for y in range(size):
+                for x in range(size):
+                    r, g, b, a = image.getXelA(x, size - y - 1)
+                    fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
+
+        elif bpp == 8:
+            # We'll have to generate a palette of 256 colors.
+            hist = PNMImage.Histogram()
+            image2 = PNMImage(image)
+            if image2.hasAlpha():
+                image2.premultiplyAlpha()
+                image2.removeAlpha()
+            image2.quantize(256)
+            image2.make_histogram(hist)
+            colors = list(hist.get_pixels())
+            assert len(colors) <= 256
+
+            # Write the palette.
+            i = 0
+            while i < 256 and i < len(colors):
+                r, g, b, a = colors[i]
+                fp.write(struct.pack('<BBBB', b, g, r, 0))
+                i += 1
+            if i < 256:
+                # Fill the rest with zeroes.
+                fp.write(b'\x00' * (4 * (256 - i)))
+
+            # Write indices.  Align rows to 4-byte boundary.
+            rowalign = b'\0' * (-size & 3)
+            for y in range(size):
+                for x in range(size):
+                    pixel = image2.get_pixel(x, size - y - 1)
+                    index = colors.index(pixel)
+                    if index >= 256:
+                        # Find closest pixel instead.
+                        index = closest_indices[index - 256]
+                    fp.write(struct.pack('<B', index))
+                fp.write(rowalign)
+        else:
+            raise ValueError("Invalid bpp %d" % (bpp))
+
+        # Create an AND mask, aligned to 4-byte boundary
+        if image.hasAlpha() and bpp <= 8:
+            rowalign = b'\0' * (-((size + 7) >> 3) & 3)
+            for y in range(size):
+                mask = 0
+                num_bits = 7
+                for x in range(size):
+                    a = image.get_alpha_val(x, size - y - 1)
+                    if a <= 1:
+                        mask |= (1 << num_bits)
+                    num_bits -= 1
+                    if num_bits < 0:
+                        fp.write(struct.pack('<B', mask))
+                        mask = 0
+                        num_bits = 7
+                if num_bits < 7:
+                    fp.write(struct.pack('<B', mask))
+                fp.write(rowalign)
+        else:
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            fp.write(b'\x00' * (andsize * size))
+
+    def makeICO(self, fn):
+        """ Writes the images to a Windows ICO file.  Returns True on success. """
+
+        if not isinstance(fn, Filename):
+            fn = Filename.fromOsSpecific(fn)
+        fn.setBinary()
+
+        # ICO files only support resolutions up to 256x256.
+        count = 0
+        for size in self.images.keys():
+            if size < 256:
+                count += 1
+            if size <= 256:
+                count += 1
+        dataoffs = 6 + count * 16
+
+        ico = open(fn, 'wb')
+        ico.write(struct.pack('<HHH', 0, 1, count))
+
+        # Write 8-bpp image headers for sizes under 256x256.
+        for size, image in self.images.items():
+            if size >= 256:
+                continue
+            ico.write(struct.pack('<BB', size, size))
+
+            # Calculate row sizes
+            xorsize = size
+            if xorsize % 4 != 0:
+                xorsize += 4 - (xorsize % 4)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + 256 * 4 + (xorsize + andsize) * size
+
+            ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs))
+            dataoffs += datasize
+
+        # Write 24/32-bpp image headers.
+        for size, image in self.images.items():
+            if size > 256:
+                continue
+            elif size == 256:
+                ico.write(b'\0\0')
+            else:
+                ico.write(struct.pack('<BB', size, size))
+
+            # Calculate the size so we can write the offset within the file.
+            if image.hasAlpha():
+                bpp = 32
+                xorsize = size * 4
+            else:
+                bpp = 24
+                xorsize = size * 3 + (-(size * 3) & 3)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + (xorsize + andsize) * size
+
+            ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs))
+            dataoffs += datasize
+
+        # Now write the actual icon bitmap data.
+        for size, image in self.images.items():
+            if size < 256:
+                self._write_bitmap(ico, image, size, 8)
+
+        for size, image in self.images.items():
+            if size <= 256:
+                bpp = 32 if image.hasAlpha() else 24
+                self._write_bitmap(ico, image, size, bpp)
+
+        assert ico.tell() == dataoffs
+        ico.close()
+
+        return True
+
+    def makeICNS(self, fn):
+        """ Writes the images to an Apple ICNS file.  Returns True on success. """
+
+        if not isinstance(fn, Filename):
+            fn = Filename.fromOsSpecific(fn)
+        fn.setBinary()
+
+        icns = open(fn, 'wb')
+        icns.write(b'icns\0\0\0\0')
+
+        icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'}
+        mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'}
+        png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'}
+
+        pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
+
+        for size, image in sorted(self.images.items(), key=lambda item:item[0]):
+            if size in png_types and pngtype is not None:
+                stream = StringStream()
+                image.write(stream, "", pngtype)
+                pngdata = stream.data
+
+                icns.write(png_types[size])
+                icns.write(struct.pack('>I', len(pngdata)))
+                icns.write(pngdata)
+
+            elif size in icon_types:
+                # If it has an alpha channel, we write out a mask too.
+                if image.hasAlpha():
+                    icns.write(mask_types[size])
+                    icns.write(struct.pack('>I', size * size + 8))
+
+                    for y in range(size):
+                        for x in range(size):
+                            icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
+
+                icns.write(icon_types[size])
+                icns.write(struct.pack('>I', size * size * 4 + 8))
+
+                for y in range(size):
+                    for x in range(size):
+                        r, g, b = image.getXel(x, y)
+                        icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
+
+        length = icns.tell()
+        icns.seek(4)
+        icns.write(struct.pack('>I', length))
+        icns.close()
+
+        return True

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

@@ -15,7 +15,7 @@ if __debug__:
 
 
 class AsyncRequest(DirectObject):
 class AsyncRequest(DirectObject):
     """
     """
-    This class is used to make asynchronos reads and creates to a database.
+    This class is used to make asynchronous reads and creates to a database.
 
 
     You can create a list of self.neededObjects and then ask for each to be
     You can create a list of self.neededObjects and then ask for each to be
     read or created, or if you only have one object that you need you can
     read or created, or if you only have one object that you need you can

+ 35 - 0
direct/src/distributed/CMakeLists.txt

@@ -0,0 +1,35 @@
+if(NOT HAVE_PYTHON)
+  return()
+endif()
+
+set(P3DISTRIBUTED_HEADERS
+  config_distributed.h
+  cConnectionRepository.h
+  cConnectionRepository.I
+  cDistributedSmoothNodeBase.h
+  cDistributedSmoothNodeBase.I
+)
+
+set(P3DISTRIBUTED_SOURCES
+  config_distributed.cxx
+)
+
+set(P3DISTRIBUTED_IGATEEXT
+  cConnectionRepository.cxx
+  cDistributedSmoothNodeBase.cxx
+)
+
+add_component_library(p3distributed NOINIT SYMBOL BUILDING_DIRECT_DISTRIBUTED
+  ${P3DISTRIBUTED_HEADERS} ${P3DISTRIBUTED_SOURCES})
+target_link_libraries(p3distributed p3directbase p3dcparser panda)
+target_interrogate(p3distributed ALL EXTENSIONS ${P3DISTRIBUTED_IGATEEXT})
+
+if(NOT BUILD_METALIBS)
+  install(TARGETS p3distributed
+    EXPORT Direct COMPONENT Direct
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
+    ARCHIVE COMPONENT DirectDevel)
+endif()
+install(FILES ${P3DISTRIBUTED_HEADERS} COMPONENT DirectDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

+ 2 - 1
direct/src/distributed/DistributedObject.py

@@ -26,11 +26,12 @@ ESNum2Str = {
     ESGenerated: 'ESGenerated',
     ESGenerated: 'ESGenerated',
     }
     }
 
 
+
 class DistributedObject(DistributedObjectBase):
 class DistributedObject(DistributedObjectBase):
     """
     """
     The Distributed Object class is the base class for all network based
     The Distributed Object class is the base class for all network based
     (i.e. distributed) objects.  These will usually (always?) have a
     (i.e. distributed) objects.  These will usually (always?) have a
-    dclass entry in a *.dc file.
+    dclass entry in a \\*.dc file.
     """
     """
     notify = directNotify.newCategory("DistributedObject")
     notify = directNotify.newCategory("DistributedObject")
 
 

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

@@ -1,11 +1,12 @@
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 
 
+
 class DistributedObjectBase(DirectObject):
 class DistributedObjectBase(DirectObject):
     """
     """
     The Distributed Object class is the base class for all network based
     The Distributed Object class is the base class for all network based
     (i.e. distributed) objects.  These will usually (always?) have a
     (i.e. distributed) objects.  These will usually (always?) have a
-    dclass entry in a *.dc file.
+    dclass entry in a \\*.dc file.
     """
     """
     notify = directNotify.newCategory("DistributedObjectBase")
     notify = directNotify.newCategory("DistributedObjectBase")
 
 

+ 9 - 8
direct/src/distributed/DoCollectionManager.py

@@ -5,6 +5,7 @@ import re
 BAD_DO_ID = BAD_ZONE_ID = 0 # 0xFFFFFFFF
 BAD_DO_ID = BAD_ZONE_ID = 0 # 0xFFFFFFFF
 BAD_CHANNEL_ID = 0 # 0xFFFFFFFFFFFFFFFF
 BAD_CHANNEL_ID = 0 # 0xFFFFFFFFFFFFFFFF
 
 
+
 class DoCollectionManager:
 class DoCollectionManager:
     def __init__(self):
     def __init__(self):
         # Dict of {DistributedObject ids: DistributedObjects}
         # Dict of {DistributedObject ids: DistributedObjects}
@@ -186,7 +187,6 @@ class DoCollectionManager:
             strToReturn = '%s%s' % (strToReturn, self._returnObjects(self.getDoTable(ownerView=False)))
             strToReturn = '%s%s' % (strToReturn, self._returnObjects(self.getDoTable(ownerView=False)))
         return strToReturn
         return strToReturn
 
 
-
     def printObjectCount(self):
     def printObjectCount(self):
         # print object counts by distributed object type
         # print object counts by distributed object type
         print('==== OBJECT COUNT ====')
         print('==== OBJECT COUNT ====')
@@ -199,13 +199,14 @@ class DoCollectionManager:
 
 
     def getDoList(self, parentId, zoneId=None, classType=None):
     def getDoList(self, parentId, zoneId=None, classType=None):
         """
         """
-        parentId is any distributed object id.
-        zoneId is a uint32, defaults to None (all zones).  Try zone 2 if
-            you're not sure which zone to use (0 is a bad/null zone and
-            1 has had reserved use in the past as a no messages zone, while
-            2 has traditionally been a global, uber, misc stuff zone).
-        dclassType is a distributed class type filter, defaults
-            to None (no filter).
+        Args:
+            parentId: any distributed object id.
+            zoneId: a uint32, defaults to None (all zones).  Try zone 2 if
+                you're not sure which zone to use (0 is a bad/null zone and
+                1 has had reserved use in the past as a no messages zone, while
+                2 has traditionally been a global, uber, misc stuff zone).
+            dclassType: a distributed class type filter, defaults to None
+                (no filter).
 
 
         If dclassName is None then all objects in the zone are returned;
         If dclassName is None then all objects in the zone are returned;
         otherwise the list is filtered to only include objects of that type.
         otherwise the list is filtered to only include objects of that type.

+ 8 - 9
direct/src/distributed/DoHierarchy.py

@@ -26,15 +26,14 @@ class DoHierarchy:
 
 
     def getDoIds(self, getDo, parentId, zoneId=None, classType=None):
     def getDoIds(self, getDo, parentId, zoneId=None, classType=None):
         """
         """
-        Moved from DoCollectionManager
-        ==============================
-        parentId is any distributed object id.
-        zoneId is a uint32, defaults to None (all zones).  Try zone 2 if
-            you're not sure which zone to use (0 is a bad/null zone and
-            1 has had reserved use in the past as a no messages zone, while
-            2 has traditionally been a global, uber, misc stuff zone).
-        dclassType is a distributed class type filter, defaults
-            to None (no filter).
+        Args:
+            parentId: any distributed object id.
+            zoneId: a uint32, defaults to None (all zones).  Try zone 2 if
+                you're not sure which zone to use (0 is a bad/null zone and
+                1 has had reserved use in the past as a no messages zone, while
+                2 has traditionally been a global, uber, misc stuff zone).
+            dclassType: a distributed class type filter, defaults to None
+                (no filter).
 
 
         If dclassName is None then all objects in the zone are returned;
         If dclassName is None then all objects in the zone are returned;
         otherwise the list is filtered to only include objects of that type.
         otherwise the list is filtered to only include objects of that type.

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

@@ -9,6 +9,7 @@ from panda3d.direct import *
 
 
 from direct.distributed.MsgTypes import *
 from direct.distributed.MsgTypes import *
 
 
+
 class PyDatagram(Datagram):
 class PyDatagram(Datagram):
 
 
     # This is a little helper Dict to replace the huge <if> statement
     # This is a little helper Dict to replace the huge <if> statement
@@ -29,8 +30,6 @@ class PyDatagram(Datagram):
         STBlob32: (Datagram.addBlob32, None),
         STBlob32: (Datagram.addBlob32, None),
         }
         }
 
 
-    #def addChannel(self, channelId):
-    #    ...
     addChannel = Datagram.addUint64
     addChannel = Datagram.addUint64
 
 
     def addServerHeader(self, channel, sender, code):
     def addServerHeader(self, channel, sender, code):
@@ -39,14 +38,12 @@ class PyDatagram(Datagram):
         self.addChannel(sender)
         self.addChannel(sender)
         self.addUint16(code)
         self.addUint16(code)
 
 
-
     def addOldServerHeader(self, channel, sender, code):
     def addOldServerHeader(self, channel, sender, code):
         self.addChannel(channel)
         self.addChannel(channel)
         self.addChannel(sender)
         self.addChannel(sender)
         self.addChannel('A')
         self.addChannel('A')
         self.addUint16(code)
         self.addUint16(code)
 
 
-
     def addServerControlHeader(self, code):
     def addServerControlHeader(self, code):
         self.addInt8(1)
         self.addInt8(1)
         self.addChannel(CONTROL_CHANNEL)
         self.addChannel(CONTROL_CHANNEL)

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

@@ -7,6 +7,7 @@ from panda3d.core import *
 from panda3d.direct import *
 from panda3d.direct import *
 # Import the type numbers
 # Import the type numbers
 
 
+
 class PyDatagramIterator(DatagramIterator):
 class PyDatagramIterator(DatagramIterator):
 
 
     # This is a little helper Dict to replace the huge <if> statement
     # This is a little helper Dict to replace the huge <if> statement

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

@@ -137,7 +137,7 @@ class ServerRepository:
 
 
         # An allocator object that assigns the next doIdBase to each
         # An allocator object that assigns the next doIdBase to each
         # client.
         # client.
-        self.idAllocator = UniqueIdAllocator(0, 0xffffffff / self.doIdRange)
+        self.idAllocator = UniqueIdAllocator(0, 0xffffffff // self.doIdRange)
 
 
         self.dcFile = DCFile()
         self.dcFile = DCFile()
         self.dcSuffix = ''
         self.dcSuffix = ''

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