Merge branch 'master' into astron-master
This commit is contained in:
commit
c9d1c870fd
|
|
@ -8,6 +8,7 @@
|
|||
core
|
||||
core.*
|
||||
vgcore.*
|
||||
*.core
|
||||
|
||||
# Editor files/directories
|
||||
*.save
|
||||
|
|
@ -22,6 +23,9 @@ vgcore.*
|
|||
*.o
|
||||
*.gch
|
||||
*.pch
|
||||
/+DESC
|
||||
/+MANIFEST
|
||||
/pkg-plist
|
||||
|
||||
# Produced installer/executables
|
||||
/*.exe
|
||||
|
|
@ -31,6 +35,7 @@ vgcore.*
|
|||
/*.pkg
|
||||
/*.dmg
|
||||
/*.whl
|
||||
/*.txz
|
||||
|
||||
# CMake
|
||||
/build/
|
||||
|
|
@ -47,6 +52,10 @@ Thumbs.db
|
|||
ehthumbs.db
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# Test tool cache directories
|
||||
.tox/
|
||||
.cache/
|
||||
|
|
|
|||
21
.travis.yml
21
.travis.yml
|
|
@ -2,15 +2,22 @@ language: cpp
|
|||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
env: PYTHONV=python2.7 FLAGS=--optimize=4
|
||||
- compiler: clang
|
||||
env: PYTHONV=python3 FLAGS=--installer
|
||||
- compiler: clang
|
||||
env: PYTHONV=python2.7 FLAGS=--override=STDFLOAT_DOUBLE=1
|
||||
- compiler: gcc
|
||||
env: PYTHONV=python2.7 FLAGS=--optimize=4
|
||||
before_install:
|
||||
- export CC=gcc-4.7
|
||||
- export CXX=g++-4.7
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- gcc-4.7
|
||||
- g++-4.7
|
||||
- bison
|
||||
- flex
|
||||
- libfreetype6-dev
|
||||
|
|
@ -27,8 +34,16 @@ addons:
|
|||
- nvidia-cg-toolkit
|
||||
- python-dev
|
||||
- python3-dev
|
||||
- python-virtualenv
|
||||
- zlib1g-dev
|
||||
script: $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4 && LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
|
||||
- fakeroot
|
||||
install:
|
||||
- virtualenv --python=$PYTHONV venv && source venv/bin/activate
|
||||
- $PYTHONV -m pip install pytest
|
||||
script:
|
||||
- $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
|
||||
- LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
|
||||
- LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest tests
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
|
|
|
|||
68
README.md
68
README.md
|
|
@ -18,6 +18,25 @@ resources. If you get stuck, ask for help from our active
|
|||
Panda3D is licensed under the Modified BSD License. See the LICENSE file for
|
||||
more details.
|
||||
|
||||
Installing Panda3D
|
||||
==================
|
||||
|
||||
By far, the easiest way to install the latest development build of Panda3D
|
||||
into an existing Python installation is using the following command:
|
||||
|
||||
```bash
|
||||
pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
|
||||
```
|
||||
|
||||
If this command fails, please make sure your version of pip is up-to-date.
|
||||
|
||||
If you prefer to install the full SDK with all tools, the latest development
|
||||
builds can be obtained from this page:
|
||||
|
||||
https://www.panda3d.org/download.php?sdk&version=devel
|
||||
|
||||
These are automatically kept up-to-date with the latest GitHub version of Panda.
|
||||
|
||||
Building Panda3D
|
||||
================
|
||||
|
||||
|
|
@ -31,8 +50,11 @@ are included as part of the Windows 7.1 SDK.
|
|||
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,
|
||||
depending on whether you are on a 32-bit or 64-bit system:
|
||||
https://www.panda3d.org/download/panda3d-1.9.3/panda3d-1.9.3-tools-win32.zip
|
||||
https://www.panda3d.org/download/panda3d-1.9.3/panda3d-1.9.3-tools-win64.zip
|
||||
https://www.panda3d.org/download/panda3d-1.9.4/panda3d-1.9.4-tools-win32.zip
|
||||
https://www.panda3d.org/download/panda3d-1.9.4/panda3d-1.9.4-tools-win64.zip
|
||||
|
||||
(It is also possible to build using MSVC 2015 and 2017, which requires a
|
||||
different set of thirdparty libraries, but that is not described here.)
|
||||
|
||||
After acquiring these dependencies, you may simply build Panda3D from the
|
||||
command prompt using the following command:
|
||||
|
|
@ -64,7 +86,7 @@ for you to install, depending on your distribution).
|
|||
The following command illustrates how to build Panda3D with some common
|
||||
options:
|
||||
```bash
|
||||
python2.7 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
|
||||
python makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2 --no-opencv
|
||||
```
|
||||
|
||||
You will probably see some warnings saying that it's unable to find several
|
||||
|
|
@ -93,11 +115,14 @@ may have to use the installpanda.py script instead, which will directly copy the
|
|||
files into the appropriate locations on your computer. You may have to run the
|
||||
`ldconfig` tool in order to update your library cache after installing Panda3D.
|
||||
|
||||
Alternatively, you can add the `--wheel` option, which will produce a .whl
|
||||
file that can be installed into a Python installation using `pip`.
|
||||
|
||||
macOS
|
||||
-----
|
||||
|
||||
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.9.3/panda3d-1.9.3-tools-mac.tar.gz).
|
||||
compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.9.4/panda3d-1.9.4-tools-mac.tar.gz).
|
||||
|
||||
After placing the thirdparty directory inside the panda3d source directory,
|
||||
you may build Panda3D using a command like the following:
|
||||
|
|
@ -114,13 +139,46 @@ If the build was successful, makepanda will have generated a .dmg file in
|
|||
the source directory containing the installer. Simply open it and run the
|
||||
package file in order to install the SDK onto your system.
|
||||
|
||||
FreeBSD
|
||||
-------
|
||||
|
||||
Building on FreeBSD is very similar to building on Linux. You will need to
|
||||
install the requisite packages using the system package manager. To install
|
||||
the recommended set of dependencies, you can use this command:
|
||||
|
||||
```bash
|
||||
pkg install pkgconf png jpeg-turbo tiff freetype2 eigen squish openal opusfile libvorbis libX11 libGL ode bullet assimp openexr
|
||||
```
|
||||
|
||||
You will also need to choose which version of Python you want to use.
|
||||
Install the appropriate package for it (such as `python2` or `python36`) and
|
||||
run the makepanda script with your chosen Python version:
|
||||
|
||||
```bash
|
||||
python3.6 makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-gles2
|
||||
```
|
||||
|
||||
If successful, this will produce a .pkg file in the root of the source
|
||||
directory which you can install using `pkg install`.
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
||||
Install [PyTest](https://docs.pytest.org/en/latest/getting-started.html#installation)
|
||||
and run the `pytest` command. If you have not installed Panda3D, you will
|
||||
need to configure your enviroment by pointing the `PYTHONPATH` variable at
|
||||
the `built` directory. On Linux, you will also need to point the
|
||||
`LD_LIBRARY_PATH` variable at the `built/lib` directory.
|
||||
|
||||
As a convenience, you can alternatively pass the `--tests` option to makepanda.
|
||||
|
||||
Reporting Issues
|
||||
================
|
||||
|
||||
If you encounter any bugs when using Panda3D, please report them in the bug
|
||||
tracker. This is hosted at:
|
||||
|
||||
https://bugs.launchpad.net/panda3d
|
||||
https://github.com/panda3d/panda3d/issues
|
||||
|
||||
Make sure to first use the search function to see if the bug has already been
|
||||
reported. When filling out a bug report, make sure that you include as much
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ PUBLISHED:
|
|||
// This function is used to enable or disable the guides for path finding.
|
||||
void set_pf_guide(bool pf_guide);
|
||||
|
||||
AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
|
||||
explicit AICharacter(string model_name, NodePath model_np, double mass, double movt_force, double max_force);
|
||||
~AICharacter();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public:
|
|||
AINode *_next;
|
||||
|
||||
PUBLISHED:
|
||||
AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
|
||||
explicit AINode(int grid_x, int grid_y, LVecBase3 pos, float w, float l, float h);
|
||||
~AINode();
|
||||
|
||||
bool contains(float x, float y);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public:
|
|||
AICharList _ai_char_list;
|
||||
|
||||
PUBLISHED:
|
||||
Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
|
||||
explicit Flock(unsigned int flock_id, double vcone_angle, double vcone_radius, unsigned int separation_wt = 2,
|
||||
unsigned int cohesion_wt = 4, unsigned int alignment_wt = 1);
|
||||
~Flock();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config_rplight.h"
|
||||
|
||||
#include "rpLight.h"
|
||||
#include "rpPointLight.h"
|
||||
|
||||
#include "dconfig.h"
|
||||
|
||||
Configure(config_rplight);
|
||||
NotifyCategoryDef(rplight, "");
|
||||
|
||||
ConfigureFn(config_rplight) {
|
||||
init_librplight();
|
||||
}
|
||||
|
||||
void
|
||||
init_librplight() {
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
// RPLight::init_type();
|
||||
// RPPointLight::init_type();
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_RPLIGHT_H
|
||||
#define CONFIG_RPLIGHT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "notifyCategoryProxy.h"
|
||||
#include "configVariableDouble.h"
|
||||
#include "configVariableString.h"
|
||||
#include "configVariableInt.h"
|
||||
|
||||
NotifyCategoryDecl(rplight, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
extern void init_librplight();
|
||||
|
||||
#endif // CONFIG_RPLIGHT_H
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
/**
|
||||
* @brief Appends an integer to the GPUCommand.
|
||||
* @details This adds an integer to the back of the GPUCommand. Depending on the
|
||||
* setting in convert_int_to_float, this will either just convert the int to a
|
||||
* float by casting it, or just do a bitwise copy.
|
||||
*
|
||||
* @param v The integer to append.
|
||||
*/
|
||||
inline void GPUCommand::push_int(int v) {
|
||||
push_float(convert_int_to_float(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to convert an integer to float
|
||||
* @details This methods gets called by the GPUCommand::push_int, and manages
|
||||
* storing an integer in a floating point variable. There are two options,
|
||||
* which are documented inside of the method.
|
||||
*
|
||||
* @param v Integer to convert
|
||||
* @return Float-representation of that integer, either casted or binary converted.s
|
||||
*/
|
||||
inline float GPUCommand::convert_int_to_float(int v) const {
|
||||
|
||||
#if !PACK_INT_AS_FLOAT
|
||||
// Just round to float, can cause rounding issues tho
|
||||
return (float)v;
|
||||
|
||||
#else
|
||||
assert(sizeof(float) == 4); // We really need this for packing! Better
|
||||
// throw an error if the compiler uses more
|
||||
// than 4 bytes.
|
||||
// Simple binary conversion, assuming sizeof(int) == sizeof(float)
|
||||
union { int32_t _int; float _float; } converter = { (int32_t)v };
|
||||
return converter._float;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a float to the GPUCommand.
|
||||
* @details This adds an integer to the back of the GPUCommand. Its used by all
|
||||
* other push_xxx methods, and simply stores the value, then increments the write
|
||||
* pointer. When the amount of floats exceeds the capacity of the GPUCommand,
|
||||
* an error will be printed, and the method returns without doing anything else.
|
||||
*
|
||||
* @param v The float to append.
|
||||
*/
|
||||
inline void GPUCommand::push_float(float v) {
|
||||
if (_current_index >= GPU_COMMAND_ENTRIES) {
|
||||
gpucommand_cat.error() << "Out of bounds! Exceeded command size of " << GPU_COMMAND_ENTRIES << endl;
|
||||
return;
|
||||
}
|
||||
_data[_current_index++] = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a 3-component floating point vector to the GPUCommand.
|
||||
* @details This appends a 3-component floating point vector to the command.
|
||||
* It basically just calls push_float() for every component, in the order
|
||||
* x, y, z, which causes the vector to occupy the space of 3 floats.
|
||||
*
|
||||
* @param v Int-Vector to append.
|
||||
*/
|
||||
inline void GPUCommand::push_vec3(const LVecBase3 &v) {
|
||||
push_float(v.get_x());
|
||||
push_float(v.get_y());
|
||||
push_float(v.get_z());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Appends a 3-component integer vector to the GPUCommand.
|
||||
* @details This appends a 3-component integer vector to the command.
|
||||
* It basically just calls push_int() for every component, in the order
|
||||
* x, y, z, which causes the vector to occupy the space of 3 floats.
|
||||
*
|
||||
* @param v Int-Vector to append.
|
||||
*/
|
||||
inline void GPUCommand::push_vec3(const LVecBase3i &v) {
|
||||
push_int(v.get_x());
|
||||
push_int(v.get_y());
|
||||
push_int(v.get_z());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a 4-component floating point vector to the GPUCommand.
|
||||
* @details This appends a 4-component floating point vector to the command.
|
||||
* It basically just calls push_float() for every component, in the order
|
||||
* x, y, z, which causes the vector to occupy the space of 3 floats.
|
||||
*
|
||||
* @param v Int-Vector to append.
|
||||
*/
|
||||
inline void GPUCommand::push_vec4(const LVecBase4 &v) {
|
||||
push_float(v.get_x());
|
||||
push_float(v.get_y());
|
||||
push_float(v.get_z());
|
||||
push_float(v.get_w());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a 4-component integer vector to the GPUCommand.
|
||||
* @details This appends a 4-component integer vector to the command.
|
||||
* It basically just calls push_int() for every component, in the order
|
||||
* x, y, z, w, which causes the vector to occupy the space of 4 floats.
|
||||
*
|
||||
* @param v Int-Vector to append.
|
||||
*/
|
||||
inline void GPUCommand::push_vec4(const LVecBase4i &v) {
|
||||
push_int(v.get_x());
|
||||
push_int(v.get_y());
|
||||
push_int(v.get_z());
|
||||
push_int(v.get_w());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a floating point 3x3 matrix to the GPUCommand.
|
||||
* @details This appends a floating point 3x3 matrix to the GPUCommand, by
|
||||
* pushing all components in row-order to the command. This occupies a space of
|
||||
* 9 floats.
|
||||
*
|
||||
* @param v Matrix to append
|
||||
*/
|
||||
inline void GPUCommand::push_mat3(const LMatrix3 &v) {
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
push_float(v.get_cell(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a floating point 4x4 matrix to the GPUCommand.
|
||||
* @details This appends a floating point 4x4 matrix to the GPUCommand, by
|
||||
* pushing all components in row-order to the command. This occupies a space of
|
||||
* 16 floats.
|
||||
*
|
||||
* @param v Matrix to append
|
||||
*/
|
||||
inline void GPUCommand::push_mat4(const LMatrix4 &v) {
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
for (size_t j = 0; j < 4; ++j) {
|
||||
push_float(v.get_cell(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether integers are packed as floats.
|
||||
* @details This returns how integer are packed into the data stream. If the
|
||||
* returned value is true, then integers are packed using their binary
|
||||
* representation converted to floating point format. If the returned value
|
||||
* is false, then integers are packed by simply casting them to float,
|
||||
* e.g. val = (float)i;
|
||||
* @return The integer representation flag
|
||||
*/
|
||||
inline bool GPUCommand::get_uses_integer_packing() {
|
||||
return PACK_INT_AS_FLOAT;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gpuCommand.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
NotifyCategoryDef(gpucommand, "");
|
||||
|
||||
/**
|
||||
* @brief Constructs a new GPUCommand with the given command type.
|
||||
* @details This will construct a new GPUCommand of the given command type.
|
||||
* The command type should be of GPUCommand::CommandType, and determines
|
||||
* what data the GPUCommand contains, and how it will be handled.
|
||||
*
|
||||
* @param command_type The type of the GPUCommand
|
||||
*/
|
||||
GPUCommand::GPUCommand(CommandType command_type) {
|
||||
_command_type = command_type;
|
||||
_current_index = 0;
|
||||
memset(_data, 0x0, sizeof(float) * GPU_COMMAND_ENTRIES);
|
||||
|
||||
// Store the command type as the first entry
|
||||
push_int(command_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints out the GPUCommand to the console
|
||||
* @details This method prints the type, size, and data of the GPUCommand to the
|
||||
* console. This helps for debugging the contents of the GPUCommand. Keep
|
||||
* in mind that integers might be shown in their binary float representation,
|
||||
* depending on the setting in the GPUCommand::convert_int_to_float method.
|
||||
*/
|
||||
void GPUCommand::write(ostream &out) const {
|
||||
out << "GPUCommand(type=" << _command_type << ", size=" << _current_index << ", data = {" << endl;
|
||||
for (size_t k = 0; k < GPU_COMMAND_ENTRIES; ++k) {
|
||||
out << std::setw(12) << std::fixed << std::setprecision(5) << _data[k] << " ";
|
||||
if (k % 6 == 5 || k == GPU_COMMAND_ENTRIES - 1) out << endl;
|
||||
}
|
||||
out << "})" << endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the GPU command to a given target.
|
||||
* @details This method writes all the data of the GPU command to a given target.
|
||||
* The target should be a pointer to memory being big enough to hold the
|
||||
* data. Presumably #dest will be a handle to texture memory.
|
||||
* The command_index controls the offset where the data will be written
|
||||
* to.
|
||||
*
|
||||
* @param dest Handle to the memory to write the command to
|
||||
* @param command_index Offset to write the command to. The command will write
|
||||
* its data to command_index * GPU_COMMAND_ENTRIES. When writing
|
||||
* the GPUCommand in a GPUCommandList, the command_index will
|
||||
* most likely be the index of the command in the list.
|
||||
*/
|
||||
void GPUCommand::write_to(const PTA_uchar &dest, size_t command_index) {
|
||||
size_t command_size = GPU_COMMAND_ENTRIES * sizeof(float);
|
||||
size_t offset = command_index * command_size;
|
||||
memcpy(dest.p() + offset, &_data, command_size);
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GPUCOMMAND_H
|
||||
#define GPUCOMMAND_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "luse.h"
|
||||
|
||||
NotifyCategoryDecl(gpucommand, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
#define GPU_COMMAND_ENTRIES 32
|
||||
|
||||
// Packs integers by storing their binary representation in floats
|
||||
// This only works if the command and light buffer is 32bit floating point.
|
||||
#define PACK_INT_AS_FLOAT 0
|
||||
|
||||
/**
|
||||
* @brief Class for storing data to be transferred to the GPU.
|
||||
* @details This class can be seen like a packet, to be transferred to the GPU.
|
||||
* It has a command type, which tells the GPU what to do once it recieved this
|
||||
* "packet". It stores a limited amount of floating point components.
|
||||
*/
|
||||
class GPUCommand {
|
||||
PUBLISHED:
|
||||
/**
|
||||
* The different types of GPUCommands. Each type has a special case in
|
||||
* the command queue processor. When adding new types, those need to
|
||||
* be handled in the command target, too.
|
||||
*/
|
||||
enum CommandType {
|
||||
CMD_invalid = 0,
|
||||
CMD_store_light = 1,
|
||||
CMD_remove_light = 2,
|
||||
CMD_store_source = 3,
|
||||
CMD_remove_sources = 4,
|
||||
};
|
||||
|
||||
GPUCommand(CommandType command_type);
|
||||
|
||||
inline void push_int(int v);
|
||||
inline void push_float(float v);
|
||||
inline void push_vec3(const LVecBase3 &v);
|
||||
inline void push_vec3(const LVecBase3i &v);
|
||||
inline void push_vec4(const LVecBase4 &v);
|
||||
inline void push_vec4(const LVecBase4i &v);
|
||||
inline void push_mat3(const LMatrix3 &v);
|
||||
inline void push_mat4(const LMatrix4 &v);
|
||||
|
||||
inline static bool get_uses_integer_packing();
|
||||
|
||||
void write_to(const PTA_uchar &dest, size_t command_index);
|
||||
void write(ostream &out) const;
|
||||
|
||||
private:
|
||||
|
||||
inline float convert_int_to_float(int v) const;
|
||||
|
||||
CommandType _command_type;
|
||||
size_t _current_index;
|
||||
float _data[GPU_COMMAND_ENTRIES];
|
||||
};
|
||||
|
||||
#include "gpuCommand.I"
|
||||
|
||||
#endif // GPUCOMMAND_H
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gpuCommandList.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs a new GPUCommandList
|
||||
* @details This constructs a new GPUCommandList. By default, there are no commands
|
||||
* in the list.
|
||||
*/
|
||||
GPUCommandList::GPUCommandList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pushes a GPUCommand to the command list.
|
||||
* @details This adds a new GPUCommand to the list of commands to be processed.
|
||||
*
|
||||
* @param cmd The command to add
|
||||
*/
|
||||
void GPUCommandList::add_command(const GPUCommand& cmd) {
|
||||
_commands.push(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of commands in this list.
|
||||
* @details This returns the amount of commands which are currently stored in this
|
||||
* list, and are waiting to get processed.
|
||||
* @return Amount of commands
|
||||
*/
|
||||
size_t GPUCommandList::get_num_commands() {
|
||||
return _commands.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the first n-commands to a destination.
|
||||
* @details This takes the first #limit commands, and writes them to the
|
||||
* destination using GPUCommand::write_to. See GPUCommand::write_to for
|
||||
* further information about #dest. The limit controls after how much
|
||||
* commands the processing will be stopped. All commands which got processed
|
||||
* will get removed from the list.
|
||||
*
|
||||
* @param dest Destination to write to, see GPUCommand::write_to
|
||||
* @param limit Maximum amount of commands to process
|
||||
*
|
||||
* @return Amount of commands processed, between 0 and #limit.
|
||||
*/
|
||||
size_t GPUCommandList::write_commands_to(const PTA_uchar &dest, size_t limit) {
|
||||
size_t num_commands_written = 0;
|
||||
|
||||
while (num_commands_written < limit && !_commands.empty()) {
|
||||
// Write the first command to the stream, and delete it afterwards
|
||||
_commands.front().write_to(dest, num_commands_written);
|
||||
_commands.pop();
|
||||
num_commands_written ++;
|
||||
}
|
||||
|
||||
return num_commands_written;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GPUCOMMANDLIST_H
|
||||
#define GPUCOMMANDLIST_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "gpuCommand.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
/**
|
||||
* @brief Class to store a list of commands.
|
||||
* @details This is a class to store a list of GPUCommands. It provides
|
||||
* functionality to only provide the a given amount of commands at one time.
|
||||
*/
|
||||
class GPUCommandList {
|
||||
PUBLISHED:
|
||||
GPUCommandList();
|
||||
|
||||
void add_command(const GPUCommand& cmd);
|
||||
size_t get_num_commands();
|
||||
size_t write_commands_to(const PTA_uchar &dest, size_t limit = 32);
|
||||
|
||||
MAKE_PROPERTY(num_commands, get_num_commands);
|
||||
|
||||
protected:
|
||||
queue<GPUCommand> _commands;
|
||||
};
|
||||
|
||||
#endif // GPUCOMMANDLIST_H
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "iesDataset.h"
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
NotifyCategoryDef(iesdataset, "")
|
||||
|
||||
/**
|
||||
* @brief Constructs a new empty dataset.
|
||||
* @details This constructs a new IESDataset with no data set.
|
||||
*/
|
||||
IESDataset::IESDataset() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the vertical angles of the dataset.
|
||||
* @details This sets the list of vertical angles of the dataset.
|
||||
*
|
||||
* @param vertical_angles Vector of all vertical angles.
|
||||
*/
|
||||
void IESDataset::set_vertical_angles(const PTA_float &vertical_angles) {
|
||||
nassertv(vertical_angles.size() > 0);
|
||||
_vertical_angles = vertical_angles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the horizontal angles of the dataset.
|
||||
* @details This sets the list of horizontal angles of the dataset.
|
||||
*
|
||||
* @param horizontal_angles Vector of all horizontal angles.
|
||||
*/
|
||||
void IESDataset::set_horizontal_angles(const PTA_float &horizontal_angles) {
|
||||
nassertv(horizontal_angles.size() > 0);
|
||||
_horizontal_angles = horizontal_angles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the candela values.
|
||||
* @details This sets the candela values of the dataset. They should be an
|
||||
* interleaved 2D array with the dimensions vertical_angles x horizontal_angles.
|
||||
* They also should be normalized by dividing by the maximum entry.
|
||||
* @param candela_values Interleaved 2D-vector of candela values.
|
||||
*/
|
||||
void IESDataset::set_candela_values(const PTA_float &candela_values) {
|
||||
nassertv(candela_values.size() == _horizontal_angles.size() * _vertical_angles.size());
|
||||
_candela_values = candela_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to access the candela data.
|
||||
* @details This lookups a candela value in the candela values. It converts a
|
||||
* two dimensional index to a onedimensional index and then returns the candela
|
||||
* value at that position.
|
||||
*
|
||||
* @param vertical_angle_idx Index of the vertical angle
|
||||
* @param horizontal_angle_idx Index of the horizontal angle
|
||||
*
|
||||
* @return Candela value between 0 .. 1
|
||||
*/
|
||||
float IESDataset::get_candela_value_from_index(size_t vertical_angle_idx, size_t horizontal_angle_idx) const {
|
||||
size_t index = vertical_angle_idx + horizontal_angle_idx * _vertical_angles.size();
|
||||
nassertr(index >= 0 && index < _candela_values.size(), 0.0);
|
||||
return _candela_values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Samples the dataset at the given position
|
||||
* @details This looks up a value in the dataset, by specifying a horizontal and
|
||||
* vertical angle. This is used for generating the LUT. The vertical and horizontal
|
||||
* angle should be inside of the bounds of the vertical and horizontal angle arrays.
|
||||
*
|
||||
* @param vertical_angle Vertical angle, from 0 .. 90 or 0 .. 180 depending on the dataset
|
||||
* @param horizontal_angle Horizontal angle, from 0 .. 180 or 0 .. 360 depending on the dataset.
|
||||
*
|
||||
* @return Candela value between 0 .. 1
|
||||
*/
|
||||
float IESDataset::get_candela_value(float vertical_angle, float horizontal_angle) const {
|
||||
|
||||
// Special case for datasets without horizontal angles
|
||||
if (_horizontal_angles.size() == 1) {
|
||||
return get_vertical_candela_value(0, vertical_angle);
|
||||
}
|
||||
|
||||
float max_angle = _horizontal_angles[_horizontal_angles.size() - 1];
|
||||
|
||||
// Wrap angle to fit from 0 .. 360 degree. Most profiles only distribute
|
||||
// candela values from 0 .. 180 or even 0 .. 90. We have to mirror the
|
||||
// values at those borders (so 2 times for 180 degree and 4 times for 90 degree)
|
||||
horizontal_angle = fmod(horizontal_angle, 2.0f * max_angle);
|
||||
if (horizontal_angle > max_angle) {
|
||||
horizontal_angle = 2.0 * max_angle - horizontal_angle;
|
||||
}
|
||||
|
||||
// Simlar to the vertical step, we now try interpolating a horizontal angle,
|
||||
// but we need to evaluate the vertical value for each row instead of fetching
|
||||
// the value directly
|
||||
for (size_t horizontal_index = 1; horizontal_index < _horizontal_angles.size(); ++horizontal_index) {
|
||||
float curr_angle = _horizontal_angles[horizontal_index];
|
||||
|
||||
if (curr_angle >= horizontal_angle) {
|
||||
|
||||
// Get previous angle data
|
||||
float prev_angle = _horizontal_angles[horizontal_index - 1];
|
||||
float prev_value = get_vertical_candela_value(horizontal_index - 1, vertical_angle);
|
||||
float curr_value = get_vertical_candela_value(horizontal_index, vertical_angle);
|
||||
|
||||
// Interpolate lineary
|
||||
float lerp = (horizontal_angle - prev_angle) / (curr_angle - prev_angle);
|
||||
|
||||
// Should never occur, but to be safe:
|
||||
if (lerp < 0.0 || lerp > 1.0) {
|
||||
iesdataset_cat.error() << "Invalid horizontal lerp: " << lerp
|
||||
<< ", requested angle was " << horizontal_angle
|
||||
<< ", prev = " << prev_angle << ", cur = " << curr_angle
|
||||
<< endl;
|
||||
}
|
||||
|
||||
return curr_value * lerp + prev_value * (1-lerp);
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetches a vertical candela value
|
||||
* @details Fetches a vertical candela value, using a given horizontal position.
|
||||
* This does an 1D interpolation in the candela values array.
|
||||
*
|
||||
* @param horizontal_angle_idx The index of the horizontal angle in the horizontal
|
||||
* angle array.
|
||||
* @param vertical_angle The vertical angle. Interpolation will be done if the
|
||||
* vertical angle is not in the vertical angles array.
|
||||
*
|
||||
* @return Candela value between 0 .. 1
|
||||
*/
|
||||
float IESDataset::get_vertical_candela_value(size_t horizontal_angle_idx, float vertical_angle) const {
|
||||
nassertr(horizontal_angle_idx >= 0 && horizontal_angle_idx < _horizontal_angles.size(), 0.0);
|
||||
|
||||
// Lower bound
|
||||
if (vertical_angle < 0.0) return 0.0;
|
||||
|
||||
// Upper bound
|
||||
if (vertical_angle > _vertical_angles[_vertical_angles.size() - 1] ) return 0.0;
|
||||
|
||||
// Find lowest enclosing angle
|
||||
for (size_t vertical_index = 1; vertical_index < _vertical_angles.size(); ++vertical_index) {
|
||||
float curr_angle = _vertical_angles[vertical_index];
|
||||
|
||||
// Found value
|
||||
if (curr_angle > vertical_angle) {
|
||||
|
||||
// Get previous angle data
|
||||
float prev_angle = _vertical_angles[vertical_index - 1];
|
||||
float prev_value = get_candela_value_from_index(vertical_index - 1, horizontal_angle_idx);
|
||||
float curr_value = get_candela_value_from_index(vertical_index, horizontal_angle_idx);
|
||||
|
||||
// Interpolate lineary
|
||||
float lerp = (vertical_angle - prev_angle) / (curr_angle - prev_angle);
|
||||
|
||||
// Should never occur, but to be safe:
|
||||
if (lerp < 0.0 || lerp > 1.0) {
|
||||
iesdataset_cat.error() << "ERROR: Invalid vertical lerp: " << lerp
|
||||
<< ", requested angle was " << vertical_angle
|
||||
<< ", prev = " << prev_angle << ", cur = " << curr_angle
|
||||
<< endl;
|
||||
}
|
||||
|
||||
return curr_value * lerp + prev_value * (1-lerp);
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates the IES LUT
|
||||
* @details This generates the LUT into a given dataset texture. The x-axis
|
||||
* referes to the vertical_angle, whereas the y-axis refers to the
|
||||
* horizontal angle.
|
||||
*
|
||||
* @param dest_tex Texture to write the LUT into
|
||||
* @param z Layer to write the LUT into, in case the texture is a 3D Texture or
|
||||
* 2D Texture Array.
|
||||
*/
|
||||
void IESDataset::generate_dataset_texture_into(Texture* dest_tex, size_t z) const {
|
||||
|
||||
size_t resolution_vertical = dest_tex->get_y_size();
|
||||
size_t resolution_horizontal = dest_tex->get_x_size();
|
||||
|
||||
// Candla values are stored flippped - vertical angles in the x - Axis
|
||||
// and horizontal angles in the y - Axis
|
||||
PNMImage dest = PNMImage(resolution_vertical, resolution_horizontal, 1, 65535);
|
||||
|
||||
for (size_t vert = 0; vert < resolution_vertical; ++vert) {
|
||||
for (size_t horiz = 0; horiz < resolution_horizontal; ++horiz) {
|
||||
float vert_angle = (float)vert / (float)(resolution_vertical-1);
|
||||
vert_angle = cos(vert_angle * M_PI) * 90.0 + 90.0;
|
||||
float horiz_angle = (float)horiz / (float)(resolution_horizontal-1) * 360.0;
|
||||
float candela = get_candela_value(vert_angle, horiz_angle);
|
||||
dest.set_xel(vert, horiz, candela);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dest_tex->load(dest, z, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IESDATASET_H
|
||||
#define IESDATASET_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "pta_float.h"
|
||||
#include "pointerToArray.h"
|
||||
#include "texture.h"
|
||||
#include "pnmImage.h"
|
||||
|
||||
NotifyCategoryDecl(iesdataset, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
|
||||
/**
|
||||
* @brief This class generates a LUT from IES data.
|
||||
* @details This class is used by the IESLoader to generate a LUT texture which
|
||||
* is used in the shaders to perform IES lighting. It takes a set of vertical
|
||||
* and horizontal angles, as well as a set of candela values, which then are
|
||||
* lineary interpolated onto a 2D LUT Texture.
|
||||
*/
|
||||
class IESDataset {
|
||||
PUBLISHED:
|
||||
IESDataset();
|
||||
|
||||
void set_vertical_angles(const PTA_float &vertical_angles);
|
||||
void set_horizontal_angles(const PTA_float &horizontal_angles);
|
||||
void set_candela_values(const PTA_float &candela_values);
|
||||
|
||||
void generate_dataset_texture_into(Texture* dest_tex, size_t z) const;
|
||||
|
||||
public:
|
||||
|
||||
float get_candela_value(float vertical_angle, float horizontal_angle) const;
|
||||
float get_candela_value_from_index(size_t vertical_angle_idx, size_t horizontal_angle_idx) const;
|
||||
float get_vertical_candela_value(size_t horizontal_angle_idx, float vertical_angle) const;
|
||||
|
||||
private:
|
||||
PTA_float _vertical_angles;
|
||||
PTA_float _horizontal_angles;
|
||||
PTA_float _candela_values;
|
||||
};
|
||||
|
||||
#endif // IESDATASET_H
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the maximum light index
|
||||
* @details This returns the maximum light index (also called slot). Any lights
|
||||
* after that slot are guaranteed to be zero-lights. This is useful when
|
||||
* iterating over the list of lights, because iteration can be stopped when
|
||||
* the maximum light index is reached.
|
||||
*
|
||||
* The maximum light index points to the last slot which is used. If no lights
|
||||
* are attached, -1 is returned. If one light is attached at slot 0, the index
|
||||
* is 0, if two are attached at the slots 0 and 1, the index is 1, and so on.
|
||||
*
|
||||
* If, for example, two lights are attached at the slots 2 and 5, then the
|
||||
* index will be 5. Keep in mind that the max-index is not an indicator for
|
||||
* how many lights are attached. Also, zero lights still may occur when iterating
|
||||
* over the light lists
|
||||
*
|
||||
* @return Maximum light index
|
||||
*/
|
||||
inline int InternalLightManager::get_max_light_index() const {
|
||||
return _lights.get_max_index();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of stored lights.
|
||||
* @details This returns the amount of stored lights. This behaves unlike
|
||||
* InternalLightManager::get_max_light_index, and instead returns the true
|
||||
* amount of lights, which is completely unrelated to the amount of used slots.
|
||||
*
|
||||
* @return Amount of stored lights
|
||||
*/
|
||||
inline size_t InternalLightManager::get_num_lights() const {
|
||||
return _lights.get_num_entries();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of shadow sources.
|
||||
* @details This returns the total amount of stored shadow sources. This does
|
||||
* not denote the amount of updated sources, but instead takes into account
|
||||
* all sources, even those out of frustum.
|
||||
* @return Amount of shadow sources.
|
||||
*/
|
||||
inline size_t InternalLightManager::get_num_shadow_sources() const {
|
||||
return _shadow_sources.get_num_entries();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the handle to the shadow manager
|
||||
* @details This sets the handle to the global shadow manager. It is usually
|
||||
* constructed on the python side, so we need to get a handle to it.
|
||||
*
|
||||
* The manager should be a handle to a ShadowManager instance, and will be
|
||||
* stored somewhere on the python side most likely. The light manager does not
|
||||
* keep a reference to it, so the python side should make sure to keep one.
|
||||
*
|
||||
* Be sure to call this before the InternalLightManager::update() method is
|
||||
* called, otherwise an assertion will get triggered.
|
||||
*
|
||||
* @param mgr The ShadowManager instance
|
||||
*/
|
||||
inline void InternalLightManager::set_shadow_manager(ShadowManager* mgr) {
|
||||
_shadow_manager = mgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a handle to the command list
|
||||
* @details This sets a handle to the global GPUCommandList. This is required to
|
||||
* emit GPUCommands, which are used for attaching and detaching lights, as well
|
||||
* as shadow source updates.
|
||||
*
|
||||
* The cmd_list should be a handle to a GPUCommandList handle, and will be
|
||||
* stored somewhere on the python side most likely. The light manager does not
|
||||
* keep a reference to it, so the python side should make sure to keep one.
|
||||
*
|
||||
* Be sure to call this before the InternalLightManager::update() method is
|
||||
* called, otherwise an assertion will get triggered.
|
||||
*
|
||||
* @param cmd_list The GPUCommandList instance
|
||||
*/
|
||||
inline void InternalLightManager::set_command_list(GPUCommandList *cmd_list) {
|
||||
_cmd_list = cmd_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the camera position
|
||||
* @details This sets the camera position, which will be used to determine which
|
||||
* shadow sources have to get updated
|
||||
*
|
||||
* @param mat View projection mat
|
||||
*/
|
||||
inline void InternalLightManager::set_camera_pos(const LPoint3 &pos) {
|
||||
_camera_pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum shadow update distance
|
||||
* @details This controls the maximum distance until which shadows are updated.
|
||||
* If a shadow source is past that distance, it is ignored and no longer recieves
|
||||
* updates until it is in range again
|
||||
*
|
||||
* @param dist Distance in world space units
|
||||
*/
|
||||
inline void InternalLightManager::set_shadow_update_distance(PN_stdfloat dist) {
|
||||
_shadow_update_distance = dist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the internal used ShadowManager
|
||||
* @details This returns a handle to the internally used shadow manager
|
||||
* @return Shadow manager
|
||||
*/
|
||||
inline ShadowManager* InternalLightManager::get_shadow_manager() const {
|
||||
return _shadow_manager;
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "internalLightManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
NotifyCategoryDef(lightmgr, "");
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs the light manager
|
||||
* @details This constructs the light manager, initializing the light and shadow
|
||||
* storage. You should set a command list and shadow manager before calling
|
||||
* InternalLightManager::update. s
|
||||
*/
|
||||
InternalLightManager::InternalLightManager() {
|
||||
_shadow_update_distance = 100.0;
|
||||
_cmd_list = nullptr;
|
||||
_shadow_manager = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a new light.
|
||||
* @details This adds a new light to the list of lights. This will throw an
|
||||
* error and return if the light is already attached. You may only call
|
||||
* this after the ShadowManager was already set.
|
||||
*
|
||||
* While the light is attached, the light manager keeps a reference to it, so
|
||||
* the light does not get destructed.
|
||||
*
|
||||
* This also setups the shadows on the light, in case shadows are enabled.
|
||||
* While a light is attached, you can not change whether it casts shadows or not.
|
||||
* To do so, detach the light, change the setting, and re-add the light.
|
||||
*
|
||||
* In case no free light slot is available, an error will be printed and no
|
||||
* action will be performed.
|
||||
*
|
||||
* If no shadow manager was set, an assertion will be triggered.
|
||||
*
|
||||
* @param light The light to add.
|
||||
*/
|
||||
void InternalLightManager::add_light(PT(RPLight) light) {
|
||||
nassertv(_shadow_manager != nullptr); // Shadow manager not set yet!
|
||||
|
||||
// Don't attach the light in case its already attached
|
||||
if (light->has_slot()) {
|
||||
lightmgr_cat.error() << "could not add light because it already is attached! "
|
||||
<< "Detach the light first, then try it again." << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a free slot
|
||||
size_t slot;
|
||||
if (!_lights.find_slot(slot)) {
|
||||
lightmgr_cat.error() << "Light limit of " << MAX_LIGHT_COUNT << " reached, "
|
||||
<< "all light slots used!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reference the light because we store it, to avoid it getting destructed
|
||||
// on the python side while we still work with it. The reference will be
|
||||
// removed when the light gets detached.
|
||||
light->ref();
|
||||
|
||||
// Reserve the slot
|
||||
light->assign_slot(slot);
|
||||
_lights.reserve_slot(slot, light);
|
||||
|
||||
// Setup the shadows in case the light uses them
|
||||
if (light->get_casts_shadows()) {
|
||||
setup_shadows(light);
|
||||
}
|
||||
|
||||
// Store the light on the gpu, to make sure the GPU directly knows about it.
|
||||
// We could wait until the next update cycle, but then we might be one frame
|
||||
// too late already.
|
||||
gpu_update_light(light);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to setup shadows for a light
|
||||
* @details This method gets called by the InternalLightManager::add_light method
|
||||
* to setup a lights shadow sources, in case shadows are enabled on that light.
|
||||
*
|
||||
* It finds a slot for all shadow sources of the ilhgt, and inits the shadow
|
||||
* sources as well. If no slot could be found, an error is printed an nothing
|
||||
* happens.
|
||||
*
|
||||
* @param light The light to init the shadow sources for
|
||||
*/
|
||||
void InternalLightManager::setup_shadows(RPLight* light) {
|
||||
|
||||
// Init the lights shadow sources, and also call update once to make sure
|
||||
// the sources are properly initialized
|
||||
light->init_shadow_sources();
|
||||
light->update_shadow_sources();
|
||||
|
||||
// Find consecutive slots, this is important for PointLights so we can just
|
||||
// store the first index of the source, and get the other slots by doing
|
||||
// first_index + 1, +2 and so on.
|
||||
size_t base_slot;
|
||||
size_t num_sources = light->get_num_shadow_sources();
|
||||
if (!_shadow_sources.find_consecutive_slots(base_slot, num_sources)) {
|
||||
lightmgr_cat.error() << "Failed to find slot for shadow sources! "
|
||||
<< "Shadow-Source limit of " << MAX_SHADOW_SOURCES
|
||||
<< " reached!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Init all sources
|
||||
for (int i = 0; i < num_sources; ++i) {
|
||||
ShadowSource* source = light->get_shadow_source(i);
|
||||
|
||||
// Set the source as dirty, so it gets updated in the beginning
|
||||
source->set_needs_update(true);
|
||||
|
||||
// Assign the slot to the source. Since we got consecutive slots, we can
|
||||
// just do base_slot + N.
|
||||
size_t slot = base_slot + i;
|
||||
_shadow_sources.reserve_slot(slot, source);
|
||||
source->set_slot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a light
|
||||
* @details This detaches a light. This prevents it from being rendered, and also
|
||||
* cleans up all resources used by that light. If no reference is kept on the
|
||||
* python side, the light will also get destructed.
|
||||
*
|
||||
* If the light was not previously attached with InternalLightManager::add_light,
|
||||
* an error will be triggered and nothing happens.
|
||||
*
|
||||
* In case the light was set to cast shadows, all shadow sources are cleaned
|
||||
* up, and their regions in the shadow atlas are freed.
|
||||
*
|
||||
* All resources used by the light in the light and shadow storage are also
|
||||
* cleaned up, by emitting cleanup GPUCommands.
|
||||
*
|
||||
* If no shadow manager was set, an assertion will be triggered.
|
||||
*
|
||||
* @param light [description]
|
||||
*/
|
||||
void InternalLightManager::remove_light(PT(RPLight) light) {
|
||||
nassertv(_shadow_manager != nullptr);
|
||||
|
||||
if (!light->has_slot()) {
|
||||
lightmgr_cat.error() << "Could not detach light, light was not attached!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Free the lights slot in the light storage
|
||||
_lights.free_slot(light->get_slot());
|
||||
|
||||
// Tell the GPU we no longer need the lights data
|
||||
gpu_remove_light(light);
|
||||
|
||||
// Mark the light as detached. After this call, we can not call get_slot
|
||||
// anymore, so its important we do this after we unregistered the light
|
||||
// from everywhere.
|
||||
light->remove_slot();
|
||||
|
||||
// Clear shadow related stuff, in case the light casts shadows
|
||||
if (light->get_casts_shadows()) {
|
||||
|
||||
// Free the slots of all sources, and also unregister their regions from
|
||||
// the shadow atlas.
|
||||
for (size_t i = 0; i < light->get_num_shadow_sources(); ++i) {
|
||||
ShadowSource* source = light->get_shadow_source(i);
|
||||
if (source->has_slot()) {
|
||||
_shadow_sources.free_slot(source->get_slot());
|
||||
}
|
||||
if (source->has_region()) {
|
||||
_shadow_manager->get_atlas()->free_region(source->get_region());
|
||||
source->clear_region();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all sources of the light by emitting a consecutive remove command
|
||||
gpu_remove_consecutive_sources(light->get_shadow_source(0),
|
||||
light->get_num_shadow_sources());
|
||||
|
||||
// Finally remove all shadow sources. This is important in case the light
|
||||
// will be re-attached. Otherwise an assertion will get triggered.
|
||||
light->clear_shadow_sources();
|
||||
}
|
||||
|
||||
// Since we referenced the light when we stored it, we have to decrease
|
||||
// the reference now. In case no reference was kept on the python side,
|
||||
// the light will get destructed soon.
|
||||
light->unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to remove consecutive sources from the GPU.
|
||||
* @details This emits a GPUCommand to consecutively remove shadow sources from
|
||||
* the GPU. This is called when a light gets removed, to free the space its
|
||||
* shadow sources took. Its not really required, because as long as the light
|
||||
* is not used, there is no reference to the sources. However, it can't hurt to
|
||||
* cleanup the memory.
|
||||
*
|
||||
* All sources starting at first_source->get_slot() until
|
||||
* first_source->get_slot() + num_sources will get cleaned up.
|
||||
*
|
||||
* @param first_source First source of the light
|
||||
* @param num_sources Amount of consecutive sources to clear
|
||||
*/
|
||||
void InternalLightManager::gpu_remove_consecutive_sources(ShadowSource *first_source,
|
||||
size_t num_sources) {
|
||||
nassertv(_cmd_list != nullptr); // No command list set yet
|
||||
nassertv(first_source->has_slot()); // Source has no slot!
|
||||
GPUCommand cmd_remove(GPUCommand::CMD_remove_sources);
|
||||
cmd_remove.push_int(first_source->get_slot());
|
||||
cmd_remove.push_int(num_sources);
|
||||
_cmd_list->add_command(cmd_remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to remove a light from the GPU.
|
||||
* @details This emits a GPUCommand to clear a lights data. This sets the data
|
||||
* to all zeros, marking that no light is stored anymore.
|
||||
*
|
||||
* This throws an assertion in case the light is not currently attached. Be
|
||||
* sure to call this before detaching the light.
|
||||
*
|
||||
* @param light The light to remove, must be attached.
|
||||
*/
|
||||
void InternalLightManager::gpu_remove_light(RPLight* light) {
|
||||
nassertv(_cmd_list != nullptr); // No command list set yet
|
||||
nassertv(light->has_slot()); // Light has no slot!
|
||||
GPUCommand cmd_remove(GPUCommand::CMD_remove_light);
|
||||
cmd_remove.push_int(light->get_slot());
|
||||
_cmd_list->add_command(cmd_remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a lights data on the GPU
|
||||
* @details This method emits a GPUCommand to update a lights data. This can
|
||||
* be used to initially store the lights data, or to update the data whenever
|
||||
* the light changed.
|
||||
*
|
||||
* This throws an assertion in case the light is not currently attached. Be
|
||||
* sure to call this after attaching the light.
|
||||
*
|
||||
* @param light The light to update
|
||||
*/
|
||||
void InternalLightManager::gpu_update_light(RPLight* light) {
|
||||
nassertv(_cmd_list != nullptr); // No command list set yet
|
||||
nassertv(light->has_slot()); // Light has no slot!
|
||||
GPUCommand cmd_update(GPUCommand::CMD_store_light);
|
||||
cmd_update.push_int(light->get_slot());
|
||||
light->write_to_command(cmd_update);
|
||||
light->set_needs_update(false);
|
||||
_cmd_list->add_command(cmd_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a shadow source data on the GPU
|
||||
* @details This emits a GPUCommand to update a given shadow source, storing all
|
||||
* data of the source on the GPU. This can also be used to initially store a
|
||||
* ShadowSource, since all data will be overridden.
|
||||
*
|
||||
* This throws an assertion if the source has no slot yet.
|
||||
*
|
||||
* @param source The source to update
|
||||
*/
|
||||
void InternalLightManager::gpu_update_source(ShadowSource* source) {
|
||||
nassertv(_cmd_list != nullptr); // No command list set yet
|
||||
nassertv(source->has_slot()); // Source has no slot!
|
||||
GPUCommand cmd_update(GPUCommand::CMD_store_source);
|
||||
cmd_update.push_int(source->get_slot());
|
||||
source->write_to_command(cmd_update);
|
||||
_cmd_list->add_command(cmd_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to update all lights
|
||||
* @details This is called by the main update method, and iterates over the list
|
||||
* of lights. If a light is marked as dirty, it will recieve an update of its
|
||||
* data and its shadow sources.
|
||||
*/
|
||||
void InternalLightManager::update_lights() {
|
||||
for (auto iter = _lights.begin(); iter != _lights.end(); ++iter) {
|
||||
RPLight* light = *iter;
|
||||
if (light && light->get_needs_update()) {
|
||||
if (light->get_casts_shadows()) {
|
||||
light->update_shadow_sources();
|
||||
}
|
||||
gpu_update_light(light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares shadow sources by their priority
|
||||
* @details Returns if a has a greater priority than b. This depends on the
|
||||
* resolution of the source, and also if the source has a region or not.
|
||||
* This method can be passed to std::sort.
|
||||
*
|
||||
* @param a First source
|
||||
* @param b Second source
|
||||
*
|
||||
* @return true if a is more important than b, else false
|
||||
*/
|
||||
bool InternalLightManager::compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const {
|
||||
|
||||
// Make sure that sources which already have a region (but maybe outdated)
|
||||
// come after sources which have no region at all.
|
||||
if (a->has_region() != b->has_region()) {
|
||||
return b->has_region();
|
||||
}
|
||||
|
||||
// Compare sources based on their distance to the camera
|
||||
PN_stdfloat dist_a = (_camera_pos - a->get_bounds().get_center()).length_squared();
|
||||
PN_stdfloat dist_b = (_camera_pos - a->get_bounds().get_center()).length_squared();
|
||||
|
||||
// XXX: Should also compare based on source size, so that huge sources recieve
|
||||
// more updates
|
||||
|
||||
return dist_b > dist_a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to update all shadow sources
|
||||
* @details This updates all shadow sources which are marked dirty. It will sort
|
||||
* the list of all dirty shadow sources by their resolution, take the first
|
||||
* n entries, and update them. The amount of sources processed depends on the
|
||||
* max_updates of the ShadowManager.
|
||||
*/
|
||||
void InternalLightManager::update_shadow_sources() {
|
||||
|
||||
// Find all dirty shadow sources and make a list of them
|
||||
vector<ShadowSource*> sources_to_update;
|
||||
for (auto iter = _shadow_sources.begin(); iter != _shadow_sources.end(); ++iter) {
|
||||
ShadowSource* source = *iter;
|
||||
if (source) {
|
||||
const BoundingSphere& bounds = source->get_bounds();
|
||||
|
||||
// Check if source is in range
|
||||
PN_stdfloat distance_to_camera = (_camera_pos - bounds.get_center()).length() - bounds.get_radius();
|
||||
if (distance_to_camera < _shadow_update_distance) {
|
||||
if (source->get_needs_update()) {
|
||||
sources_to_update.push_back(source);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Free regions of sources which are out of the update radius,
|
||||
// to make space for other regions
|
||||
if (source->has_region()) {
|
||||
_shadow_manager->get_atlas()->free_region(source->get_region());
|
||||
source->clear_region();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sort the sources based on their importance, so that sources with a bigger
|
||||
// priority come first. This helps to get a better packing on the shadow atlas.
|
||||
// However, we also need to prioritize sources which have no current region,
|
||||
// because no shadows are worse than outdated-shadows.
|
||||
std::sort(sources_to_update.begin(), sources_to_update.end(), [this](const ShadowSource* a, const ShadowSource* b) {
|
||||
return this->compare_shadow_sources(a, b);
|
||||
});
|
||||
|
||||
// Get a handle to the atlas, will be frequently used
|
||||
ShadowAtlas *atlas = _shadow_manager->get_atlas();
|
||||
|
||||
// Free the regions of all sources which will get updated. We have to take into
|
||||
// account that only a limited amount of sources can get updated per frame.
|
||||
size_t update_slots = min(sources_to_update.size(),
|
||||
_shadow_manager->get_num_update_slots_left());
|
||||
for(size_t i = 0; i < update_slots; ++i) {
|
||||
if (sources_to_update[i]->has_region()) {
|
||||
atlas->free_region(sources_to_update[i]->get_region());
|
||||
}
|
||||
}
|
||||
|
||||
// Find an atlas spot for all regions which are supposed to get an update
|
||||
for (size_t i = 0; i < update_slots; ++i) {
|
||||
ShadowSource *source = sources_to_update[i];
|
||||
|
||||
if(!_shadow_manager->add_update(source)) {
|
||||
// In case the ShadowManager lied about the number of updates left
|
||||
lightmgr_cat.error() << "ShadowManager ensured update slot, but slot is taken!" << endl;
|
||||
break;
|
||||
}
|
||||
|
||||
// We have an update slot, and are guaranteed to get updated as soon
|
||||
// as possible, so we can start getting a new atlas position.
|
||||
size_t region_size = atlas->get_required_tiles(source->get_resolution());
|
||||
LVecBase4i new_region = atlas->find_and_reserve_region(region_size, region_size);
|
||||
LVecBase4 new_uv_region = atlas->region_to_uv(new_region);
|
||||
source->set_region(new_region, new_uv_region);
|
||||
|
||||
// Mark the source as updated
|
||||
source->set_needs_update(false);
|
||||
gpu_update_source(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main update method
|
||||
* @details This is the main update method of the InternalLightManager. It
|
||||
* processes all lights and shadow sources, updates them, and notifies the
|
||||
* GPU about it. This should be called on a per-frame basis.
|
||||
*
|
||||
* If the InternalLightManager was not initialized yet, an assertion is thrown.
|
||||
*/
|
||||
void InternalLightManager::update() {
|
||||
nassertv(_shadow_manager != nullptr); // Not initialized yet!
|
||||
nassertv(_cmd_list != nullptr); // Not initialized yet!
|
||||
|
||||
update_lights();
|
||||
update_shadow_sources();
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INTERNALLIGHTMANAGER_H
|
||||
#define INTERNALLIGHTMANAGER_H
|
||||
|
||||
#include "referenceCount.h"
|
||||
#include "rpLight.h"
|
||||
#include "shadowSource.h"
|
||||
#include "shadowAtlas.h"
|
||||
#include "shadowManager.h"
|
||||
#include "pointerSlotStorage.h"
|
||||
#include "gpuCommandList.h"
|
||||
|
||||
#define MAX_LIGHT_COUNT 65535
|
||||
#define MAX_SHADOW_SOURCES 2048
|
||||
|
||||
NotifyCategoryDecl(lightmgr, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
/**
|
||||
* @brief Internal class used for handling lights and shadows.
|
||||
* @details This is the internal class used by the pipeline to handle all
|
||||
* lights and shadows. It stores references to the lights, manages handling
|
||||
* the light and shadow slots, and also communicates with the GPU with the
|
||||
* GPUCommandQueue to store light and shadow source data.
|
||||
*/
|
||||
class InternalLightManager {
|
||||
PUBLISHED:
|
||||
InternalLightManager();
|
||||
|
||||
void add_light(PT(RPLight) light);
|
||||
void remove_light(PT(RPLight) light);
|
||||
|
||||
void update();
|
||||
inline void set_camera_pos(const LPoint3 &pos);
|
||||
inline void set_shadow_update_distance(PN_stdfloat dist);
|
||||
|
||||
inline int get_max_light_index() const;
|
||||
MAKE_PROPERTY(max_light_index, get_max_light_index);
|
||||
|
||||
inline size_t get_num_lights() const;
|
||||
MAKE_PROPERTY(num_lights, get_num_lights);
|
||||
|
||||
inline size_t get_num_shadow_sources() const;
|
||||
MAKE_PROPERTY(num_shadow_sources, get_num_shadow_sources);
|
||||
|
||||
inline void set_shadow_manager(ShadowManager* mgr);
|
||||
inline ShadowManager* get_shadow_manager() const;
|
||||
MAKE_PROPERTY(shadow_manager, get_shadow_manager, set_shadow_manager);
|
||||
|
||||
inline void set_command_list(GPUCommandList *cmd_list);
|
||||
|
||||
protected:
|
||||
|
||||
void gpu_update_light(RPLight* light);
|
||||
void gpu_update_source(ShadowSource* source);
|
||||
void gpu_remove_light(RPLight* light);
|
||||
void gpu_remove_consecutive_sources(ShadowSource *first_source, size_t num_sources);
|
||||
|
||||
void setup_shadows(RPLight* light);
|
||||
bool compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const;
|
||||
|
||||
void update_lights();
|
||||
void update_shadow_sources();
|
||||
|
||||
GPUCommandList* _cmd_list;
|
||||
ShadowManager* _shadow_manager;
|
||||
|
||||
PointerSlotStorage<RPLight*, MAX_LIGHT_COUNT> _lights;
|
||||
PointerSlotStorage<ShadowSource*, MAX_SHADOW_SOURCES> _shadow_sources;
|
||||
|
||||
LPoint3 _camera_pos;
|
||||
float _shadow_update_distance;
|
||||
|
||||
};
|
||||
|
||||
#include "internalLightManager.I"
|
||||
|
||||
#endif // INTERNALLIGHTMANAGER_H
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#include "config_rplight.cxx"
|
||||
#include "gpuCommand.cxx"
|
||||
#include "gpuCommandList.cxx"
|
||||
#include "iesDataset.cxx"
|
||||
#include "internalLightManager.cxx"
|
||||
#include "pssmCameraRig.cxx"
|
||||
#include "rpLight.cxx"
|
||||
#include "rpPointLight.cxx"
|
||||
#include "rpSpotLight.cxx"
|
||||
#include "shadowAtlas.cxx"
|
||||
#include "shadowManager.cxx"
|
||||
#include "shadowSource.cxx"
|
||||
#include "tagStateManager.cxx"
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef POINTERSLOTSTORAGE_H
|
||||
#define POINTERSLOTSTORAGE_H
|
||||
|
||||
|
||||
#ifdef CPPPARSER
|
||||
|
||||
// Dummy implementation for interrogate
|
||||
template <typename T, int SIZE>
|
||||
class PointerSlotStorage {};
|
||||
|
||||
#else // CPPPARSER
|
||||
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
// Apple has an outdated libstdc++, so pull the class from TR1.
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
|
||||
#include <tr1/array>
|
||||
using std::tr1::array;
|
||||
#else
|
||||
#include <array>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Class to keep a list of pointers and nullpointers.
|
||||
* @details This class stores a fixed size list of pointers, whereas pointers
|
||||
* may be a nullptr as well. It provides functionality to find free slots,
|
||||
* and also to find free consecutive slots, as well as taking care of reserving slots.
|
||||
*
|
||||
* @tparam T* Pointer-Type
|
||||
* @tparam SIZE Size of the storage
|
||||
*/
|
||||
template <typename T, int SIZE>
|
||||
class PointerSlotStorage {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new PointerSlotStorage
|
||||
* @details This constructs a new PointerSlotStorage, with all slots
|
||||
* initialized to a nullptr.
|
||||
*/
|
||||
PointerSlotStorage() {
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20070719
|
||||
_data.assign(nullptr);
|
||||
#else
|
||||
_data.fill(nullptr);
|
||||
#endif
|
||||
_max_index = 0;
|
||||
_num_entries = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the maximum index of the container
|
||||
* @details This returns the greatest index of any element which is not zero.
|
||||
* This can be useful for iterating the container, since all elements
|
||||
* coming after the returned index are guaranteed to be a nullptr.
|
||||
*
|
||||
* If no elements are in this container, -1 is returned.
|
||||
* @return Maximum index of the container
|
||||
*/
|
||||
int get_max_index() const {
|
||||
return _max_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of elements of the container
|
||||
* @details This returns the amount of elements in the container which are
|
||||
* no nullptr.
|
||||
* @return Amount of elements
|
||||
*/
|
||||
size_t get_num_entries() const {
|
||||
return _num_entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds a free slot
|
||||
* @details This finds the first slot which is a nullptr and returns it.
|
||||
* This is most likely useful in combination with reserve_slot.
|
||||
*
|
||||
* When no slot found was found, slot will be undefined, and false will
|
||||
* be returned.
|
||||
*
|
||||
* @param slot Output-Variable, slot will be stored there
|
||||
* @return true if a slot was found, otherwise false
|
||||
*/
|
||||
bool find_slot(size_t &slot) const {
|
||||
for (size_t i = 0; i < SIZE; ++i) {
|
||||
if (_data[i] == nullptr) {
|
||||
slot = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds free consecutive slots
|
||||
* @details This behaves like find_slot, but it tries to find a slot
|
||||
* after which <num_consecutive-1> free slots follow as well.
|
||||
*
|
||||
* When no slot found was found, slot will be undefined, and false will
|
||||
* be returned.
|
||||
*
|
||||
* @param slot Output-Variable, index of the first slot of the consecutive
|
||||
* slots will be stored there.
|
||||
* @param num_consecutive Amount of consecutive slots to find, including the
|
||||
* first slot.
|
||||
*
|
||||
* @return true if consecutive slots were found, otherwise false.
|
||||
*/
|
||||
bool find_consecutive_slots(size_t &slot, size_t num_consecutive) const {
|
||||
nassertr(num_consecutive > 0, false);
|
||||
|
||||
// Fall back to default search algorithm in case the parameters are equal
|
||||
if (num_consecutive == 1) {
|
||||
return find_slot(slot);
|
||||
}
|
||||
|
||||
// Try to find consecutive slots otherwise
|
||||
for (size_t i = 0; i < SIZE; ++i) {
|
||||
bool any_taken = false;
|
||||
for (size_t k = 0; !any_taken && k < num_consecutive; ++k) {
|
||||
any_taken = _data[i + k] != nullptr;
|
||||
}
|
||||
if (!any_taken) {
|
||||
slot = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees an allocated slot
|
||||
* @details This frees an allocated slot. If the slot was already freed
|
||||
* before, this method throws an assertion.
|
||||
*
|
||||
* @param slot Slot to free
|
||||
*/
|
||||
void free_slot(size_t slot) {
|
||||
nassertv(slot >= 0 && slot < SIZE);
|
||||
nassertv(_data[slot] != nullptr); // Slot was already empty!
|
||||
_data[slot] = nullptr;
|
||||
_num_entries--;
|
||||
|
||||
// Update maximum index
|
||||
if (slot == _max_index) {
|
||||
while (_max_index >= 0 && !_data[_max_index--]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees consecutive allocated slots
|
||||
* @details This behaves like PointerSlotStorage::free_slot, but deletes
|
||||
* consecutive slots.
|
||||
*
|
||||
* @param slot Start of the consecutive slots to free
|
||||
* @param num_consecutive Number of consecutive slots
|
||||
*/
|
||||
void free_consecutive_slots(size_t slot, size_t num_consecutive) {
|
||||
for (size_t i = slot; i < slot + num_consecutive; ++i) {
|
||||
free_slot(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reserves a slot
|
||||
* @details This reserves a slot by storing a pointer in it. If the slot
|
||||
* was already taken, throws an assertion.
|
||||
* If the ptr is a nullptr, also throws an assertion.
|
||||
* If the slot was out of bounds, also throws an assertion.
|
||||
*
|
||||
* @param slot Slot to reserve
|
||||
* @param ptr Pointer to store
|
||||
*/
|
||||
void reserve_slot(size_t slot, T ptr) {
|
||||
nassertv(slot >= 0 && slot < SIZE);
|
||||
nassertv(_data[slot] == nullptr); // Slot already taken!
|
||||
nassertv(ptr != nullptr); // nullptr passed as argument!
|
||||
_max_index = max(_max_index, (int)slot);
|
||||
_data[slot] = ptr;
|
||||
_num_entries++;
|
||||
}
|
||||
|
||||
typedef array<T, SIZE> InternalContainer;
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the begin of the container
|
||||
* @details This returns an iterator to the beginning of the container
|
||||
* @return Begin-Iterator
|
||||
*/
|
||||
typename InternalContainer::iterator begin() {
|
||||
return _data.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end of the container
|
||||
* @details This returns an iterator to the end of the iterator. This only
|
||||
* iterates to PointerSlotStorage::get_max_index()
|
||||
* @return [description]
|
||||
*/
|
||||
typename InternalContainer::iterator end() {
|
||||
return _data.begin() + _max_index + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
int _max_index;
|
||||
size_t _num_entries;
|
||||
InternalContainer _data;
|
||||
};
|
||||
|
||||
#endif // CPPPARSER
|
||||
|
||||
#endif // POINTERSLOTSTORAGE_H
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum pssm distance.
|
||||
* @details This sets the maximum distance in world space until which shadows
|
||||
* are rendered. After this distance, no shadows will be rendered.
|
||||
*
|
||||
* If the distance is below zero, an assertion is triggered.
|
||||
*
|
||||
* @param distance Maximum distance in world space
|
||||
*/
|
||||
inline void PSSMCameraRig::set_pssm_distance(float distance) {
|
||||
nassertv(distance > 0.0 && distance < 100000.0);
|
||||
_pssm_distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the suns distance
|
||||
* @details This sets the distance the cameras will have from the cameras frustum.
|
||||
* This prevents far objects from having no shadows, which can occur when these
|
||||
* objects are between the cameras frustum and the sun, but not inside of the
|
||||
* cameras frustum. Setting the sun distance high enough will move the cameras
|
||||
* away from the camera frustum, being able to cover those distant objects too.
|
||||
*
|
||||
* If the sun distance is set too high, artifacts will occur due to the reduced
|
||||
* range of depth. If a value below zero is passed, an assertion will get
|
||||
* triggered.
|
||||
*
|
||||
* @param distance The sun distance
|
||||
*/
|
||||
inline void PSSMCameraRig::set_sun_distance(float distance) {
|
||||
nassertv(distance > 0.0 && distance < 100000.0);
|
||||
_sun_distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the logarithmic factor
|
||||
* @details This sets the logarithmic factor, which is the core of the algorithm.
|
||||
* PSSM splits the camera frustum based on a linear and a logarithmic factor.
|
||||
* While a linear factor provides a good distribution, it often is not applicable
|
||||
* for wider distances. A logarithmic distribution provides a better distribution
|
||||
* at distance, but suffers from splitting in the near areas.
|
||||
*
|
||||
* The logarithmic factor mixes the logarithmic and linear split distribution,
|
||||
* to get the best of both. A greater factor will make the distribution more
|
||||
* logarithmic, while a smaller factor will make it more linear.
|
||||
*
|
||||
* If the factor is below zero, an ssertion is triggered.
|
||||
*
|
||||
* @param factor The logarithmic factor
|
||||
*/
|
||||
inline void PSSMCameraRig::set_logarithmic_factor(float factor) {
|
||||
nassertv(factor > 0.0);
|
||||
_logarithmic_factor = factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets whether to use a fixed film size
|
||||
* @details This controls if a fixed film size should be used. This will cause
|
||||
* the camera rig to cache the current film size, and only change it in case
|
||||
* it gets too small. This provides less flickering when moving, because the
|
||||
* film size will stay roughly constant. However, to prevent the cached film
|
||||
* size getting too big, one should call PSSMCameraRig::reset_film_size
|
||||
* once in a while, otherwise there might be a lot of wasted space.
|
||||
*
|
||||
* @param flag Whether to use a fixed film size
|
||||
*/
|
||||
inline void PSSMCameraRig::set_use_fixed_film_size(bool flag) {
|
||||
_use_fixed_film_size = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the resolution of each split
|
||||
* @details This sets the resolution of each split. Currently it is equal for
|
||||
* each split. This is required when using PSSMCameraRig::set_use_stable_csm,
|
||||
* to compute how bix a texel is.
|
||||
*
|
||||
* It has to match the y-resolution of the pssm shadow map. If an invalid
|
||||
* resolution is triggered, an assertion is thrown.
|
||||
*
|
||||
* @param resolution The resolution of each split.
|
||||
*/
|
||||
inline void PSSMCameraRig::set_resolution(size_t resolution) {
|
||||
nassertv(resolution >= 0 && resolution < 65535);
|
||||
_resolution = resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets whether to use stable CSM snapping.
|
||||
* @details This option controls if stable CSM snapping should be used. When the
|
||||
* option is enabled, all splits will snap to their texels, so that when moving,
|
||||
* no flickering will occur. However, this only works when the splits do not
|
||||
* change their film size, rotation and angle.
|
||||
*
|
||||
* @param flag Whether to use stable CSM snapping
|
||||
*/
|
||||
inline void PSSMCameraRig::set_use_stable_csm(bool flag) {
|
||||
_use_stable_csm = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the border bias for each split
|
||||
* @details This sets the border bias for every split. This increases each
|
||||
* splits frustum by multiplying it by (1 + bias), and helps reducing artifacts
|
||||
* at the borders of the splits. Artifacts can occur when the bias is too low,
|
||||
* because then the filtering will go over the bounds of the split, producing
|
||||
* invalid results.
|
||||
*
|
||||
* If the bias is below zero, an assertion is thrown.
|
||||
*
|
||||
* @param bias Border bias
|
||||
*/
|
||||
inline void PSSMCameraRig::set_border_bias(float bias) {
|
||||
nassertv(bias >= 0.0);
|
||||
_border_bias = bias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the film size cache
|
||||
* @details In case PSSMCameraRig::set_use_fixed_film_size is used, this resets
|
||||
* the film size cache. This might lead to a small "jump" in the shadows,
|
||||
* because the film size changes, however it leads to a better shadow distribution.
|
||||
*
|
||||
* This is the case because when using a fixed film size, the cache will get
|
||||
* bigger and bigger, whenever the camera moves to a grazing angle. However,
|
||||
* when moving back to a normal angle, the film size cache still stores this
|
||||
* big angle, and thus the splits will have a much bigger film size than actualy
|
||||
* required. To prevent this, call this method once in a while, so an optimal
|
||||
* distribution is ensured.
|
||||
*/
|
||||
inline void PSSMCameraRig::reset_film_size_cache() {
|
||||
for (size_t i = 0; i < _max_film_sizes.size(); ++i) {
|
||||
_max_film_sizes[i].fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the n-th camera
|
||||
* @details This returns the n-th camera of the camera rig, which can be used
|
||||
* for various stuff like showing its frustum, passing it as a shader input,
|
||||
* and so on.
|
||||
*
|
||||
* The first camera is the camera which is the camera of the first split,
|
||||
* which is the split closest to the camera. All cameras follow in descending
|
||||
* order until to the last camera, which is the split furthest away from the
|
||||
* camera.
|
||||
*
|
||||
* If an invalid index is passed, an assertion is thrown.
|
||||
*
|
||||
* @param index Index of the camera.
|
||||
* @return [description]
|
||||
*/
|
||||
inline NodePath PSSMCameraRig::get_camera(size_t index) {
|
||||
nassertr(index >= 0 && index < _cam_nodes.size(), NodePath());
|
||||
return _cam_nodes[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to compute the distance of a split
|
||||
* @details This is the internal method to perform the weighting of the
|
||||
* logarithmic and linear distribution. It computes the distance to the
|
||||
* camera from which a given split starts, by weighting the logarithmic and
|
||||
* linear factor.
|
||||
*
|
||||
* The return value is a value ranging from 0 .. 1. To get the distance in
|
||||
* world space, the value has to get multiplied with the maximum shadow distance.
|
||||
*
|
||||
* @param split_index The index of the split
|
||||
* @return Distance of the split, ranging from 0 .. 1
|
||||
*/
|
||||
inline float PSSMCameraRig::get_split_start(size_t split_index) {
|
||||
float x = (float)split_index / (float)_cam_nodes.size();
|
||||
return (exp(_logarithmic_factor*x)-1) / (exp(_logarithmic_factor)-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method for interpolating a point along the camera frustum
|
||||
* @details This method takes a given distance in the 0 .. 1 range, whereas
|
||||
* 0 denotes the camera near plane, and 1 denotes the camera far plane,
|
||||
* and lineary interpolates between them.
|
||||
*
|
||||
* @param origin Edge of the frustum
|
||||
* @param depth Distance in the 0 .. 1 range
|
||||
*
|
||||
* @return interpolated point in world space
|
||||
*/
|
||||
inline LPoint3 PSSMCameraRig::get_interpolated_point(CoordinateOrigin origin, float depth) {
|
||||
nassertr(depth >= 0.0 && depth <= 1.0, LPoint3());
|
||||
return _curr_near_points[origin] * (1.0 - depth) + _curr_far_points[origin] * depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the MVP array
|
||||
* @details This returns a handle to the array of view-projection matrices
|
||||
* of the different splits. This can be used for computing shadows. The array
|
||||
* is a PTALMatrix4 and thus can be directly bound to a shader.
|
||||
*
|
||||
* @return view-projection matrix array
|
||||
*/
|
||||
inline const PTA_LMatrix4 &PSSMCameraRig::get_mvp_array() {
|
||||
return _camera_mvps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the near and far planes array
|
||||
* @details This returns a handle to the near and far plane array. Each split
|
||||
* has an entry in the array, whereas the x component of the vecto denotes the
|
||||
* near plane, and the y component denotes the far plane of the split.
|
||||
*
|
||||
* This is required because the near and far planes of the splits change
|
||||
* constantly. To access them in a shader, the shader needs access to the
|
||||
* array.
|
||||
*
|
||||
* @return Array of near and far planes
|
||||
*/
|
||||
inline const PTA_LVecBase2 &PSSMCameraRig::get_nearfar_array() {
|
||||
return _camera_nearfar;
|
||||
}
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "pssmCameraRig.h"
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include "orthographicLens.h"
|
||||
|
||||
|
||||
PStatCollector PSSMCameraRig::_update_collector("App:Show code:RP_PSSM_update");
|
||||
|
||||
/**
|
||||
* @brief Constructs a new PSSM camera rig
|
||||
* @details This constructs a new camera rig, with a given amount of splits.
|
||||
* The splits can not be changed later on. Splits are also called Cascades.
|
||||
*
|
||||
* An assertion will be triggered if the splits are below zero.
|
||||
*
|
||||
* @param num_splits Amount of PSSM splits
|
||||
*/
|
||||
PSSMCameraRig::PSSMCameraRig(size_t num_splits) {
|
||||
nassertv(num_splits > 0);
|
||||
_num_splits = num_splits;
|
||||
_pssm_distance = 100.0;
|
||||
_sun_distance = 500.0;
|
||||
_use_fixed_film_size = false;
|
||||
_use_stable_csm = true;
|
||||
_logarithmic_factor = 1.0;
|
||||
_resolution = 512;
|
||||
_border_bias = 0.1;
|
||||
_camera_mvps = PTA_LMatrix4::empty_array(num_splits);
|
||||
_camera_nearfar = PTA_LVecBase2::empty_array(num_splits);
|
||||
init_cam_nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructs the camera rig
|
||||
* @details This destructs the camera rig, cleaning up all used resources.
|
||||
*/
|
||||
PSSMCameraRig::~PSSMCameraRig() {
|
||||
// TODO: Detach all cameras and call remove_node. Most likely this is not
|
||||
// an issue tho, because the camera rig will never get destructed.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to init the cameras
|
||||
* @details This method constructs all cameras and their required lens nodes
|
||||
* for all splits. It also resets the film size array.
|
||||
*/
|
||||
void PSSMCameraRig::init_cam_nodes() {
|
||||
_cam_nodes.reserve(_num_splits);
|
||||
_max_film_sizes.resize(_num_splits);
|
||||
_cameras.resize(_num_splits);
|
||||
for (size_t i = 0; i < _num_splits; ++i)
|
||||
{
|
||||
// Construct a new lens
|
||||
Lens *lens = new OrthographicLens();
|
||||
lens->set_film_size(1, 1);
|
||||
lens->set_near_far(1, 1000);
|
||||
|
||||
// Construct a new camera
|
||||
_cameras[i] = new Camera("pssm-cam-" + format_string(i), lens);
|
||||
_cam_nodes.push_back(NodePath(_cameras[i]));
|
||||
_max_film_sizes[i].fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reparents the camera rig
|
||||
* @details This reparents all cameras to the given parent. Usually the parent
|
||||
* will be ShowBase.render. The parent should be the same node where the
|
||||
* main camera is located in, too.
|
||||
*
|
||||
* If an empty parrent is passed, an assertion will get triggered.
|
||||
*
|
||||
* @param parent Parent node path
|
||||
*/
|
||||
void PSSMCameraRig::reparent_to(NodePath parent) {
|
||||
nassertv(!parent.is_empty());
|
||||
for (size_t i = 0; i < _num_splits; ++i) {
|
||||
_cam_nodes[i].reparent_to(parent);
|
||||
}
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to compute the view-projection matrix of a camera
|
||||
* @details This returns the view-projection matrix of the given split. No bounds
|
||||
* checking is done. If an invalid index is passed, undefined behaviour occurs.
|
||||
*
|
||||
* @param split_index Index of the split
|
||||
* @return view-projection matrix of the split
|
||||
*/
|
||||
LMatrix4 PSSMCameraRig::compute_mvp(size_t split_index) {
|
||||
LMatrix4 transform = _parent.get_transform(_cam_nodes[split_index])->get_mat();
|
||||
return transform * _cameras[split_index]->get_lens()->get_projection_mat();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method used for stable CSM
|
||||
* @details This method is used when stable CSM is enabled. It ensures that each
|
||||
* source only moves in texel-steps, thus preventing flickering. This works by
|
||||
* projecting the point (0, 0, 0) to NDC space, making sure that it gets projected
|
||||
* to a texel center, and then projecting that texel back.
|
||||
*
|
||||
* This only works if the camera does not rotate, change its film size, or change
|
||||
* its angle.
|
||||
*
|
||||
* @param mat view-projection matrix of the camera
|
||||
* @param resolution resolution of the split
|
||||
*
|
||||
* @return Offset to add to the camera position to achieve stable snapping
|
||||
*/
|
||||
LVecBase3 PSSMCameraRig::get_snap_offset(const LMatrix4& mat, size_t resolution) {
|
||||
// Transform origin to camera space
|
||||
LPoint4 base_point = mat.get_row(3) * 0.5 + 0.5;
|
||||
|
||||
// Compute the snap offset
|
||||
float texel_size = 1.0 / (float)(resolution);
|
||||
float offset_x = fmod(base_point.get_x(), texel_size);
|
||||
float offset_y = fmod(base_point.get_y(), texel_size);
|
||||
|
||||
// Reproject the offset back, for that we need the inverse MVP
|
||||
LMatrix4 inv_mat(mat);
|
||||
inv_mat.invert_in_place();
|
||||
LVecBase3 new_base_point = inv_mat.xform_point(LVecBase3(
|
||||
(base_point.get_x() - offset_x) * 2.0 - 1.0,
|
||||
(base_point.get_y() - offset_y) * 2.0 - 1.0,
|
||||
base_point.get_z() * 2.0 - 1.0
|
||||
));
|
||||
return -new_base_point;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Computes the average of a list of points
|
||||
* @details This computes the average over a given set of points in 3D space.
|
||||
* It returns the average of those points, namely sum_of_points / num_points.
|
||||
*
|
||||
* It is designed to work with a frustum, which is why it takes two arrays
|
||||
* with a dimension of 4. Usually the first array are the camera near points,
|
||||
* and the second array are the camera far points.
|
||||
*
|
||||
* @param starts First array of points
|
||||
* @param ends Second array of points
|
||||
* @return Average of points
|
||||
*/
|
||||
LPoint3 get_average_of_points(LVecBase3 const (&starts)[4], LVecBase3 const (&ends)[4]) {
|
||||
LPoint3 mid_point(0, 0, 0);
|
||||
for (size_t k = 0; k < 4; ++k) {
|
||||
mid_point += starts[k];
|
||||
mid_point += ends[k];
|
||||
}
|
||||
return mid_point / 8.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds the minimum and maximum extends of the given projection
|
||||
* @details This projects each point of the given array of points using the
|
||||
* cameras view-projection matrix, and computes the minimum and maximum
|
||||
* of the projected points.
|
||||
*
|
||||
* @param min_extent Will store the minimum extent of the projected points in NDC space
|
||||
* @param max_extent Will store the maximum extent of the projected points in NDC space
|
||||
* @param transform The transformation matrix of the camera
|
||||
* @param proj_points The array of points to project
|
||||
* @param cam The camera to be used to project the points
|
||||
*/
|
||||
void find_min_max_extents(LVecBase3 &min_extent, LVecBase3 &max_extent, const LMatrix4 &transform, LVecBase3 const (&proj_points)[8], Camera *cam) {
|
||||
|
||||
min_extent.fill(1e10);
|
||||
max_extent.fill(-1e10);
|
||||
LPoint2 screen_points[8];
|
||||
|
||||
// Now project all points to the screen space of the current camera and also
|
||||
// find the minimum and maximum extents
|
||||
for (size_t k = 0; k < 8; ++k) {
|
||||
LVecBase4 point(proj_points[k], 1);
|
||||
LPoint4 proj_point = transform.xform(point);
|
||||
LPoint3 proj_point_3d(proj_point.get_x(), proj_point.get_y(), proj_point.get_z());
|
||||
cam->get_lens()->project(proj_point_3d, screen_points[k]);
|
||||
|
||||
// Find min / max extents
|
||||
if (screen_points[k].get_x() > max_extent.get_x()) max_extent.set_x(screen_points[k].get_x());
|
||||
if (screen_points[k].get_y() > max_extent.get_y()) max_extent.set_y(screen_points[k].get_y());
|
||||
|
||||
if (screen_points[k].get_x() < min_extent.get_x()) min_extent.set_x(screen_points[k].get_x());
|
||||
if (screen_points[k].get_y() < min_extent.get_y()) min_extent.set_y(screen_points[k].get_y());
|
||||
|
||||
// Find min / max projected depth to adjust far plane
|
||||
if (proj_point.get_y() > max_extent.get_z()) max_extent.set_z(proj_point.get_y());
|
||||
if (proj_point.get_y() < min_extent.get_z()) min_extent.set_z(proj_point.get_y());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Computes a film size from a given minimum and maximum extend
|
||||
* @details This takes a minimum and maximum extent in NDC space and computes
|
||||
* the film size and film offset needed to cover that extent.
|
||||
*
|
||||
* @param film_size Output film size, can be used for Lens::set_film_size
|
||||
* @param film_offset Output film offset, can be used for Lens::set_film_offset
|
||||
* @param min_extent Minimum extent
|
||||
* @param max_extent Maximum extent
|
||||
*/
|
||||
inline void get_film_properties(LVecBase2 &film_size, LVecBase2 &film_offset, const LVecBase3 &min_extent, const LVecBase3 &max_extent) {
|
||||
float x_center = (min_extent.get_x() + max_extent.get_x()) * 0.5;
|
||||
float y_center = (min_extent.get_y() + max_extent.get_y()) * 0.5;
|
||||
float x_size = max_extent.get_x() - x_center;
|
||||
float y_size = max_extent.get_y() - y_center;
|
||||
film_size.set(x_size, y_size);
|
||||
film_offset.set(x_center * 0.5, y_center * 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Merges two arrays
|
||||
* @details This takes two arrays which each 4 members and produces an array
|
||||
* with both arrays contained.
|
||||
*
|
||||
* @param dest Destination array
|
||||
* @param array1 First array
|
||||
* @param array2 Second array
|
||||
*/
|
||||
inline void merge_points_interleaved(LVecBase3 (&dest)[8], LVecBase3 const (&array1)[4], LVecBase3 const (&array2)[4]) {
|
||||
for (size_t k = 0; k < 4; ++k) {
|
||||
dest[k] = array1[k];
|
||||
dest[k+4] = array2[k];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Internal method to compute the splits
|
||||
* @details This is the internal update method to update the PSSM splits.
|
||||
* It distributes the camera splits over the frustum, and updates the
|
||||
* MVP array aswell as the nearfar array.
|
||||
*
|
||||
* @param transform Main camera transform
|
||||
* @param max_distance Maximum pssm distance, relative to the camera far plane
|
||||
* @param light_vector Sun-Vector
|
||||
*/
|
||||
void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_distance, const LVecBase3& light_vector) {
|
||||
nassertv(!_parent.is_empty());
|
||||
|
||||
// PSSM Distance should never be smaller than camera far plane.
|
||||
nassertv(max_distance <= 1.0);
|
||||
|
||||
float filmsize_bias = 1.0 + _border_bias;
|
||||
|
||||
// Compute the positions of all cameras
|
||||
for (size_t i = 0; i < _cam_nodes.size(); ++i) {
|
||||
float split_start = get_split_start(i) * max_distance;
|
||||
float split_end = get_split_start(i + 1) * max_distance;
|
||||
|
||||
LVecBase3 start_points[4];
|
||||
LVecBase3 end_points[4];
|
||||
LVecBase3 proj_points[8];
|
||||
|
||||
// Get split bounding box, and collect all points which define the frustum
|
||||
for (size_t k = 0; k < 4; ++k) {
|
||||
start_points[k] = get_interpolated_point((CoordinateOrigin)k, split_start);
|
||||
end_points[k] = get_interpolated_point((CoordinateOrigin)k, split_end);
|
||||
proj_points[k] = start_points[k];
|
||||
proj_points[k + 4] = end_points[k];
|
||||
}
|
||||
|
||||
// Compute approximate split mid point
|
||||
LPoint3 split_mid = get_average_of_points(start_points, end_points);
|
||||
LPoint3 cam_start = split_mid + light_vector * _sun_distance;
|
||||
|
||||
// Reset the film size, offset and far-plane
|
||||
Camera* cam = DCAST(Camera, _cam_nodes[i].node());
|
||||
cam->get_lens()->set_film_size(1, 1);
|
||||
cam->get_lens()->set_film_offset(0, 0);
|
||||
cam->get_lens()->set_near_far(1, 100);
|
||||
|
||||
// Find a good initial position
|
||||
_cam_nodes[i].set_pos(cam_start);
|
||||
_cam_nodes[i].look_at(split_mid);
|
||||
|
||||
LVecBase3 best_min_extent, best_max_extent;
|
||||
|
||||
// Find minimum and maximum extents of the points
|
||||
LMatrix4 merged_transform = _parent.get_transform(_cam_nodes[i])->get_mat();
|
||||
find_min_max_extents(best_min_extent, best_max_extent, merged_transform, proj_points, cam);
|
||||
|
||||
// Find the film size to cover all points
|
||||
LVecBase2 film_size, film_offset;
|
||||
get_film_properties(film_size, film_offset, best_min_extent, best_max_extent);
|
||||
|
||||
if (_use_fixed_film_size) {
|
||||
// In case we use a fixed film size, store the maximum film size, and
|
||||
// only change the film size if a new maximum is there
|
||||
if (_max_film_sizes[i].get_x() < film_size.get_x()) _max_film_sizes[i].set_x(film_size.get_x());
|
||||
if (_max_film_sizes[i].get_y() < film_size.get_y()) _max_film_sizes[i].set_y(film_size.get_y());
|
||||
|
||||
cam->get_lens()->set_film_size(_max_film_sizes[i] * filmsize_bias);
|
||||
} else {
|
||||
// If we don't use a fixed film size, we can just set the film size
|
||||
// on the lens.
|
||||
cam->get_lens()->set_film_size(film_size * filmsize_bias);
|
||||
}
|
||||
|
||||
// Compute new film offset
|
||||
cam->get_lens()->set_film_offset(film_offset);
|
||||
cam->get_lens()->set_near_far(10, best_max_extent.get_z());
|
||||
_camera_nearfar[i] = LVecBase2(10, best_max_extent.get_z());
|
||||
|
||||
// Compute the camera MVP
|
||||
LMatrix4 mvp = compute_mvp(i);
|
||||
|
||||
// Stable CSM Snapping
|
||||
if (_use_stable_csm) {
|
||||
LPoint3 snap_offset = get_snap_offset(mvp, _resolution);
|
||||
_cam_nodes[i].set_pos(_cam_nodes[i].get_pos() + snap_offset);
|
||||
|
||||
// Compute the new mvp, since we changed the snap offset
|
||||
mvp = compute_mvp(i);
|
||||
}
|
||||
|
||||
_camera_mvps.set_element(i, mvp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Updates the PSSM camera rig
|
||||
* @details This updates the rig with an updated camera position, and a given
|
||||
* light vector. This should be called on a per-frame basis. It will reposition
|
||||
* all camera sources to fit the frustum based on the pssm distribution.
|
||||
*
|
||||
* The light vector should be the vector from the light source, not the
|
||||
* vector to the light source.
|
||||
*
|
||||
* @param cam_node Target camera node
|
||||
* @param light_vector The vector from the light to any point
|
||||
*/
|
||||
void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) {
|
||||
nassertv(!cam_node.is_empty());
|
||||
_update_collector.start();
|
||||
|
||||
// Get camera node transform
|
||||
LMatrix4 transform = cam_node.get_transform()->get_mat();
|
||||
|
||||
// Get Camera and Lens pointers
|
||||
Camera* cam = DCAST(Camera, cam_node.get_child(0).node());
|
||||
nassertv(cam != nullptr);
|
||||
Lens* lens = cam->get_lens();
|
||||
|
||||
// Extract near and far points:
|
||||
lens->extrude(LPoint2(-1, 1), _curr_near_points[UpperLeft], _curr_far_points[UpperLeft]);
|
||||
lens->extrude(LPoint2(1, 1), _curr_near_points[UpperRight], _curr_far_points[UpperRight]);
|
||||
lens->extrude(LPoint2(-1, -1), _curr_near_points[LowerLeft], _curr_far_points[LowerLeft]);
|
||||
lens->extrude(LPoint2(1, -1), _curr_near_points[LowerRight], _curr_far_points[LowerRight]);
|
||||
|
||||
// Construct MVP to project points to world space
|
||||
LMatrix4 mvp = transform * lens->get_view_mat();
|
||||
|
||||
// Project all points to world space
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
LPoint4 ws_near = mvp.xform(_curr_near_points[i]);
|
||||
LPoint4 ws_far = mvp.xform(_curr_far_points[i]);
|
||||
_curr_near_points[i].set(ws_near.get_x(), ws_near.get_y(), ws_near.get_z());
|
||||
_curr_far_points[i].set(ws_far.get_x(), ws_far.get_y(), ws_far.get_z());
|
||||
}
|
||||
|
||||
// Do the actual PSSM
|
||||
compute_pssm_splits( transform, _pssm_distance / lens->get_far(), light_vector );
|
||||
|
||||
_update_collector.stop();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PSSMCAMERARIG_H
|
||||
#define PSSMCAMERARIG_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "luse.h"
|
||||
#include "camera.h"
|
||||
#include "nodePath.h"
|
||||
#include "pStatCollector.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Main class used for handling PSSM
|
||||
* @details This is the main class for supporting PSSM, it is used by the PSSM
|
||||
* plugin to compute the position of the splits.
|
||||
*
|
||||
* It supports handling a varying amount of cameras, and fitting those cameras
|
||||
* into the main camera frustum, to render distant shadows. It also supports
|
||||
* various optimizations for fitting the frustum, e.g. rotating the sources
|
||||
* to get a better coverage.
|
||||
*
|
||||
* It also provides methods to get arrays of data about the used cameras
|
||||
* view-projection matrices and their near and far plane, which is required for
|
||||
* processing the data in the shadow sampling shader.
|
||||
*
|
||||
* In this class, there is often referred to "Splits" or also called "Cascades".
|
||||
* These denote the different cameras which are used to split the frustum,
|
||||
* and are a common term related to the PSSM algorithm.
|
||||
*
|
||||
* To understand the functionality of this class, a detailed knowledge of the
|
||||
* PSSM algorithm is helpful.
|
||||
*/
|
||||
class PSSMCameraRig {
|
||||
PUBLISHED:
|
||||
PSSMCameraRig(size_t num_splits);
|
||||
~PSSMCameraRig();
|
||||
|
||||
inline void set_pssm_distance(float distance);
|
||||
inline void set_sun_distance(float distance);
|
||||
inline void set_use_fixed_film_size(bool flag);
|
||||
inline void set_resolution(size_t resolution);
|
||||
inline void set_use_stable_csm(bool flag);
|
||||
inline void set_logarithmic_factor(float factor);
|
||||
inline void set_border_bias(float bias);
|
||||
|
||||
void update(NodePath cam_node, const LVecBase3 &light_vector);
|
||||
inline void reset_film_size_cache();
|
||||
|
||||
inline NodePath get_camera(size_t index);
|
||||
|
||||
void reparent_to(NodePath parent);
|
||||
inline const PTA_LMatrix4 &get_mvp_array();
|
||||
inline const PTA_LVecBase2 &get_nearfar_array();
|
||||
|
||||
public:
|
||||
// Used to access the near and far points in the array
|
||||
enum CoordinateOrigin {
|
||||
UpperLeft = 0,
|
||||
UpperRight,
|
||||
LowerLeft,
|
||||
LowerRight
|
||||
};
|
||||
|
||||
protected:
|
||||
void init_cam_nodes();
|
||||
void compute_pssm_splits(const LMatrix4& transform, float max_distance,
|
||||
const LVecBase3 &light_vector);
|
||||
inline float get_split_start(size_t split_index);
|
||||
LMatrix4 compute_mvp(size_t cam_index);
|
||||
inline LPoint3 get_interpolated_point(CoordinateOrigin origin, float depth);
|
||||
LVecBase3 get_snap_offset(const LMatrix4& mat, size_t resolution);
|
||||
|
||||
vector<NodePath> _cam_nodes;
|
||||
vector<Camera*> _cameras;
|
||||
vector<LVecBase2> _max_film_sizes;
|
||||
|
||||
// Current near and far points
|
||||
// Order: UL, UR, LL, LR (See CoordinateOrigin)
|
||||
LPoint3 _curr_near_points[4];
|
||||
LPoint3 _curr_far_points[4];
|
||||
float _pssm_distance;
|
||||
float _sun_distance;
|
||||
float _logarithmic_factor;
|
||||
float _border_bias;
|
||||
bool _use_fixed_film_size;
|
||||
bool _use_stable_csm;
|
||||
size_t _resolution;
|
||||
size_t _num_splits;
|
||||
NodePath _parent;
|
||||
|
||||
PTA_LMatrix4 _camera_mvps;
|
||||
PTA_LVecBase2 _camera_nearfar;
|
||||
|
||||
static PStatCollector _update_collector;
|
||||
};
|
||||
|
||||
#include "pssmCameraRig.I"
|
||||
|
||||
#endif // PSSMCAMERARIG_H
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of shadow sources
|
||||
* @details This returns the amount of shadow sources attached to this light.
|
||||
* In case the light has no shadows enabled, or the light was not attached
|
||||
* yet, this returns 0.
|
||||
*
|
||||
* @return Amount of shadow sources
|
||||
*/
|
||||
inline int RPLight::get_num_shadow_sources() const {
|
||||
return _shadow_sources.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the n-th shadow source
|
||||
* @details This returns the n-th attached shadow source. This ranges from
|
||||
* 0 .. RPLight::get_num_shadow_sources(). If an invalid index is passed,
|
||||
* an assertion is thrown.
|
||||
*
|
||||
* @param index Index of the source
|
||||
* @return Handle to the shadow source
|
||||
*/
|
||||
inline ShadowSource* RPLight::get_shadow_source(size_t index) const {
|
||||
nassertr(index < _shadow_sources.size(), nullptr); // Invalid shadow source index
|
||||
return _shadow_sources[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears all shadow source
|
||||
* @details This removes and destructs all shadow sources attached to this light.
|
||||
* This usually gets called when the light gets detached or destructed.
|
||||
* All shadows sources are freed, and then removed from the shadow source list.
|
||||
*/
|
||||
inline void RPLight::clear_shadow_sources() {
|
||||
for (size_t i = 0; i < _shadow_sources.size(); ++i) {
|
||||
delete _shadow_sources[i];
|
||||
}
|
||||
_shadow_sources.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets whether the light needs an update
|
||||
* @details This controls whether the light needs to get an update. This is the
|
||||
* case when a property of the light changed, e.g. position or color. It does
|
||||
* not affect the shadows (For that use RPLight::invalidate_shadows()).
|
||||
* When this flag is set to true, the light will get resubmitted to the GPU
|
||||
* in the next update cycle.
|
||||
*
|
||||
* You should usually never set the flag to false manually. The
|
||||
* InternalLightManager will do this when the data got sucessfully updated.
|
||||
*
|
||||
* @param flag Update-Flag
|
||||
*/
|
||||
inline void RPLight::set_needs_update(bool flag) {
|
||||
_needs_update = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the light needs an update
|
||||
* @details This returns whether the light needs an update. This might be the
|
||||
* case when a property of the light was changed, e.g. position or color.
|
||||
* It does not affect the shadows, you have to query the update flag of each
|
||||
* individual source for that.
|
||||
* The return value is the value previously set with RPLight::set_needs_update.
|
||||
*
|
||||
* @return Update-flag
|
||||
*/
|
||||
inline bool RPLight::get_needs_update() const {
|
||||
return _needs_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the light has a slot
|
||||
* @details This returns wheter the light currently is attached, and thus has
|
||||
* a slot in the InternalLightManagers light list. When the light is attached,
|
||||
* this returns true, otherwise it will return false.
|
||||
*
|
||||
* @return true if the light has a slot, false otherwise
|
||||
*/
|
||||
inline bool RPLight::has_slot() const {
|
||||
return _slot >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the slot of the light
|
||||
* @details This returns the slot of the light. This is the space on the GPU
|
||||
* where the light is stored. If the light is not attached yet, this will
|
||||
* return -1, otherwise the index of the light.
|
||||
*
|
||||
* @return Light-Slot
|
||||
*/
|
||||
inline int RPLight::get_slot() const {
|
||||
return _slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the light slot
|
||||
* @details This is an internal method to remove the slot of the light. It gets
|
||||
* called by the InternalLightManager when a light gets detached. It internally
|
||||
* sets the slot to -1 to indicate the light is no longer attached.
|
||||
*/
|
||||
inline void RPLight::remove_slot() {
|
||||
_slot = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a slot to the light
|
||||
* @details This assigns a slot to the light, marking it as attached. The slot
|
||||
* relates to the index in the GPU's storage of lights. This is an internal
|
||||
* method called by the InternalLightManager when the light got attached.
|
||||
*
|
||||
* @param slot Slot of the light
|
||||
*/
|
||||
inline void RPLight::assign_slot(int slot) {
|
||||
_slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Invalidates the shadows
|
||||
* @details This invalidates all shadows of the light, causing them to get
|
||||
* regenerated. This might be the case when the lights position or similar
|
||||
* changed. This will cause all shadow sources to be updated, emitting a
|
||||
* shadow update. Be careful when calling this method if you don't want all
|
||||
* sources to get updated. If you only have to invalidate a single shadow source,
|
||||
* use get_shadow_source(n)->set_needs_update(true).
|
||||
*/
|
||||
inline void RPLight::invalidate_shadows() {
|
||||
for (size_t i = 0; i < _shadow_sources.size(); ++i) {
|
||||
_shadow_sources[i]->set_needs_update(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the position of the light
|
||||
* @details This sets the position of the light in world space. It will cause
|
||||
* the light to get invalidated, and resubmitted to the GPU.
|
||||
*
|
||||
* @param pos Position in world space
|
||||
*/
|
||||
inline void RPLight::set_pos(const LVecBase3 &pos) {
|
||||
set_pos(pos.get_x(), pos.get_y(), pos.get_z());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the position of the light
|
||||
* @details @copydetails RPLight::set_pos(const LVecBase3 &pos)
|
||||
*
|
||||
* @param x X-component of the position
|
||||
* @param y Y-component of the position
|
||||
* @param z Z-component of the position
|
||||
*/
|
||||
inline void RPLight::set_pos(float x, float y, float z) {
|
||||
_position.set(x, y, z);
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the position of the light
|
||||
* @details This returns the position of the light previously set with
|
||||
* RPLight::set_pos(). The returned position is in world space.
|
||||
* @return Light-position
|
||||
*/
|
||||
inline const LVecBase3& RPLight::get_pos() const {
|
||||
return _position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the lights color
|
||||
* @details This sets the lights color. The color should not include the brightness
|
||||
* of the light, you should control that with the energy. The color specifies
|
||||
* the lights "tint" and will get multiplied with its specular and diffuse
|
||||
* contribution.
|
||||
*
|
||||
* The color will be normalized by dividing by the colors luminance. Setting
|
||||
* higher values than 1.0 will have no effect.
|
||||
*
|
||||
* @param color Light color
|
||||
*/
|
||||
inline void RPLight::set_color(const LVecBase3 &color) {
|
||||
_color = color;
|
||||
_color /= 0.2126 * color.get_x() + 0.7152 * color.get_y() + 0.0722 * color.get_z();
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the lights color
|
||||
* @details @copydetails RPLight::set_color(const LVecBase3 &color)
|
||||
*
|
||||
* @param r Red-component of the color
|
||||
* @param g Green-component of the color
|
||||
* @param b Blue-component of the color
|
||||
*/
|
||||
inline void RPLight::set_color(float r, float g, float b) {
|
||||
set_color(LVecBase3(r, g, b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the lights color
|
||||
* @details This returns the light color, previously set with RPLight::set_color.
|
||||
* This does not include the energy of the light. It might differ from what
|
||||
* was set with set_color, because the color is normalized by dividing it
|
||||
* by its luminance.
|
||||
* @return Light-color
|
||||
*/
|
||||
inline const LVecBase3& RPLight::get_color() const {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the energy of the light
|
||||
* @details This sets the energy of the light, which can be seen as the brightness
|
||||
* of the light. It will get multiplied with the normalized color.
|
||||
*
|
||||
* @param energy energy of the light
|
||||
*/
|
||||
inline void RPLight::set_energy(float energy) {
|
||||
_energy = energy;
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the energy of the light
|
||||
* @details This returns the energy of the light, previously set with
|
||||
* RPLight::set_energy.
|
||||
*
|
||||
* @return energy of the light
|
||||
*/
|
||||
inline float RPLight::get_energy() const {
|
||||
return _energy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the type of the light
|
||||
* @details This returns the internal type of the light, which was specified
|
||||
* in the lights constructor. This can be used to distinguish between light
|
||||
* types.
|
||||
* @return Type of the light
|
||||
*/
|
||||
inline RPLight::LightType RPLight::get_light_type() const {
|
||||
return _light_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Controls whether the light casts shadows
|
||||
* @details This sets whether the light casts shadows. You can not change this
|
||||
* while the light is attached. When flag is set to true, the light will be
|
||||
* setup to cast shadows, spawning shadow sources based on the lights type.
|
||||
* If the flag is set to false, the light will be inddicated to cast no shadows.
|
||||
*
|
||||
* @param flag Whether the light casts shadows
|
||||
*/
|
||||
inline void RPLight::set_casts_shadows(bool flag) {
|
||||
if (has_slot()) {
|
||||
cerr << "Light is already attached, can not call set_casts_shadows!" << endl;
|
||||
return;
|
||||
}
|
||||
_casts_shadows = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the light casts shadows
|
||||
* @details This returns whether the light casts shadows, the returned value
|
||||
* is the one previously set with RPLight::set_casts_shadows.
|
||||
*
|
||||
* @return true if the light casts shadows, false otherwise
|
||||
*/
|
||||
inline bool RPLight::get_casts_shadows() const {
|
||||
return _casts_shadows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the lights shadow map resolution
|
||||
* @details This sets the lights shadow map resolution. This has no effect
|
||||
* when the light is not told to cast shadows (Use RPLight::set_casts_shadows).
|
||||
*
|
||||
* When calling this on a light with multiple shadow sources (e.g. PointLight),
|
||||
* this controls the resolution of each source. If the light has 6 shadow sources,
|
||||
* and you use a resolution of 512x512, the lights shadow map will occur a
|
||||
* space of 6 * 512x512 maps in the shadow atlas.
|
||||
*
|
||||
* @param resolution Resolution of the shadow map in pixels
|
||||
*/
|
||||
inline void RPLight::set_shadow_map_resolution(size_t resolution) {
|
||||
nassertv(resolution >= 32 && resolution <= 16384);
|
||||
_source_resolution = resolution;
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the shadow map resolution
|
||||
* @details This returns the shadow map resolution of each source of the light.
|
||||
* If the light is not setup to cast shadows, this value is meaningless.
|
||||
* The returned value is the one previously set with RPLight::set_shadow_map_resolution.
|
||||
*
|
||||
* @return Shadow map resolution in pixels
|
||||
*/
|
||||
inline size_t RPLight::get_shadow_map_resolution() const {
|
||||
return _source_resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the ies profile
|
||||
* @details This sets the ies profile of the light. The parameter should be a
|
||||
* handle previously returned by RenderPipeline.load_ies_profile. Using a
|
||||
* value of -1 indicates no ies profile.
|
||||
*
|
||||
* Notice that for ies profiles which cover a whole range, you should use
|
||||
* PointLights, whereas for ies profiles which only cover the lower hemisphere
|
||||
* you should use SpotLights for the best performance.
|
||||
*
|
||||
* @param profile IES Profile handle
|
||||
*/
|
||||
inline void RPLight::set_ies_profile(int profile) {
|
||||
_ies_profile = profile;
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the lights ies profile
|
||||
* @details This returns the ies profile of a light, previously set with
|
||||
* RPLight::set_ies_profile. In case no ies profile was set, returns -1.
|
||||
*
|
||||
* @return IES Profile handle
|
||||
*/
|
||||
inline int RPLight::get_ies_profile() const {
|
||||
return _ies_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the light has an ies profile assigned
|
||||
* @details This returns whether the light has an ies profile assigned,
|
||||
* previously done with RPLight::set_ies_profile.
|
||||
*
|
||||
* @return true if the light has an ies profile assigned, false otherwise
|
||||
*/
|
||||
inline bool RPLight::has_ies_profile() const {
|
||||
return _ies_profile >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the ies profile
|
||||
* @details This clears the ies profile of the light, telling it to no longer
|
||||
* use an ies profile, and instead use the default attenuation.
|
||||
*/
|
||||
inline void RPLight::clear_ies_profile() {
|
||||
set_ies_profile(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the near plane of the light
|
||||
* @details This sets the near plane of all shadow sources of the light. It has
|
||||
* no effects if the light does not cast shadows. This prevents artifacts from
|
||||
* objects near to the light. It behaves like Lens::set_near_plane.
|
||||
*
|
||||
* It can also help increasing shadow map precision, low near planes will
|
||||
* cause the precision to suffer. Try setting the near plane as big as possible.
|
||||
*
|
||||
* If a negative or zero near plane is passed, an assertion is thrown.
|
||||
*
|
||||
* @param near_plane Near-plane
|
||||
*/
|
||||
inline void RPLight::set_near_plane(float near_plane) {
|
||||
nassertv(near_plane > 0.00001);
|
||||
_near_plane = near_plane;
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the near plane of the light
|
||||
* @details This returns the lights near plane, previously set with
|
||||
* RPLight::set_near_plane. If the light does not cast shadows, this value
|
||||
* is meaningless.
|
||||
*
|
||||
* @return Near-plane
|
||||
*/
|
||||
inline float RPLight::get_near_plane() const {
|
||||
return _near_plane;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "rpLight.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs a new light with the given type
|
||||
* @details This constructs a new base light with the given light type.
|
||||
* Sub-Classes should call this to initialize all properties.
|
||||
*
|
||||
* @param light_type Type of the light
|
||||
*/
|
||||
RPLight::RPLight(LightType light_type) {
|
||||
_light_type = light_type;
|
||||
_needs_update = false;
|
||||
_casts_shadows = false;
|
||||
_slot = -1;
|
||||
_position.fill(0);
|
||||
_color.fill(1);
|
||||
_ies_profile = -1;
|
||||
_source_resolution = 512;
|
||||
_near_plane = 0.5;
|
||||
_energy = 20.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the light to a GPUCommand
|
||||
* @details This writes all of the lights data to the given GPUCommand handle.
|
||||
* Subclasses should first call this method, and then append their own
|
||||
* data. This makes sure that for unpacking a light, no information about
|
||||
* the type of the light is required.
|
||||
*
|
||||
* @param cmd The GPUCommand to write to
|
||||
*/
|
||||
void RPLight::write_to_command(GPUCommand &cmd) {
|
||||
cmd.push_int(_light_type);
|
||||
cmd.push_int(_ies_profile);
|
||||
|
||||
if (_casts_shadows) {
|
||||
// If we casts shadows, write the index of the first source, we expect
|
||||
// them to be consecutive
|
||||
nassertv(_shadow_sources.size() >= 0);
|
||||
nassertv(_shadow_sources[0]->has_slot());
|
||||
cmd.push_int(_shadow_sources[0]->get_slot());
|
||||
} else {
|
||||
// If we cast no shadows, just push a negative number
|
||||
cmd.push_int(-1);
|
||||
}
|
||||
|
||||
cmd.push_vec3(_position);
|
||||
|
||||
// Get the lights color by multiplying color with energy. Divide by
|
||||
// 100, since 16bit floating point buffers only go up to 65000.0, which
|
||||
// prevents very bright lights
|
||||
cmd.push_vec3(_color * _energy / 100.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Light destructor
|
||||
* @details This destructs the light, cleaning up all resourced used. The light
|
||||
* should be detached at this point, because while the Light is attached,
|
||||
* the InternalLightManager holds a reference to prevent it from being
|
||||
* destructed.
|
||||
*/
|
||||
RPLight::~RPLight() {
|
||||
nassertv(!has_slot()); // Light still attached - should never happen
|
||||
clear_shadow_sources();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the lights color from a given color temperature
|
||||
* @details This sets the lights color, given a temperature. This is more
|
||||
* physically based than setting a user defined color. The color will be
|
||||
* computed from the given temperature.
|
||||
*
|
||||
* @param temperature Light temperature
|
||||
*/
|
||||
void RPLight::set_color_from_temperature(float temperature) {
|
||||
|
||||
// Thanks to rdb for this conversion script
|
||||
float mm = 1000.0 / temperature;
|
||||
float mm2 = mm * mm;
|
||||
float mm3 = mm2 * mm;
|
||||
float x, y;
|
||||
|
||||
if (temperature < 4000) {
|
||||
x = -0.2661239 * mm3 - 0.2343580 * mm2 + 0.8776956 * mm + 0.179910;
|
||||
} else {
|
||||
x = -3.0258469 * mm3 + 2.1070379 * mm2 + 0.2226347 * mm + 0.240390;
|
||||
}
|
||||
|
||||
float x2 = x * x;
|
||||
float x3 = x2 * x;
|
||||
if (temperature < 2222) {
|
||||
y = -1.1063814 * x3 - 1.34811020 * x2 + 2.18555832 * x - 0.20219683;
|
||||
} else if (temperature < 4000) {
|
||||
y = -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867;
|
||||
} else {
|
||||
y = 3.0817580 * x3 - 5.87338670 * x2 + 3.75112997 * x - 0.37001483;
|
||||
}
|
||||
|
||||
// xyY to XYZ, assuming Y=1.
|
||||
LVecBase3 xyz(x / y, 1, (1 - x - y) / y);
|
||||
|
||||
// Convert XYZ to linearized sRGB.
|
||||
const static LMatrix3 xyz_to_rgb(
|
||||
3.2406, -0.9689, 0.0557,
|
||||
-1.5372, 1.8758, -0.2050,
|
||||
-0.4986, 0.0415, 1.0570);
|
||||
|
||||
set_color(xyz_to_rgb.xform(xyz));
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPLIGHT_H
|
||||
#define RPLIGHT_H
|
||||
|
||||
#include "referenceCount.h"
|
||||
#include "luse.h"
|
||||
#include "gpuCommand.h"
|
||||
#include "shadowSource.h"
|
||||
|
||||
/**
|
||||
* @brief Base class for Lights
|
||||
* @details This is the base class for all lights in the render pipeline. It
|
||||
* stores common properties, and provides methods to modify these.
|
||||
* It also defines some interface functions which subclasses have to implement.
|
||||
*/
|
||||
class RPLight : public ReferenceCount {
|
||||
PUBLISHED:
|
||||
/**
|
||||
* Different types of light.
|
||||
*/
|
||||
enum LightType {
|
||||
LT_empty = 0,
|
||||
LT_point_light = 1,
|
||||
LT_spot_light = 2,
|
||||
};
|
||||
|
||||
public:
|
||||
RPLight(LightType light_type);
|
||||
virtual ~RPLight();
|
||||
|
||||
virtual void init_shadow_sources() = 0;
|
||||
virtual void update_shadow_sources() = 0;
|
||||
virtual void write_to_command(GPUCommand &cmd);
|
||||
|
||||
inline int get_num_shadow_sources() const;
|
||||
inline ShadowSource* get_shadow_source(size_t index) const;
|
||||
inline void clear_shadow_sources();
|
||||
|
||||
inline void set_needs_update(bool flag);
|
||||
inline bool get_needs_update() const;
|
||||
|
||||
inline bool has_slot() const;
|
||||
inline int get_slot() const;
|
||||
inline void remove_slot();
|
||||
inline void assign_slot(int slot);
|
||||
|
||||
PUBLISHED:
|
||||
inline void invalidate_shadows();
|
||||
|
||||
inline void set_pos(const LVecBase3 &pos);
|
||||
inline void set_pos(float x, float y, float z);
|
||||
inline const LVecBase3& get_pos() const;
|
||||
MAKE_PROPERTY(pos, get_pos, set_pos);
|
||||
|
||||
inline void set_color(const LVecBase3 &color);
|
||||
inline void set_color(float r, float g, float b);
|
||||
inline const LVecBase3& get_color() const;
|
||||
MAKE_PROPERTY(color, get_color, set_color);
|
||||
|
||||
void set_color_from_temperature(float temperature);
|
||||
|
||||
inline void set_energy(float energy);
|
||||
inline float get_energy() const;
|
||||
MAKE_PROPERTY(energy, get_energy, set_energy);
|
||||
|
||||
inline LightType get_light_type() const;
|
||||
MAKE_PROPERTY(light_type, get_light_type);
|
||||
|
||||
inline void set_casts_shadows(bool flag = true);
|
||||
inline bool get_casts_shadows() const;
|
||||
MAKE_PROPERTY(casts_shadows, get_casts_shadows, set_casts_shadows);
|
||||
|
||||
inline void set_shadow_map_resolution(size_t resolution);
|
||||
inline size_t get_shadow_map_resolution() const;
|
||||
MAKE_PROPERTY(shadow_map_resolution, get_shadow_map_resolution, set_shadow_map_resolution);
|
||||
|
||||
inline void set_ies_profile(int profile);
|
||||
inline int get_ies_profile() const;
|
||||
inline bool has_ies_profile() const;
|
||||
inline void clear_ies_profile();
|
||||
MAKE_PROPERTY2(ies_profile, has_ies_profile, get_ies_profile,
|
||||
set_ies_profile, clear_ies_profile);
|
||||
|
||||
inline void set_near_plane(float near_plane);
|
||||
inline float get_near_plane() const;
|
||||
MAKE_PROPERTY(near_plane, get_near_plane, set_near_plane);
|
||||
|
||||
protected:
|
||||
int _slot;
|
||||
int _ies_profile;
|
||||
size_t _source_resolution;
|
||||
bool _needs_update;
|
||||
bool _casts_shadows;
|
||||
LVecBase3 _position;
|
||||
LVecBase3 _color;
|
||||
float _energy;
|
||||
LightType _light_type;
|
||||
float _near_plane;
|
||||
|
||||
vector<ShadowSource*> _shadow_sources;
|
||||
};
|
||||
|
||||
#include "rpLight.I"
|
||||
|
||||
#endif // RP_LIGHT_H
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the radius of the light
|
||||
* @details This sets the radius of the light. It controls the lights
|
||||
* influence. After a distance greater than this radius, the light influence
|
||||
* is zero.
|
||||
*
|
||||
* @param radius Light radius in world space
|
||||
*/
|
||||
inline void RPPointLight::set_radius(float radius) {
|
||||
nassertv(radius > 0); // Invalid light radius
|
||||
_radius = radius;
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the lights radius
|
||||
* @details This returns the lights radius previously set with
|
||||
* RPPointLight::set_radius
|
||||
* @return Light radius in world space
|
||||
*/
|
||||
inline float RPPointLight::get_radius() const {
|
||||
return _radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the inner radius of the light
|
||||
* @details This sets the inner radius of the light. Anything greater than
|
||||
* zero causes the light to get an area light. This has influence on the
|
||||
* specular highlights of the light aswell as the shadows.
|
||||
*
|
||||
* The inner radius controls the size of the lights sphere size in world
|
||||
* space units. A radius of 0 means the light has no inner radius, and the
|
||||
* light will be have like an infinite small point light source.
|
||||
* A radius greater than zero will cause the light to behave like it would be
|
||||
* an emissive sphere with the given inner radius emitting light. This is
|
||||
* more physically correct.
|
||||
*
|
||||
* @param inner_radius Inner-radius in world space
|
||||
*/
|
||||
inline void RPPointLight::set_inner_radius(float inner_radius) {
|
||||
nassertv(inner_radius >= 0.01); // Invalid inner radius
|
||||
_inner_radius = inner_radius;
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the inner radius of the light
|
||||
* @details This returns the inner radius of the light, previously set with
|
||||
* RPPointLight::get_inner_radius.
|
||||
* @return [description]
|
||||
*/
|
||||
inline float RPPointLight::get_inner_radius() const {
|
||||
return _inner_radius;
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "rpPointLight.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs a new point light
|
||||
* @details This contructs a new point light with default settings. By default
|
||||
* the light is set to be an infinitely small point light source. You can
|
||||
* change this with RPPointLight::set_inner_radius.
|
||||
*/
|
||||
RPPointLight::RPPointLight() : RPLight(RPLight::LT_point_light) {
|
||||
_radius = 10.0;
|
||||
_inner_radius = 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the light to a GPUCommand
|
||||
* @details This writes the point light data to a GPUCommand.
|
||||
* @see RPLight::write_to_command
|
||||
*
|
||||
* @param cmd The target GPUCommand
|
||||
*/
|
||||
void RPPointLight::write_to_command(GPUCommand &cmd) {
|
||||
RPLight::write_to_command(cmd);
|
||||
cmd.push_float(_radius);
|
||||
cmd.push_float(_inner_radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inits the shadow sources of the light
|
||||
* @details This inits all required shadow sources for the point light.
|
||||
* @see RPLight::init_shadow_sources
|
||||
*/
|
||||
void RPPointLight::init_shadow_sources() {
|
||||
nassertv(_shadow_sources.size() == 0);
|
||||
// Create 6 shadow sources, one for each direction
|
||||
for(size_t i = 0; i < 6; ++i) {
|
||||
_shadow_sources.push_back(new ShadowSource());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the shadow sources
|
||||
* @details This updates all shadow sources of the light.
|
||||
* @see RPLight::update_shadow_sources
|
||||
*/
|
||||
void RPPointLight::update_shadow_sources() {
|
||||
LVecBase3 directions[6] = {
|
||||
LVecBase3( 1, 0, 0),
|
||||
LVecBase3(-1, 0, 0),
|
||||
LVecBase3( 0, 1, 0),
|
||||
LVecBase3( 0, -1, 0),
|
||||
LVecBase3( 0, 0, 1),
|
||||
LVecBase3( 0, 0, -1)
|
||||
};
|
||||
|
||||
// Increase fov to prevent artifacts at the shadow map transitions
|
||||
const float fov = 90.0f + 3.0f;
|
||||
for (size_t i = 0; i < _shadow_sources.size(); ++i) {
|
||||
_shadow_sources[i]->set_resolution(get_shadow_map_resolution());
|
||||
_shadow_sources[i]->set_perspective_lens(fov, _near_plane, _radius,
|
||||
_position, directions[i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPPOINTLIGHT_H
|
||||
#define RPPOINTLIGHT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "rpLight.h"
|
||||
|
||||
/**
|
||||
* @brief PointLight class
|
||||
* @details This represents a point light, a light which has a position and
|
||||
* radius. Checkout the RenderPipeline documentation for more information
|
||||
* about this type of light.
|
||||
*/
|
||||
class RPPointLight : public RPLight {
|
||||
PUBLISHED:
|
||||
RPPointLight();
|
||||
|
||||
inline void set_radius(float radius);
|
||||
inline float get_radius() const;
|
||||
MAKE_PROPERTY(radius, get_radius, set_radius);
|
||||
|
||||
inline void set_inner_radius(float inner_radius);
|
||||
inline float get_inner_radius() const;
|
||||
MAKE_PROPERTY(inner_radius, get_inner_radius, set_inner_radius);
|
||||
|
||||
public:
|
||||
virtual void write_to_command(GPUCommand &cmd);
|
||||
virtual void update_shadow_sources();
|
||||
virtual void init_shadow_sources();
|
||||
|
||||
protected:
|
||||
float _radius;
|
||||
float _inner_radius;
|
||||
};
|
||||
|
||||
#include "rpPointLight.I"
|
||||
|
||||
#endif // RPPOINTLIGHT_H
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
inline void RPSpotLight::set_radius(float radius) {
|
||||
_radius = radius;
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
inline float RPSpotLight::get_radius() const {
|
||||
return _radius;
|
||||
}
|
||||
|
||||
|
||||
inline void RPSpotLight::set_fov(float fov) {
|
||||
_fov = fov;
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
inline float RPSpotLight::get_fov() const {
|
||||
return _fov;
|
||||
}
|
||||
|
||||
inline void RPSpotLight::set_direction(LVecBase3 direction) {
|
||||
_direction = direction;
|
||||
_direction.normalize();
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
inline void RPSpotLight::set_direction(float dx, float dy, float dz) {
|
||||
_direction.set(dx, dy, dz);
|
||||
_direction.normalize();
|
||||
set_needs_update(true);
|
||||
invalidate_shadows();
|
||||
}
|
||||
|
||||
inline const LVecBase3& RPSpotLight::get_direction() const {
|
||||
return _direction;
|
||||
}
|
||||
|
||||
inline void RPSpotLight::look_at(LVecBase3 point) {
|
||||
set_direction(point - _position);
|
||||
}
|
||||
|
||||
inline void RPSpotLight::look_at(float x, float y, float z) {
|
||||
set_direction(LVecBase3(x, y, z) - _position);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "rpSpotLight.h"
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Creates a new spot light
|
||||
* @details This creates a new spot light with default properties set. You should
|
||||
* set at least a direction, fov, radius and position to make the light useful.
|
||||
*/
|
||||
RPSpotLight::RPSpotLight() : RPLight(RPLight::LT_spot_light) {
|
||||
_radius = 10.0;
|
||||
_fov = 45.0;
|
||||
_direction.set(0, 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the light to a GPUCommand
|
||||
* @details This writes the spot light data to a GPUCommand.
|
||||
* @see RPLight::write_to_command
|
||||
*
|
||||
* @param cmd The target GPUCommand
|
||||
*/
|
||||
void RPSpotLight::write_to_command(GPUCommand &cmd) {
|
||||
RPLight::write_to_command(cmd);
|
||||
cmd.push_float(_radius);
|
||||
|
||||
// Encode FOV as cos(fov)
|
||||
cmd.push_float(cos(_fov / 360.0 * M_PI));
|
||||
cmd.push_vec3(_direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inits the shadow sources of the light
|
||||
* @details This inits all required shadow sources for the spot light.
|
||||
* @see RPLight::init_shadow_sources
|
||||
*/
|
||||
void RPSpotLight::init_shadow_sources() {
|
||||
nassertv(_shadow_sources.size() == 0);
|
||||
_shadow_sources.push_back(new ShadowSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the shadow sources
|
||||
* @details This updates all shadow sources of the light.
|
||||
* @see RPLight::update_shadow_sources
|
||||
*/
|
||||
void RPSpotLight::update_shadow_sources() {
|
||||
_shadow_sources[0]->set_resolution(get_shadow_map_resolution());
|
||||
_shadow_sources[0]->set_perspective_lens(_fov, _near_plane, _radius, _position, _direction);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RPSPOTLIGHT_H
|
||||
#define RPSPOTLIGHT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "rpLight.h"
|
||||
|
||||
/**
|
||||
* @brief SpotLight class
|
||||
* @details This represents a spot light, a light which has a position, radius,
|
||||
* direction and FoV. Checkout the RenderPipeline documentation for more
|
||||
* information about this type of light.
|
||||
*/
|
||||
class RPSpotLight : public RPLight {
|
||||
PUBLISHED:
|
||||
RPSpotLight();
|
||||
|
||||
inline void set_radius(float radius);
|
||||
inline float get_radius() const;
|
||||
MAKE_PROPERTY(radius, get_radius, set_radius);
|
||||
|
||||
inline void set_fov(float fov);
|
||||
inline float get_fov() const;
|
||||
MAKE_PROPERTY(fov, get_fov, set_fov);
|
||||
|
||||
inline void set_direction(LVecBase3 direction);
|
||||
inline void set_direction(float dx, float dy, float dz);
|
||||
inline const LVecBase3& get_direction() const;
|
||||
inline void look_at(LVecBase3 point);
|
||||
inline void look_at(float x, float y, float z);
|
||||
MAKE_PROPERTY(direction, get_direction, set_direction);
|
||||
|
||||
public:
|
||||
virtual void write_to_command(GPUCommand &cmd);
|
||||
virtual void init_shadow_sources();
|
||||
virtual void update_shadow_sources();
|
||||
|
||||
protected:
|
||||
float _radius;
|
||||
float _fov;
|
||||
LVecBase3 _direction;
|
||||
};
|
||||
|
||||
#include "rpSpotLight.I"
|
||||
|
||||
#endif // RPSPOTLIGHT_H
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the tile size of the atlas.
|
||||
* @details This returns the tile size of the atlas, which was set in the
|
||||
* constructor. This is the smalles unit of the atlas, and every resolution
|
||||
* has to be a multiple of the tile size.
|
||||
* @return Tile size in pixels
|
||||
*/
|
||||
inline int ShadowAtlas::get_tile_size() const {
|
||||
return _tile_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a specific tile status.
|
||||
* @details This marks a tile to either reserved or free, depending on the flag.
|
||||
* If flag is true, the tile gets marked as reserved. If flag is false, the
|
||||
* tile gets marked as free.
|
||||
*
|
||||
* No bounds checking is done for performance reasons. Passing an invalid tile
|
||||
* index causes a crash. The coordinates are expected to be in tile space.
|
||||
*
|
||||
* @param x x-position of the tile
|
||||
* @param y y-position of the tile
|
||||
* @param flag Flag to set the tile to
|
||||
*/
|
||||
inline void ShadowAtlas::set_tile(size_t x, size_t y, bool flag) {
|
||||
_flags[x + y * _num_tiles] = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the status of a given tile.
|
||||
* @details This returns the value of a tile. If the tile is true, this means
|
||||
* the tile is already reserved. If the tile is false, the tile can be
|
||||
* used, and is not reserved.
|
||||
*
|
||||
* No bounds checking is done for performance reasons. Passing an invalid tile
|
||||
* index causes a crash. The coordinates are expected to be in tile space.
|
||||
*
|
||||
* @param x x-position of the tile
|
||||
* @param y y-position of the tile
|
||||
*
|
||||
* @return Tile-Status
|
||||
*/
|
||||
inline bool ShadowAtlas::get_tile(size_t x, size_t y) const {
|
||||
return _flags[x + y * _num_tiles];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks wheter a given region is free.
|
||||
* @details This checks whether a given region in the atlas is still free. This
|
||||
* is true if *all* tiles in that region are false, and thus are not taken yet.
|
||||
* The coordinates are expected to be in tile space.
|
||||
*
|
||||
* Passing an invalid region, causes an assertion, in case those are enabled.
|
||||
* If assertions are optimized out, this method will crash when passing invalid
|
||||
* bounds.
|
||||
*
|
||||
* @param x x- start position of the region
|
||||
* @param y y- start position of the region
|
||||
* @param w width of the region
|
||||
* @param h height of the region
|
||||
* @return true if the region is completely free, else false
|
||||
*/
|
||||
inline bool ShadowAtlas::region_is_free(size_t x, size_t y, size_t w, size_t h) const {
|
||||
// Check if we are out of bounds, this should be disabled for performance
|
||||
// reasons at some point.
|
||||
nassertr(x >= 0 && y >= 0 && x + w <= _num_tiles && y + h <= _num_tiles, false);
|
||||
|
||||
// Iterate over every tile in that region and check if it is still free.
|
||||
for (size_t cx = 0; cx < w; ++cx) {
|
||||
for (size_t cy = 0; cy < h; ++cy) {
|
||||
if (get_tile(cx + x, cy + y)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of tiles required to store a resolution.
|
||||
* @details Returns the amount of tiles which would be required to store a
|
||||
* given resolution. This basically just returns resolution / tile_size.
|
||||
*
|
||||
* When an invalid resolution is passed (not a multiple of the tile size),
|
||||
* an error is printed and 1 is returned.
|
||||
* When a negative or zero resolution is passed, undefined behaviour occurs.
|
||||
*
|
||||
* @param resolution The resolution to compute the amount of tiles for
|
||||
* @return Amount of tiles to store the resolution
|
||||
*/
|
||||
inline int ShadowAtlas::get_required_tiles(size_t resolution) const {
|
||||
nassertr(resolution > 0, -1);
|
||||
|
||||
if (resolution % _tile_size != 0) {
|
||||
shadowatlas_cat.error() << "Resolution " << resolution << " is not a multiple "
|
||||
<< "of the shadow atlas tile size (" << _tile_size << ")!" << endl;
|
||||
return 1;
|
||||
}
|
||||
return resolution / _tile_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a tile-space region to uv space.
|
||||
* @details This converts a region (presumably from ShadowAtlas::find_and_reserve_region)
|
||||
* to uv space (0 .. 1 range). This can be used in shaders, since they expect
|
||||
* floating point coordinates instead of integer coordinates.
|
||||
*
|
||||
* @param region tile-space region
|
||||
* @return uv-space region
|
||||
*/
|
||||
inline LVecBase4 ShadowAtlas::region_to_uv(const LVecBase4i& region) {
|
||||
LVecBase4 flt = LVecBase4(region.get_x(), region.get_y(), region.get_z(), region.get_w());
|
||||
return flt * ((float)_tile_size / (float)_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of used tiles
|
||||
* @details Returns the amount of used tiles in the atlas
|
||||
* @return Amount of used tiles
|
||||
*/
|
||||
inline int ShadowAtlas::get_num_used_tiles() const {
|
||||
return _num_used_tiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the amount of used tiles in percentage
|
||||
* @details This returns in percentage from 0 to 1 how much space of the atlas
|
||||
* is used right now. A value of 1 means the atlas is completely full, whereas
|
||||
* a value of 0 means the atlas is completely free.
|
||||
* @return Atlas usage in percentage
|
||||
*/
|
||||
inline float ShadowAtlas::get_coverage() const {
|
||||
return float(_num_used_tiles) / float(_num_tiles * _num_tiles);
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "shadowAtlas.h"
|
||||
#include <string.h>
|
||||
|
||||
NotifyCategoryDef(shadowatlas, "");
|
||||
|
||||
/**
|
||||
* @brief Constructs a new shadow atlas.
|
||||
* @details This constructs a new shadow atlas with the given size and tile size.
|
||||
*
|
||||
* The size determines the total size of the atlas in pixels. It should be a
|
||||
* power-of-two to favour the GPU.
|
||||
*
|
||||
* The tile_size determines the smallest unit of tiles the atlas can store.
|
||||
* If, for example, a tile_size of 32 is used, then every entry stored must
|
||||
* have a resolution of 32 or greater, and the resolution must be a multiple
|
||||
* of 32. This is to optimize the search in the atlas, so the atlas does not
|
||||
* have to check every pixel, and instead can just check whole tiles.
|
||||
*
|
||||
* If you want to disable the use of tiles, set the tile_size to 1, which
|
||||
* will make the shadow atlas use pixels instead of tiles.
|
||||
*
|
||||
* @param size Atlas-size in pixels
|
||||
* @param tile_size tile-size in pixels, or 1 to use no tiles.
|
||||
*/
|
||||
ShadowAtlas::ShadowAtlas(size_t size, size_t tile_size) {
|
||||
nassertv(size > 1 && tile_size >= 1);
|
||||
nassertv(tile_size < size && size % tile_size == 0);
|
||||
_size = size;
|
||||
_tile_size = tile_size;
|
||||
_num_used_tiles = 0;
|
||||
init_tiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructs the shadow atlas.
|
||||
* @details This destructs the shadow atlas, freeing all used resources.
|
||||
*/
|
||||
ShadowAtlas::~ShadowAtlas() {
|
||||
delete [] _flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to init the storage.
|
||||
* @details This method setups the storage used for storing the tile flags.
|
||||
*/
|
||||
void ShadowAtlas::init_tiles() {
|
||||
_num_tiles = _size / _tile_size;
|
||||
_flags = new bool[_num_tiles * _num_tiles];
|
||||
memset(_flags, 0x0, sizeof(bool) * _num_tiles * _num_tiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Internal method to reserve a region in the atlas.
|
||||
* @details This reserves a given region in the shadow atlas. The region should
|
||||
* be in tile space.This is called by the ShadowAtlas::find_and_reserve_region.
|
||||
* It sets all flags in that region to true, indicating that those are used.
|
||||
* When an invalid region is passed, an assertion is triggered. If assertions
|
||||
* are optimized out, undefined behaviour occurs.
|
||||
*
|
||||
* @param x x- start positition of the region
|
||||
* @param y y- start position of the region
|
||||
* @param w width of the region
|
||||
* @param h height of the region
|
||||
*/
|
||||
void ShadowAtlas::reserve_region(size_t x, size_t y, size_t w, size_t h) {
|
||||
// Check if we are out of bounds, this should be disabled for performance
|
||||
// reasons at some point.
|
||||
nassertv(x >= 0 && y >= 0 && x + w <= _num_tiles && y + h <= _num_tiles);
|
||||
|
||||
_num_used_tiles += w * h;
|
||||
|
||||
// Iterate over every tile in the region and mark it as used
|
||||
for (size_t cx = 0; cx < w; ++cx) {
|
||||
for (size_t cy = 0; cy < h; ++cy) {
|
||||
set_tile(cx + x, cy + y, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds space for a map of the given size in the atlas.
|
||||
* @details This methods searches for a space to store a region of the given
|
||||
* size in the atlas. tile_width and tile_height should be already in tile
|
||||
* space. They can be converted using ShadowAtlas::get_required_tiles.
|
||||
*
|
||||
* If no region is found, or an invalid size is passed, an integer vector with
|
||||
* all components set to -1 is returned.
|
||||
*
|
||||
* If a region is found, an integer vector with the given layout is returned:
|
||||
* x: x- Start of the region
|
||||
* y: y- Start of the region
|
||||
* z: width of the region
|
||||
* w: height of the region
|
||||
*
|
||||
* The layout is in tile space, and can get converted to uv space using
|
||||
* ShadowAtlas::region_to_uv.
|
||||
*
|
||||
* @param tile_width Width of the region in tile space
|
||||
* @param tile_height Height of the region in tile space
|
||||
*
|
||||
* @return Region, see description, or -1 when no region is found.
|
||||
*/
|
||||
LVecBase4i ShadowAtlas::find_and_reserve_region(size_t tile_width, size_t tile_height) {
|
||||
|
||||
// Check for empty region
|
||||
if (tile_width < 1 || tile_height < 1) {
|
||||
shadowatlas_cat.error() << "Called find_and_reserve_region with null-region!" << endl;
|
||||
return LVecBase4i(-1);
|
||||
}
|
||||
|
||||
// Check for region bigger than the shadow atlas
|
||||
if (tile_width > _num_tiles || tile_height > _num_tiles) {
|
||||
shadowatlas_cat.error() << "Requested region exceeds shadow atlas size!" << endl;
|
||||
return LVecBase4i(-1);
|
||||
}
|
||||
|
||||
// Iterate over every possible region and check if its still free
|
||||
for (size_t x = 0; x <= _num_tiles - tile_width; ++x) {
|
||||
for (size_t y = 0; y <= _num_tiles - tile_height; ++y) {
|
||||
if (region_is_free(x, y, tile_width, tile_height)) {
|
||||
// Found free region, now reserve it
|
||||
reserve_region(x, y, tile_width, tile_height);
|
||||
return LVecBase4i(x, y, tile_width, tile_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we reached this part, we couldn't find a free region, so the atlas
|
||||
// seems to be full.
|
||||
shadowatlas_cat.error() << "Failed to find a free region of size " << tile_width
|
||||
<< " x " << tile_height << "!" << endl;
|
||||
return LVecBase4i(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees a given region
|
||||
* @details This frees a given region, marking it as free so that other shadow
|
||||
* maps can use the space again. The region should be the same as returned
|
||||
* by ShadowAtlas::find_and_reserve_region.
|
||||
*
|
||||
* If an invalid region is passed, an assertion is triggered. If assertions
|
||||
* are compiled out, undefined behaviour will occur.
|
||||
*
|
||||
* @param region Region to free
|
||||
*/
|
||||
void ShadowAtlas::free_region(const LVecBase4i& region) {
|
||||
// Out of bounds check, can't hurt
|
||||
nassertv(region.get_x() >= 0 && region.get_y() >= 0);
|
||||
nassertv(region.get_x() + region.get_z() <= _num_tiles && region.get_y() + region.get_w() <= _num_tiles);
|
||||
|
||||
_num_used_tiles -= region.get_z() * region.get_w();
|
||||
|
||||
for (size_t x = 0; x < region.get_z(); ++x) {
|
||||
for (size_t y = 0; y < region.get_w(); ++y) {
|
||||
// Could do an assert here, that the tile should have been used (=true) before
|
||||
set_tile(region.get_x() + x, region.get_y() + y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHADOWATLAS_H
|
||||
#define SHADOWATLAS_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "lvecBase4.h"
|
||||
|
||||
NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Class which manages distributing shadow maps in an atlas.
|
||||
* @details This class manages the shadow atlas. It handles finding and reserving
|
||||
* space for new shadow maps.
|
||||
*/
|
||||
class ShadowAtlas {
|
||||
PUBLISHED:
|
||||
ShadowAtlas(size_t size, size_t tile_size = 32);
|
||||
~ShadowAtlas();
|
||||
|
||||
inline int get_num_used_tiles() const;
|
||||
inline float get_coverage() const;
|
||||
|
||||
MAKE_PROPERTY(num_used_tiles, get_num_used_tiles);
|
||||
MAKE_PROPERTY(coverage, get_coverage);
|
||||
|
||||
public:
|
||||
|
||||
LVecBase4i find_and_reserve_region(size_t tile_width, size_t tile_height);
|
||||
void free_region(const LVecBase4i& region);
|
||||
inline LVecBase4 region_to_uv(const LVecBase4i& region);
|
||||
|
||||
inline int get_tile_size() const;
|
||||
inline int get_required_tiles(size_t resolution) const;
|
||||
|
||||
protected:
|
||||
|
||||
void init_tiles();
|
||||
|
||||
inline void set_tile(size_t x, size_t y, bool flag);
|
||||
inline bool get_tile(size_t x, size_t y) const;
|
||||
|
||||
inline bool region_is_free(size_t x, size_t y, size_t w, size_t h) const;
|
||||
void reserve_region(size_t x, size_t y, size_t w, size_t h);
|
||||
|
||||
size_t _size;
|
||||
size_t _num_tiles;
|
||||
size_t _tile_size;
|
||||
size_t _num_used_tiles;
|
||||
bool* _flags;
|
||||
};
|
||||
|
||||
#include "shadowAtlas.I"
|
||||
|
||||
#endif // SHADOWATLAS_H
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum amount of updates per frame.
|
||||
* @details This controls the maximum amount of updated ShadowSources per frame.
|
||||
* The ShadowManager will take the first <max_updates> ShadowSources, and
|
||||
* generate shadow maps for them every frame. If there are more ShadowSources
|
||||
* waiting to get updated than available updates, the sources are sorted by
|
||||
* priority, and the update of the less important sources is delayed to the
|
||||
* next frame.
|
||||
*
|
||||
* If the update count is set too low, and there are a lot of ShadowSources
|
||||
* waiting to get updated, artifacts will occur, and there might be ShadowSources
|
||||
* which never get updated, due to low priority.
|
||||
*
|
||||
* If an update count of 0 is passed, no updates will happen. This also means
|
||||
* that there are no shadows. This is not recommended.
|
||||
*
|
||||
* If an update count < 0 is passed, undefined behaviour occurs.
|
||||
*
|
||||
* This method has to get called before ShadowManager::init, otherwise an
|
||||
* assertion will get triggered.
|
||||
*
|
||||
* @param max_updates Maximum amoumt of updates
|
||||
*/
|
||||
inline void ShadowManager::set_max_updates(size_t max_updates) {
|
||||
nassertv(max_updates >= 0);
|
||||
nassertv(_atlas == nullptr); // ShadowManager was already initialized
|
||||
if (max_updates == 0) {
|
||||
shadowmanager_cat.warning() << "max_updates set to 0, no shadows will be updated." << endl;
|
||||
}
|
||||
_max_updates = max_updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the shadow atlas size
|
||||
* @details This sets the desired shadow atlas size. It should be big enough
|
||||
* to store all important shadow sources, with some buffer, because the shadow
|
||||
* maps usually won't be fitted perfectly, so gaps can occur.
|
||||
*
|
||||
* This has to get called before calling ShadowManager::init. When calling this
|
||||
* method after initialization, an assertion will get triggered.
|
||||
*
|
||||
* @param atlas_size Size of the shadow atlas in pixels
|
||||
*/
|
||||
inline void ShadowManager::set_atlas_size(size_t atlas_size) {
|
||||
nassertv(atlas_size >= 16 && atlas_size <= 16384);
|
||||
nassertv(_atlas == nullptr); // ShadowManager was already initialized
|
||||
_atlas_size = atlas_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the shadow atlas size.
|
||||
* @details This returns the shadow atlas size previously set with
|
||||
* ShadowManager::set_atlas_size.
|
||||
* @return Shadow atlas size in pixels
|
||||
*/
|
||||
inline size_t ShadowManager::get_atlas_size() const {
|
||||
return _atlas_size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the shadow atlas.
|
||||
* @details This returns a handle to the internal shadow atlas instance. This
|
||||
* is only valid after calling ShadowManager::init. Calling this earlier will
|
||||
* trigger an assertion and undefined behaviour.
|
||||
* @return The internal ShadowAtlas instance
|
||||
*/
|
||||
inline ShadowAtlas* ShadowManager::get_atlas() const {
|
||||
nassertr(_atlas != nullptr, nullptr); // Can't hurt to check
|
||||
return _atlas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the target scene
|
||||
* @details This sets the target scene for rendering shadows. All shadow cameras
|
||||
* will be parented to this scene to render shadows.
|
||||
*
|
||||
* Usually the scene will be ShowBase.render. If the scene is an empty or
|
||||
* invalid NodePath, an assertion will be triggered.
|
||||
*
|
||||
* This method has to get called before calling ShadowManager::init, or an
|
||||
* assertion will get triggered.
|
||||
*
|
||||
* @param scene_parent The target scene
|
||||
*/
|
||||
inline void ShadowManager::set_scene(NodePath scene_parent) {
|
||||
nassertv(!scene_parent.is_empty());
|
||||
nassertv(_atlas == nullptr); // ShadowManager was already initialized
|
||||
_scene_parent = scene_parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the handle to the TagStageManager.
|
||||
* @details This sets the handle to the TagStateManager used by the pipeline.
|
||||
* Usually this is RenderPipeline.get_tag_mgr().
|
||||
*
|
||||
* This has to get called before ShadowManager::init, otherwise an assertion
|
||||
* will get triggered.
|
||||
*
|
||||
* @param tag_mgr [description]
|
||||
*/
|
||||
inline void ShadowManager::set_tag_state_manager(TagStateManager* tag_mgr) {
|
||||
nassertv(tag_mgr != nullptr);
|
||||
nassertv(_atlas == nullptr); // ShadowManager was already initialized
|
||||
_tag_state_mgr = tag_mgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the handle to the Shadow targets output
|
||||
* @details This sets the handle to the GraphicsOutput of the shadow atlas.
|
||||
* Usually this is RenderTarget.get_internal_buffer(), whereas the RenderTarget
|
||||
* is the target of the ShadowStage.
|
||||
*
|
||||
* This is used for creating display regions and attaching cameras to them,
|
||||
* for performing shadow updates.
|
||||
*
|
||||
* This has to get called before ShadowManager::init, otherwise an assertion
|
||||
* will be triggered.
|
||||
*
|
||||
* @param graphics_output [description]
|
||||
*/
|
||||
inline void ShadowManager::set_atlas_graphics_output(GraphicsOutput* graphics_output) {
|
||||
nassertv(graphics_output != nullptr);
|
||||
nassertv(_atlas == nullptr); // ShadowManager was already initialized
|
||||
_atlas_graphics_output = graphics_output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Adds a new shadow update
|
||||
* @details This adds a new update to the update queue. When the queue is already
|
||||
* full, this method returns false, otherwise it returns true. The next time
|
||||
* the manager is updated, the shadow source will recieve an update of its
|
||||
* shadow map.
|
||||
*
|
||||
* @param source The shadow source to update
|
||||
*
|
||||
* @return Whether the shadow source udpate was sucessfully queued.
|
||||
*/
|
||||
inline bool ShadowManager::add_update(const ShadowSource* source) {
|
||||
nassertr(_atlas != nullptr, false); // ShadowManager::init not called yet.
|
||||
nassertr(source != nullptr, false); // nullptr-Pointer passed
|
||||
|
||||
if (_queued_updates.size() >= _max_updates) {
|
||||
if (shadowmanager_cat.is_debug()) {
|
||||
shadowmanager_cat.debug() << "cannot update source, out of update slots" << endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the update to the queue
|
||||
_queued_updates.push_back(source);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns how many update slots are left.
|
||||
* @details This returns how many update slots are left. You can assume the
|
||||
* next n calls to add_update will succeed, whereas n is the value returned
|
||||
* by this function.
|
||||
* @return Number of update slots left.
|
||||
*/
|
||||
inline size_t ShadowManager::get_num_update_slots_left() const {
|
||||
return _max_updates - _queued_updates.size();
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "shadowManager.h"
|
||||
|
||||
NotifyCategoryDef(shadowmanager, "");
|
||||
|
||||
/**
|
||||
* @brief Constructs a new shadow atlas
|
||||
* @details This constructs a new shadow atlas. There are a set of properties
|
||||
* which should be set before calling ShadowManager::init, see the set-Methods.
|
||||
* After all properties are set, ShadowManager::init should get called.
|
||||
* ShadowManager::update should get called on a per frame basis.
|
||||
*/
|
||||
ShadowManager::ShadowManager() {
|
||||
_max_updates = 10;
|
||||
_atlas = nullptr;
|
||||
_atlas_size = 4096;
|
||||
_tag_state_mgr = nullptr;
|
||||
_atlas_graphics_output = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructs the ShadowManager
|
||||
* @details This destructs the shadow manager, clearing all resources used
|
||||
*/
|
||||
ShadowManager::~ShadowManager() {
|
||||
delete _atlas;
|
||||
|
||||
// Todo: Could eventually unregister all shadow cameras. Since the tag state
|
||||
// manager does this on cleanup already, and we get destructed at the same
|
||||
// time (if at all), this is not really necessary
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes the ShadowManager.
|
||||
* @details This initializes the ShadowManager. All properties should have
|
||||
* been set before calling this, otherwise assertions will get triggered.
|
||||
*
|
||||
* This setups everything required for rendering shadows, including the
|
||||
* shadow atlas and the various shadow cameras. After calling this method,
|
||||
* no properties can be changed anymore.
|
||||
*/
|
||||
void ShadowManager::init() {
|
||||
nassertv(!_scene_parent.is_empty()); // Scene parent not set, call set_scene_parent before init!
|
||||
nassertv(_tag_state_mgr != nullptr); // TagStateManager not set, call set_tag_state_mgr before init!
|
||||
nassertv(_atlas_graphics_output != nullptr); // AtlasGraphicsOutput not set, call set_atlas_graphics_output before init!
|
||||
|
||||
_cameras.resize(_max_updates);
|
||||
_display_regions.resize(_max_updates);
|
||||
_camera_nps.reserve(_max_updates);
|
||||
|
||||
// Create the cameras and regions
|
||||
for(size_t i = 0; i < _max_updates; ++i) {
|
||||
|
||||
// Create the camera
|
||||
PT(Camera) camera = new Camera("ShadowCam-" + format_string(i));
|
||||
camera->set_lens(new MatrixLens());
|
||||
camera->set_active(false);
|
||||
camera->set_scene(_scene_parent);
|
||||
_tag_state_mgr->register_camera("shadow", camera);
|
||||
_camera_nps.push_back(_scene_parent.attach_new_node(camera));
|
||||
_cameras[i] = camera;
|
||||
|
||||
// Create the display region
|
||||
PT(DisplayRegion) region = _atlas_graphics_output->make_display_region();
|
||||
region->set_sort(1000);
|
||||
region->set_clear_depth_active(true);
|
||||
region->set_clear_depth(1.0);
|
||||
region->set_clear_color_active(false);
|
||||
region->set_camera(_camera_nps[i]);
|
||||
region->set_active(false);
|
||||
_display_regions[i] = region;
|
||||
}
|
||||
|
||||
// Create the atlas
|
||||
_atlas = new ShadowAtlas(_atlas_size);
|
||||
|
||||
// Reserve enough space for the updates
|
||||
_queued_updates.reserve(_max_updates);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Updates the ShadowManager
|
||||
* @details This updates the ShadowManager, processing all shadow sources which
|
||||
* need to get updated.
|
||||
*
|
||||
* This first collects all sources which require an update, sorts them by priority,
|
||||
* and then processes the first <max_updates> ShadowSources.
|
||||
*
|
||||
* This may not get called before ShadowManager::init, or an assertion will be
|
||||
* thrown.
|
||||
*/
|
||||
void ShadowManager::update() {
|
||||
nassertv(_atlas != nullptr); // ShadowManager::init not called yet
|
||||
nassertv(_queued_updates.size() <= _max_updates); // Internal error, should not happen
|
||||
|
||||
// Disable all cameras and regions which will not be used
|
||||
for (size_t i = _queued_updates.size(); i < _max_updates; ++i) {
|
||||
_cameras[i]->set_active(false);
|
||||
_display_regions[i]->set_active(false);
|
||||
}
|
||||
|
||||
// Iterate over all queued updates
|
||||
for (size_t i = 0; i < _queued_updates.size(); ++i) {
|
||||
const ShadowSource* source = _queued_updates[i];
|
||||
|
||||
// Enable the camera and display region, so they perform a render
|
||||
_cameras[i]->set_active(true);
|
||||
_display_regions[i]->set_active(true);
|
||||
|
||||
// Set the view projection matrix
|
||||
DCAST(MatrixLens, _cameras[i]->get_lens())->set_user_mat(source->get_mvp());
|
||||
|
||||
// Optional: Show the camera frustum for debugging
|
||||
// _cameras[i]->show_frustum();
|
||||
|
||||
// Set the correct dimensions on the display region
|
||||
const LVecBase4& uv = source->get_uv_region();
|
||||
_display_regions[i]->set_dimensions(
|
||||
uv.get_x(), // left
|
||||
uv.get_x() + uv.get_z(), // right
|
||||
uv.get_y(), // bottom
|
||||
uv.get_y() + uv.get_w() // top
|
||||
);
|
||||
}
|
||||
|
||||
// Clear the update list
|
||||
_queued_updates.clear();
|
||||
_queued_updates.reserve(_max_updates);
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHADOWMANAGER_H
|
||||
#define SHADOWMANAGER_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "camera.h"
|
||||
#include "luse.h"
|
||||
#include "matrixLens.h"
|
||||
#include "referenceCount.h"
|
||||
#include "nodePath.h"
|
||||
#include "displayRegion.h"
|
||||
#include "graphicsOutput.h"
|
||||
|
||||
#include "tagStateManager.h"
|
||||
#include "shadowSource.h"
|
||||
#include "shadowAtlas.h"
|
||||
|
||||
NotifyCategoryDecl(shadowmanager, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
|
||||
class ShadowManager : public ReferenceCount {
|
||||
PUBLISHED:
|
||||
ShadowManager();
|
||||
~ShadowManager();
|
||||
|
||||
inline void set_max_updates(size_t max_updates);
|
||||
inline void set_scene(NodePath scene_parent);
|
||||
inline void set_tag_state_manager(TagStateManager* tag_mgr);
|
||||
inline void set_atlas_graphics_output(GraphicsOutput* graphics_output);
|
||||
|
||||
inline void set_atlas_size(size_t atlas_size);
|
||||
inline size_t get_atlas_size() const;
|
||||
MAKE_PROPERTY(atlas_size, get_atlas_size, set_atlas_size);
|
||||
|
||||
inline size_t get_num_update_slots_left() const;
|
||||
MAKE_PROPERTY(num_update_slots_left, get_num_update_slots_left);
|
||||
|
||||
inline ShadowAtlas* get_atlas() const;
|
||||
MAKE_PROPERTY(atlas, get_atlas);
|
||||
|
||||
void init();
|
||||
void update();
|
||||
|
||||
public:
|
||||
inline bool add_update(const ShadowSource* source);
|
||||
|
||||
private:
|
||||
size_t _max_updates;
|
||||
size_t _atlas_size;
|
||||
NodePath _scene_parent;
|
||||
|
||||
pvector<PT(Camera)> _cameras;
|
||||
pvector<NodePath> _camera_nps;
|
||||
pvector<PT(DisplayRegion)> _display_regions;
|
||||
|
||||
ShadowAtlas* _atlas;
|
||||
TagStateManager* _tag_state_mgr;
|
||||
GraphicsOutput* _atlas_graphics_output;
|
||||
|
||||
typedef pvector<const ShadowSource*> UpdateQueue;
|
||||
UpdateQueue _queued_updates;
|
||||
};
|
||||
|
||||
#include "shadowManager.I"
|
||||
|
||||
#endif // SHADOWMANAGER_H
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns whether the shadow source needs an update.
|
||||
* @details This returns the update flag, which was previously set with
|
||||
* ShadowSource::set_needs_update. If the value is true, it means that the
|
||||
* ShadowSource is invalid and should be regenerated. This can either be the
|
||||
* case because the scene changed and affected the shadow source, or the light
|
||||
* moved.
|
||||
* @return Update-Flag
|
||||
*/
|
||||
inline bool ShadowSource::get_needs_update() const {
|
||||
return !has_region() || _needs_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the slot of the shadow source.
|
||||
* @details This returns the assigned slot of the ShadowSource, or -1 if no slot
|
||||
* was assigned yet. You can check if a slot exists with ShadowSource::has_slot.
|
||||
* The slot is the index of the ShadowSource in the global source buffer.
|
||||
* @return Slot, or -1 to indicate no slot.
|
||||
*/
|
||||
inline int ShadowSource::get_slot() const {
|
||||
return _slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the source has a slot.
|
||||
* @details This returns whether the ShadowSource currently has an assigned slot.
|
||||
* If the source has a slot assigned, this returns true, otherwise false. Cases
|
||||
* where the source has no slot might be when the source just got attached, but
|
||||
* never got rendered yet.
|
||||
* @return [description]
|
||||
*/
|
||||
inline bool ShadowSource::has_slot() const {
|
||||
return _slot >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the source a slot
|
||||
* @details This assigns a slot to the ShadowSource. This is called from the
|
||||
* ShadowManager, when the source gets attached first time. This should not
|
||||
* get called by the user.
|
||||
*
|
||||
* @param slot Slot of the source, or -1 to indicate no slot.
|
||||
*/
|
||||
inline void ShadowSource::set_slot(int slot) {
|
||||
_slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Setups a perspective lens for the source.
|
||||
* @details This makes the shadow source behave like a perspective lens. The
|
||||
* parameters are similar to the ones of a PerspectiveLens.
|
||||
*
|
||||
* @param fov FoV of the lens
|
||||
* @param near_plane The near plane of the lens, to avoid artifacts at low distance
|
||||
* @param far_plane The far plane of the lens
|
||||
* @param pos Position of the lens, in world space
|
||||
* @param direction Direction (Orientation) of the lens
|
||||
*/
|
||||
inline void ShadowSource::
|
||||
set_perspective_lens(PN_stdfloat fov, PN_stdfloat near_plane,
|
||||
PN_stdfloat far_plane, LVecBase3 pos,
|
||||
LVecBase3 direction) {
|
||||
// Construct the transfo*rmation matrix
|
||||
LMatrix4 transform_mat = LMatrix4::translate_mat(-pos);
|
||||
|
||||
// Construct a temporary lens to generate the lens matrix
|
||||
PerspectiveLens temp_lens = PerspectiveLens(fov, fov);
|
||||
temp_lens.set_film_offset(0, 0);
|
||||
temp_lens.set_near_far(near_plane, far_plane);
|
||||
temp_lens.set_view_vector(direction, LVector3::up());
|
||||
set_matrix_lens(transform_mat * temp_lens.get_projection_mat());
|
||||
|
||||
// Set new bounds, approximate with sphere
|
||||
CPT(BoundingHexahedron) hexahedron = DCAST(BoundingHexahedron, temp_lens.make_bounds());
|
||||
LPoint3 center = (hexahedron->get_min() + hexahedron->get_max()) * 0.5f;
|
||||
_bounds = BoundingSphere(pos + center, (hexahedron->get_max() - center).length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a custom matrix for the source.
|
||||
* @details This tells the source to use a custom matrix for rendering, just like
|
||||
* the matrix lens. The matrix should include all transformations, rotations and
|
||||
* scales. No other matrices will be used for rendering this shadow source (not
|
||||
* even a coordinate system conversion).
|
||||
*
|
||||
* @param mvp Custom View-Projection matrix
|
||||
*/
|
||||
inline void ShadowSource::set_matrix_lens(const LMatrix4& mvp) {
|
||||
_mvp = mvp;
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the update flag of the source.
|
||||
* @details Sets whether the source is still valid, or needs to get regenerated.
|
||||
* Usually you only want to flag the shadow source as invalid, by passing
|
||||
* true as the flag. However, the ShadowManager will set the flag to false
|
||||
* after updating the source.
|
||||
*
|
||||
* @param flag The update flag
|
||||
*/
|
||||
inline void ShadowSource::set_needs_update(bool flag) {
|
||||
_needs_update = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the source has a valid region.
|
||||
* @details This returns whether the ShadowSource has a valid shadow atlas region
|
||||
* assigned. This might be not the case when the source never was rendered yet,
|
||||
* or is about to get updated.
|
||||
* @return true if the source has a valid region, else false.
|
||||
*/
|
||||
inline bool ShadowSource::has_region() const {
|
||||
return _region.get_x() >= 0 && _region.get_y() >= 0 && _region.get_z() >= 0 && _region.get_w() >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the resolution of the source.
|
||||
* @details Returns the shadow map resolution of source, in pixels. This is the
|
||||
* space the source takes in the shadow atlas, in pixels.
|
||||
* @return Resolution in pixels
|
||||
*/
|
||||
inline size_t ShadowSource::get_resolution() const {
|
||||
return _resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the assigned region of the source in atlas space.
|
||||
* @details This returns the region of the source, in atlas space. This is the
|
||||
* region set by ShadowSource::set_region. If no region was set yet, returns
|
||||
* a 4-component integer vector with all components set to -1. To check this,
|
||||
* you should call ShadowSource::has_region() first.
|
||||
*
|
||||
* @return [description]
|
||||
*/
|
||||
inline const LVecBase4i& ShadowSource::get_region() const {
|
||||
return _region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the assigned region of the source in UV space.
|
||||
* @details This returns the region of the source, in UV space. This is the
|
||||
* region set by ShadowSource::set_region. If no region was set yet, returns
|
||||
* a 4-component integer vector with all components set to -1. To check this,
|
||||
* you should call ShadowSource::has_region() first.
|
||||
*
|
||||
* @return [description]
|
||||
*/
|
||||
inline const LVecBase4& ShadowSource::get_uv_region() const {
|
||||
return _region_uv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the assigned region of the source in atlas and uv space.
|
||||
* @details This sets the assigned region of the ShadowSource. The region in
|
||||
* atlas space should be the region returned from the
|
||||
* ShadowAtlas::find_and_reserve_region. The uv-region should be the same region,
|
||||
* but in the 0 .. 1 range (can be converted with ShadowAtlas::region_to_uv).
|
||||
* This is required for the shaders, because they expect coordinates in the
|
||||
* 0 .. 1 range for sampling.
|
||||
*
|
||||
* @param region Atlas-Space region
|
||||
* @param region_uv UV-Space region
|
||||
*/
|
||||
inline void ShadowSource::set_region(const LVecBase4i& region, const LVecBase4& region_uv) {
|
||||
_region = region;
|
||||
_region_uv = region_uv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the View-Projection matrix of the source.
|
||||
* @details This returns the current view-projection matrix of the ShadowSource.
|
||||
* If no matrix was set yet, returns a matrix with all components zero.
|
||||
* If a matrix was set with ShadowSource::set_matrix_lens, returns the matrix
|
||||
* set by that function call.
|
||||
*
|
||||
* If a matrix was set with ShadowSource::set_perspective_lens, returns a
|
||||
* perspective view-projection matrix setup by those parameters.
|
||||
*
|
||||
* The matrix returned is the matrix used for rendering the shadow map, and
|
||||
* includes the camera transform as well as the projection matrix.
|
||||
*
|
||||
* @return View-Projection matrix.
|
||||
*/
|
||||
inline const LMatrix4& ShadowSource::get_mvp() const {
|
||||
return _mvp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the source to a GPUCommand.
|
||||
* @details This writes the ShadowSource to a GPUCommand. This stores the
|
||||
* mvp and the uv-region in the command.
|
||||
*
|
||||
* @param cmd GPUCommand to write to.
|
||||
*/
|
||||
inline void ShadowSource::write_to_command(GPUCommand &cmd) const {
|
||||
// When storing on the gpu, we should already have a valid slot
|
||||
nassertv(_slot >= 0);
|
||||
cmd.push_mat4(_mvp);
|
||||
cmd.push_vec4(_region_uv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the resolution of the source.
|
||||
* @details This sets the resolution of the ShadowSource, in pixels. It should be
|
||||
* a multiple of the tile size of the ShadowAtlas, and greater than zero.
|
||||
*
|
||||
* @param resolution [description]
|
||||
*/
|
||||
inline void ShadowSource::set_resolution(size_t resolution) {
|
||||
nassertv(resolution > 0);
|
||||
_resolution = resolution;
|
||||
set_needs_update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the shadow sources bounds
|
||||
* @details This returns the bounds of the shadow source, approximated as a sphere
|
||||
* @return Bounds as a BoundingSphere
|
||||
*/
|
||||
inline const BoundingSphere& ShadowSource::get_bounds() const {
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the assigned region of the source
|
||||
* @details This unassigns any shadow atlas region from the source, previously
|
||||
* set with set_region
|
||||
*/
|
||||
inline void ShadowSource::clear_region() {
|
||||
_region.fill(-1);
|
||||
_region_uv.fill(0);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shadowSource.h"
|
||||
|
||||
/**
|
||||
* @brief Constructs a new shadow source
|
||||
* @details This constructs a new shadow source, with no projection setup,
|
||||
* and no slot assigned.
|
||||
*/
|
||||
ShadowSource::ShadowSource() {
|
||||
_slot = -1;
|
||||
_needs_update = true;
|
||||
_resolution = 512;
|
||||
_mvp.fill(0.0);
|
||||
_region.fill(-1);
|
||||
_region_uv.fill(0);
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SHADOWSOURCE_H
|
||||
#define SHADOWSOURCE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "luse.h"
|
||||
#include "transformState.h"
|
||||
#include "look_at.h"
|
||||
#include "compose_matrix.h"
|
||||
#include "perspectiveLens.h"
|
||||
#include "boundingVolume.h"
|
||||
#include "boundingSphere.h"
|
||||
#include "boundingHexahedron.h"
|
||||
#include "geometricBoundingVolume.h"
|
||||
|
||||
#include "gpuCommand.h"
|
||||
|
||||
/**
|
||||
* @brief This class represents a single shadow source.
|
||||
* @details The ShadowSource can be seen as a Camera. It is used by the Lights
|
||||
* to render their shadows. Each ShadowSource has a position in the atlas,
|
||||
* and a view-projection matrix. The shadow manager regenerates the shadow maps
|
||||
* using the data from the shadow sources.
|
||||
*/
|
||||
class ShadowSource {
|
||||
public:
|
||||
ShadowSource();
|
||||
|
||||
inline void write_to_command(GPUCommand &cmd) const;
|
||||
|
||||
inline void set_needs_update(bool flag);
|
||||
inline void set_slot(int slot);
|
||||
inline void set_region(const LVecBase4i& region, const LVecBase4& region_uv);
|
||||
inline void set_resolution(size_t resolution);
|
||||
inline void set_perspective_lens(PN_stdfloat fov, PN_stdfloat near_plane,
|
||||
PN_stdfloat far_plane, LVecBase3 pos,
|
||||
LVecBase3 direction);
|
||||
inline void set_matrix_lens(const LMatrix4& mvp);
|
||||
|
||||
inline bool has_region() const;
|
||||
inline bool has_slot() const;
|
||||
|
||||
inline void clear_region();
|
||||
|
||||
inline int get_slot() const;
|
||||
inline bool get_needs_update() const;
|
||||
inline size_t get_resolution() const;
|
||||
inline const LMatrix4& get_mvp() const;
|
||||
inline const LVecBase4i& get_region() const;
|
||||
inline const LVecBase4& get_uv_region() const;
|
||||
|
||||
inline const BoundingSphere& get_bounds() const;
|
||||
|
||||
private:
|
||||
int _slot;
|
||||
bool _needs_update;
|
||||
size_t _resolution;
|
||||
LMatrix4 _mvp;
|
||||
LVecBase4i _region;
|
||||
LVecBase4 _region_uv;
|
||||
|
||||
BoundingSphere _bounds;
|
||||
};
|
||||
|
||||
#include "shadowSource.I"
|
||||
|
||||
#endif // SHADOWSOURCE_H
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Registers a new camera which renders a certain pass
|
||||
* @details This registers a new camera which will be used to render the given
|
||||
* pass. The TagStateManager will keep track of the camera and
|
||||
* applies all registered states onto the camera with Camera::set_tag_state.
|
||||
* It also applies the appropriate camera mask to the camera,
|
||||
* and sets an initial state to disable color write depending on the pass.
|
||||
*
|
||||
* @param source Camera which will be used to render shadows
|
||||
*/
|
||||
inline void TagStateManager::
|
||||
register_camera(const string& name, Camera* source) {
|
||||
ContainerList::iterator entry = _containers.find(name);
|
||||
nassertv(entry != _containers.end());
|
||||
register_camera(entry->second, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a camera from the list of shadow cameras
|
||||
* @details This unregisters a camera from the list of shadows cameras. It also
|
||||
* resets all tag states of the camera, and also its initial state.
|
||||
*
|
||||
* @param source Camera to unregister
|
||||
*/
|
||||
inline void TagStateManager::
|
||||
unregister_camera(const string& name, Camera* source) {
|
||||
ContainerList::iterator entry = _containers.find(name);
|
||||
nassertv(entry != _containers.end());
|
||||
unregister_camera(entry->second, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a given state for a pass to a NodePath
|
||||
* @details This applies a shader to the given NodePath which is used when the
|
||||
* NodePath is rendered by any registered camera for that pass.
|
||||
* It also disables color write depending on the pass.
|
||||
*
|
||||
* @param np The nodepath to apply the shader to
|
||||
* @param shader A handle to the shader to apply
|
||||
* @param name Name of the state, should be a unique identifier
|
||||
* @param sort Determines the sort with which the shader will be applied.
|
||||
*/
|
||||
inline void TagStateManager::
|
||||
apply_state(const string& state, NodePath np, Shader* shader,
|
||||
const string &name, int sort) {
|
||||
ContainerList::iterator entry = _containers.find(state);
|
||||
nassertv(entry != _containers.end());
|
||||
apply_state(entry->second, np, shader, name, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the render mask for the given state
|
||||
* @details This returns the mask of a given render pass, which can be used
|
||||
* to either show or hide objects from this pass.
|
||||
*
|
||||
* @param container_name Name of the render-pass
|
||||
* @return Bit mask of the render pass
|
||||
*/
|
||||
inline BitMask32 TagStateManager::
|
||||
get_mask(const string &container_name) {
|
||||
if (container_name == "gbuffer") {
|
||||
return BitMask32::bit(1);
|
||||
}
|
||||
ContainerList::iterator entry = _containers.find(container_name);
|
||||
nassertr(entry != _containers.end(), BitMask32());
|
||||
return entry->second.mask;
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "tagStateManager.h"
|
||||
|
||||
|
||||
NotifyCategoryDef(tagstatemgr, "");
|
||||
|
||||
/**
|
||||
* @brief Constructs a new TagStateManager
|
||||
* @details This constructs a new TagStateManager. The #main_cam_node should
|
||||
* refer to the main scene camera, and will most likely be base.cam.
|
||||
* It is necessary to pass the camera because the C++ code does not have
|
||||
* access to the showbase.
|
||||
*
|
||||
* @param main_cam_node The main scene camera
|
||||
*/
|
||||
TagStateManager::
|
||||
TagStateManager(NodePath main_cam_node) {
|
||||
nassertv(!main_cam_node.is_empty());
|
||||
nassertv(DCAST(Camera, main_cam_node.node()) != nullptr);
|
||||
_main_cam_node = main_cam_node;
|
||||
|
||||
// Set default camera mask
|
||||
DCAST(Camera, _main_cam_node.node())->set_camera_mask(BitMask32::bit(1));
|
||||
|
||||
// Init containers
|
||||
_containers["shadow"] = StateContainer("Shadows", 2, false);
|
||||
_containers["voxelize"] = StateContainer("Voxelize", 3, false);
|
||||
_containers["envmap"] = StateContainer("Envmap", 4, true);
|
||||
_containers["forward"] = StateContainer("Forward", 5, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructs the TagStateManager
|
||||
* @details This destructs the TagStateManager, and cleans up all resources used.
|
||||
*/
|
||||
TagStateManager::
|
||||
~TagStateManager() {
|
||||
cleanup_states();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a given state to a NodePath
|
||||
* @details This applies a shader to the given NodePath which is used when the
|
||||
* NodePath is rendered by any registered camera of the container.
|
||||
*
|
||||
* @param container The container which is used to store the state
|
||||
* @param np The nodepath to apply the shader to
|
||||
* @param shader A handle to the shader to apply
|
||||
* @param name Name of the state, should be a unique identifier
|
||||
* @param sort Changes the sort with which the shader will be applied.
|
||||
*/
|
||||
void TagStateManager::
|
||||
apply_state(StateContainer& container, NodePath np, Shader* shader,
|
||||
const string &name, int sort) {
|
||||
if (tagstatemgr_cat.is_spam()) {
|
||||
tagstatemgr_cat.spam() << "Constructing new state " << name
|
||||
<< " with shader " << shader << endl;
|
||||
}
|
||||
|
||||
// Construct the render state
|
||||
CPT(RenderState) state = RenderState::make_empty();
|
||||
|
||||
// Disable color write for all stages except the environment container
|
||||
if (!container.write_color) {
|
||||
state = state->set_attrib(ColorWriteAttrib::make(ColorWriteAttrib::C_off), 10000);
|
||||
}
|
||||
state = state->set_attrib(ShaderAttrib::make(shader, sort), sort);
|
||||
|
||||
// Emit a warning if we override an existing state
|
||||
if (container.tag_states.count(name) != 0) {
|
||||
tagstatemgr_cat.warning() << "Overriding existing state " << name << endl;
|
||||
}
|
||||
|
||||
// Store the state, this is required whenever we attach a new camera, so
|
||||
// it can also track the existing states
|
||||
container.tag_states[name] = state;
|
||||
|
||||
// Save the tag on the node path
|
||||
np.set_tag(container.tag_name, name);
|
||||
|
||||
// Apply the state on all cameras which are attached so far
|
||||
for (size_t i = 0; i < container.cameras.size(); ++i) {
|
||||
container.cameras[i]->set_tag_state(name, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cleans up all registered states.
|
||||
* @details This cleans up all states which were registered to the TagStateManager.
|
||||
* It also calls Camera::clear_tag_states() on the main_cam_node and all attached
|
||||
* cameras.
|
||||
*/
|
||||
void TagStateManager::
|
||||
cleanup_states() {
|
||||
if (tagstatemgr_cat.is_info()) {
|
||||
tagstatemgr_cat.info() << "cleaning up states" << endl;
|
||||
}
|
||||
|
||||
// Clear all tag states of the main camera
|
||||
DCAST(Camera, _main_cam_node.node())->clear_tag_states();
|
||||
|
||||
// Clear the containers
|
||||
// XXX: Just iterate over the _container map
|
||||
cleanup_container_states(_containers["shadow"]);
|
||||
cleanup_container_states(_containers["voxelize"]);
|
||||
cleanup_container_states(_containers["envmap"]);
|
||||
cleanup_container_states(_containers["forward"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cleans up the states of a given container
|
||||
* @details This cleans all tag states of the given container,
|
||||
* and also calls Camera::clear_tag_states on every assigned camera.
|
||||
*
|
||||
* @param container Container to clear
|
||||
*/
|
||||
void TagStateManager::
|
||||
cleanup_container_states(StateContainer& container) {
|
||||
for (size_t i = 0; i < container.cameras.size(); ++i) {
|
||||
container.cameras[i]->clear_tag_states();
|
||||
}
|
||||
container.tag_states.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a new camera to a given container
|
||||
* @details This registers a new camera to a container, and sets its initial
|
||||
* state as well as the camera mask.
|
||||
*
|
||||
* @param container The container to add the camera to
|
||||
* @param source The camera to add
|
||||
*/
|
||||
void TagStateManager::
|
||||
register_camera(StateContainer& container, Camera* source) {
|
||||
source->set_tag_state_key(container.tag_name);
|
||||
source->set_camera_mask(container.mask);
|
||||
|
||||
// Construct an initial state which also disables color write, additionally
|
||||
// to the ColorWriteAttrib on each unique state.
|
||||
CPT(RenderState) state = RenderState::make_empty();
|
||||
|
||||
if (!container.write_color) {
|
||||
state = state->set_attrib(ColorWriteAttrib::make(ColorWriteAttrib::C_off), 10000);
|
||||
}
|
||||
source->set_initial_state(state);
|
||||
|
||||
// Store the camera so we can keep track of it
|
||||
container.cameras.push_back(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a camera from a container
|
||||
* @details This unregisters a camera from the list of cameras of a given
|
||||
* container. It also resets all tag states of the camera, and also its initial
|
||||
* state.
|
||||
*
|
||||
* @param source Camera to unregister
|
||||
*/
|
||||
void TagStateManager::
|
||||
unregister_camera(StateContainer& container, Camera* source) {
|
||||
CameraList& cameras = container.cameras;
|
||||
|
||||
// Make sure the camera was attached so far
|
||||
if (std::find(cameras.begin(), cameras.end(), source) == cameras.end()) {
|
||||
tagstatemgr_cat.error()
|
||||
<< "Called unregister_camera but camera was never registered!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the camera from the list of attached cameras
|
||||
cameras.erase(std::remove(cameras.begin(), cameras.end(), source), cameras.end());
|
||||
|
||||
// Reset the camera
|
||||
source->clear_tag_states();
|
||||
source->set_initial_state(RenderState::make_empty());
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
*
|
||||
* RenderPipeline
|
||||
*
|
||||
* Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGSTATEMANAGER_H
|
||||
#define TAGSTATEMANAGER_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "bitMask.h"
|
||||
#include "camera.h"
|
||||
#include "nodePath.h"
|
||||
#include "shader.h"
|
||||
#include "renderState.h"
|
||||
#include "shaderAttrib.h"
|
||||
#include "colorWriteAttrib.h"
|
||||
|
||||
NotifyCategoryDecl(tagstatemgr, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
/**
|
||||
* @brief This class handles all different tag states
|
||||
* @details The TagStateManager stores a list of RenderStates assigned to different
|
||||
* steps in the pipeline. For example, there are a list of shadow states, which
|
||||
* are applied whenever objects are rendered from a shadow camera.
|
||||
*
|
||||
* The Manager also stores a list of all cameras used in the different stages,
|
||||
* to keep track of the states used and to be able to attach new states.
|
||||
*/
|
||||
class TagStateManager {
|
||||
PUBLISHED:
|
||||
TagStateManager(NodePath main_cam_node);
|
||||
~TagStateManager();
|
||||
|
||||
inline void apply_state(const string& state, NodePath np, Shader* shader, const string &name, int sort);
|
||||
void cleanup_states();
|
||||
|
||||
inline void register_camera(const string& state, Camera* source);
|
||||
inline void unregister_camera(const string& state, Camera* source);
|
||||
inline BitMask32 get_mask(const string &container_name);
|
||||
|
||||
private:
|
||||
typedef vector<Camera*> CameraList;
|
||||
typedef pmap<string, CPT(RenderState)> TagStateList;
|
||||
|
||||
struct StateContainer {
|
||||
CameraList cameras;
|
||||
TagStateList tag_states;
|
||||
string tag_name;
|
||||
BitMask32 mask;
|
||||
bool write_color;
|
||||
|
||||
StateContainer() {};
|
||||
StateContainer(const string &tag_name, size_t mask, bool write_color)
|
||||
: tag_name(tag_name), mask(BitMask32::bit(mask)), write_color(write_color) {};
|
||||
};
|
||||
|
||||
void apply_state(StateContainer& container, NodePath np, Shader* shader,
|
||||
const string& name, int sort);
|
||||
void cleanup_container_states(StateContainer& container);
|
||||
void register_camera(StateContainer &container, Camera* source);
|
||||
void unregister_camera(StateContainer &container, Camera* source);
|
||||
|
||||
typedef pmap<string, StateContainer> ContainerList;
|
||||
ContainerList _containers;
|
||||
|
||||
NodePath _main_cam_node;
|
||||
};
|
||||
|
||||
|
||||
#include "tagStateManager.I"
|
||||
|
||||
#endif // TAGSTATEMANAGER_H
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* @file direct.cxx
|
||||
* @author drose
|
||||
* @date 2000-05-18
|
||||
*/
|
||||
|
||||
// This is a dummy file whose sole purpose is to give the compiler something
|
||||
// to compile when making libdirect.so in NO_DEFER mode, which generates an
|
||||
// empty library that itself links with all the other shared libraries that
|
||||
// make up libdirect.
|
||||
|
|
@ -50,6 +50,10 @@ class Actor(DirectObject, NodePath):
|
|||
def __repr__(self):
|
||||
return 'Actor.PartDef(%s, %s)' % (repr(self.partBundleNP), repr(self.partModel))
|
||||
|
||||
|
||||
#snake_case alias:
|
||||
get_bundle = getBundle
|
||||
|
||||
class AnimDef:
|
||||
|
||||
"""Instances of this class are stored within the
|
||||
|
|
@ -72,6 +76,10 @@ class Actor(DirectObject, NodePath):
|
|||
def __repr__(self):
|
||||
return 'Actor.AnimDef(%s)' % (repr(self.filename))
|
||||
|
||||
|
||||
#snake_case alias:
|
||||
make_copy = makeCopy
|
||||
|
||||
class SubpartDef:
|
||||
|
||||
"""Instances of this class are stored within the SubpartDict
|
||||
|
|
@ -889,7 +897,7 @@ class Actor(DirectObject, NodePath):
|
|||
return ((toFrame+1)-fromFrame) / animControl.getFrameRate()
|
||||
|
||||
def getNumFrames(self, animName=None, partName=None):
|
||||
lodName = next(iter(self.__animControlDict))
|
||||
#lodName = next(iter(self.__animControlDict))
|
||||
controls = self.getAnimControls(animName, partName)
|
||||
if len(controls) == 0:
|
||||
return None
|
||||
|
|
@ -1095,8 +1103,8 @@ class Actor(DirectObject, NodePath):
|
|||
# Get a handle to the joint.
|
||||
joint = bundle.findChild(jointName)
|
||||
|
||||
if node == None:
|
||||
node = self.attachNewNode(jointName)
|
||||
if node is None:
|
||||
node = partDef.partBundleNP.attachNewNode(jointName)
|
||||
|
||||
if (joint):
|
||||
if localTransform:
|
||||
|
|
@ -2549,3 +2557,89 @@ class Actor(DirectObject, NodePath):
|
|||
for partBundleDict in self.__partBundleDict.values():
|
||||
partDef = partBundleDict.get(subpartDef.truePartName)
|
||||
partDef.getBundle().setName(newBundleName)
|
||||
|
||||
#snake_case alias:
|
||||
control_joint = controlJoint
|
||||
set_lod_animation = setLODAnimation
|
||||
get_anim_control_dict = getAnimControlDict
|
||||
get_actor_info = getActorInfo
|
||||
clear_lod_animation = clearLODAnimation
|
||||
reset_lod = resetLOD
|
||||
fix_bounds = fixBounds
|
||||
get_anim_filename = getAnimFilename
|
||||
get_subparts_complete = getSubpartsComplete
|
||||
verify_subparts_complete = verifySubpartsComplete
|
||||
get_play_rate = getPlayRate
|
||||
clear_python_data = clearPythonData
|
||||
load_anims = loadAnims
|
||||
set_subparts_complete = setSubpartsComplete
|
||||
draw_in_front = drawInFront
|
||||
get_lod_node = getLODNode
|
||||
hide_part = hidePart
|
||||
get_joint_transform_state = getJointTransformState
|
||||
set_control_effect = setControlEffect
|
||||
get_anim_controls = getAnimControls
|
||||
release_joint = releaseJoint
|
||||
print_anim_blends = printAnimBlends
|
||||
get_lod = getLOD
|
||||
disable_blend = disableBlend
|
||||
show_part = showPart
|
||||
get_joint_transform = getJointTransform
|
||||
face_away_from_viewer = faceAwayFromViewer
|
||||
set_lod = setLOD
|
||||
osd_anim_blends = osdAnimBlends
|
||||
get_current_frame = getCurrentFrame
|
||||
set_play_rate = setPlayRate
|
||||
bind_all_anims = bindAllAnims
|
||||
unload_anims = unloadAnims
|
||||
remove_part = removePart
|
||||
use_lod = useLOD
|
||||
get_anim_blends = getAnimBlends
|
||||
get_lod_index = getLODIndex
|
||||
get_num_frames = getNumFrames
|
||||
post_flatten = postFlatten
|
||||
get_lod_names = getLODNames
|
||||
list_joints = listJoints
|
||||
make_subpart = makeSubpart
|
||||
get_anim_control = getAnimControl
|
||||
get_part_bundle = getPartBundle
|
||||
get_part_bundle_dict = getPartBundleDict
|
||||
get_duration = getDuration
|
||||
has_lod = hasLOD
|
||||
print_lod = printLOD
|
||||
fix_bounds_old = fixBounds_old
|
||||
get_anim_names = getAnimNames
|
||||
get_part_bundles = getPartBundles
|
||||
anim_panel = animPanel
|
||||
stop_joint = stopJoint
|
||||
actor_interval = actorInterval
|
||||
hide_all_bounds = hideAllBounds
|
||||
show_all_bounds = showAllBounds
|
||||
init_anims_on_all_lods = initAnimsOnAllLODs
|
||||
get_part = getPart
|
||||
add_lod = addLOD
|
||||
show_all_parts = showAllParts
|
||||
get_joints = getJoints
|
||||
get_overlapping_joints = getOverlappingJoints
|
||||
enable_blend = enableBlend
|
||||
face_towards_viewer = faceTowardsViewer
|
||||
bind_anim = bindAnim
|
||||
set_blend = setBlend
|
||||
get_frame_time = getFrameTime
|
||||
remove_node = removeNode
|
||||
wait_pending = waitPending
|
||||
expose_joint = exposeJoint
|
||||
set_lod_node = setLODNode
|
||||
get_frame_rate = getFrameRate
|
||||
get_current_anim = getCurrentAnim
|
||||
get_part_names = getPartNames
|
||||
freeze_joint = freezeJoint
|
||||
set_center = setCenter
|
||||
rename_part_bundles = renamePartBundles
|
||||
get_geom_node = getGeomNode
|
||||
set_geom_node = setGeomNode
|
||||
load_model = loadModel
|
||||
copy_actor = copyActor
|
||||
get_base_frame_rate = getBaseFrameRate
|
||||
remove_anim_control_dict = removeAnimControlDict
|
||||
load_anims_on_all_lods = loadAnimsOnAllLODs
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
This package contains the :class:`.Actor` class as well as a
|
||||
distributed variant thereof. Actor is a high-level interface around
|
||||
the lower-level :class:`panda3d.core.Character` implementation.
|
||||
It loads and controls an animated character and manages the animations
|
||||
playing on it.
|
||||
"""
|
||||
|
|
@ -21,6 +21,9 @@ class InputStateToken:
|
|||
def __hash__(self):
|
||||
return self._hash
|
||||
|
||||
#snake_case alias:
|
||||
is_valid = isValid
|
||||
|
||||
class InputStateWatchToken(InputStateToken, DirectObject.DirectObject):
|
||||
def release(self):
|
||||
self._inputState._ignore(self)
|
||||
|
|
@ -39,6 +42,9 @@ class InputStateTokenGroup:
|
|||
token.release()
|
||||
self._tokens = []
|
||||
|
||||
#snake_case alias:
|
||||
add_token = addToken
|
||||
|
||||
class InputState(DirectObject.DirectObject):
|
||||
"""
|
||||
InputState is for tracking the on/off state of some events.
|
||||
|
|
@ -235,3 +241,10 @@ class InputState(DirectObject.DirectObject):
|
|||
"""for debugging"""
|
||||
return self.notify.debug(
|
||||
"%s (%s) %s"%(id(self), len(self._state), message))
|
||||
|
||||
#snake_case alias:
|
||||
watch_with_modifiers = watchWithModifiers
|
||||
is_set = isSet
|
||||
get_event_name = getEventName
|
||||
debug_print = debugPrint
|
||||
release_inputs = releaseInputs
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
This package contains various types of character controllers, handling basic
|
||||
control mechanics and setting up collisions for them.
|
||||
"""
|
||||
|
|
@ -117,7 +117,7 @@ PUBLISHED:
|
|||
Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id, PyObject *optional_fields) const;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual void output(ostream &out, bool brief) const;
|
||||
|
|
|
|||
|
|
@ -708,7 +708,7 @@ pack_object(PyObject *object) {
|
|||
pack_int64(PyLong_AsLongLong(object));
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
} else if (PyUnicode_Check(object)) {
|
||||
char *buffer;
|
||||
const char *buffer;
|
||||
Py_ssize_t length;
|
||||
buffer = PyUnicode_AsUTF8AndSize(object, &length);
|
||||
if (buffer) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
""" This is a deprecated module that creates a global instance of ShowBase. """
|
||||
"""
|
||||
This is a shortcut that instantiates ShowBase automatically on import,
|
||||
opening a graphical window and setting up the scene graph.
|
||||
This example demonstrates its use:
|
||||
|
||||
import direct.directbase.DirectStart
|
||||
run()
|
||||
|
||||
While it may be considered useful for quick prototyping in the interactive
|
||||
Python shell, using it in applications is not considered good style.
|
||||
As such, it has been deprecated starting with Panda3D 1.9. It is equivalent
|
||||
to and may be replaced by the following code:
|
||||
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
base = ShowBase()
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
This package contains modules to quickly set up a Panda environment for
|
||||
quick prototyping in the interactive Python shell. Merely importing
|
||||
one of these modules will create a :class:`.ShowBase` instance, opening
|
||||
a graphical window and setting up the scene graph.
|
||||
|
||||
The most commonly used module from this package is :mod:`.DirectStart`,
|
||||
importing which executes the following code::
|
||||
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
base = ShowBase()
|
||||
"""
|
||||
|
|
@ -9,10 +9,6 @@ ANALOG_MAX = 0.95
|
|||
ANALOG_DEADBAND = 0.125
|
||||
ANALOG_CENTER = 0.0
|
||||
|
||||
try:
|
||||
myBase = base
|
||||
except:
|
||||
myBase = simbase
|
||||
|
||||
class DirectDeviceManager(VrpnClient, DirectObject):
|
||||
def __init__(self, server = None):
|
||||
|
|
@ -52,8 +48,13 @@ class DirectButtons(ButtonNode, DirectObject):
|
|||
ButtonNode.__init__(self, vrpnClient, device)
|
||||
# Create a unique name for this button object
|
||||
self.name = 'DirectButtons-' + repr(DirectButtons.buttonCount)
|
||||
|
||||
# Attach node to data graph
|
||||
self.nodePath = myBase.dataRoot.attachNewNode(self)
|
||||
try:
|
||||
self._base = base
|
||||
except:
|
||||
self._base = simbase
|
||||
self.nodePath = self._base.dataRoot.attachNewNode(self)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if (index < 0) or (index >= self.getNumButtons()):
|
||||
|
|
@ -64,10 +65,10 @@ class DirectButtons(ButtonNode, DirectObject):
|
|||
return self.getNumButtons()
|
||||
|
||||
def enable(self):
|
||||
self.nodePath.reparentTo(myBase.dataRoot)
|
||||
self.nodePath.reparentTo(self._base.dataRoot)
|
||||
|
||||
def disable(self):
|
||||
self.nodePath.reparentTo(myBase.dataUnused)
|
||||
self.nodePath.reparentTo(self._base.dataUnused)
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
|
@ -83,6 +84,12 @@ class DirectButtons(ButtonNode, DirectObject):
|
|||
|
||||
class DirectAnalogs(AnalogNode, DirectObject):
|
||||
analogCount = 0
|
||||
|
||||
_analogDeadband = ConfigVariableDouble('vrpn-analog-deadband', ANALOG_DEADBAND)
|
||||
_analogMin = ConfigVariableDouble('vrpn-analog-min', ANALOG_MIN)
|
||||
_analogMax = ConfigVariableDouble('vrpn-analog-max', ANALOG_MAX)
|
||||
_analogCenter = ConfigVariableDouble('vrpn-analog-center', ANALOG_CENTER)
|
||||
|
||||
def __init__(self, vrpnClient, device):
|
||||
# Keep track of number of analogs created
|
||||
DirectAnalogs.analogCount += 1
|
||||
|
|
@ -90,19 +97,20 @@ class DirectAnalogs(AnalogNode, DirectObject):
|
|||
AnalogNode.__init__(self, vrpnClient, device)
|
||||
# Create a unique name for this analog object
|
||||
self.name = 'DirectAnalogs-' + repr(DirectAnalogs.analogCount)
|
||||
# Attach node to data graph
|
||||
self.nodePath = myBase.dataRoot.attachNewNode(self)
|
||||
# See if any of the general analog parameters are dconfig'd
|
||||
self.analogDeadband = myBase.config.GetFloat('vrpn-analog-deadband',
|
||||
ANALOG_DEADBAND)
|
||||
self.analogMin = myBase.config.GetFloat('vrpn-analog-min',
|
||||
ANALOG_MIN)
|
||||
self.analogMax = myBase.config.GetFloat('vrpn-analog-max',
|
||||
ANALOG_MAX)
|
||||
self.analogCenter = myBase.config.GetFloat('vrpn-analog-center',
|
||||
ANALOG_CENTER)
|
||||
self.analogRange = self.analogMax - self.analogMin
|
||||
|
||||
# Attach node to data graph
|
||||
try:
|
||||
self._base = base
|
||||
except:
|
||||
self._base = simbase
|
||||
self.nodePath = self._base.dataRoot.attachNewNode(self)
|
||||
|
||||
# See if any of the general analog parameters are dconfig'd
|
||||
self.analogDeadband = self._analogDeadband.getValue()
|
||||
self.analogMin = self._analogMin.getValue()
|
||||
self.analogMax = self._analogMax.getValue()
|
||||
self.analogCenter = self._analogCenter.getValue()
|
||||
self.analogRange = self.analogMax - self.analogMin
|
||||
|
||||
def __getitem__(self, index):
|
||||
if (index < 0) or (index >= self.getNumControls()):
|
||||
|
|
@ -113,10 +121,10 @@ class DirectAnalogs(AnalogNode, DirectObject):
|
|||
return self.getNumControls()
|
||||
|
||||
def enable(self):
|
||||
self.nodePath.reparentTo(myBase.dataRoot)
|
||||
self.nodePath.reparentTo(self._base.dataRoot)
|
||||
|
||||
def disable(self):
|
||||
self.nodePath.reparentTo(myBase.dataUnused)
|
||||
self.nodePath.reparentTo(self._base.dataUnused)
|
||||
|
||||
def normalizeWithoutCentering(self, val, minVal = -1, maxVal = 1):
|
||||
#
|
||||
|
|
@ -186,14 +194,19 @@ class DirectTracker(TrackerNode, DirectObject):
|
|||
TrackerNode.__init__(self, vrpnClient, device)
|
||||
# Create a unique name for this tracker object
|
||||
self.name = 'DirectTracker-' + repr(DirectTracker.trackerCount)
|
||||
|
||||
# Attach node to data graph
|
||||
self.nodePath = myBase.dataRoot.attachNewNode(self)
|
||||
try:
|
||||
self._base = base
|
||||
except:
|
||||
self._base = simbase
|
||||
self.nodePath = self._base.dataRoot.attachNewNode(self)
|
||||
|
||||
def enable(self):
|
||||
self.nodePath.reparentTo(myBase.dataRoot)
|
||||
self.nodePath.reparentTo(self._base.dataRoot)
|
||||
|
||||
def disable(self):
|
||||
self.nodePath.reparentTo(myBase.dataUnused)
|
||||
self.nodePath.reparentTo(self._base.dataUnused)
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
|
@ -213,8 +226,13 @@ class DirectDials(DialNode, DirectObject):
|
|||
DialNode.__init__(self, vrpnClient, device)
|
||||
# Create a unique name for this dial object
|
||||
self.name = 'DirectDials-' + repr(DirectDials.dialCount)
|
||||
|
||||
# Attach node to data graph
|
||||
self.nodePath = myBase.dataRoot.attachNewNode(self)
|
||||
try:
|
||||
self._base = base
|
||||
except:
|
||||
self._base = simbase
|
||||
self.nodePath = self._base.dataRoot.attachNewNode(self)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
|
|
@ -227,10 +245,10 @@ class DirectDials(DialNode, DirectObject):
|
|||
return self.getNumDials()
|
||||
|
||||
def enable(self):
|
||||
self.nodePath.reparentTo(myBase.dataRoot)
|
||||
self.nodePath.reparentTo(self._base.dataRoot)
|
||||
|
||||
def disable(self):
|
||||
self.nodePath.reparentTo(myBase.dataUnused)
|
||||
self.nodePath.reparentTo(self._base.dataUnused)
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
|
@ -259,14 +277,19 @@ class DirectTimecodeReader(AnalogNode, DirectObject):
|
|||
self.seconds = 0
|
||||
self.minutes = 0
|
||||
self.hours = 0
|
||||
|
||||
# Attach node to data graph
|
||||
self.nodePath = myBase.dataRoot.attachNewNode(self)
|
||||
try:
|
||||
self._base = base
|
||||
except:
|
||||
self._base = simbase
|
||||
self.nodePath = self._base.dataRoot.attachNewNode(self)
|
||||
|
||||
def enable(self):
|
||||
self.nodePath.reparentTo(myBase.dataRoot)
|
||||
self.nodePath.reparentTo(self._base.dataRoot)
|
||||
|
||||
def disable(self):
|
||||
self.nodePath.reparentTo(myBase.dataUnused)
|
||||
self.nodePath.reparentTo(self._base.dataUnused)
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class DirectRadamec(DirectObject):
|
|||
radamecCount = 0
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DirectRadamec')
|
||||
|
||||
def __init__(self, device = 'Analog0', nodePath = base.direct.camera):
|
||||
def __init__(self, device = 'Analog0', nodePath = None):
|
||||
# See if device manager has been initialized
|
||||
if base.direct.deviceManager == None:
|
||||
base.direct.deviceManager = DirectDeviceManager()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
This package contains a high-level interface for VRPN devices.
|
||||
|
||||
Also see the :mod:`panda3d.vprn` module.
|
||||
"""
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
This package contains notification and logging utilities for Python code.
|
||||
"""
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
This package contains the DIRECT tools, a set of tkinter tools for exploring
|
||||
and manipulating the Panda3D scene graph. By default, these are disabled,
|
||||
but they can be explicitly enabled using the following PRC configuration::
|
||||
|
||||
want-directtools true
|
||||
want-tk true
|
||||
"""
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
This package contains assorted utility classes.
|
||||
"""
|
||||
|
|
@ -2,9 +2,6 @@ from direct.distributed.CachedDOData import CachedDOData
|
|||
from panda3d.core import ConfigVariableInt
|
||||
|
||||
|
||||
# This has to be imported for __builtin__.config
|
||||
from direct.showbase import ShowBase
|
||||
|
||||
__all__ = ["CRDataCache"]
|
||||
|
||||
class CRDataCache:
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ class ClientRepositoryBase(ConnectionRepository):
|
|||
"generate" messages when they are replayed().
|
||||
"""
|
||||
|
||||
if msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
|
||||
if msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
|
||||
# It's a generate message.
|
||||
doId = extra
|
||||
if doId in self.deferredDoIds:
|
||||
|
|
@ -381,7 +381,7 @@ class ClientRepositoryBase(ConnectionRepository):
|
|||
# The object had been deferred. Great; we don't even have
|
||||
# to generate it now.
|
||||
del self.deferredDoIds[doId]
|
||||
i = self.deferredGenerates.index((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId))
|
||||
i = self.deferredGenerates.index((CLIENT_ENTER_OBJECT_REQUIRED_OTHER, doId))
|
||||
del self.deferredGenerates[i]
|
||||
if len(self.deferredGenerates) == 0:
|
||||
taskMgr.remove('deferredGenerate')
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ class DistributedObjectBase(DirectObject):
|
|||
|
||||
def delete(self):
|
||||
"""
|
||||
Overwrite this to handle cleanup right before this object
|
||||
Override this to handle cleanup right before this object
|
||||
gets deleted.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -6,19 +6,21 @@ from . import DistributedNode
|
|||
from . import DistributedSmoothNodeBase
|
||||
from direct.task.Task import cont
|
||||
|
||||
config = get_config_showbase()
|
||||
|
||||
# This number defines our tolerance for out-of-sync telemetry packets.
|
||||
# If a packet appears to have originated from more than MaxFuture
|
||||
# seconds in the future, assume we're out of sync with the other
|
||||
# avatar and suggest a resync for both.
|
||||
MaxFuture = base.config.GetFloat("smooth-max-future", 0.2)
|
||||
MaxFuture = config.GetFloat("smooth-max-future", 0.2)
|
||||
|
||||
# How frequently can we suggest a resynchronize with another client?
|
||||
MinSuggestResync = base.config.GetFloat("smooth-min-suggest-resync", 15)
|
||||
MinSuggestResync = config.GetFloat("smooth-min-suggest-resync", 15)
|
||||
|
||||
# These flags indicate whether global smoothing and/or prediction is
|
||||
# allowed or disallowed.
|
||||
EnableSmoothing = base.config.GetBool("smooth-enable-smoothing", 1)
|
||||
EnablePrediction = base.config.GetBool("smooth-enable-prediction", 1)
|
||||
EnableSmoothing = config.GetBool("smooth-enable-smoothing", 1)
|
||||
EnablePrediction = config.GetBool("smooth-enable-prediction", 1)
|
||||
|
||||
# These values represent the amount of time, in seconds, to delay the
|
||||
# apparent position of other avatars, when non-predictive and
|
||||
|
|
@ -26,8 +28,8 @@ EnablePrediction = base.config.GetBool("smooth-enable-prediction", 1)
|
|||
# addition to the automatic delay of the observed average latency from
|
||||
# each avatar, which is intended to compensate for relative clock
|
||||
# skew.
|
||||
Lag = base.config.GetDouble("smooth-lag", 0.2)
|
||||
PredictionLag = base.config.GetDouble("smooth-prediction-lag", 0.0)
|
||||
Lag = config.GetDouble("smooth-lag", 0.2)
|
||||
PredictionLag = config.GetDouble("smooth-prediction-lag", 0.0)
|
||||
|
||||
|
||||
GlobalSmoothing = 0
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ MsgName2Id = {
|
|||
'CLIENT_HEARTBEAT_CMU' : 9011,
|
||||
'CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU' : 9011,
|
||||
|
||||
'CLIENT_OBJECT_UPDATE_FIELD' : 24, # Matches MsgTypes.CLIENT_OBJECT_UPDATE_FIELD
|
||||
'CLIENT_OBJECT_UPDATE_FIELD' : 120, # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
|
||||
}
|
||||
|
||||
# create id->name table for debugging
|
||||
|
|
|
|||
|
|
@ -1,208 +0,0 @@
|
|||
"""OldClientRepository module: contains the OldClientRepository class"""
|
||||
|
||||
from .ClientRepositoryBase import *
|
||||
|
||||
class OldClientRepository(ClientRepositoryBase):
|
||||
"""
|
||||
This is the open-source ClientRepository as provided by CMU. It
|
||||
communicates with the ServerRepository in this same directory.
|
||||
|
||||
If you are looking for the VR Studio's implementation of the
|
||||
client repository, look to OTPClientRepository (elsewhere).
|
||||
"""
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
|
||||
|
||||
def __init__(self, dcFileNames = None):
|
||||
ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames)
|
||||
|
||||
# The DOID allocator. The CMU LAN server may choose to
|
||||
# send us a block of DOIDs. If it chooses to do so, then we
|
||||
# may create objects, using those DOIDs.
|
||||
self.DOIDbase = 0
|
||||
self.DOIDnext = 0
|
||||
self.DOIDlast = 0
|
||||
|
||||
def handleSetDOIDrange(self, di):
|
||||
self.DOIDbase = di.getUint32()
|
||||
self.DOIDlast = self.DOIDbase + di.getUint32()
|
||||
self.DOIDnext = self.DOIDbase
|
||||
|
||||
def handleRequestGenerates(self, di):
|
||||
# When new clients join the zone of an object, they need to hear
|
||||
# about it, so we send out all of our information about objects in
|
||||
# that particular zone.
|
||||
|
||||
assert self.DOIDnext < self.DOIDlast
|
||||
zone = di.getUint32()
|
||||
for obj in self.doId2do.values():
|
||||
if obj.zone == zone:
|
||||
id = obj.doId
|
||||
if (self.isLocalId(id)):
|
||||
self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
|
||||
|
||||
def createWithRequired(self, className, zoneId = 0, optionalFields=None):
|
||||
if self.DOIDnext >= self.DOIDlast:
|
||||
self.notify.error(
|
||||
"Cannot allocate a distributed object ID: all IDs used up.")
|
||||
return None
|
||||
id = self.DOIDnext
|
||||
self.DOIDnext = self.DOIDnext + 1
|
||||
dclass = self.dclassesByName[className]
|
||||
classDef = dclass.getClassDef()
|
||||
if classDef == None:
|
||||
self.notify.error("Could not create an undefined %s object." % (
|
||||
dclass.getName()))
|
||||
obj = classDef(self)
|
||||
obj.dclass = dclass
|
||||
obj.zone = zoneId
|
||||
obj.doId = id
|
||||
self.doId2do[id] = obj
|
||||
obj.generateInit()
|
||||
obj._retrieveCachedData()
|
||||
obj.generate()
|
||||
obj.announceGenerate()
|
||||
datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
|
||||
self.send(datagram)
|
||||
return obj
|
||||
|
||||
def sendDisableMsg(self, doId):
|
||||
datagram = PyDatagram()
|
||||
datagram.addUint16(CLIENT_OBJECT_DISABLE)
|
||||
datagram.addUint32(doId)
|
||||
self.send(datagram)
|
||||
|
||||
def sendDeleteMsg(self, doId):
|
||||
datagram = PyDatagram()
|
||||
datagram.addUint16(CLIENT_OBJECT_DELETE)
|
||||
datagram.addUint32(doId)
|
||||
self.send(datagram)
|
||||
|
||||
def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None):
|
||||
datagram = PyDatagram()
|
||||
datagram.addUint16(CLIENT_REMOVE_ZONE)
|
||||
datagram.addUint32(zoneId)
|
||||
|
||||
# if we have an explicit list of visible zones, add them
|
||||
if visibleZoneList is not None:
|
||||
vzl = list(visibleZoneList)
|
||||
vzl.sort()
|
||||
assert PythonUtil.uniqueElements(vzl)
|
||||
for zone in vzl:
|
||||
datagram.addUint32(zone)
|
||||
|
||||
# send the message
|
||||
self.send(datagram)
|
||||
|
||||
def sendUpdateZone(self, obj, zoneId):
|
||||
id = obj.doId
|
||||
assert self.isLocalId(id)
|
||||
self.sendDeleteMsg(id, 1)
|
||||
obj.zone = zoneId
|
||||
self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, []))
|
||||
|
||||
def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
|
||||
datagram = PyDatagram()
|
||||
# Add message type
|
||||
datagram.addUint16(CLIENT_SET_ZONE_CMU)
|
||||
# Add zone id
|
||||
datagram.addUint32(zoneId)
|
||||
|
||||
# if we have an explicit list of visible zones, add them
|
||||
if visibleZoneList is not None:
|
||||
vzl = list(visibleZoneList)
|
||||
vzl.sort()
|
||||
assert PythonUtil.uniqueElements(vzl)
|
||||
for zone in vzl:
|
||||
datagram.addUint32(zone)
|
||||
|
||||
# send the message
|
||||
self.send(datagram)
|
||||
|
||||
def isLocalId(self, id):
|
||||
return ((id >= self.DOIDbase) and (id < self.DOIDlast))
|
||||
|
||||
def haveCreateAuthority(self):
|
||||
return (self.DOIDlast > self.DOIDnext)
|
||||
|
||||
def handleDatagram(self, di):
|
||||
if self.notify.getDebug():
|
||||
print("ClientRepository received datagram:")
|
||||
di.getDatagram().dumpHex(ostream)
|
||||
|
||||
msgType = self.getMsgType()
|
||||
|
||||
# These are the sort of messages we may expect from the public
|
||||
# Panda server.
|
||||
|
||||
if msgType == CLIENT_SET_DOID_RANGE:
|
||||
self.handleSetDOIDrange(di)
|
||||
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_RESP:
|
||||
self.handleGenerateWithRequired(di)
|
||||
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP:
|
||||
self.handleGenerateWithRequiredOther(di)
|
||||
elif msgType == CLIENT_OBJECT_UPDATE_FIELD_RESP:
|
||||
self.handleUpdateField(di)
|
||||
elif msgType == CLIENT_OBJECT_DELETE_RESP:
|
||||
self.handleDelete(di)
|
||||
elif msgType == CLIENT_OBJECT_DISABLE_RESP:
|
||||
self.handleDisable(di)
|
||||
elif msgType == CLIENT_REQUEST_GENERATES:
|
||||
self.handleRequestGenerates(di)
|
||||
else:
|
||||
self.handleMessageType(msgType, di)
|
||||
|
||||
# If we're processing a lot of datagrams within one frame, we
|
||||
# may forget to send heartbeats. Keep them coming!
|
||||
self.considerHeartbeat()
|
||||
|
||||
def handleGenerateWithRequired(self, di):
|
||||
# Get the class Id
|
||||
classId = di.getUint16()
|
||||
# Get the DO Id
|
||||
doId = di.getUint32()
|
||||
# Look up the dclass
|
||||
dclass = self.dclassesByNumber[classId]
|
||||
dclass.startGenerate()
|
||||
# Create a new distributed object, and put it in the dictionary
|
||||
distObj = self.generateWithRequiredFields(dclass, doId, di)
|
||||
dclass.stopGenerate()
|
||||
|
||||
def generateWithRequiredFields(self, dclass, doId, di):
|
||||
if doId in self.doId2do:
|
||||
# ...it is in our dictionary.
|
||||
# Just update it.
|
||||
distObj = self.doId2do[doId]
|
||||
assert distObj.dclass == dclass
|
||||
distObj.generate()
|
||||
distObj.updateRequiredFields(dclass, di)
|
||||
# updateRequiredFields calls announceGenerate
|
||||
elif self.cache.contains(doId):
|
||||
# ...it is in the cache.
|
||||
# Pull it out of the cache:
|
||||
distObj = self.cache.retrieve(doId)
|
||||
assert distObj.dclass == dclass
|
||||
# put it in the dictionary:
|
||||
self.doId2do[doId] = distObj
|
||||
# and update it.
|
||||
distObj.generate()
|
||||
distObj.updateRequiredFields(dclass, di)
|
||||
# updateRequiredFields calls announceGenerate
|
||||
else:
|
||||
# ...it is not in the dictionary or the cache.
|
||||
# Construct a new one
|
||||
classDef = dclass.getClassDef()
|
||||
if classDef == None:
|
||||
self.notify.error("Could not create an undefined %s object." % (
|
||||
dclass.getName()))
|
||||
distObj = classDef(self)
|
||||
distObj.dclass = dclass
|
||||
# Assign it an Id
|
||||
distObj.doId = doId
|
||||
# Put the new do in the dictionary
|
||||
self.doId2do[doId] = distObj
|
||||
# Update the required fields
|
||||
distObj.generateInit() # Only called when constructed
|
||||
distObj.generate()
|
||||
distObj.updateRequiredFields(dclass, di)
|
||||
# updateRequiredFields calls announceGenerate
|
||||
return distObj
|
||||
|
|
@ -17,7 +17,6 @@ class StagedObject:
|
|||
call any "handle" functions.
|
||||
"""
|
||||
self.__state = initState
|
||||
pass
|
||||
|
||||
def goOnStage(self, *args, **kw):
|
||||
"""
|
||||
|
|
@ -29,8 +28,6 @@ class StagedObject:
|
|||
|
||||
if not self.isOnStage():
|
||||
self.handleOnStage(*args, **kw)
|
||||
pass
|
||||
pass
|
||||
|
||||
def handleOnStage(self):
|
||||
"""
|
||||
|
|
@ -39,7 +36,6 @@ class StagedObject:
|
|||
Don't forget to call down to this one, though.
|
||||
"""
|
||||
self.__state = StagedObject.ON
|
||||
pass
|
||||
|
||||
def goOffStage(self, *args, **kw):
|
||||
"""
|
||||
|
|
@ -51,8 +47,6 @@ class StagedObject:
|
|||
|
||||
if not self.isOffStage():
|
||||
self.handleOffStage(*args, **kw)
|
||||
pass
|
||||
pass
|
||||
|
||||
def handleOffStage(self):
|
||||
"""
|
||||
|
|
@ -61,7 +55,6 @@ class StagedObject:
|
|||
Don't forget to call down to this one, though.
|
||||
"""
|
||||
self.__state = StagedObject.OFF
|
||||
pass
|
||||
|
||||
def isOnStage(self):
|
||||
return self.__state == StagedObject.ON
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
This package contains an implementation of the Distributed Networking
|
||||
API, a high-level networking system that automatically propagates
|
||||
changes made on distributed objects to interested clients.
|
||||
"""
|
||||
|
|
@ -708,7 +708,7 @@ handle_update_field() {
|
|||
Py_DECREF(dclass_obj);
|
||||
nassertr(dclass_this != NULL, false);
|
||||
|
||||
DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
|
||||
DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
|
||||
Py_DECREF(dclass_this);
|
||||
|
||||
// If in quiet zone mode, throw update away unless distobj has
|
||||
|
|
@ -799,7 +799,7 @@ handle_update_field_owner() {
|
|||
Py_DECREF(dclass_obj);
|
||||
nassertr(dclass_this != NULL, false);
|
||||
|
||||
DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
|
||||
DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
|
||||
Py_DECREF(dclass_this);
|
||||
|
||||
// check if we should forward this update to the owner view
|
||||
|
|
@ -841,7 +841,7 @@ handle_update_field_owner() {
|
|||
Py_DECREF(dclass_obj);
|
||||
nassertr(dclass_this != NULL, false);
|
||||
|
||||
DCClass *dclass = (DCClass *)PyLong_AsLong(dclass_this);
|
||||
DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
|
||||
Py_DECREF(dclass_this);
|
||||
|
||||
// check if we should forward this update to the owner view
|
||||
|
|
@ -974,7 +974,7 @@ describe_message(ostream &out, const string &prefix,
|
|||
Py_DECREF(dclass_obj);
|
||||
nassertv(dclass_this != NULL);
|
||||
|
||||
dclass = (DCClass *)PyLong_AsLong(dclass_this);
|
||||
dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
|
||||
Py_DECREF(dclass_this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ class SocketStream;
|
|||
*/
|
||||
class EXPCL_DIRECT CConnectionRepository {
|
||||
PUBLISHED:
|
||||
CConnectionRepository(bool has_owner_view = false,
|
||||
bool threaded_net = false);
|
||||
explicit CConnectionRepository(bool has_owner_view = false,
|
||||
bool threaded_net = false);
|
||||
~CConnectionRepository();
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
|
||||
"""
|
||||
This package contains various Python methods that extend some of Panda's
|
||||
underlying C++ classes.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -131,6 +131,9 @@ class CommonFilters:
|
|||
if (len(configuration) == 0):
|
||||
return
|
||||
|
||||
if not self.manager.win.gsg.getSupportsBasicShaders():
|
||||
return False
|
||||
|
||||
auxbits = 0
|
||||
needtex = set(["color"])
|
||||
needtexcoord = set(["color"])
|
||||
|
|
@ -338,7 +341,10 @@ class CommonFilters:
|
|||
text += " o_color = float4(1, 1, 1, 1) - o_color;\n"
|
||||
text += "}\n"
|
||||
|
||||
self.finalQuad.setShader(Shader.make(text, Shader.SL_Cg))
|
||||
shader = Shader.make(text, Shader.SL_Cg)
|
||||
if not shader:
|
||||
return False
|
||||
self.finalQuad.setShader(shader)
|
||||
for tex in self.textures:
|
||||
self.finalQuad.setShaderInput("tx"+tex, self.textures[tex])
|
||||
|
||||
|
|
@ -536,3 +542,24 @@ class CommonFilters:
|
|||
del self.configuration["GammaAdjust"]
|
||||
return self.reconfigure((old_gamma != 1.0), "GammaAdjust")
|
||||
return True
|
||||
|
||||
#snake_case alias:
|
||||
del_cartoon_ink = delCartoonInk
|
||||
set_half_pixel_shift = setHalfPixelShift
|
||||
del_half_pixel_shift = delHalfPixelShift
|
||||
set_inverted = setInverted
|
||||
del_inverted = delInverted
|
||||
del_view_glow = delViewGlow
|
||||
set_volumetric_lighting = setVolumetricLighting
|
||||
del_gamma_adjust = delGammaAdjust
|
||||
set_bloom = setBloom
|
||||
set_view_glow = setViewGlow
|
||||
set_ambient_occlusion = setAmbientOcclusion
|
||||
set_cartoon_ink = setCartoonInk
|
||||
del_bloom = delBloom
|
||||
del_ambient_occlusion = delAmbientOcclusion
|
||||
load_shader = loadShader
|
||||
set_blur_sharpen = setBlurSharpen
|
||||
del_blur_sharpen = delBlurSharpen
|
||||
del_volumetric_lighting = delVolumetricLighting
|
||||
set_gamma_adjust = setGammaAdjust
|
||||
|
|
|
|||
|
|
@ -349,3 +349,15 @@ class FilterManager(DirectObject):
|
|||
self.nextsort = self.win.getSort() - 1000
|
||||
self.basex = 0
|
||||
self.basey = 0
|
||||
|
||||
#snake_case alias:
|
||||
is_fullscreen = isFullscreen
|
||||
resize_buffers = resizeBuffers
|
||||
set_stacked_clears = setStackedClears
|
||||
render_scene_into = renderSceneInto
|
||||
get_scaled_size = getScaledSize
|
||||
render_quad_into = renderQuadInto
|
||||
get_clears = getClears
|
||||
set_clears = setClears
|
||||
create_buffer = createBuffer
|
||||
window_event = windowEvent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
"""
|
||||
This package contains functionality for applying post-processing
|
||||
filters to the result of rendering a 3-D scene. This is done by
|
||||
rendering the scene to an off-screen buffer, and then applying this to
|
||||
a full-screen card that has a shader applied which manipulates the
|
||||
texture values as desired.
|
||||
|
||||
The :class:`.CommonFilters` class contains various filters that are
|
||||
provided out of the box, whereas the :class:`.FilterManager` class
|
||||
is a lower-level class that allows you to set up your own filters.
|
||||
"""
|
||||
|
|
@ -12,8 +12,9 @@ void vshader(float4 vtx_position : POSITION,
|
|||
l_position=mul(mat_modelproj, vtx_position);
|
||||
float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
|
||||
float offset = texpix_src.x;
|
||||
l_texcoord0 = float4(c.x-offset* -4, c.x-offset* -3, c.x-offset* -2, c.y);
|
||||
l_texcoord1 = float4(c.x-offset* -1, c.x-offset* 0, c.x-offset* 1, c.y);
|
||||
float pad = texpad_src.x * 2;
|
||||
l_texcoord0 = float4(min(c.x-offset* -4, pad), min(c.x-offset* -3, pad), min(c.x-offset* -2, pad), c.y);
|
||||
l_texcoord1 = float4(min(c.x-offset* -1, pad), c.x-offset* 0, c.x-offset* 1, c.y);
|
||||
l_texcoord2 = float4(c.x-offset* 2, c.x-offset* 3, c.x-offset* 4, c.y);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ void vshader(float4 vtx_position : POSITION,
|
|||
l_position=mul(mat_modelproj, vtx_position);
|
||||
float2 c=(vtx_position.xz * texpad_src.xy) + texpad_src.xy;
|
||||
float offset = texpix_src.y;
|
||||
l_texcoord0 = float4(c.y-offset* -4, c.y-offset* -3, c.y-offset* -2, c.x);
|
||||
l_texcoord1 = float4(c.y-offset* -1, c.y-offset* 0, c.y-offset* 1, c.x);
|
||||
float pad = texpad_src.y * 2;
|
||||
l_texcoord0 = float4(min(c.y-offset* -4, pad), min(c.y-offset* -3, pad), min(c.y-offset* -2, pad), c.x);
|
||||
l_texcoord1 = float4(min(c.y-offset* -1, pad), c.y-offset* 0, c.y-offset* 1, c.x);
|
||||
l_texcoord2 = float4(c.y-offset* 2, c.y-offset* 3, c.y-offset* 4, c.x);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
//Cg profile arbvp1 arbfp1
|
||||
|
||||
void vshader(float4 vtx_position : POSITION,
|
||||
void vshader(float4 vtx_position : POSITION,
|
||||
float2 vtx_texcoord0 : TEXCOORD0,
|
||||
out float4 l_position : POSITION,
|
||||
out float2 l_texcoord0 : TEXCOORD0,
|
||||
|
|
@ -17,16 +17,18 @@ void vshader(float4 vtx_position : POSITION,
|
|||
void fshader(float2 l_texcoord0 : TEXCOORD0,
|
||||
out float4 o_color : COLOR,
|
||||
uniform float2 texpix_src,
|
||||
uniform float4 texpad_src,
|
||||
uniform sampler2D k_src : TEXUNIT0)
|
||||
{
|
||||
float pad = texpad_src.x * 2;
|
||||
float3 offset = float3(1.0*texpix_src.x, 2.0*texpix_src.x, 3.0*texpix_src.x);
|
||||
o_color = tex2D(k_src, l_texcoord0);
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x - offset.z, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x - offset.y, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x - offset.x, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x + offset.x, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x + offset.y, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x + offset.z, l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.x, pad), l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.y, pad), l_texcoord0.y));
|
||||
o_color += tex2D(k_src, float2(min(l_texcoord0.x + offset.z, pad), l_texcoord0.y));
|
||||
o_color /= 7;
|
||||
o_color.w = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
//Cg profile arbvp1 arbfp1
|
||||
|
||||
void vshader(float4 vtx_position : POSITION,
|
||||
void vshader(float4 vtx_position : POSITION,
|
||||
float2 vtx_texcoord0 : TEXCOORD0,
|
||||
out float4 l_position : POSITION,
|
||||
out float2 l_texcoord0 : TEXCOORD0,
|
||||
|
|
@ -17,16 +17,18 @@ void vshader(float4 vtx_position : POSITION,
|
|||
void fshader(float2 l_texcoord0 : TEXCOORD0,
|
||||
out float4 o_color : COLOR,
|
||||
uniform float2 texpix_src,
|
||||
uniform float4 texpad_src,
|
||||
uniform sampler2D k_src : TEXUNIT0)
|
||||
{
|
||||
float pad = texpad_src.y * 2;
|
||||
float3 offset = float3(1.0*texpix_src.y, 2.0*texpix_src.y, 3.0*texpix_src.y);
|
||||
o_color = tex2D(k_src, l_texcoord0);
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.z));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y - offset.x));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.x));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.y));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, l_texcoord0.y + offset.z));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.x, pad)));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.y, pad)));
|
||||
o_color += tex2D(k_src, float2(l_texcoord0.x, min(l_texcoord0.y + offset.z, pad)));
|
||||
o_color /= 7;
|
||||
o_color.w = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""Undocumented Module"""
|
||||
|
||||
__all__ = ['ClassicFSM']
|
||||
|
||||
"""Finite State Machine module: contains the ClassicFSM class.
|
||||
|
||||
This module and class exist only for backward compatibility with
|
||||
existing code. New code should use the FSM module instead.
|
||||
.. note::
|
||||
|
||||
This module and class exist only for backward compatibility with
|
||||
existing code. New code should use the :mod:`.FSM` module instead.
|
||||
"""
|
||||
|
||||
__all__ = ['ClassicFSM']
|
||||
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
import weakref
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""The new Finite State Machine module. This replaces the module
|
||||
previously called FSM.py (now called ClassicFSM.py).
|
||||
previously called FSM (now called :mod:`.ClassicFSM`).
|
||||
"""
|
||||
|
||||
__all__ = ['FSMException', 'FSM']
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the FourState class."""
|
||||
|
||||
__all__ = ['FourState']
|
||||
|
||||
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
#import DistributedObject
|
||||
from . import ClassicFSM
|
||||
|
|
@ -20,7 +18,7 @@ class FourState:
|
|||
Inherit from FourStateFSM and pass in your states. Two of
|
||||
the states should be oposites of each other and the other
|
||||
two should be the transition states between the first two.
|
||||
E.g.
|
||||
E.g::
|
||||
|
||||
+--------+
|
||||
-->| closed | --
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the FourStateAI class. See also :mod:`.FourState`."""
|
||||
|
||||
__all__ = ['FourStateAI']
|
||||
|
||||
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
#import DistributedObjectAI
|
||||
from . import ClassicFSM
|
||||
|
|
@ -21,7 +19,7 @@ class FourStateAI:
|
|||
Inherit from FourStateFSM and pass in your states. Two of
|
||||
the states should be oposites of each other and the other
|
||||
two should be the transition states between the first two.
|
||||
E.g.
|
||||
E.g::
|
||||
|
||||
+--------+
|
||||
-->| closed | --
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
This package contains implementations of a Finite State Machine, an
|
||||
abstract construct that holds a particular state and can transition
|
||||
between several defined states. These are useful for a range of logic
|
||||
programming tasks.
|
||||
"""
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""This module contains the DirectButton class."""
|
||||
|
||||
__all__ = ['DirectButton']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
"""Undocumented Module"""
|
||||
"""A DirectCheckButton is a type of button that toggles between two states
|
||||
when clicked. It also has a separate indicator that can be modified
|
||||
separately."""
|
||||
|
||||
__all__ = ['DirectCheckButton']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""This module defines various dialog windows for the DirectGUI system."""
|
||||
|
||||
__all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the DirectEntry class, a type of DirectGUI widget that accepts
|
||||
text entered using the keyboard."""
|
||||
|
||||
__all__ = ['DirectEntry']
|
||||
|
||||
|
|
@ -58,6 +59,8 @@ class DirectEntry(DirectFrame):
|
|||
# Text used for the PGEntry text node
|
||||
# NOTE: This overrides the DirectFrame text option
|
||||
('initialText', '', DGG.INITOPT),
|
||||
# Enable or disable text overflow scrolling
|
||||
('overflow', 0, self.setOverflowMode),
|
||||
# Command to be called on hitting Enter
|
||||
('command', None, None),
|
||||
('extraArgs', [], None),
|
||||
|
|
@ -158,6 +161,9 @@ class DirectEntry(DirectFrame):
|
|||
def setCursorKeysActive(self):
|
||||
PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])
|
||||
|
||||
def setOverflowMode(self):
|
||||
PGEntry.set_overflow_mode(self.guiItem, self['overflow'])
|
||||
|
||||
def setObscureMode(self):
|
||||
PGEntry.setObscureMode(self.guiItem, self['obscured'])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
"""Undocumented Module"""
|
||||
"""A DirectFrame is a basic DirectGUI component that acts as the base
|
||||
class for various other components, and can also serve as a basic
|
||||
container to hold other DirectGUI components.
|
||||
|
||||
A DirectFrame can have:
|
||||
|
||||
* A background texture (pass in path to image, or Texture Card)
|
||||
* A midground geometry item (pass in geometry)
|
||||
* A foreground text Node (pass in text string or OnscreenText)
|
||||
|
||||
Each of these has 1 or more states. The same object can be used for
|
||||
all states or each state can have a different text/geom/image (for
|
||||
radio button and check button indicators, for example).
|
||||
"""
|
||||
|
||||
__all__ = ['DirectFrame']
|
||||
|
||||
|
|
@ -19,14 +32,6 @@ class DirectFrame(DirectGuiWidget):
|
|||
DefDynGroups = ('text', 'geom', 'image')
|
||||
def __init__(self, parent = None, **kw):
|
||||
# Inherits from DirectGuiWidget
|
||||
# A Direct Frame can have:
|
||||
# - A background texture (pass in path to image, or Texture Card)
|
||||
# - A midground geometry item (pass in geometry)
|
||||
# - A foreground text Node (pass in text string or Onscreen Text)
|
||||
# Each of these has 1 or more states
|
||||
# The same object can be used for all states or each
|
||||
# state can have a different text/geom/image (for radio button
|
||||
# and check button indicators, for example).
|
||||
optiondefs = (
|
||||
# Define type of DirectGuiWidget
|
||||
('pgFunc', PGItem, None),
|
||||
|
|
|
|||
|
|
@ -1,32 +1,7 @@
|
|||
"""Undocumented Module"""
|
||||
|
||||
__all__ = ['DirectGuiBase', 'DirectGuiWidget']
|
||||
|
||||
|
||||
from panda3d.core import *
|
||||
from panda3d.direct import get_config_showbase
|
||||
from . import DirectGuiGlobals as DGG
|
||||
from .OnscreenText import *
|
||||
from .OnscreenGeom import *
|
||||
from .OnscreenImage import *
|
||||
from direct.directtools.DirectUtil import ROUND_TO
|
||||
from direct.showbase import DirectObject
|
||||
from direct.task import Task
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
stringType = str
|
||||
else:
|
||||
stringType = basestring
|
||||
|
||||
guiObjectCollector = PStatCollector("Client::GuiObjects")
|
||||
|
||||
"""
|
||||
Base class for all Direct Gui items. Handles composite widgets and
|
||||
command line argument parsing.
|
||||
"""
|
||||
|
||||
"""
|
||||
Code Overview:
|
||||
|
||||
1 Each widget defines a set of options (optiondefs) as a list of tuples
|
||||
|
|
@ -101,7 +76,32 @@ Code Overview:
|
|||
are left unused. If so, an error is raised.
|
||||
"""
|
||||
|
||||
__all__ = ['DirectGuiBase', 'DirectGuiWidget']
|
||||
|
||||
|
||||
from panda3d.core import *
|
||||
from direct.showbase import ShowBaseGlobal
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from . import DirectGuiGlobals as DGG
|
||||
from .OnscreenText import *
|
||||
from .OnscreenGeom import *
|
||||
from .OnscreenImage import *
|
||||
from direct.directtools.DirectUtil import ROUND_TO
|
||||
from direct.showbase import DirectObject
|
||||
from direct.task import Task
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
stringType = str
|
||||
else:
|
||||
stringType = basestring
|
||||
|
||||
guiObjectCollector = PStatCollector("Client::GuiObjects")
|
||||
|
||||
|
||||
class DirectGuiBase(DirectObject.DirectObject):
|
||||
"""Base class of all DirectGUI widgets."""
|
||||
|
||||
def __init__(self):
|
||||
# Default id of all gui object, subclasses should override this
|
||||
self.guiId = 'guiObject'
|
||||
|
|
@ -634,7 +634,7 @@ class DirectGuiBase(DirectObject.DirectObject):
|
|||
"""
|
||||
# Need to tack on gui item specific id
|
||||
gEvent = event + self.guiId
|
||||
if get_config_showbase().GetBool('debug-directgui-msgs', False):
|
||||
if ShowBase.config.GetBool('debug-directgui-msgs', False):
|
||||
from direct.showbase.PythonUtil import StackTrace
|
||||
print(gEvent)
|
||||
print(StackTrace())
|
||||
|
|
@ -663,7 +663,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
|
|||
# Determine the default initial state for inactive (or
|
||||
# unclickable) components. If we are in edit mode, these are
|
||||
# actually clickable by default.
|
||||
guiEdit = get_config_showbase().GetBool('direct-gui-edit', 0)
|
||||
guiEdit = ShowBase.config.GetBool('direct-gui-edit', False)
|
||||
if guiEdit:
|
||||
inactiveInitState = DGG.NORMAL
|
||||
else:
|
||||
|
|
@ -724,21 +724,24 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
|
|||
if self['guiId']:
|
||||
self.guiItem.setId(self['guiId'])
|
||||
self.guiId = self.guiItem.getId()
|
||||
if __dev__:
|
||||
|
||||
if ShowBaseGlobal.__dev__:
|
||||
guiObjectCollector.addLevel(1)
|
||||
guiObjectCollector.flushLevel()
|
||||
# track gui items by guiId for tracking down leaks
|
||||
if hasattr(base, 'guiItems'):
|
||||
if self.guiId in base.guiItems:
|
||||
base.notify.warning('duplicate guiId: %s (%s stomping %s)' %
|
||||
(self.guiId, self,
|
||||
base.guiItems[self.guiId]))
|
||||
base.guiItems[self.guiId] = self
|
||||
if hasattr(base, 'printGuiCreates'):
|
||||
printStack()
|
||||
if ShowBase.config.GetBool('track-gui-items', False):
|
||||
if not hasattr(ShowBase, 'guiItems'):
|
||||
ShowBase.guiItems = {}
|
||||
if self.guiId in ShowBase.guiItems:
|
||||
ShowBase.notify.warning('duplicate guiId: %s (%s stomping %s)' %
|
||||
(self.guiId, self,
|
||||
ShowBase.guiItems[self.guiId]))
|
||||
ShowBase.guiItems[self.guiId] = self
|
||||
|
||||
# Attach button to parent and make that self
|
||||
if (parent == None):
|
||||
parent = aspect2d
|
||||
if parent is None:
|
||||
parent = ShowBaseGlobal.aspect2d
|
||||
|
||||
self.assign(parent.attachNewNode(self.guiItem, self['sortOrder']))
|
||||
# Update pose to initial values
|
||||
if self['pos']:
|
||||
|
|
@ -1025,17 +1028,12 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
|
|||
|
||||
def destroy(self):
|
||||
if hasattr(self, "frameStyle"):
|
||||
if __dev__:
|
||||
if ShowBaseGlobal.__dev__:
|
||||
guiObjectCollector.subLevel(1)
|
||||
guiObjectCollector.flushLevel()
|
||||
if hasattr(base, 'guiItems'):
|
||||
if self.guiId in base.guiItems:
|
||||
del base.guiItems[self.guiId]
|
||||
else:
|
||||
base.notify.warning(
|
||||
'DirectGuiWidget.destroy(): '
|
||||
'gui item %s not in base.guiItems' %
|
||||
self.guiId)
|
||||
if hasattr(ShowBase, 'guiItems'):
|
||||
ShowBase.guiItems.pop(self.guiId, None)
|
||||
|
||||
# Destroy children
|
||||
for child in self.getChildren():
|
||||
childGui = self.guiDict.get(child.getName())
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
"""Undocumented Module"""
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
"""
|
||||
Global definitions used by Direct Gui Classes and handy constants
|
||||
that can be used during widget construction
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
|
||||
from panda3d.core import *
|
||||
|
||||
defaultFont = None
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the DirectLabel class."""
|
||||
|
||||
__all__ = ['DirectLabel']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Implements a pop-up menu containing multiple clickable options."""
|
||||
|
||||
__all__ = ['DirectOptionMenu']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
"""Undocumented Module"""
|
||||
"""A DirectRadioButton is a type of button that, similar to a
|
||||
DirectCheckButton, has a separate indicator and can be toggled between
|
||||
two states. However, only one DirectRadioButton in a group can be enabled
|
||||
at a particular time."""
|
||||
|
||||
__all__ = ['DirectRadioButton']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Defines the DirectScrollBar class."""
|
||||
|
||||
__all__ = ['DirectScrollBar']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the DirectScrolledFrame class."""
|
||||
|
||||
__all__ = ['DirectScrolledFrame']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the DirectScrolledList class."""
|
||||
|
||||
__all__ = ['DirectScrolledListItem', 'DirectScrolledList']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Defines the DirectSlider class."""
|
||||
|
||||
__all__ = ['DirectSlider']
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Undocumented Module"""
|
||||
"""Contains the DirectWaitBar class, a progress bar widget."""
|
||||
|
||||
__all__ = ['DirectWaitBar']
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class OnscreenText(NodePath):
|
|||
font = None,
|
||||
parent = None,
|
||||
sort = 0,
|
||||
mayChange = True):
|
||||
mayChange = True,
|
||||
direction = None):
|
||||
"""
|
||||
Make a text node from string, put it into the 2d sg and set it
|
||||
up with all the indicated parameters.
|
||||
|
|
@ -95,6 +96,9 @@ class OnscreenText(NodePath):
|
|||
mayChange: pass true if the text or its properties may need
|
||||
to be changed at runtime, false if it is static once
|
||||
created (which leads to better memory optimization).
|
||||
|
||||
direction: this can be set to 'ltr' or 'rtl' to override the
|
||||
direction of the text.
|
||||
"""
|
||||
if parent == None:
|
||||
parent = aspect2d
|
||||
|
|
@ -192,6 +196,17 @@ class OnscreenText(NodePath):
|
|||
textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
|
||||
textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)
|
||||
|
||||
if direction is not None:
|
||||
if isinstance(direction, str):
|
||||
direction = direction.lower()
|
||||
if direction == 'rtl':
|
||||
direction = TextProperties.D_rtl
|
||||
elif direction == 'ltr':
|
||||
direction = TextProperties.D_ltr
|
||||
else:
|
||||
raise ValueError('invalid direction')
|
||||
textNode.setDirection(direction)
|
||||
|
||||
# Create a transform for the text for our scale and position.
|
||||
# We'd rather do it here, on the text itself, rather than on
|
||||
# our NodePath, so we have one fewer transforms in the scene
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
This package contains the DirectGui system, a set of classes
|
||||
responsible for drawing graphical widgets to the 2-D scene graph.
|
||||
|
||||
It is based on the lower-level PGui system, which is implemented in
|
||||
C++.
|
||||
|
||||
For convenience, all of the DirectGui widgets may be imported from a
|
||||
single module as follows::
|
||||
|
||||
from direct.gui.DirectGui import *
|
||||
"""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue