diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a6236e261d..71d41e1b8b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,7 +11,7 @@ jobs:
matrix:
profile:
- - ubuntu-xenial-standard-unity-makefile
+ - ubuntu-bionic-standard-unity-makefile
- ubuntu-bionic-coverage-ninja
- macos-eigen-coverage-unity-xcode
- macos-nometa-standard-makefile
@@ -19,8 +19,8 @@ jobs:
- windows-nopython-nometa-standard-msvc
include:
- - profile: ubuntu-xenial-standard-unity-makefile
- os: ubuntu-16.04
+ - profile: ubuntu-bionic-standard-unity-makefile
+ os: ubuntu-18.04
config: Standard
unity: YES
generator: Unix Makefiles
@@ -333,12 +333,12 @@ jobs:
if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')"
strategy:
matrix:
- os: [ubuntu-16.04, windows-2016, macOS-latest]
+ os: [ubuntu-18.04, windows-2016, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Install dependencies (Ubuntu)
- if: matrix.os == 'ubuntu-16.04'
+ if: matrix.os == 'ubuntu-18.04'
run: |
sudo apt-get update
sudo apt-get install build-essential bison flex libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bab8a9fcfc..4be6705cc4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,7 +18,7 @@ It is important for acceptance that the change is implemented in a way that fits
the general design principles of the Panda3D API, and fits well with the general
priorities of the team. Therefore, prior discussion with other developers is
critical. Issues can be used to facilitate this, but we also invite you to
-visit the #development channel on Discord (or #panda3d-devel on FreeNode IRC).
+visit the #development channel on Discord (or #panda3d-devel on Libera Chat).
We also recommend that you familiarize yourself with the established coding
style and design patterns of Panda3D, to reduce the amount of changes that have
diff --git a/README.md b/README.md
index 2139a61a68..c59d43258b 100644
--- a/README.md
+++ b/README.md
@@ -249,10 +249,3 @@ to everyone who has donated!
-
-### Gold Sponsors
-[](https://opencollective.com/panda3d/tiers/gold-sponsor/0/website)
-[](https://opencollective.com/panda3d/tiers/gold-sponsor/1/website)
-[](https://opencollective.com/panda3d/tiers/gold-sponsor/2/website)
-[](https://opencollective.com/panda3d/tiers/gold-sponsor/3/website)
-[](https://opencollective.com/panda3d/tiers/gold-sponsor/4/website)
diff --git a/contrib/src/ai/aiCharacter.cxx b/contrib/src/ai/aiCharacter.cxx
index 58485081c3..8f46bd8ee1 100644
--- a/contrib/src/ai/aiCharacter.cxx
+++ b/contrib/src/ai/aiCharacter.cxx
@@ -117,6 +117,10 @@ NodePath AICharacter::get_char_render() {
return _window_render;
}
+std::string AICharacter::get_name() {
+ return _name;
+}
+
void AICharacter::set_pf_guide(bool pf_guide) {
_pf_guide = pf_guide;
}
diff --git a/contrib/src/ai/aiCharacter.h b/contrib/src/ai/aiCharacter.h
index 81b3a01210..5ea5e590ec 100644
--- a/contrib/src/ai/aiCharacter.h
+++ b/contrib/src/ai/aiCharacter.h
@@ -46,6 +46,8 @@ class EXPCL_PANDAAI AICharacter : public ReferenceCount {
void set_char_render(NodePath render);
NodePath get_char_render();
+ std::string get_name();
+
PUBLISHED:
double get_mass();
void set_mass(double m);
@@ -65,6 +67,8 @@ PUBLISHED:
explicit AICharacter(std::string model_name, NodePath model_np, double mass, double movt_force, double max_force);
~AICharacter();
+
+ MAKE_PROPERTY(name, get_name);
};
#endif
diff --git a/contrib/src/speedtree/speedTreeNode.cxx b/contrib/src/speedtree/speedTreeNode.cxx
index 198160b584..ac0884e641 100644
--- a/contrib/src/speedtree/speedTreeNode.cxx
+++ b/contrib/src/speedtree/speedTreeNode.cxx
@@ -1002,17 +1002,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool SpeedTreeNode::
-is_renderable() const {
- return true;
-}
-
/**
* Adds the node's contents to the CullResult we are building up during the
* cull traversal, so that it will be drawn at render time. For most nodes
@@ -1162,6 +1151,7 @@ set_transparent_texture_mode(SpeedTree::ETextureAlphaRenderMode eMode) const {
void SpeedTreeNode::
init_node() {
PandaNode::set_cull_callback();
+ PandaNode::set_renderable();
_is_valid = false;
_needs_repopulate = false;
diff --git a/contrib/src/speedtree/speedTreeNode.h b/contrib/src/speedtree/speedTreeNode.h
index 3c95bbce76..9c3f0ca886 100644
--- a/contrib/src/speedtree/speedTreeNode.h
+++ b/contrib/src/speedtree/speedTreeNode.h
@@ -157,7 +157,6 @@ public:
GeomTransformer &transformer);
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
void prepare_scene(GraphicsStateGuardianBase *gsgbase, const RenderState *net_state);
diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py
index 8f5d5183e0..dbd1fd253e 100644
--- a/direct/src/actor/Actor.py
+++ b/direct/src/actor/Actor.py
@@ -1,7 +1,7 @@
"""Actor module: contains the Actor class.
-See the :ref:`models-and-actors` page in the Programming Guide to learn
-more about loading models and animated actors.
+See the :ref:`loading-actors-and-animations` page in the Programming Guide
+to learn more about loading animated models.
"""
__all__ = ['Actor']
diff --git a/direct/src/actor/__init__.py b/direct/src/actor/__init__.py
index 14d116777f..191aacd08f 100644
--- a/direct/src/actor/__init__.py
+++ b/direct/src/actor/__init__.py
@@ -5,6 +5,6 @@ the lower-level :class:`panda3d.core.Character` implementation.
It loads and controls an animated character and manages the animations
playing on it.
-See the :ref:`models-and-actors` page in the Programming Guide to learn
-more about loading models and animated actors.
+See the :ref:`loading-actors-and-animations` page in the Programming Guide
+to learn more about loading animated models.
"""
diff --git a/direct/src/dcparser/dcPacker_ext.cxx b/direct/src/dcparser/dcPacker_ext.cxx
index c0a5164551..2e78df416d 100644
--- a/direct/src/dcparser/dcPacker_ext.cxx
+++ b/direct/src/dcparser/dcPacker_ext.cxx
@@ -226,6 +226,10 @@ unpack_object() {
std::string str;
_this->unpack_string(str);
object = PyUnicode_FromStringAndSize(str.data(), str.size());
+ if (object == nullptr) {
+ nassert_raise("Unable to decode UTF-8 string; use blob type for binary data");
+ return nullptr;
+ }
}
break;
diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py
index 761593f511..e0071a2030 100644
--- a/direct/src/dist/FreezeTool.py
+++ b/direct/src/dist/FreezeTool.py
@@ -90,6 +90,7 @@ ignoreImports = {
'direct.showbase.PythonUtil': ['pstats', 'profile'],
'toml.encoder': ['numpy'],
+ 'py._builtin': ['__builtin__'],
}
if sys.version_info >= (3, 8):
@@ -106,7 +107,7 @@ overrideModules = {
# Used by the warnings module, among others, to get line numbers. Since
# we set __file__, this would cause it to try and extract Python code
# lines from the main executable, which we don't want.
- 'linecache': """__all__ = ["getline", "clearcache", "checkcache"]
+ 'linecache': """__all__ = ["getline", "clearcache", "checkcache", "lazycache"]
cache = {}
@@ -662,7 +663,7 @@ okMissing = [
'email.Iterators', '_subprocess', 'gestalt', 'java.lang',
'direct.extensions_native.extensions_darwin', '_manylinux',
'collections.Iterable', 'collections.Mapping', 'collections.MutableMapping',
- 'collections.Sequence', 'numpy_distutils',
+ 'collections.Sequence', 'numpy_distutils', '_winapi',
]
# Since around macOS 10.15, Apple's codesigning process has become more strict.
@@ -2625,9 +2626,9 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
(or self.path if None). Returns a tuple like (fp, path, stuff), where
stuff is a tuple like (suffix, mode, type). """
- if imp.is_frozen(name):
- # Don't pick up modules that are frozen into p3dpython.
- raise ImportError("'%s' is a frozen module" % (name))
+ #if imp.is_frozen(name):
+ # # Don't pick up modules that are frozen into p3dpython.
+ # raise ImportError("'%s' is a frozen module" % (name))
if parent is not None:
fullname = parent.__name__+'.'+name
diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py
index d2d4946dca..a4582cc81d 100644
--- a/direct/src/dist/commands.py
+++ b/direct/src/dist/commands.py
@@ -95,6 +95,7 @@ PACKAGE_DATA_DIRS = {
('cefpython3/Chromium Embedded Framework.framework/Chromium Embedded Framework', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
],
'pytz': [('pytz/zoneinfo/*', 'zoneinfo', ())],
+ 'certifi': [('certifi/cacert.pem', '', {})],
}
# Some dependencies have extra directories that need to be scanned for DLLs.
diff --git a/direct/src/gui/OnscreenImage.py b/direct/src/gui/OnscreenImage.py
index 8f85613956..bb1494b60a 100644
--- a/direct/src/gui/OnscreenImage.py
+++ b/direct/src/gui/OnscreenImage.py
@@ -107,7 +107,9 @@ class OnscreenImage(DirectObject, NodePath):
tex = image
else:
# It's a Texture file name
- tex = base.loader.loadTexture(image)
+ tex = TexturePool.loadTexture(image)
+ if not tex:
+ raise IOError('Could not load texture: %s' % (image))
cm = CardMaker('OnscreenImage')
cm.setFrame(-1, 1, -1, 1)
self.assign(parent.attachNewNode(cm.generate(), sort))
diff --git a/direct/src/interval/MetaInterval.py b/direct/src/interval/MetaInterval.py
index 2986071ba4..f706c338aa 100644
--- a/direct/src/interval/MetaInterval.py
+++ b/direct/src/interval/MetaInterval.py
@@ -348,10 +348,14 @@ class MetaInterval(CMetaInterval):
def getManager(self):
return self.__manager
+ manager = property(getManager, setManager)
+
def setT(self, t):
self.__updateIvals()
CMetaInterval.setT(self, t)
+ t = property(CMetaInterval.getT, setT)
+
def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
self.__updateIvals()
self.setupPlay(startT, endT, playRate, 0)
@@ -475,6 +479,8 @@ class MetaInterval(CMetaInterval):
else:
CMetaInterval.setPlayRate(self, playRate)
+ play_rate = property(CMetaInterval.getPlayRate, setPlayRate)
+
def __doPythonCallbacks(self):
# This function invokes any Python-level Intervals that need
# to be invoked at this point in time. It must be called
@@ -549,6 +555,8 @@ class MetaInterval(CMetaInterval):
self.__updateIvals()
return CMetaInterval.getDuration(self)
+ duration = property(getDuration)
+
def __repr__(self, *args, **kw):
# This function overrides from the parent level to force it to
# update the interval list first, if necessary.
diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py
index 6057a1a617..de49761c21 100644
--- a/direct/src/showbase/PythonUtil.py
+++ b/direct/src/showbase/PythonUtil.py
@@ -1127,8 +1127,6 @@ def normalDistrib(a, b, gauss=random.gauss):
uniformly onto the curve inside [a, b]
------------------------------------------------------------------------
- https://statweb.stanford.edu/~naras/jsm/NormalDensity/NormalDensity.html
-
The 68-95-99.7% Rule
====================
All normal density curves satisfy the following property which is often
diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py
index 53c30a3e9c..c7edea715e 100644
--- a/direct/src/showbase/ShowBase.py
+++ b/direct/src/showbase/ShowBase.py
@@ -725,7 +725,7 @@ class ShowBase(DirectObject.DirectObject):
of :meth:`~panda3d.core.GraphicsWindow.setUnexposedDraw()`.
:param callbackWindowDict: If not None, a
- :class:`~panda3d.core.CallbackGraphicWindow`
+ :class:`~panda3d.core.CallbackGraphicsWindow`
is created instead, which allows the caller
to create the actual window with its own
OpenGL context, and direct Panda's rendering
diff --git a/dtool/src/dtoolbase/cmath.I b/dtool/src/dtoolbase/cmath.I
index b303671fbb..12ec33f1df 100644
--- a/dtool/src/dtoolbase/cmath.I
+++ b/dtool/src/dtoolbase/cmath.I
@@ -64,10 +64,14 @@ csincos(float v, float *sin_result, float *cos_result) {
fstp DWORD ptr [edx]
fstp DWORD ptr [eax]
}
-#else //!_X86_
+#elif defined(__APPLE__)
+ __sincosf(v, sin_result, cos_result);
+#elif defined(_GNU_SOURCE)
+ sincosf(v, sin_result, cos_result);
+#else
*sin_result = sinf(v);
*cos_result = cosf(v);
-#endif //!_X86_
+#endif
}
/**
@@ -231,6 +235,10 @@ csincos(double v, double *sin_result, double *cos_result) {
fstp QWORD ptr [edx]
fstp QWORD ptr [eax]
}
+#elif defined(__APPLE__)
+ __sincos(v, sin_result, cos_result);
+#elif defined(_GNU_SOURCE)
+ sincos(v, sin_result, cos_result);
#else //!_X86_
*sin_result = sin(v);
*cos_result = cos(v);
diff --git a/dtool/src/dtoolutil/filename.cxx b/dtool/src/dtoolutil/filename.cxx
index 0998a8f7a5..b3b84a84d7 100644
--- a/dtool/src/dtoolutil/filename.cxx
+++ b/dtool/src/dtoolutil/filename.cxx
@@ -1876,13 +1876,13 @@ open_read(std::ifstream &stream) const {
#endif
stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
wstring os_specific = to_os_specific_w();
stream.open(os_specific.c_str(), open_mode);
#else
string os_specific = to_os_specific();
stream.open(os_specific.c_str(), open_mode);
-#endif // _WIN32
+#endif // _MSC_VER
return (!stream.fail());
}
@@ -1926,11 +1926,11 @@ open_write(std::ofstream &stream, bool truncate) const {
#endif
stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
wstring os_specific = to_os_specific_w();
#else
string os_specific = to_os_specific();
-#endif // _WIN32
+#endif // _MSC_VER
stream.open(os_specific.c_str(), open_mode);
return (!stream.fail());
@@ -1958,11 +1958,11 @@ open_append(std::ofstream &stream) const {
#endif
stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
wstring os_specific = to_os_specific_w();
#else
string os_specific = to_os_specific();
-#endif // _WIN32
+#endif // _MSC_VER
stream.open(os_specific.c_str(), open_mode);
return (!stream.fail());
@@ -2000,11 +2000,11 @@ open_read_write(std::fstream &stream, bool truncate) const {
#endif
stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
wstring os_specific = to_os_specific_w();
#else
string os_specific = to_os_specific();
-#endif // _WIN32
+#endif // _MSC_VER
stream.open(os_specific.c_str(), open_mode);
return (!stream.fail());
@@ -2032,11 +2032,11 @@ open_read_append(std::fstream &stream) const {
#endif
stream.clear();
-#ifdef _WIN32
+#ifdef _MSC_VER
wstring os_specific = to_os_specific_w();
#else
string os_specific = to_os_specific();
-#endif // _WIN32
+#endif // _MSC_VER
stream.open(os_specific.c_str(), open_mode);
return (!stream.fail());
diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi
index a22c8b848c..08c7f8bd3a 100644
--- a/makepanda/installer.nsi
+++ b/makepanda/installer.nsi
@@ -376,6 +376,7 @@ SectionGroup "Python modules" SecGroupPython
!insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
!insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
!insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
+ !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
!else
!insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
!insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
@@ -383,6 +384,7 @@ SectionGroup "Python modules" SecGroupPython
!insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
!insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
!insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
+ !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
!endif
SectionGroupEnd
@@ -491,6 +493,7 @@ Function .onInit
${If} ${AtLeastWin8}
!insertmacro MaybeEnablePyBindingSection 3.9-32
!insertmacro MaybeEnablePyBindingSection 3.10-32
+ !insertmacro MaybeEnablePyBindingSection 3.11-32
${EndIf}
!else
!insertmacro MaybeEnablePyBindingSection 3.5
@@ -500,6 +503,7 @@ Function .onInit
${If} ${AtLeastWin8}
!insertmacro MaybeEnablePyBindingSection 3.9
!insertmacro MaybeEnablePyBindingSection 3.10
+ !insertmacro MaybeEnablePyBindingSection 3.11
${EndIf}
!endif
@@ -513,6 +517,10 @@ Function .onInit
SectionSetFlags ${SecPyBindings3.10} ${SF_RO}
SectionSetInstTypes ${SecPyBindings3.10} 0
!endif
+ !ifdef SecPyBindings3.11
+ SectionSetFlags ${SecPyBindings3.11} ${SF_RO}
+ SectionSetInstTypes ${SecPyBindings3.11} 0
+ !endif
${EndUnless}
FunctionEnd
@@ -824,6 +832,7 @@ Section Uninstall
!insertmacro RemovePythonPath 3.8-32
!insertmacro RemovePythonPath 3.9-32
!insertmacro RemovePythonPath 3.10-32
+ !insertmacro RemovePythonPath 3.11-32
!else
!insertmacro RemovePythonPath 3.5
!insertmacro RemovePythonPath 3.6
@@ -831,6 +840,7 @@ Section Uninstall
!insertmacro RemovePythonPath 3.8
!insertmacro RemovePythonPath 3.9
!insertmacro RemovePythonPath 3.10
+ !insertmacro RemovePythonPath 3.11
!endif
SetDetailsPrint both
@@ -899,6 +909,7 @@ SectionEnd
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
!else
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
@@ -906,6 +917,7 @@ SectionEnd
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
!insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
!endif
!ifdef INCLUDE_PYVER
!insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py
index 0221bf3474..8687536259 100755
--- a/makepanda/makepanda.py
+++ b/makepanda/makepanda.py
@@ -1313,10 +1313,6 @@ def CompileCxx(obj,src,opts):
if "SYSROOT" in SDK:
if GetTarget() != "android":
cmd += ' --sysroot=%s' % (SDK["SYSROOT"])
- else:
- ndk_dir = SDK["ANDROID_NDK"].replace('\\', '/')
- cmd += ' -isystem %s/sysroot/usr/include' % (ndk_dir)
- cmd += ' -isystem %s/sysroot/usr/include/%s' % (ndk_dir, SDK["ANDROID_TRIPLE"])
cmd += ' -no-canonical-prefixes'
# Android-specific flags.
@@ -1325,33 +1321,24 @@ def CompileCxx(obj,src,opts):
if GetTarget() == "android":
# Most of the specific optimization flags here were
# just copied from the default Android Makefiles.
- if "ANDROID_API" in SDK:
- cmd += ' -D__ANDROID_API__=' + str(SDK["ANDROID_API"])
if "ANDROID_GCC_TOOLCHAIN" in SDK:
cmd += ' -gcc-toolchain ' + SDK["ANDROID_GCC_TOOLCHAIN"].replace('\\', '/')
cmd += ' -ffunction-sections -funwind-tables'
+ cmd += ' -target ' + SDK["ANDROID_TRIPLE"]
if arch == 'armv7a':
- cmd += ' -target armv7-none-linux-androideabi'
cmd += ' -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16'
cmd += ' -fno-integrated-as'
elif arch == 'arm':
- cmd += ' -target armv5te-none-linux-androideabi'
cmd += ' -march=armv5te -mtune=xscale -msoft-float'
cmd += ' -fno-integrated-as'
- elif arch == 'aarch64':
- cmd += ' -target aarch64-none-linux-android'
elif arch == 'mips':
- cmd += ' -target mipsel-none-linux-android'
cmd += ' -mips32'
elif arch == 'mips64':
- cmd += ' -target mips64el-none-linux-android'
cmd += ' -fintegrated-as'
elif arch == 'x86':
- cmd += ' -target i686-none-linux-android'
cmd += ' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32'
cmd += ' -mstackrealign'
elif arch == 'x86_64':
- cmd += ' -target x86_64-none-linux-android'
cmd += ' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel'
cmd += " -Wa,--noexecstack"
@@ -1853,28 +1840,16 @@ def CompileLink(dll, obj, opts):
if "ANDROID_GCC_TOOLCHAIN" in SDK:
cmd += ' -gcc-toolchain ' + SDK["ANDROID_GCC_TOOLCHAIN"].replace('\\', '/')
cmd += " -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now"
+ cmd += ' -target ' + SDK["ANDROID_TRIPLE"]
if arch == 'armv7a':
- cmd += ' -target armv7-none-linux-androideabi'
cmd += " -march=armv7-a -Wl,--fix-cortex-a8"
- elif arch == 'arm':
- cmd += ' -target armv5te-none-linux-androideabi'
- elif arch == 'aarch64':
- cmd += ' -target aarch64-none-linux-android'
elif arch == 'mips':
- cmd += ' -target mipsel-none-linux-android'
cmd += ' -mips32'
- elif arch == 'mips64':
- cmd += ' -target mips64el-none-linux-android'
- elif arch == 'x86':
- cmd += ' -target i686-none-linux-android'
- elif arch == 'x86_64':
- cmd += ' -target x86_64-none-linux-android'
cmd += ' -lc -lm'
else:
cmd += " -pthread"
-
- if "SYSROOT" in SDK:
- cmd += " --sysroot=%s -no-canonical-prefixes" % (SDK["SYSROOT"])
+ if "SYSROOT" in SDK:
+ cmd += " --sysroot=%s -no-canonical-prefixes" % (SDK["SYSROOT"])
if LDFLAGS != "":
cmd += " " + LDFLAGS
@@ -2451,6 +2426,7 @@ def WriteConfigSettings():
dtool_config["PHAVE_GLOB_H"] = 'UNDEF'
dtool_config["PHAVE_LOCKF"] = 'UNDEF'
dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF'
+ dtool_config["HAVE_X11"] = 'UNDEF'
if (GetOptimize() <= 2 and GetTarget() == "windows"):
dtool_config["USE_DEBUG_PYTHON"] = '1'
@@ -3080,8 +3056,10 @@ else:
if not PkgSkip("PANDATOOL"):
CopyAllFiles(GetOutputDir()+"/plugins/", "pandatool/src/scripts/", ".mel")
CopyAllFiles(GetOutputDir()+"/plugins/", "pandatool/src/scripts/", ".ms")
-if not PkgSkip("PYTHON") and os.path.isdir(GetThirdpartyBase()+"/Pmw"):
- CopyTree(GetOutputDir()+'/Pmw', GetThirdpartyBase()+'/Pmw')
+
+if not PkgSkip("PYTHON") and os.path.isdir(GetThirdpartyBase() + "/Pmw"):
+ CopyTree(GetOutputDir() + "/Pmw", GetThirdpartyBase() + "/Pmw", exclude=["Pmw_1_3", "Pmw_1_3_3"])
+
ConditionalWriteFile(GetOutputDir()+'/include/ctl3d.h', '/* dummy file to make MAX happy */')
# Since Eigen is included by all sorts of core headers, as a convenience
@@ -6322,7 +6300,7 @@ def ParallelMake(tasklist):
# Create the workers
for slave in range(THREADCOUNT):
th = threading.Thread(target=BuildWorker, args=[taskqueue, donequeue])
- th.setDaemon(1)
+ th.daemon = True
th.start()
# Feed tasks to the workers.
tasksqueued = 0
diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py
index c01d27461a..745db73813 100644
--- a/makepanda/makepandacore.py
+++ b/makepanda/makepandacore.py
@@ -28,7 +28,7 @@ SUFFIX_LIB = [".lib",".ilb"]
VCS_DIRS = set(["CVS", "CVSROOT", ".git", ".hg", "__pycache__"])
VCS_FILES = set([".cvsignore", ".gitignore", ".gitmodules", ".hgignore"])
STARTTIME = time.time()
-MAINTHREAD = threading.currentThread()
+MAINTHREAD = threading.current_thread()
OUTPUTDIR = "built"
CUSTOM_OUTPUTDIR = False
THIRDPARTYBASE = None
@@ -242,7 +242,7 @@ def ProgressOutput(progress, msg, target = None):
sys.stdout.flush()
sys.stderr.flush()
prefix = ""
- thisthread = threading.currentThread()
+ thisthread = threading.current_thread()
if thisthread is MAINTHREAD:
if progress is None:
prefix = ""
@@ -272,7 +272,7 @@ def ProgressOutput(progress, msg, target = None):
def exit(msg = ""):
sys.stdout.flush()
sys.stderr.flush()
- if threading.currentThread() == MAINTHREAD:
+ if threading.current_thread() == MAINTHREAD:
SaveDependencyCache()
print("Elapsed Time: " + PrettyTime(time.time() - STARTTIME))
print(msg)
@@ -404,8 +404,8 @@ def SetTarget(target, arch=None):
# 64-bit platforms were introduced in Android 21.
ANDROID_API = 21
else:
- # Default to the lowest API level supported by NDK r16.
- ANDROID_API = 14
+ # Default to the lowest API level still supported by Google.
+ ANDROID_API = 19
# Determine the prefix for our gcc tools, eg. arm-linux-androideabi-gcc
global ANDROID_ABI, ANDROID_TRIPLE
@@ -433,6 +433,7 @@ def SetTarget(target, arch=None):
else:
exit('Android architecture must be arm, armv7a, aarch64, mips, mips64, x86 or x86_64')
+ ANDROID_TRIPLE += str(ANDROID_API)
TOOLCHAIN_PREFIX = ANDROID_TRIPLE + '-'
elif target == 'linux':
@@ -1556,8 +1557,8 @@ def PkgConfigGetIncDirs(pkgname, tool = "pkg-config"):
else:
handle = os.popen(LocateBinary(tool) + " --cflags")
result = handle.read().strip()
- if len(result) == 0: return []
handle.close()
+ if len(result) == 0: return []
dirs = []
for opt in result.split(" "):
if (opt.startswith("-I")):
@@ -2544,15 +2545,6 @@ def SdkLocateAndroid():
SDK["SYSROOT"] = os.path.join(ndk_root, 'platforms', 'android-%s' % (api), arch_dir).replace('\\', '/')
#IncDirectory("ALWAYS", os.path.join(SDK["SYSROOT"], 'usr', 'include'))
- # Starting with NDK r16, libc++ is the recommended STL to use.
- stdlibc = os.path.join(ndk_root, 'sources', 'cxx-stl', 'llvm-libc++')
- IncDirectory("ALWAYS", os.path.join(stdlibc, 'include').replace('\\', '/'))
- LibDirectory("ALWAYS", os.path.join(stdlibc, 'libs', abi).replace('\\', '/'))
-
- stl_lib = os.path.join(stdlibc, 'libs', abi, 'libc++_shared.so')
- LibName("ALWAYS", stl_lib.replace('\\', '/'))
- CopyFile(os.path.join(GetOutputDir(), 'lib', 'libc++_shared.so'), stl_lib)
-
# The Android support library polyfills C++ features not available in the
# STL that ships with Android.
support = os.path.join(ndk_root, 'sources', 'android', 'support', 'include')
@@ -2773,7 +2765,7 @@ def SetupVisualStudioEnviron():
elif not win_kit.endswith('\\'):
win_kit += '\\'
- for vnum in 10150, 10240, 10586, 14393, 15063, 16299, 17134, 17763, 18362:
+ for vnum in 10150, 10240, 10586, 14393, 15063, 16299, 17134, 17763, 18362, 19041:
version = "10.0.{0}.0".format(vnum)
if os.path.isfile(win_kit + "Include\\" + version + "\\ucrt\\assert.h"):
print("Using Universal CRT %s" % (version))
@@ -2961,12 +2953,7 @@ def SetupBuildEnvironment(compiler):
# Now extract the preprocessor's include directories.
cmd = GetCXX() + " -x c++ -v -E " + os.devnull
- if "ANDROID_NDK" in SDK:
- ndk_dir = SDK["ANDROID_NDK"].replace('\\', '/')
- cmd += ' -isystem %s/sysroot/usr/include' % (ndk_dir)
- cmd += ' -isystem %s/sysroot/usr/include/%s' % (ndk_dir, SDK["ANDROID_TRIPLE"])
- else:
- cmd += sysroot_flag
+ cmd += sysroot_flag
null = open(os.devnull, 'w')
handle = subprocess.Popen(cmd, stdout=null, stderr=subprocess.PIPE, shell=True)
@@ -3108,13 +3095,16 @@ def CopyAllHeaders(dir, skip=[]):
WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
JustBuilt([dstfile], [srcfile])
-def CopyTree(dstdir, srcdir, omitVCS=True):
+def CopyTree(dstdir, srcdir, omitVCS=True, exclude=()):
if os.path.isdir(dstdir):
source_entries = os.listdir(srcdir)
for entry in source_entries:
srcpth = os.path.join(srcdir, entry)
dstpth = os.path.join(dstdir, entry)
+ if entry in exclude:
+ continue
+
if os.path.islink(srcpth) or os.path.isfile(srcpth):
if not omitVCS or entry not in VCS_FILES:
CopyFile(dstpth, srcpth)
@@ -3124,7 +3114,7 @@ def CopyTree(dstdir, srcdir, omitVCS=True):
# Delete files in dstdir that are not in srcdir.
for entry in os.listdir(dstdir):
- if entry not in source_entries:
+ if entry not in source_entries or entry in exclude:
path = os.path.join(dstdir, entry)
if os.path.islink(path) or os.path.isfile(path):
os.remove(path)
@@ -3140,6 +3130,13 @@ def CopyTree(dstdir, srcdir, omitVCS=True):
if subprocess.call(['cp', '-R', '-f', srcdir, dstdir]) != 0:
exit("Copy failed.")
+ for entry in exclude:
+ path = os.path.join(dstdir, entry)
+ if os.path.islink(path) or os.path.isfile(path):
+ os.remove(path)
+ elif os.path.isdir(path):
+ shutil.rmtree(path)
+
if omitVCS:
DeleteVCS(dstdir)
@@ -3270,6 +3267,22 @@ def WriteResourceFile(basename, **kwargs):
return basename
+def GenerateEmbeddedStringFile(string_name, data):
+ yield 'extern const char %s[] = {\n' % (string_name)
+ i = 0
+ for byte in data:
+ if i == 0:
+ yield ' '
+
+ yield ' 0x%02x,' % (byte)
+ i += 1
+ if i >= 12:
+ yield '\n'
+ i = 0
+
+ yield '\n};\n'
+
+
def WriteEmbeddedStringFile(basename, inputs, string_name=None):
if os.path.splitext(basename)[1] not in SUFFIX_INC:
basename += '.cxx'
@@ -3294,20 +3307,7 @@ def WriteEmbeddedStringFile(basename, inputs, string_name=None):
data.append(0)
- output = 'extern const char %s[] = {\n' % (string_name)
-
- i = 0
- for byte in data:
- if i == 0:
- output += ' '
-
- output += ' 0x%02x,' % (byte)
- i += 1
- if i >= 12:
- output += '\n'
- i = 0
-
- output += '\n};\n'
+ output = ''.join(GenerateEmbeddedStringFile(string_name, data))
ConditionalWriteFile(target, output)
return target
@@ -3478,7 +3478,8 @@ def UpdatePythonVersionInfoFile(new_info):
json_data = []
if os.path.isfile(json_file) and not PkgSkip("PYTHON"):
try:
- json_data = json.load(open(json_file, 'r'))
+ with open(json_file, 'r') as fh:
+ json_data = json.load(fh)
except:
json_data = []
@@ -3498,7 +3499,9 @@ def UpdatePythonVersionInfoFile(new_info):
if VERBOSE:
print("Writing %s" % (json_file))
- json.dump(json_data, open(json_file, 'w'), indent=4)
+
+ with open(json_file, 'w') as fh:
+ json.dump(json_data, fh, indent=4)
def ReadPythonVersionInfoFile():
diff --git a/panda/src/bullet/bulletDebugNode.I b/panda/src/bullet/bulletDebugNode.I
index eae681aba5..e29863669f 100644
--- a/panda/src/bullet/bulletDebugNode.I
+++ b/panda/src/bullet/bulletDebugNode.I
@@ -20,7 +20,7 @@ INLINE BulletDebugNode::
}
/**
- *
+ * If true, displays collision shapes in wireframe mode.
*/
INLINE void BulletDebugNode::
show_wireframe(bool show) {
@@ -39,7 +39,8 @@ get_show_wireframe() const {
}
/**
- *
+ * If true, display limits defined for constraints, e.g. a pivot axis or maximum
+ * amplitude.
*/
INLINE void BulletDebugNode::
show_constraints(bool show) {
@@ -58,7 +59,7 @@ get_show_constraints() const {
}
/**
- *
+ * If true, displays axis aligned bounding boxes for objects.
*/
INLINE void BulletDebugNode::
show_bounding_boxes(bool show) {
@@ -77,7 +78,7 @@ get_show_bounding_boxes() const {
}
/**
- *
+ * If true, displays normal vectors for triangle mesh and heightfield faces.
*/
INLINE void BulletDebugNode::
show_normals(bool show) {
@@ -92,4 +93,4 @@ INLINE bool BulletDebugNode::
get_show_normals() const {
return _drawer._normals;
-}
\ No newline at end of file
+}
diff --git a/panda/src/bullet/bulletDebugNode.cxx b/panda/src/bullet/bulletDebugNode.cxx
index 8b758c59ca..7bc26ae1c1 100644
--- a/panda/src/bullet/bulletDebugNode.cxx
+++ b/panda/src/bullet/bulletDebugNode.cxx
@@ -48,6 +48,7 @@ BulletDebugNode(const char *name) : PandaNode(name) {
set_bounds(bounds);
set_final(true);
set_overall_hidden(true);
+ set_renderable();
}
/**
@@ -151,17 +152,6 @@ draw_mask_changed() {
}
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool BulletDebugNode::
-is_renderable() const {
- return true;
-}
-
/**
* Adds the node's contents to the CullResult we are building up during the
* cull traversal, so that it will be drawn at render time. For most nodes
diff --git a/panda/src/bullet/bulletDebugNode.h b/panda/src/bullet/bulletDebugNode.h
index 720e26fbd3..471c059f99 100644
--- a/panda/src/bullet/bulletDebugNode.h
+++ b/panda/src/bullet/bulletDebugNode.h
@@ -55,7 +55,6 @@ public:
virtual bool safe_to_combine_children() const;
virtual bool safe_to_flatten_below() const;
- virtual bool is_renderable() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
private:
diff --git a/panda/src/bullet/bulletPersistentManifold.cxx b/panda/src/bullet/bulletPersistentManifold.cxx
index 9876ff35ee..ba1ca9a2e3 100644
--- a/panda/src/bullet/bulletPersistentManifold.cxx
+++ b/panda/src/bullet/bulletPersistentManifold.cxx
@@ -49,6 +49,8 @@ get_contact_processing_threshold() const {
*/
void BulletPersistentManifold::
clear_manifold() {
+ nassertv_always(_manifold != nullptr);
+
LightMutexHolder holder(BulletWorld::get_global_lock());
_manifold->clearManifold();
@@ -59,6 +61,8 @@ clear_manifold() {
*/
PandaNode *BulletPersistentManifold::
get_node0() {
+ nassertr_always(_manifold != nullptr, nullptr);
+
LightMutexHolder holder(BulletWorld::get_global_lock());
#if BT_BULLET_VERSION >= 281
@@ -75,6 +79,8 @@ get_node0() {
*/
PandaNode *BulletPersistentManifold::
get_node1() {
+ nassertr_always(_manifold != nullptr, nullptr);
+
LightMutexHolder holder(BulletWorld::get_global_lock());
#if BT_BULLET_VERSION >= 281
@@ -91,6 +97,8 @@ get_node1() {
*/
int BulletPersistentManifold::
get_num_manifold_points() const {
+ nassertr_always(_manifold != nullptr, 0);
+
LightMutexHolder holder(BulletWorld::get_global_lock());
return _manifold->getNumContacts();
@@ -99,11 +107,8 @@ get_num_manifold_points() const {
/**
*
*/
-BulletManifoldPoint *BulletPersistentManifold::
+BulletManifoldPoint BulletPersistentManifold::
get_manifold_point(int idx) const {
LightMutexHolder holder(BulletWorld::get_global_lock());
-
- nassertr(idx < _manifold->getNumContacts(), nullptr)
-
- return new BulletManifoldPoint(_manifold->getContactPoint(idx));
+ return BulletManifoldPoint(_manifold->getContactPoint(idx));
}
diff --git a/panda/src/bullet/bulletPersistentManifold.h b/panda/src/bullet/bulletPersistentManifold.h
index 0b994c6bac..13954ede0c 100644
--- a/panda/src/bullet/bulletPersistentManifold.h
+++ b/panda/src/bullet/bulletPersistentManifold.h
@@ -34,7 +34,7 @@ PUBLISHED:
PandaNode *get_node1();
int get_num_manifold_points() const;
- BulletManifoldPoint *get_manifold_point(int idx) const;
+ BulletManifoldPoint get_manifold_point(int idx) const;
MAKE_SEQ(get_manifold_points, get_num_manifold_points, get_manifold_point);
PN_stdfloat get_contact_breaking_threshold() const;
diff --git a/panda/src/bullet/bulletWorld.cxx b/panda/src/bullet/bulletWorld.cxx
index a76b0e8007..f422df2f86 100644
--- a/panda/src/bullet/bulletWorld.cxx
+++ b/panda/src/bullet/bulletWorld.cxx
@@ -125,7 +125,7 @@ BulletWorld() {
// Some prefered settings
_world->getDispatchInfo().m_enableSPU = true; // default: true
_world->getDispatchInfo().m_useContinuous = true; // default: true
- _world->getSolverInfo().m_splitImpulse = false; // default: false
+ _world->getSolverInfo().m_splitImpulse = bullet_split_impulse;
_world->getSolverInfo().m_numIterations = bullet_solver_iterations;
}
@@ -1064,14 +1064,14 @@ contact_test_pair(PandaNode *node0, PandaNode *node1) const {
/**
*
*/
-BulletPersistentManifold *BulletWorld::
+BulletPersistentManifold BulletWorld::
get_manifold(int idx) const {
LightMutexHolder holder(get_global_lock());
- nassertr(idx < _dispatcher->getNumManifolds(), nullptr);
+ nassertr(idx < _dispatcher->getNumManifolds(), BulletPersistentManifold(nullptr));
btPersistentManifold *ptr = _dispatcher->getManifoldByIndexInternal(idx);
- return (ptr) ? new BulletPersistentManifold(ptr) : nullptr;
+ return BulletPersistentManifold(ptr);
}
/**
diff --git a/panda/src/bullet/bulletWorld.h b/panda/src/bullet/bulletWorld.h
index b655e0b349..83c0ac634c 100644
--- a/panda/src/bullet/bulletWorld.h
+++ b/panda/src/bullet/bulletWorld.h
@@ -127,7 +127,7 @@ PUBLISHED:
// Manifolds
int get_num_manifolds() const;
- BulletPersistentManifold *get_manifold(int idx) const;
+ BulletPersistentManifold get_manifold(int idx) const;
MAKE_SEQ(get_manifolds, get_num_manifolds, get_manifold);
// Collision filtering
diff --git a/panda/src/bullet/config_bullet.cxx b/panda/src/bullet/config_bullet.cxx
index e4627c931e..caee5fb2c6 100644
--- a/panda/src/bullet/config_bullet.cxx
+++ b/panda/src/bullet/config_bullet.cxx
@@ -100,6 +100,11 @@ PRC_DESC("Specifies if events should be send when new contacts are "
"contact events might create more load on the event queue "
"then you might want! Default value is FALSE."));
+ConfigVariableBool bullet_split_impulse
+("bullet-split-impulse", false,
+PRC_DESC("Penetrating recovery won't add momentum. "
+ "btContactSolverInfo::m_splitImpulse. Default value is false."));
+
ConfigVariableInt bullet_solver_iterations
("bullet-solver-iterations", 10,
PRC_DESC("Specifies the number of iterations for the Bullet contact "
diff --git a/panda/src/bullet/config_bullet.h b/panda/src/bullet/config_bullet.h
index b9fe8dcc05..cf74435d4b 100644
--- a/panda/src/bullet/config_bullet.h
+++ b/panda/src/bullet/config_bullet.h
@@ -33,6 +33,7 @@ extern ConfigVariableEnum bullet_broadphase_al
extern ConfigVariableEnum bullet_filter_algorithm;
extern ConfigVariableDouble bullet_sap_extents;
extern ConfigVariableBool bullet_enable_contact_events;
+extern ConfigVariableBool bullet_split_impulse;
extern ConfigVariableInt bullet_solver_iterations;
extern ConfigVariableBool bullet_additional_damping;
extern ConfigVariableDouble bullet_additional_damping_linear_factor;
diff --git a/panda/src/chan/partBundle.cxx b/panda/src/chan/partBundle.cxx
index ddd31898ee..2cd6eca594 100644
--- a/panda/src/chan/partBundle.cxx
+++ b/panda/src/chan/partBundle.cxx
@@ -599,6 +599,10 @@ do_bind_anim(AnimControl *control, AnimBundle *anim,
return false;
}
+ // Grabbing the lock early prevents any other thread in stage 0 from also
+ // trying to modify the channel list at the same time.
+ CDLockedReader cdata(_cycler);
+
plist holes;
int channel_index = 0;
pick_channel_index(holes, channel_index);
@@ -616,7 +620,6 @@ do_bind_anim(AnimControl *control, AnimBundle *anim,
subset.is_include_empty(), bound_joints, subset);
control->setup_anim(this, anim, channel_index, bound_joints);
- CDReader cdata(_cycler);
determine_effective_channels(cdata);
return true;
diff --git a/panda/src/collide/collisionLevelState.I b/panda/src/collide/collisionLevelState.I
index db618f99e7..1001f6d08d 100644
--- a/panda/src/collide/collisionLevelState.I
+++ b/panda/src/collide/collisionLevelState.I
@@ -30,9 +30,9 @@ CollisionLevelState(const NodePath &node_path) :
*/
template
INLINE CollisionLevelState::
-CollisionLevelState(const CollisionLevelState &parent, PandaNode *child) :
+CollisionLevelState(const CollisionLevelState &parent, const PandaNode::DownConnection &child, MaskType mask) :
CollisionLevelStateBase(parent, child),
- _current(parent._current)
+ _current(mask)
{
}
#endif // CPPPARSER
@@ -101,21 +101,21 @@ prepare_collider(const ColliderDef &def, const NodePath &root) {
template
bool CollisionLevelState::
any_in_bounds() {
-#ifndef NDEBUG
+#ifdef NDEBUG
+ const bool is_spam = false;
+#else
+ const bool is_spam = collide_cat.is_spam();
+#endif
int indent_level = 0;
- if (collide_cat.is_spam()) {
+ if (is_spam) {
indent_level = _node_path.get_num_nodes() * 2;
collide_cat.spam();
indent(collide_cat.spam(false), indent_level)
<< "Considering " << _node_path.get_node_path() << "\n";
}
-#endif // NDEBUG
- PandaNode *pnode = node();
-
- CPT(BoundingVolume) node_bv = pnode->get_bounds();
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- const GeometricBoundingVolume *node_gbv = (const GeometricBoundingVolume *)node_bv.p();
+ if (_node_gbv != nullptr) {
+ PandaNode *pnode = node();
CollideMask this_mask = pnode->get_net_collide_mask();
int num_colliders = get_num_colliders();
@@ -131,13 +131,11 @@ any_in_bounds() {
// Also don't test a node with itself, or with any of its
// descendants.
if (pnode == cnode) {
-#ifndef NDEBUG
- if (collide_cat.is_spam()) {
+ if (is_spam) {
indent(collide_cat.spam(false), indent_level)
<< "Not comparing " << c << " to " << _node_path
<< " (same node)\n";
}
-#endif // NDEBUG
} else {
// There are bits in common, and it's not the same instance, so go
@@ -148,16 +146,14 @@ any_in_bounds() {
is_in = true; // If there's no bounding volume, we're implicitly in.
if (col_gbv != nullptr) {
- is_in = (node_gbv->contains(col_gbv) != 0);
+ is_in = (_node_gbv->contains(col_gbv) != 0);
_node_volume_pcollector.add_level(1);
-#ifndef NDEBUG
- if (collide_cat.is_spam()) {
+ if (is_spam) {
indent(collide_cat.spam(false), indent_level)
<< "Comparing " << c << ": " << *col_gbv
- << " to " << *node_gbv << ", is_in = " << is_in << "\n";
+ << " to " << *_node_gbv << ", is_in = " << is_in << "\n";
}
-#endif // NDEBUG
}
}
}
@@ -171,8 +167,7 @@ any_in_bounds() {
}
}
-#ifndef NDEBUG
- if (collide_cat.is_spam()) {
+ if (is_spam) {
int num_active_colliders = 0;
int num_colliders = get_num_colliders();
for (int c = 0; c < num_colliders; c++) {
@@ -201,11 +196,121 @@ any_in_bounds() {
collide_cat.spam(false)
<< "\n";
}
-#endif // NDEBUG
return has_any_collider();
}
#endif // CPPPARSER
+#ifndef CPPPARSER
+/**
+ * Checks the bounding volume of the given child of the current node against
+ * each of our colliders. Returns a mask indicating which colliders are inside
+ * of the bounding volume.
+ */
+template
+MaskType CollisionLevelState::
+get_child_mask(const PandaNode::DownConnection &child) const {
+ PandaNode *pnode = child.get_child();
+#ifdef NDEBUG
+ const bool is_spam = false;
+#else
+ const bool is_spam = collide_cat.is_spam();
+#endif
+ int indent_level = 0;
+ if (is_spam) {
+ indent_level = (_node_path.get_num_nodes() + 1) * 2;
+ collide_cat.spam();
+ indent(collide_cat.spam(false), indent_level)
+ << "Considering " << _node_path << "/" << pnode->get_name() << "\n";
+ }
+
+ MaskType mask = _current;
+
+ const GeometricBoundingVolume *node_gbv = child.get_bounds();
+ if (node_gbv != nullptr) {
+ CollideMask node_mask = child.get_net_collide_mask();
+
+ int num_colliders = get_num_colliders();
+ for (int c = 0; c < num_colliders; c++) {
+ if (mask.get_bit(c)) {
+ CollisionNode *cnode = get_collider_node(c);
+ bool is_in = false;
+
+ // Don't even bother testing the bounding volume if there are no
+ // collide bits in common between our collider and this node.
+ CollideMask from_mask = cnode->get_from_collide_mask() & _include_mask;
+ if (!(from_mask & node_mask).is_zero()) {
+ // Also don't test a node with itself, or with any of its
+ // descendants.
+ if (pnode == cnode) {
+ if (is_spam) {
+ indent(collide_cat.spam(false), indent_level)
+ << "Not comparing " << c << " to " << _node_path << "/"
+ << pnode->get_name() << " (same node)\n";
+ }
+
+ } else {
+ // There are bits in common, and it's not the same instance, so go
+ // ahead and try the bounding volume.
+ const GeometricBoundingVolume *col_gbv =
+ get_local_bound(c);
+
+ is_in = true; // If there's no bounding volume, we're implicitly in.
+
+ if (col_gbv != nullptr) {
+ is_in = (node_gbv->contains(col_gbv) != 0);
+ _node_volume_pcollector.add_level(1);
+
+ if (is_spam) {
+ indent(collide_cat.spam(false), indent_level)
+ << "Comparing " << c << ": " << *col_gbv
+ << " to " << *node_gbv << ", is_in = " << is_in << "\n";
+ }
+ }
+ }
+ }
+
+ if (!is_in) {
+ // This collider cannot intersect with any geometry at this node or
+ // below.
+ mask.clear_bit(c);
+ }
+ }
+ }
+ }
+
+ if (is_spam) {
+ int num_active_colliders = 0;
+ int num_colliders = get_num_colliders();
+ for (int c = 0; c < num_colliders; c++) {
+ if (mask.get_bit(c)) {
+ num_active_colliders++;
+ }
+ }
+
+ collide_cat.spam();
+ indent(collide_cat.spam(false), indent_level)
+ << _node_path.get_node_path() << "/" << pnode->get_name() << " has "
+ << num_active_colliders << " interested colliders";
+ if (num_colliders != 0) {
+ collide_cat.spam(false)
+ << " (";
+ for (int c = 0; c < num_colliders; c++) {
+ if (mask.get_bit(c)) {
+ CollisionNode *cnode = get_collider_node(c);
+ collide_cat.spam(false)
+ << " " << c << ". " << cnode->get_name();
+ }
+ }
+ collide_cat.spam(false)
+ << " )";
+ }
+ collide_cat.spam(false)
+ << "\n";
+ }
+ return mask;
+}
+#endif // CPPPARSER
+
#ifndef CPPPARSER
/**
* Applies the inverse transform from the current node, if any, onto all the
diff --git a/panda/src/collide/collisionLevelState.h b/panda/src/collide/collisionLevelState.h
index f06a025f1e..851b908a8c 100644
--- a/panda/src/collide/collisionLevelState.h
+++ b/panda/src/collide/collisionLevelState.h
@@ -37,7 +37,8 @@ public:
#ifndef CPPPARSER
INLINE CollisionLevelState(const NodePath &node_path);
INLINE CollisionLevelState(const CollisionLevelState &parent,
- PandaNode *child);
+ const PandaNode::DownConnection &child,
+ MaskType mask);
INLINE CollisionLevelState(const CollisionLevelState ©);
INLINE void operator = (const CollisionLevelState ©);
@@ -45,6 +46,7 @@ public:
INLINE void prepare_collider(const ColliderDef &def, const NodePath &root);
bool any_in_bounds();
+ MaskType get_child_mask(const PandaNode::DownConnection &child) const;
bool apply_transform();
INLINE static bool has_max_colliders();
diff --git a/panda/src/collide/collisionLevelStateBase.I b/panda/src/collide/collisionLevelStateBase.I
index 20923a9c22..5b649838f7 100644
--- a/panda/src/collide/collisionLevelStateBase.I
+++ b/panda/src/collide/collisionLevelStateBase.I
@@ -18,7 +18,8 @@ INLINE CollisionLevelStateBase::
CollisionLevelStateBase(const NodePath &node_path) :
_node_path(node_path),
_colliders(get_class_type()),
- _include_mask(CollideMask::all_on())
+ _include_mask(CollideMask::all_on()),
+ _node_gbv(node_path.node()->get_bounds()->as_geometric_bounding_volume())
{
}
@@ -30,7 +31,21 @@ CollisionLevelStateBase(const CollisionLevelStateBase &parent, PandaNode *child)
_node_path(parent._node_path, child),
_colliders(parent._colliders),
_include_mask(parent._include_mask),
- _local_bounds(parent._local_bounds)
+ _local_bounds(parent._local_bounds),
+ _node_gbv(child->get_bounds()->as_geometric_bounding_volume())
+{
+}
+
+/**
+ * This constructor goes to the next child node in the traversal.
+ */
+INLINE CollisionLevelStateBase::
+CollisionLevelStateBase(const CollisionLevelStateBase &parent, const PandaNode::DownConnection &child) :
+ _node_path(parent._node_path, child.get_child()),
+ _colliders(parent._colliders),
+ _include_mask(parent._include_mask),
+ _local_bounds(parent._local_bounds),
+ _node_gbv(child.get_bounds())
{
}
@@ -43,7 +58,8 @@ CollisionLevelStateBase(const CollisionLevelStateBase ©) :
_colliders(copy._colliders),
_include_mask(copy._include_mask),
_local_bounds(copy._local_bounds),
- _parent_bounds(copy._parent_bounds)
+ _parent_bounds(copy._parent_bounds),
+ _node_gbv(copy._node_gbv)
{
}
@@ -57,6 +73,7 @@ operator = (const CollisionLevelStateBase ©) {
_include_mask = copy._include_mask;
_local_bounds = copy._local_bounds;
_parent_bounds = copy._parent_bounds;
+ _node_gbv = copy._node_gbv;
}
/**
@@ -113,6 +130,14 @@ get_collider_node_path(int n) const {
return _colliders[n]._node_path;
}
+/**
+ * Returns the bounding volume of the current node.
+ */
+INLINE const GeometricBoundingVolume *CollisionLevelStateBase::
+get_node_bound() const {
+ return _node_gbv;
+}
+
/**
* Returns the bounding volume of the indicated collider, transformed into the
* current node's transform space.
diff --git a/panda/src/collide/collisionLevelStateBase.h b/panda/src/collide/collisionLevelStateBase.h
index 4c2abccb58..f84d492965 100644
--- a/panda/src/collide/collisionLevelStateBase.h
+++ b/panda/src/collide/collisionLevelStateBase.h
@@ -52,6 +52,8 @@ public:
INLINE CollisionLevelStateBase(const NodePath &node_path);
INLINE CollisionLevelStateBase(const CollisionLevelStateBase &parent,
PandaNode *child);
+ INLINE CollisionLevelStateBase(const CollisionLevelStateBase &parent,
+ const PandaNode::DownConnection &child);
INLINE CollisionLevelStateBase(const CollisionLevelStateBase ©);
INLINE void operator = (const CollisionLevelStateBase ©);
@@ -67,6 +69,7 @@ public:
INLINE const CollisionSolid *get_collider(int n) const;
INLINE CollisionNode *get_collider_node(int n) const;
INLINE NodePath get_collider_node_path(int n) const;
+ INLINE const GeometricBoundingVolume *get_node_bound() const;
INLINE const GeometricBoundingVolume *get_local_bound(int n) const;
INLINE const GeometricBoundingVolume *get_parent_bound(int n) const;
@@ -80,6 +83,8 @@ protected:
Colliders _colliders;
CollideMask _include_mask;
+ const GeometricBoundingVolume *_node_gbv = nullptr;
+
typedef PTA(CPT(GeometricBoundingVolume)) BoundingVolumes;
BoundingVolumes _local_bounds;
BoundingVolumes _parent_bounds;
diff --git a/panda/src/collide/collisionNode.cxx b/panda/src/collide/collisionNode.cxx
index 1196ba20c7..2df893ef18 100644
--- a/panda/src/collide/collisionNode.cxx
+++ b/panda/src/collide/collisionNode.cxx
@@ -43,6 +43,7 @@ CollisionNode(const std::string &name) :
_collider_sort(0)
{
set_cull_callback();
+ set_renderable();
// CollisionNodes are hidden by default.
set_overall_hidden(true);
@@ -186,11 +187,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
CPT(CollisionSolid) solid = (*si).get_read_pointer();
PT(PandaNode) node = solid->get_viz(trav, data, false);
if (node != nullptr) {
- CullTraverserData next_data(data, node);
-
// We don't want to inherit the render state from above for these guys.
- next_data._state = RenderState::make_empty();
- trav->traverse(next_data);
+ trav->traverse_down(data, node, data._net_transform, RenderState::make_empty());
}
}
@@ -208,12 +206,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
CPT(CollisionSolid) solid = (*si).get_read_pointer();
PT(PandaNode) node = solid->get_viz(trav, data, false);
if (node != nullptr) {
- CullTraverserData next_data(data, node);
-
- next_data._net_transform =
- next_data._net_transform->compose(transform);
- next_data._state = get_last_pos_state();
- trav->traverse(next_data);
+ trav->traverse_down(data, node,
+ data._net_transform->compose(transform), get_last_pos_state());
}
}
}
@@ -223,17 +217,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool CollisionNode::
-is_renderable() const {
- return true;
-}
-
/**
* A simple downcast check. Returns true if this kind of node happens to
* inherit from CollisionNode, false otherwise.
diff --git a/panda/src/collide/collisionNode.h b/panda/src/collide/collisionNode.h
index 8dd46f6a28..fee71fce78 100644
--- a/panda/src/collide/collisionNode.h
+++ b/panda/src/collide/collisionNode.h
@@ -43,7 +43,6 @@ public:
virtual CollideMask get_legal_collide_mask() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual bool is_collision_node() const;
virtual void output(std::ostream &out) const;
diff --git a/panda/src/collide/collisionTraverser.cxx b/panda/src/collide/collisionTraverser.cxx
index 2feb236241..23459d88f3 100644
--- a/panda/src/collide/collisionTraverser.cxx
+++ b/panda/src/collide/collisionTraverser.cxx
@@ -288,7 +288,9 @@ traverse(const NodePath &root) {
#ifdef DO_PSTATS
PStatTimer pass_timer(get_pass_collector(pass));
#endif
- r_traverse_single(level_states[pass], pass);
+ if (level_states[pass].any_in_bounds()) {
+ r_traverse_single(level_states[pass], pass);
+ }
}
}
}
@@ -562,22 +564,13 @@ prepare_colliders_single(CollisionTraverser::LevelStatesSingle &level_states,
*/
void CollisionTraverser::
r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
- if (!level_state.any_in_bounds()) {
- return;
- }
if (!level_state.apply_transform()) {
return;
}
PandaNode *node = level_state.node();
if (node->is_collision_node()) {
- CollisionNode *cnode;
- DCAST_INTO_V(cnode, node);
- CPT(BoundingVolume) node_bv = cnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ CollisionNode *cnode = (CollisionNode *)node;
CollisionEntry entry;
entry._into_node = cnode;
@@ -589,13 +582,14 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
int num_colliders = level_state.get_num_colliders();
for (int c = 0; c < num_colliders; ++c) {
if (level_state.has_collider(c)) {
- entry._from_node = level_state.get_collider_node(c);
+ CollisionNode *from_node = level_state.get_collider_node(c);
- if ((entry._from_node->get_from_collide_mask() &
+ if ((from_node->get_from_collide_mask() &
cnode->get_into_collide_mask()) != 0) {
#ifdef DO_PSTATS
// PStatTimer collide_timer(_solid_collide_collectors[pass]);
#endif
+ entry._from_node = from_node;
entry._from_node_path = level_state.get_collider_node_path(c);
entry._from = level_state.get_collider(c);
@@ -603,7 +597,7 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -616,13 +610,7 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
}
#endif
- GeomNode *gnode;
- DCAST_INTO_V(gnode, node);
- CPT(BoundingVolume) node_bv = gnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ GeomNode *gnode = (GeomNode *)node;
CollisionEntry entry;
entry._into_node = gnode;
@@ -648,7 +636,7 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -658,9 +646,14 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
// If it's a switch node or sequence node, visit just the one visible
// child.
int index = node->get_visible_child();
- if (index >= 0 && index < node->get_num_children()) {
- CollisionLevelStateSingle next_state(level_state, node->get_child(index));
- r_traverse_single(next_state, pass);
+ PandaNode::Children children = node->get_children();
+ if (index >= 0 && index < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ CollisionLevelStateSingle::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateSingle next_state(level_state, child, mask);
+ r_traverse_single(next_state, pass);
+ }
}
} else if (node->is_lod_node()) {
@@ -673,12 +666,16 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateSingle next_state(level_state, children.get_child(i));
- if (i != index) {
- next_state.set_include_mask(next_state.get_include_mask() &
- ~GeomNode::get_default_collide_mask());
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateSingle::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateSingle next_state(level_state, child, mask);
+ if (i != index) {
+ next_state.set_include_mask(next_state.get_include_mask() &
+ ~GeomNode::get_default_collide_mask());
+ }
+ r_traverse_single(next_state, pass);
}
- r_traverse_single(next_state, pass);
}
} else {
@@ -686,8 +683,12 @@ r_traverse_single(CollisionLevelStateSingle &level_state, size_t pass) {
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateSingle next_state(level_state, children.get_child(i));
- r_traverse_single(next_state, pass);
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateSingle::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateSingle next_state(level_state, child, mask);
+ r_traverse_single(next_state, pass);
+ }
}
}
}
@@ -773,22 +774,13 @@ prepare_colliders_double(CollisionTraverser::LevelStatesDouble &level_states,
*/
void CollisionTraverser::
r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
- if (!level_state.any_in_bounds()) {
- return;
- }
if (!level_state.apply_transform()) {
return;
}
PandaNode *node = level_state.node();
if (node->is_collision_node()) {
- CollisionNode *cnode;
- DCAST_INTO_V(cnode, node);
- CPT(BoundingVolume) node_bv = cnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ CollisionNode *cnode = (CollisionNode *)node;
CollisionEntry entry;
entry._into_node = cnode;
@@ -814,7 +806,7 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -827,13 +819,7 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
}
#endif
- GeomNode *gnode;
- DCAST_INTO_V(gnode, node);
- CPT(BoundingVolume) node_bv = gnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ GeomNode *gnode = (GeomNode *)node;
CollisionEntry entry;
entry._into_node = gnode;
@@ -859,7 +845,7 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -869,9 +855,14 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
// If it's a switch node or sequence node, visit just the one visible
// child.
int index = node->get_visible_child();
- if (index >= 0 && index < node->get_num_children()) {
- CollisionLevelStateDouble next_state(level_state, node->get_child(index));
- r_traverse_double(next_state, pass);
+ PandaNode::Children children = node->get_children();
+ if (index >= 0 && index < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ CollisionLevelStateDouble::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateDouble next_state(level_state, child, mask);
+ r_traverse_double(next_state, pass);
+ }
}
} else if (node->is_lod_node()) {
@@ -880,16 +871,20 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
// visit all other levels without GeomNode::get_default_collide_mask(),
// allowing only collision with CollisionNodes and special geometry under
// higher levels of detail.
- int index = DCAST(LODNode, node)->get_lowest_switch();
+ int index = ((LODNode *)node)->get_lowest_switch();
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateDouble next_state(level_state, children.get_child(i));
- if (i != index) {
- next_state.set_include_mask(next_state.get_include_mask() &
- ~GeomNode::get_default_collide_mask());
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateDouble::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateDouble next_state(level_state, child, mask);
+ if (i != index) {
+ next_state.set_include_mask(next_state.get_include_mask() &
+ ~GeomNode::get_default_collide_mask());
+ }
+ r_traverse_double(next_state, pass);
}
- r_traverse_double(next_state, pass);
}
} else {
@@ -897,8 +892,12 @@ r_traverse_double(CollisionLevelStateDouble &level_state, size_t pass) {
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateDouble next_state(level_state, children.get_child(i));
- r_traverse_double(next_state, pass);
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateDouble::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateDouble next_state(level_state, child, mask);
+ r_traverse_double(next_state, pass);
+ }
}
}
}
@@ -984,22 +983,13 @@ prepare_colliders_quad(CollisionTraverser::LevelStatesQuad &level_states,
*/
void CollisionTraverser::
r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
- if (!level_state.any_in_bounds()) {
- return;
- }
if (!level_state.apply_transform()) {
return;
}
PandaNode *node = level_state.node();
if (node->is_collision_node()) {
- CollisionNode *cnode;
- DCAST_INTO_V(cnode, node);
- CPT(BoundingVolume) node_bv = cnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ CollisionNode *cnode = (CollisionNode *)node;
CollisionEntry entry;
entry._into_node = cnode;
@@ -1025,7 +1015,7 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -1038,13 +1028,7 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
}
#endif
- GeomNode *gnode;
- DCAST_INTO_V(gnode, node);
- CPT(BoundingVolume) node_bv = gnode->get_bounds();
- const GeometricBoundingVolume *node_gbv = nullptr;
- if (node_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- DCAST_INTO_V(node_gbv, node_bv);
- }
+ GeomNode *gnode = (GeomNode *)node;
CollisionEntry entry;
entry._into_node = gnode;
@@ -1070,7 +1054,7 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
entry,
level_state.get_parent_bound(c),
level_state.get_local_bound(c),
- node_gbv);
+ level_state.get_node_bound());
}
}
}
@@ -1080,9 +1064,14 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
// If it's a switch node or sequence node, visit just the one visible
// child.
int index = node->get_visible_child();
- if (index >= 0 && index < node->get_num_children()) {
- CollisionLevelStateQuad next_state(level_state, node->get_child(index));
- r_traverse_quad(next_state, pass);
+ PandaNode::Children children = node->get_children();
+ if (index >= 0 && index < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ CollisionLevelStateQuad::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateQuad next_state(level_state, child, mask);
+ r_traverse_quad(next_state, pass);
+ }
}
} else if (node->is_lod_node()) {
@@ -1091,16 +1080,20 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
// visit all other levels without GeomNode::get_default_collide_mask(),
// allowing only collision with CollisionNodes and special geometry under
// higher levels of detail.
- int index = DCAST(LODNode, node)->get_lowest_switch();
+ int index = ((LODNode *)node)->get_lowest_switch();
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateQuad next_state(level_state, children.get_child(i));
- if (i != index) {
- next_state.set_include_mask(next_state.get_include_mask() &
- ~GeomNode::get_default_collide_mask());
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateQuad::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateQuad next_state(level_state, child, mask);
+ if (i != index) {
+ next_state.set_include_mask(next_state.get_include_mask() &
+ ~GeomNode::get_default_collide_mask());
+ }
+ r_traverse_quad(next_state, pass);
}
- r_traverse_quad(next_state, pass);
}
} else {
@@ -1108,8 +1101,12 @@ r_traverse_quad(CollisionLevelStateQuad &level_state, size_t pass) {
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
- CollisionLevelStateQuad next_state(level_state, children.get_child(i));
- r_traverse_quad(next_state, pass);
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ CollisionLevelStateQuad::CurrentMask mask = level_state.get_child_mask(child);
+ if (!mask.is_zero()) {
+ CollisionLevelStateQuad next_state(level_state, child, mask);
+ r_traverse_quad(next_state, pass);
+ }
}
}
}
@@ -1162,10 +1159,7 @@ compare_collider_to_node(CollisionEntry &entry,
// CollisionNodes. We are already filtering out tests for a
// CollisionNode into itself.
CPT(BoundingVolume) solid_bv = entry._into->get_bounds();
- const GeometricBoundingVolume *solid_gbv = nullptr;
- if (solid_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
- solid_gbv = (const GeometricBoundingVolume *)solid_bv.p();
- }
+ const GeometricBoundingVolume *solid_gbv = solid_bv->as_geometric_bounding_volume();
compare_collider_to_solid(entry, from_node_gbv, solid_gbv);
}
@@ -1198,14 +1192,13 @@ compare_collider_to_geom_node(CollisionEntry &entry,
if (geom != nullptr) {
CPT(BoundingVolume) geom_bv = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv = nullptr;
- if (num_geoms > 1 &&
- geom_bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
+ if (num_geoms > 1) {
// Only bother to test against each geom's bounding volume if we
// have more than one geom in the node, as a slight optimization.
// (If the node contains just one geom, then the node's bounding
// volume, which we just tested, is the same as the geom's bounding
// volume.)
- DCAST_INTO_V(geom_gbv, geom_bv);
+ geom_gbv = geom_bv->as_geometric_bounding_volume();
}
compare_collider_to_geom(entry, geom, from_node_gbv, geom_gbv);
diff --git a/panda/src/collide/collisionVisualizer.cxx b/panda/src/collide/collisionVisualizer.cxx
index 44fefce973..d8e70d11df 100644
--- a/panda/src/collide/collisionVisualizer.cxx
+++ b/panda/src/collide/collisionVisualizer.cxx
@@ -43,6 +43,7 @@ TypeHandle CollisionVisualizer::_type_handle;
CollisionVisualizer::
CollisionVisualizer(const std::string &name) : PandaNode(name), _lock("CollisionVisualizer") {
set_cull_callback();
+ set_renderable();
// We always want to render the CollisionVisualizer node itself (even if it
// doesn't appear to have any geometry within it).
@@ -62,6 +63,7 @@ CollisionVisualizer(const CollisionVisualizer ©) :
_normal_scale(copy._normal_scale) {
set_cull_callback();
+ set_renderable();
// We always want to render the CollisionVisualizer node itself (even if it
// doesn't appear to have any geometry within it).
@@ -145,12 +147,9 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
bool was_detected = (solid_info._detected_count > 0);
PT(PandaNode) node = solid->get_viz(trav, xform_data, !was_detected);
if (node != nullptr) {
- CullTraverserData next_data(xform_data, node);
-
// We don't want to inherit the render state from above for these
// guys.
- next_data._state = get_viz_state();
- trav->traverse(next_data);
+ trav->traverse_down(xform_data, node, xform_data._net_transform, get_viz_state());
}
}
@@ -251,18 +250,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool CollisionVisualizer::
-is_renderable() const {
- return true;
-}
-
-
/**
* Writes a brief description of the node to the indicated output stream.
* This is invoked by the << operator. It may be overridden in derived
diff --git a/panda/src/collide/collisionVisualizer.h b/panda/src/collide/collisionVisualizer.h
index cc245e101b..d289b74c0c 100644
--- a/panda/src/collide/collisionVisualizer.h
+++ b/panda/src/collide/collisionVisualizer.h
@@ -50,7 +50,6 @@ public:
// from parent class PandaNode.
virtual PandaNode *make_copy() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void output(std::ostream &out) const;
// from parent class CollisionRecorder.
diff --git a/panda/src/display/displayRegion.I b/panda/src/display/displayRegion.I
index ef18ab693e..f77a78bf31 100644
--- a/panda/src/display/displayRegion.I
+++ b/panda/src/display/displayRegion.I
@@ -160,6 +160,8 @@ set_dimensions(const LVecBase4 &dimensions) {
* Changes the range of the depth buffer this DisplayRegion writes to.
* The parameters range from 0 to 1. It is legal for the near value to be
* larger than the far value.
+ *
+ * @since 1.11.0
*/
INLINE void DisplayRegion::
set_depth_range(PN_stdfloat near_depth, PN_stdfloat far_depth) {
diff --git a/panda/src/display/graphicsOutput.cxx b/panda/src/display/graphicsOutput.cxx
index 1f97ade9e7..58d4099a45 100644
--- a/panda/src/display/graphicsOutput.cxx
+++ b/panda/src/display/graphicsOutput.cxx
@@ -834,6 +834,9 @@ get_active_display_region(int n) const {
* which will be a texture suitable for applying to geometry within the scene
* rendered into this window.
*
+ * If you pass zero as the buffer size, the buffer will have the same size as
+ * the host window, and will automatically be resized when the host window is.
+ *
* If tex is not NULL, it is the texture that will be set up for rendering
* into; otherwise, a new Texture object will be created. In either case, the
* target texture can be retrieved from the return value with
diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx
index 3e73649ad1..797dba0fcd 100644
--- a/panda/src/display/graphicsStateGuardian.cxx
+++ b/panda/src/display/graphicsStateGuardian.cxx
@@ -3581,31 +3581,8 @@ async_reload_texture(TextureContext *tc) {
priority = _current_display_region->get_texture_reload_priority();
}
- string task_name = string("reload:") + tc->get_texture()->get_name();
- PT(AsyncTaskManager) task_mgr = _loader->get_task_manager();
-
- // See if we are already loading this task.
- AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name);
- size_t num_tasks = orig_tasks.get_num_tasks();
- for (size_t ti = 0; ti < num_tasks; ++ti) {
- AsyncTask *task = orig_tasks.get_task(ti);
- if (task->is_exact_type(TextureReloadRequest::get_class_type()) &&
- ((TextureReloadRequest *)task)->get_texture() == tc->get_texture()) {
- // This texture is already queued to be reloaded. Don't queue it again,
- // just make sure the priority is updated, and return.
- task->set_priority(std::max(task->get_priority(), priority));
- return (AsyncFuture *)task;
- }
- }
-
- // This texture has not yet been queued to be reloaded. Queue it up now.
- PT(AsyncTask) request =
- new TextureReloadRequest(task_name,
- _prepared_objects, tc->get_texture(),
- _supports_compressed_texture);
- request->set_priority(priority);
- _loader->load_async(request);
- return (AsyncFuture *)request.p();
+ Texture *tex = tc->get_texture();
+ return tex->async_ensure_ram_image(_supports_compressed_texture, priority);
}
/**
diff --git a/panda/src/doc/eggSyntax.txt b/panda/src/doc/eggSyntax.txt
index 4b57d91192..b81eb45c0e 100644
--- a/panda/src/doc/eggSyntax.txt
+++ b/panda/src/doc/eggSyntax.txt
@@ -3,7 +3,7 @@ THE PHILOSOPHY OF EGG FILES (vs. bam files)
Egg files are used by Panda3D to describe many properties of a scene:
simple geometry, including special effects and collision surfaces,
characters including skeletons, morphs, and multiple-joint
-assignments, and character animation tables.
+assignments, and character animation tables.
Egg files are designed to be the lingua franca of model manipulation
for Panda tools. A number of utilities are provided that read and
@@ -168,8 +168,9 @@ appear before they are referenced.
The remaining formats are generic and specify only the semantic
meaning of the channels. The size of the texels is determined by
the width of the components in the image file. RGBA is the most
- general; RGB is the same, but without any alpha channel. RGBM is
- like RGBA, except that it requests only one bit of alpha, if the
+ general; RGB is the same, but without any alpha channel.
+
+ RGBM is like RGBA, except that it requests only one bit of alpha, if the
graphics card can provide that, to leave more room for the RGB
components, which is especially important for older 16-bit
graphics cards (the "M" stands for "mask", as in a cutout).
@@ -211,7 +212,7 @@ appear before they are referenced.
Although less often used, for 3-d textures wrapw may also be
specified, and it behaves similarly to wrapu and wrapv.
- There are other legal values in addtional to REPEAT and CLAMP.
+ There are other legal values in additional to REPEAT and CLAMP.
The full list is:
CLAMP
@@ -278,7 +279,7 @@ appear before they are referenced.
/ (number of views).
read-mipmaps { flag }
-
+
If this flag is nonzero, then pre-generated mipmap levels will be
loaded along with the texture. In this case, the filename should
contain a sequence of one or more hash mark ("#") characters,
@@ -413,7 +414,7 @@ appear before they are referenced.
combine-alpha-operand2 { src-alpha }
saved-result { flag }
-
+
If flag is nonzero, then it indicates that this particular texture
stage will be supplied as the "last_saved_result" source for any
future texture stages.
@@ -704,7 +705,7 @@ appear before they are referenced.
[name] { u v [w] [tangent] [binormal] [morph-list] }
This gives the texture coordinates of the vertex. This must be
- specified if a texture is to be mapped onto this geometry.
+ specified if a texture is to be mapped onto this geometry.
The texture coordinates are usually two-dimensional, with two
component values (u v), but they may also be three-dimensional,
@@ -739,7 +740,7 @@ appear before they are referenced.
meaning to custom code or a custom shader. Like named UV's, there
may be multiple Aux entries for a given vertex, each with a
different name.
-
+
name { vertices }
@@ -758,18 +759,18 @@ appear before they are referenced.
At the present time, the DynamicVertexPool is not implemented in
Panda3D.
-
+
GEOMETRY ENTRIES
- name {
- [attributes]
- {
- indices
- [ { pool-name }
- }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
+ }
}
A polygon consists of a sequence of vertices from a single vertex
@@ -822,7 +823,7 @@ GEOMETRY ENTRIES
used unless all vertices also have a normal. If no normal is
defined, none will be supplied. The polygon normal, like the
vertex normal, may be morphed by specifying a series of
- entries.
+ entries.
The polygon normal is used only for lighting and environment
mapping calculations, and is not related to the implicit normal
@@ -844,7 +845,7 @@ GEOMETRY ENTRIES
disabled, and polygons are one-sided; specifying a nonzero value
disables backface culling for this particular polygon and allows
it to be viewed from either side.
-
+
bin { bin-name }
@@ -916,12 +917,12 @@ GEOMETRY ENTRIES
or even within a texture.
- name {
- [attributes]
- {
- indices
- ][ { pool-name }
- }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
+ }
}
A patch is similar to a polygon, but it is a special primitive that
@@ -936,12 +937,12 @@ GEOMETRY ENTRIES
specified for Patch.
- name {
- [attributes]
- {
- indices
- ][ { pool-name }
- }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
+ }
}
A PointLight is a set of single points. One point is drawn for each
@@ -964,12 +965,12 @@ GEOMETRY ENTRIES
viewer normally.
- name {
- [attributes]
- {
- indices
- ][ { pool-name }
- }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
+ }
[component attributes]
}
@@ -985,11 +986,11 @@ GEOMETRY ENTRIES
line segment, as in TriangleStrip, below.
- name {
- [attributes]
- {
- indices
- ][ { pool-name }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
}
[component attributes]
}
@@ -1013,7 +1014,7 @@ GEOMETRY ENTRIES
It is possible for the individual triangles of a triangle strip to
have a separate normal and/or color. If so, a entry
should be given for each so-modified triangle:
-
+
index {
{ r g b a [morph-list] }
{ x y z [morph-list] }
@@ -1024,11 +1025,11 @@ GEOMETRY ENTRIES
must always follow the vertex list.
- name {
- [attributes]
- {
- indices
- ][ { pool-name }
+ name {
+ [attributes]
+ {
+ indices
+ ][ { pool-name }
}
[component attributes]
}
@@ -1057,7 +1058,7 @@ itself doesn't support them and will always create static curves and
surfaces. External tools like egg-qtess, however, may respect them.
{
- [attributes]
+ [attributes]
{ order }
{ knot-list }
@@ -1083,7 +1084,7 @@ surfaces. External tools like egg-qtess, however, may respect them.
type { curve-type }
- This defines the semanting meaning of this curve, either XYZ, HPR,
+ This defines the semantic meaning of this curve, either XYZ, HPR,
or T. If the type is XYZ, the curve will automatically be
transformed between Y-up and Z-up if necessary; otherwise, it will
be left alone.
@@ -1107,16 +1108,16 @@ surfaces. External tools like egg-qtess, however, may respect them.
name {
- [attributes]
+ [attributes]
{ u-order v-order }
{ u-knot-list }
{ v-knot-list }
- {
- indices
- ][ { pool-name }
- }
+ {
+ indices
+ ][ { pool-name }
+ }
}
A NURBS surface is an extension of a NURBS curve into two parametric
@@ -1137,7 +1138,7 @@ surfaces. External tools like egg-qtess, however, may respect them.
These define the number of subdivisions to make in the U and V
directions to represent the surface. A uniform subdivision is
always made, and trim curves are not respected (though they will
- be drawn in if the trim curves themselves also have a subiv
+ be drawn in if the trim curves themselves also have a subdiv
parameter). This is only intended as a cheesy visualization.
@@ -1194,7 +1195,7 @@ surfaces. External tools like egg-qtess, however, may respect them.
Although the egg syntax supports trim curves, there are at present
no egg processing tools that respect them. For instance, egg-qtess
- ignores trim curves and always tesselates the entire NURBS surface.
+ ignores trim curves and always tessellates the entire NURBS surface.
MORPH DESCRIPTION ENTRIES
@@ -1267,7 +1268,7 @@ GROUPING ENTRIES
attributes of the group:
GROUP BINARY ATTRIBUTES
-
+
These attributes may be either on or off; they are off by default.
They are turned on by specifying a non-zero "boolean-value".
@@ -1310,6 +1311,15 @@ GROUPING ENTRIES
vertices; joints and morphs appearing outside of a hierarchy
identified with a flag are undefined.
+ { structured }
+
+ This is an optional alternative for the flag.
+ By default, Panda will collapse all of the geometry in a group (with the { 1 } flag)
+ a single node. While this is optimal for conditions such as characters moving around
+ a scene, it may be suboptimal for larger or more complex characters.
+ This entry is typically generated by the egg-optchar program with the "-dart structured" flag.
+ { structured } implies { 1 }.
+
{ boolean-value }
This attribute indicates that the child nodes of this group
@@ -1446,7 +1456,7 @@ GROUPING ENTRIES
of the billboard, not at the origin of the scene.
{
- {
+ {
in out [fade] { x y z }
}
}
@@ -1483,7 +1493,7 @@ GROUPING ENTRIES
Valid types so far are:
Plane
-
+
The geometry represents an infinite plane. The first polygon
found in the group will define the plane.
@@ -1550,7 +1560,7 @@ GROUPING ENTRIES
most compatibility.
keep
-
+
Don't discard the visible geometry after using it to define a
collision surface; create both an invisible collision surface
and the visible geometry.
@@ -1619,7 +1629,8 @@ GROUPING ENTRIES
There may also be additional predefined egg object types not
listed here; see the *.pp files that are installed into the etc
- directory for a complete list.
+ directory for a complete list. Additionally, you can reference
+ the file located in $PANDA/src/doc/howto.MultiGenModelFlags
{ transform-definition }
@@ -1683,9 +1694,9 @@ GROUPING ENTRIES
current group, regardless of the group in which the geometry is
actually defined. See the description, below.
- {
+ {
fps { float-value }
- num-frames { integer-value }
+ num-frames { integer-value }
}
One or more AnimPreload entries may appear within the that
@@ -1720,7 +1731,7 @@ GROUPING ENTRIES
geometry in the scene graph. The syntax is:
name {
- ][ { group-name }
+ ][ { group-name }
[ ][ { group-name } ... ]
}
@@ -1746,8 +1757,8 @@ GROUPING ENTRIES
and it may contain other joints.
A tree of nodes only makes sense within a character
- definition, which is created by applying the flag to a group.
- See , above.
+ definition, which is created by applying the flag to a group.
+ See , above.
The vertex assignment is crucial. This is how the geometry of a
character is made to move with the joints. The character's geometry
@@ -1790,7 +1801,7 @@ GROUPING ENTRIES
bundles, or any one of the following ( entries are optional,
and default as shown):
- ] name {
+ name {
fps { 24 }
{ values }
}
@@ -1844,7 +1855,7 @@ GROUPING ENTRIES
an animation sequence.
- name {
+ name {
width { table-width }
fps { 24 }
{ values }
@@ -1913,6 +1924,10 @@ ANIMATION STRUCTURE
animated model description. Without the flag, joints will be
treated as ordinary groups, and morphs will be ignored.
+ It is important to note that utilizing { 1 } will collapse all of the
+ model's geometry into a single node. To omit this, use { structured } instead.
+ (See above.)
+
In the above, UPPERCASE NAMES represent an arbitrary name that you
may choose. The name of the enclosing group, CHARACTER_NAME, is
taken as the name of the animated model. It should generally match
diff --git a/panda/src/doc/howto.MultiGenModelFlags b/panda/src/doc/howto.MultiGenModelFlags
index ac67b7e93f..3989859803 100644
--- a/panda/src/doc/howto.MultiGenModelFlags
+++ b/panda/src/doc/howto.MultiGenModelFlags
@@ -3,7 +3,7 @@
This document describes the different kinds of model flags one can place in
the comment field of MultiGen group beads. The general format for a model
-flag is:
+flag is:
{ {value} }
The most up-to-date version of this document can be found in:
@@ -30,7 +30,7 @@ The most up-to-date version of this document can be found in:
{ {camera-barrier} } Invisible collision surface for camera and colliders
{ {camera-barrier-sphere} } Invisible sphere collision surface for camera and colliders
{ {backstage} } Modeling reference object
- { {1} } Decal the node below to me
+ { {1} } Decal the node below to me
(like a window on a wall)
{ fps { # } } Set rate of animation for a pfSequence
@@ -52,12 +52,12 @@ common flag/value pairs and describes what they are used for.
which one places the flag (so names like red-hut are more useful than
names like o34).
- { {1} }
+ { {1} }
- Used to show/hide, change the color, or change the collision properties
+ Used to show/hide, change the color, or change the collision properties
of a chunk.
- { {1} }
+ { {1} }
Used to move, rotate, or scale a chunk of the model. Also can be used
(like the flag) to show/hide, change the color, and change the
@@ -91,7 +91,7 @@ common flag/value pairs and describes what they are used for.
a door, for example, so the player can tell when the avatar has
moved through the door.
- BACKSTAGE objects are not translated over to the player. Modelers
- should use this flag on reference objects that they include to help
+ should use this flag on reference objects that they include to help
in the modeling task (such as scale references)
IMPORTANT NOTE:
@@ -108,7 +108,7 @@ common flag/value pairs and describes what they are used for.
********** PROPERTIES **********
-These are used to control properties of selected chunks.
+These are used to control properties of selected chunks.
{ fps { frame-rate } }
@@ -124,7 +124,7 @@ These are used to control properties of selected chunks.
Multiple Flag/value pairs can be combined within an single field.
For example:
- { {1}
+ { {1}
{barrier} }
Generally, the flag can be combined with most other flags
@@ -134,7 +134,7 @@ These are used to control properties of selected chunks.
could also be written as:
{{1}{barrier}}
-
+
3) Where to place the flags
All model flags except flags are generally placed in the
@@ -154,6 +154,6 @@ These are used to control properties of selected chunks.
4) Flags at different levels in the model
Flags in lower level beads generally override flags in upper level
- beads.
+ beads.
5) For more detailed information see $PANDA/src/doc/eggSyntax.txt.
diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
index 35f86ae3d0..371a73f47e 100644
--- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
+++ b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
@@ -357,10 +357,7 @@ upload_texture(DXTextureContext9 *dtc, bool force) {
async_reload_texture(dtc);
has_image = _supports_compressed_texture ? tex->has_ram_image() : tex->has_uncompressed_ram_image();
if (!has_image) {
- if (dtc->was_simple_image_modified()) {
- return dtc->create_simple_texture(*_screen);
- }
- return true;
+ return dtc->create_simple_texture(*_screen);
}
}
}
diff --git a/panda/src/dxgsg9/dxTextureContext9.cxx b/panda/src/dxgsg9/dxTextureContext9.cxx
index 2c90a56910..19a094fc45 100644
--- a/panda/src/dxgsg9/dxTextureContext9.cxx
+++ b/panda/src/dxgsg9/dxTextureContext9.cxx
@@ -1157,7 +1157,7 @@ create_simple_texture(DXScreenData &scrn) {
goto error_exit;
}
- mark_simple_loaded();
+ mark_loaded();
return true;
error_exit:
diff --git a/panda/src/egg2pg/characterMaker.cxx b/panda/src/egg2pg/characterMaker.cxx
index f087ebf88e..0c8bb03650 100644
--- a/panda/src/egg2pg/characterMaker.cxx
+++ b/panda/src/egg2pg/characterMaker.cxx
@@ -36,10 +36,6 @@
using std::string;
-
-
-
-
/**
*
*/
@@ -589,16 +585,15 @@ determine_bin_home(EggBin *egg_bin) {
// ahead and make an implicit node for the joint.
if (egg_group->get_dcs_type() == EggGroup::DC_none) {
-/*
- * Unless the user specifically forbade exposing the joint by putting an
- * explicit " { none }" entry in the joint. In this case, we return NULL
- * to treat the geometry as dynamic (and animate it by animating its
- * vertices), but display lists and vertex buffers will perform better if more
- * geometry is rigid. There's a tradeoff, though, since the cull traverser
- * will have to do more work with additional transforms in the scene graph,
- * and this may also break up the geometry into more individual pieces, which
- * is the biggest limiting factor on modern PC graphics cards.
- */
+ // Unless the user specifically forbade exposing the joint by putting an
+ // explicit " { none }" entry in the joint. In this case, we return
+ // nullptr to treat the geometry as dynamic (and animate it by animating
+ // its vertices), but display lists and vertex buffers will perform better
+ // if more geometry is rigid. There's a tradeoff, though, since the cull
+ // traverser will have to do more work with additional transforms in the
+ // scene graph, and this may also break up the geometry into more
+ // individual pieces, which is the biggest limiting factor on modern PC
+ // graphics cards.
return nullptr;
}
diff --git a/panda/src/event/CMakeLists.txt b/panda/src/event/CMakeLists.txt
index b0464ff793..47136d1b5e 100644
--- a/panda/src/event/CMakeLists.txt
+++ b/panda/src/event/CMakeLists.txt
@@ -9,6 +9,7 @@ set(P3EVENT_HEADERS
config_event.h
buttonEvent.I buttonEvent.h
buttonEventList.I buttonEventList.h
+ functionAsyncTask.h functionAsyncTask.I
genericAsyncTask.h genericAsyncTask.I
pointerEvent.I pointerEvent.h
pointerEventList.I pointerEventList.h
@@ -28,6 +29,7 @@ set(P3EVENT_SOURCES
asyncTaskSequence.cxx
buttonEvent.cxx
buttonEventList.cxx
+ functionAsyncTask.cxx
genericAsyncTask.cxx
pointerEvent.cxx
pointerEventList.cxx
diff --git a/panda/src/event/asyncTaskManager.I b/panda/src/event/asyncTaskManager.I
index 8cf788dc9e..6be7ef3508 100644
--- a/panda/src/event/asyncTaskManager.I
+++ b/panda/src/event/asyncTaskManager.I
@@ -32,6 +32,21 @@ get_clock() {
return _clock;
}
+#ifndef CPPPARSER
+/**
+ * Adds a new task which calls the indicated function to the task manager.
+ * Returns the newly created FunctionAsyncTask object.
+ *
+ * @since 1.11.0
+ */
+INLINE AsyncTask *AsyncTaskManager::
+add(const std::string &name, FunctionAsyncTask::TaskFunction function) {
+ AsyncTask *task = new FunctionAsyncTask(name, std::move(function));
+ add(task);
+ return task;
+}
+#endif
+
/**
* Returns the number of tasks that are currently active or sleeping within
* the task manager.
diff --git a/panda/src/event/asyncTaskManager.h b/panda/src/event/asyncTaskManager.h
index 0bb137deb4..0982e5e2c7 100644
--- a/panda/src/event/asyncTaskManager.h
+++ b/panda/src/event/asyncTaskManager.h
@@ -30,6 +30,7 @@
#include "clockObject.h"
#include "ordered_vector.h"
#include "indirectCompareNames.h"
+#include "functionAsyncTask.h"
/**
* A class to manage a loose queue of isolated tasks, which can be performed
@@ -64,6 +65,9 @@ PUBLISHED:
BLOCKING bool remove_task_chain(const std::string &name);
void add(AsyncTask *task);
+#ifndef CPPPARSER
+ INLINE AsyncTask *add(const std::string &name, FunctionAsyncTask::TaskFunction function);
+#endif
bool has_task(AsyncTask *task) const;
AsyncTask *find_task(const std::string &name) const;
diff --git a/panda/src/event/buttonEvent.I b/panda/src/event/buttonEvent.I
index f082a4bee9..959f66dc47 100644
--- a/panda/src/event/buttonEvent.I
+++ b/panda/src/event/buttonEvent.I
@@ -149,3 +149,35 @@ update_mods(ModifierButtons &mods) const {
return false;
}
}
+
+/**
+ *
+ */
+INLINE ButtonHandle ButtonEvent::
+get_button() const {
+ return _button;
+}
+
+/**
+ *
+ */
+INLINE int ButtonEvent::
+get_keycode() const {
+ return _keycode;
+}
+
+/**
+ *
+ */
+INLINE ButtonEvent::Type ButtonEvent::
+get_type() const {
+ return _type;
+}
+
+/**
+ *
+ */
+INLINE double ButtonEvent::
+get_time() const {
+ return _time;
+}
diff --git a/panda/src/event/buttonEvent.h b/panda/src/event/buttonEvent.h
index e839924c48..d80eabf701 100644
--- a/panda/src/event/buttonEvent.h
+++ b/panda/src/event/buttonEvent.h
@@ -42,21 +42,25 @@ class DatagramIterator;
* keyboard (and other buttons for which there is a corresponding ButtonHandle
* object), while keystroke events are defined across the entire Unicode
* character set.
+ *
+ * This API should not be considered stable and may change in a future version
+ * of Panda3D.
*/
class EXPCL_PANDA_EVENT ButtonEvent {
-public:
+PUBLISHED:
enum Type {
-/*
- * T_down and T_up represent a button changing state correspondingly.
- * T_resume_down is a special event that is only thrown when focus is returned
- * to a window and a button is detected as being held down at that point; it
- * indicates that the button should be considered down now (if it wasn't
- * already), but it didn't just get pressed down at this moment, it was
- * depressed some time ago. It's mainly used for correct tracking of modifier
- * keys like shift and control, and can be ignored for other keys.
- */
+ // T_down is sent when a button was just pressed.
T_down,
+
+ // T_resume_down is a special event that is only thrown when focus is
+ // returned to a window and a button is detected as being held down at that
+ // point; it indicates that the button should be considered down now (if it
+ // wasn't already), but it didn't just get pressed down at this moment, it
+ // was depressed some time ago. It's mainly used for correct tracking of
+ // modifier keys like shift and control, and can be ignored for other keys.
T_resume_down,
+
+ // T_down is sent when a button is released.
T_up,
// T_repeat is sent for each a keyrepeat event generated by the system,
@@ -84,6 +88,7 @@ public:
T_raw_up,
};
+public:
INLINE ButtonEvent();
INLINE ButtonEvent(ButtonHandle button, Type type, double time = ClockObject::get_global_clock()->get_frame_time());
INLINE ButtonEvent(int keycode, double time = ClockObject::get_global_clock()->get_frame_time());
@@ -92,17 +97,30 @@ public:
INLINE ButtonEvent(const ButtonEvent ©);
INLINE void operator = (const ButtonEvent ©);
+PUBLISHED:
INLINE bool operator == (const ButtonEvent &other) const;
INLINE bool operator != (const ButtonEvent &other) const;
INLINE bool operator < (const ButtonEvent &other) const;
+public:
INLINE bool update_mods(ModifierButtons &mods) const;
+ INLINE ButtonHandle get_button() const;
+ INLINE int get_keycode() const;
+ INLINE Type get_type() const;
+ INLINE double get_time() const;
+
void output(std::ostream &out) const;
void write_datagram(Datagram &dg) const;
void read_datagram(DatagramIterator &scan);
+PUBLISHED:
+ MAKE_PROPERTY(button, get_button);
+ MAKE_PROPERTY(keycode, get_keycode);
+ MAKE_PROPERTY(type, get_type);
+ MAKE_PROPERTY(time, get_time);
+
public:
// _button will be filled in if type is T_down, T_resume_down, or T_up.
ButtonHandle _button;
diff --git a/panda/src/event/buttonEventList.h b/panda/src/event/buttonEventList.h
index 0a7822fc63..a3cbcfb1e7 100644
--- a/panda/src/event/buttonEventList.h
+++ b/panda/src/event/buttonEventList.h
@@ -47,6 +47,8 @@ PUBLISHED:
virtual void output(std::ostream &out) const;
void write(std::ostream &out, int indent_level = 0) const;
+ MAKE_SEQ_PROPERTY(events, get_num_events, get_event);
+
private:
typedef pvector Events;
Events _events;
diff --git a/panda/src/event/config_event.cxx b/panda/src/event/config_event.cxx
index ccae0d95ae..93dc8d916b 100644
--- a/panda/src/event/config_event.cxx
+++ b/panda/src/event/config_event.cxx
@@ -22,6 +22,7 @@
#include "event.h"
#include "eventHandler.h"
#include "eventParameter.h"
+#include "functionAsyncTask.h"
#include "genericAsyncTask.h"
#include "pointerEventList.h"
@@ -49,6 +50,7 @@ ConfigureFn(config_event) {
EventHandler::init_type();
EventStoreInt::init_type("EventStoreInt");
EventStoreDouble::init_type("EventStoreDouble");
+ FunctionAsyncTask::init_type();
GenericAsyncTask::init_type();
ButtonEventList::register_with_read_factory();
diff --git a/panda/src/event/eventHandler.cxx b/panda/src/event/eventHandler.cxx
index e3e389e1bf..5ad4bbe0e6 100644
--- a/panda/src/event/eventHandler.cxx
+++ b/panda/src/event/eventHandler.cxx
@@ -104,6 +104,20 @@ dispatch_event(const Event *event) {
}
}
+ // now for lambda hooks
+ LambdaHooks::const_iterator lhi;
+ lhi = _lambdahooks.find(event->get_name());
+
+ if (lhi != _lambdahooks.end()) {
+ // found one
+ const LambdaFunctions &functions = (*lhi).second;
+
+ size_t num_functions = functions.size();
+ for (size_t i = 0; i < num_functions; ++i) {
+ functions[i](event);
+ }
+ }
+
// Finally, check for futures that need to be triggered.
Futures::iterator fi;
fi = _futures.find(event->get_name());
@@ -189,6 +203,19 @@ add_hook(const string &event_name, EventCallbackFunction *function,
return _cbhooks[event_name].insert(CallbackFunction(function, data)).second;
}
+/**
+ * Adds the indicated function to the list of those that will be called when
+ * the named event is thrown. Returns true if the function was successfully
+ * added, false if it was already defined on the indicated event name. This
+ * version stores an arbitrary C++ lambda.
+ */
+void EventHandler::
+add_hook(const string &event_name, EventLambda function) {
+ assert(!event_name.empty());
+ assert(function);
+ _lambdahooks[event_name].push_back(function);
+}
+
/**
* Returns true if there is any hook added on the indicated event name, false
* otherwise.
@@ -212,6 +239,14 @@ has_hook(const string &event_name) const {
}
}
+ LambdaHooks::const_iterator lhi;
+ lhi = _lambdahooks.find(event_name);
+ if (lhi != _lambdahooks.end()) {
+ if (!(*lhi).second.empty()) {
+ return true;
+ }
+ }
+
return false;
}
@@ -306,6 +341,14 @@ remove_hooks(const string &event_name) {
_cbhooks.erase(chi);
}
+ LambdaHooks::iterator lhi = _lambdahooks.find(event_name);
+ if (lhi != _lambdahooks.end()) {
+ if (!(*lhi).second.empty()) {
+ any_removed = true;
+ }
+ _lambdahooks.erase(lhi);
+ }
+
return any_removed;
}
@@ -344,6 +387,7 @@ void EventHandler::
remove_all_hooks() {
_hooks.clear();
_cbhooks.clear();
+ _lambdahooks.clear();
}
/**
diff --git a/panda/src/event/eventHandler.h b/panda/src/event/eventHandler.h
index 8e616f1581..4aa3d0924e 100644
--- a/panda/src/event/eventHandler.h
+++ b/panda/src/event/eventHandler.h
@@ -23,6 +23,10 @@
#include "pset.h"
#include "pmap.h"
+#ifndef CPPPARSER
+#include
+#endif
+
class EventQueue;
/**
@@ -40,6 +44,12 @@ public:
typedef void EventFunction(const Event *);
typedef void EventCallbackFunction(const Event *, void *);
+#ifdef CPPPARSER
+ typedef EventFunction EventLambda;
+#else
+ typedef std::function EventLambda;
+#endif
+
PUBLISHED:
explicit EventHandler(EventQueue *ev_queue);
~EventHandler() {}
@@ -58,6 +68,7 @@ public:
bool add_hook(const std::string &event_name, EventFunction *function);
bool add_hook(const std::string &event_name, EventCallbackFunction *function,
void *data);
+ void add_hook(const std::string &event_name, EventLambda function);
bool has_hook(const std::string &event_name) const;
bool has_hook(const std::string &event_name, EventFunction *function) const;
bool has_hook(const std::string &event_name, EventCallbackFunction *function,
@@ -78,10 +89,13 @@ protected:
typedef std::pair CallbackFunction;
typedef pset CallbackFunctions;
typedef pmap CallbackHooks;
+ typedef pvector LambdaFunctions;
+ typedef pmap LambdaHooks;
typedef pmap Futures;
Hooks _hooks;
CallbackHooks _cbhooks;
+ LambdaHooks _lambdahooks;
Futures _futures;
EventQueue &_queue;
diff --git a/panda/src/event/functionAsyncTask.I b/panda/src/event/functionAsyncTask.I
new file mode 100644
index 0000000000..aa413dacf0
--- /dev/null
+++ b/panda/src/event/functionAsyncTask.I
@@ -0,0 +1,83 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University. All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license. You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file functionAsyncTask.I
+ * @author rdb
+ * @date 2021-11-29
+ */
+
+/**
+ *
+ */
+INLINE FunctionAsyncTask::
+FunctionAsyncTask(const std::string &name) :
+ AsyncTask(name)
+{
+}
+
+/**
+ *
+ */
+INLINE FunctionAsyncTask::
+FunctionAsyncTask(const std::string &name, FunctionAsyncTask::TaskFunction function) :
+ AsyncTask(name),
+ _function(std::move(function))
+{
+}
+
+/**
+ * Replaces the function that is called when the task runs.
+ */
+INLINE void FunctionAsyncTask::
+set_function(TaskFunction function) {
+ _function = std::move(function);
+}
+
+/**
+ * Returns the function that is called when the task runs.
+ */
+INLINE const FunctionAsyncTask::TaskFunction &FunctionAsyncTask::
+get_function() const {
+ return _function;
+}
+
+/**
+ * Replaces the function that is called when the task begins. This is an
+ * optional function.
+ */
+INLINE void FunctionAsyncTask::
+set_upon_birth(BirthFunction upon_birth) {
+ _upon_birth = std::move(upon_birth);
+}
+
+/**
+ * Returns the function that is called when the task begins, or NULL if the
+ * function is not defined.
+ */
+INLINE const FunctionAsyncTask::BirthFunction &FunctionAsyncTask::
+get_upon_birth() const {
+ return _upon_birth;
+}
+
+/**
+ * Replaces the function that is called when the task ends. This is an
+ * optional function.
+ */
+INLINE void FunctionAsyncTask::
+set_upon_death(FunctionAsyncTask::DeathFunction upon_death) {
+ _upon_death = upon_death;
+}
+
+/**
+ * Returns the function that is called when the task ends, or NULL if the
+ * function is not defined.
+ */
+INLINE const FunctionAsyncTask::DeathFunction &FunctionAsyncTask::
+get_upon_death() const {
+ return _upon_death;
+}
diff --git a/panda/src/event/functionAsyncTask.cxx b/panda/src/event/functionAsyncTask.cxx
new file mode 100644
index 0000000000..54b591918d
--- /dev/null
+++ b/panda/src/event/functionAsyncTask.cxx
@@ -0,0 +1,81 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University. All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license. You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file functionAsyncTask.cxx
+ * @author rdb
+ * @date 2021-11-29
+ */
+
+#include "functionAsyncTask.h"
+#include "pnotify.h"
+
+#ifndef CPPPARSER
+
+TypeHandle FunctionAsyncTask::_type_handle;
+
+/**
+ * Override this function to return true if the task can be successfully
+ * executed, false if it cannot. Mainly intended as a sanity check when
+ * attempting to add the task to a task manager.
+ *
+ * This function is called with the lock held.
+ */
+bool FunctionAsyncTask::
+is_runnable() {
+ return !!_function;
+}
+
+/**
+ * Override this function to do something useful for the task.
+ *
+ * This function is called with the lock *not* held.
+ */
+AsyncTask::DoneStatus FunctionAsyncTask::
+do_task() {
+ nassertr(_function, DS_interrupt);
+ return _function(this);
+}
+
+/**
+ * Override this function to do something useful when the task has been added
+ * to the active queue.
+ *
+ * This function is called with the lock *not* held.
+ */
+void FunctionAsyncTask::
+upon_birth(AsyncTaskManager *manager) {
+ AsyncTask::upon_birth(manager);
+
+ if (_upon_birth) {
+ _upon_birth(this);
+ }
+}
+
+/**
+ * Override this function to do something useful when the task has been
+ * removed from the active queue. The parameter clean_exit is true if the
+ * task has been removed because it exited normally (returning DS_done), or
+ * false if it was removed for some other reason (e.g.
+ * AsyncTaskManager::remove()). By the time this method is called, _manager
+ * has been cleared, so the parameter manager indicates the original
+ * AsyncTaskManager that owned this task.
+ *
+ * The normal behavior is to throw the done_event only if clean_exit is true.
+ *
+ * This function is called with the lock *not* held.
+ */
+void FunctionAsyncTask::
+upon_death(AsyncTaskManager *manager, bool clean_exit) {
+ AsyncTask::upon_death(manager, clean_exit);
+
+ if (_upon_death) {
+ _upon_death(this, clean_exit);
+ }
+}
+
+#endif // CPPPARSER
diff --git a/panda/src/event/functionAsyncTask.h b/panda/src/event/functionAsyncTask.h
new file mode 100644
index 0000000000..297d71c691
--- /dev/null
+++ b/panda/src/event/functionAsyncTask.h
@@ -0,0 +1,83 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University. All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license. You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file functionAsyncTask.h
+ * @author rdb
+ * @date 2021-11-29
+ */
+
+#ifndef FUNCTIONASYNCTASK_H
+#define FUNCTIONASYNCTASK_H
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+
+#ifndef CPPPARSER
+#include
+
+/**
+ * Associates a generic std::function (eg. a lambda) with an AsyncTask object.
+ * You can use this when you want to create an AsyncTask without having to
+ * subclass.
+ *
+ * @since 1.11.0
+ */
+class EXPCL_PANDA_EVENT FunctionAsyncTask final : public AsyncTask {
+public:
+ typedef std::function TaskFunction;
+ typedef std::function BirthFunction;
+ typedef std::function DeathFunction;
+
+ INLINE FunctionAsyncTask(const std::string &name = std::string());
+ INLINE FunctionAsyncTask(const std::string &name, TaskFunction function);
+ ALLOC_DELETED_CHAIN(FunctionAsyncTask);
+
+ INLINE void set_function(TaskFunction function);
+ INLINE const TaskFunction &get_function() const;
+
+ INLINE void set_upon_birth(BirthFunction function);
+ INLINE const BirthFunction &get_upon_birth() const;
+
+ INLINE void set_upon_death(DeathFunction function);
+ INLINE const DeathFunction &get_upon_death() const;
+
+protected:
+ virtual bool is_runnable() override;
+ virtual DoneStatus do_task() override;
+ virtual void upon_birth(AsyncTaskManager *manager) override;
+ virtual void upon_death(AsyncTaskManager *manager, bool clean_exit) override;
+
+private:
+ TaskFunction _function;
+ BirthFunction _upon_birth;
+ DeathFunction _upon_death;
+
+public:
+ static TypeHandle get_class_type() {
+ return _type_handle;
+ }
+ static void init_type() {
+ AsyncTask::init_type();
+ register_type(_type_handle, "FunctionAsyncTask",
+ AsyncTask::get_class_type());
+ }
+ virtual TypeHandle get_type() const override {
+ return get_class_type();
+ }
+ virtual TypeHandle force_init_type() override {init_type(); return get_class_type();}
+
+private:
+ static TypeHandle _type_handle;
+};
+
+#include "functionAsyncTask.I"
+
+#endif // CPPPARSER
+
+#endif
diff --git a/panda/src/event/genericAsyncTask.h b/panda/src/event/genericAsyncTask.h
index f35d780e3b..ce7f635156 100644
--- a/panda/src/event/genericAsyncTask.h
+++ b/panda/src/event/genericAsyncTask.h
@@ -22,6 +22,8 @@
* Associates a generic C-style function pointer with an AsyncTask object.
* You can use this when you want to create an AsyncTask without having to
* subclass.
+ *
+ * @deprecated See FunctionAsyncTask instead, which is more powerful.
*/
class EXPCL_PANDA_EVENT GenericAsyncTask : public AsyncTask {
public:
diff --git a/panda/src/event/p3event_composite1.cxx b/panda/src/event/p3event_composite1.cxx
index dc72d2b03f..34fcb9a0c9 100644
--- a/panda/src/event/p3event_composite1.cxx
+++ b/panda/src/event/p3event_composite1.cxx
@@ -7,6 +7,7 @@
#include "asyncTaskSequence.cxx"
#include "buttonEvent.cxx"
#include "buttonEventList.cxx"
+#include "functionAsyncTask.cxx"
#include "genericAsyncTask.cxx"
#include "pointerEvent.cxx"
#include "pointerEventList.cxx"
diff --git a/panda/src/express/virtualFileSystem.cxx b/panda/src/express/virtualFileSystem.cxx
index 0092d9943e..1655c214be 100644
--- a/panda/src/express/virtualFileSystem.cxx
+++ b/panda/src/express/virtualFileSystem.cxx
@@ -156,7 +156,7 @@ mount(const Filename &physical_filename, const Filename &mount_point,
// read-write on multifiles and .zip files.
flags |= MF_read_only;
- char ch = stream->get();
+ int ch = stream->get();
if (ch == '#' || ch == 'p') {
// It *might* be a multifile.
while (ch == '#') {
diff --git a/panda/src/ffmpeg/ffmpegVideoCursor.cxx b/panda/src/ffmpeg/ffmpegVideoCursor.cxx
index ed77a9da30..5cc3d2cffb 100644
--- a/panda/src/ffmpeg/ffmpegVideoCursor.cxx
+++ b/panda/src/ffmpeg/ffmpegVideoCursor.cxx
@@ -274,7 +274,9 @@ start_thread() {
// Create and start the thread object.
_thread_status = TS_wait;
- _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
+ _thread = new GenericThread(_filename.get_basename(), _sync_name, [this]{
+ thread_main();
+ });
if (!_thread->start(_thread_priority, true)) {
// Couldn't start the thread.
_thread = nullptr;
@@ -668,14 +670,6 @@ cleanup() {
}
}
-/**
- * The thread main function, static version (for passing to GenericThread).
- */
-void FfmpegVideoCursor::
-st_thread_main(void *self) {
- ((FfmpegVideoCursor *)self)->thread_main();
-}
-
/**
* The thread main function.
*/
diff --git a/panda/src/ffmpeg/ffmpegVideoCursor.h b/panda/src/ffmpeg/ffmpegVideoCursor.h
index 58d63eb6ba..21e648af41 100644
--- a/panda/src/ffmpeg/ffmpegVideoCursor.h
+++ b/panda/src/ffmpeg/ffmpegVideoCursor.h
@@ -134,7 +134,6 @@ private:
private:
// The following functions will be called in the sub-thread.
- static void st_thread_main(void *self);
void thread_main();
bool do_poll();
diff --git a/panda/src/framework/pandaFramework.cxx b/panda/src/framework/pandaFramework.cxx
index c046deef8a..6c0fb681fc 100644
--- a/panda/src/framework/pandaFramework.cxx
+++ b/panda/src/framework/pandaFramework.cxx
@@ -310,6 +310,39 @@ define_key(const string &event_name, const string &description,
}
}
+/**
+ * Sets up a handler for the indicated key. When the key is pressed in a
+ * window, the given callback will be called. The description is a one-line
+ * description of the function of the key, for display to the user.
+ */
+void PandaFramework::
+define_key(const string &event_name, const string &description,
+ EventHandler::EventLambda function) {
+ if (_event_handler.has_hook(event_name)) {
+ // If there is already a hook for the indicated keyname, we're most likely
+ // replacing a previous definition of a key. Search for the old
+ // definition and remove it.
+ KeyDefinitions::iterator di;
+ di = _key_definitions.begin();
+ while (di != _key_definitions.end() && (*di)._event_name != event_name) {
+ ++di;
+ }
+ if (di != _key_definitions.end()) {
+ _key_definitions.erase(di);
+ }
+ }
+
+ // Now add a new hook for the keyname, and also add the new description.
+ _event_handler.add_hook(event_name, function);
+
+ if (!description.empty()) {
+ KeyDefinition keydef;
+ keydef._event_name = event_name;
+ keydef._description = description;
+ _key_definitions.push_back(keydef);
+ }
+}
+
/**
* Fills in the indicated window properties structure according to the normal
* window properties for this application.
diff --git a/panda/src/framework/pandaFramework.h b/panda/src/framework/pandaFramework.h
index 3c901d8e27..18747dcfa1 100644
--- a/panda/src/framework/pandaFramework.h
+++ b/panda/src/framework/pandaFramework.h
@@ -56,6 +56,9 @@ public:
const std::string &description,
EventHandler::EventCallbackFunction *function,
void *data);
+ void define_key(const std::string &event_name,
+ const std::string &description,
+ EventHandler::EventLambda function);
INLINE void set_window_title(const std::string &title);
virtual void get_default_window_props(WindowProperties &props);
diff --git a/panda/src/framework/windowFramework.cxx b/panda/src/framework/windowFramework.cxx
index 0d3f387eff..ae10964b54 100644
--- a/panda/src/framework/windowFramework.cxx
+++ b/panda/src/framework/windowFramework.cxx
@@ -675,7 +675,7 @@ load_default_model(const NodePath &parent) {
GeomNode *geomnode = new GeomNode("tri");
PT(GeomVertexData) vdata = new GeomVertexData
- ("tri", vertex_colors_prefer_packed ? GeomVertexFormat::get_v3n3ct2() : GeomVertexFormat::get_v3n3ct2(),
+ ("tri", GeomVertexFormat::get_v3n3t2(),
Geom::UH_static);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
GeomVertexWriter normal(vdata, InternalName::get_normal());
@@ -1429,8 +1429,11 @@ create_anim_controls() {
setup_shuttle_button("4", 2, st_play_button);
setup_shuttle_button(":", 3, st_forward_button);
- _update_anim_controls_task = new GenericAsyncTask("controls", st_update_anim_controls, (void *)this);
- _panda_framework->get_task_mgr().add(_update_anim_controls_task);
+ AsyncTaskManager &task_mgr = _panda_framework->get_task_mgr();
+ _update_anim_controls_task = task_mgr.add("controls", [this](AsyncTask *task) {
+ update_anim_controls();
+ return AsyncTask::DS_cont;
+ });
}
/**
@@ -1443,7 +1446,7 @@ destroy_anim_controls() {
_panda_framework->get_event_handler().remove_hooks_with((void *)this);
if (_update_anim_controls_task != nullptr) {
- _panda_framework->get_task_mgr().remove(_update_anim_controls_task);
+ _update_anim_controls_task->remove();
_update_anim_controls_task.clear();
}
}
@@ -1559,18 +1562,6 @@ forward_button() {
control->pose(control->get_frame() + 1);
}
-
-/**
- * The static task function.
- */
-AsyncTask::DoneStatus WindowFramework::
-st_update_anim_controls(GenericAsyncTask *, void *data) {
- WindowFramework *self = (WindowFramework *)data;
- self->update_anim_controls();
- return AsyncTask::DS_cont;
-}
-
-
/**
* The static event handler function.
*/
diff --git a/panda/src/framework/windowFramework.h b/panda/src/framework/windowFramework.h
index b6e93041eb..782989aad9 100644
--- a/panda/src/framework/windowFramework.h
+++ b/panda/src/framework/windowFramework.h
@@ -154,8 +154,6 @@ private:
void play_button();
void forward_button();
- static AsyncTask::DoneStatus st_update_anim_controls(GenericAsyncTask *task, void *data);
-
static void st_back_button(const Event *, void *data);
static void st_pause_button(const Event *, void *data);
static void st_play_button(const Event *, void *data);
@@ -183,7 +181,7 @@ private:
PT(PGSliderBar) _anim_slider;
PT(PGSliderBar) _play_rate_slider;
PT(TextNode) _frame_number;
- PT(GenericAsyncTask) _update_anim_controls_task;
+ PT(AsyncTask) _update_anim_controls_task;
NodePath _mouse;
NodePath _button_thrower;
diff --git a/panda/src/glstuff/glGraphicsBuffer_src.I b/panda/src/glstuff/glGraphicsBuffer_src.I
index 021116b7ff..4a3250eee8 100644
--- a/panda/src/glstuff/glGraphicsBuffer_src.I
+++ b/panda/src/glstuff/glGraphicsBuffer_src.I
@@ -11,14 +11,18 @@
* @date 2006-01-15
*/
+/**
+ *
+ */
INLINE int CLP(GraphicsBuffer)::
-get_multisample_count()
-{
- return _requested_multisamples;
+get_multisample_count() {
+ return _requested_multisamples;
}
+/**
+ *
+ */
INLINE int CLP(GraphicsBuffer)::
-get_coverage_sample_count()
-{
- return _requested_coverage_samples;
+get_coverage_sample_count() {
+ return _requested_coverage_samples;
}
diff --git a/panda/src/glstuff/glGraphicsBuffer_src.h b/panda/src/glstuff/glGraphicsBuffer_src.h
index 0253cb0c89..dfd4a92e94 100644
--- a/panda/src/glstuff/glGraphicsBuffer_src.h
+++ b/panda/src/glstuff/glGraphicsBuffer_src.h
@@ -23,11 +23,14 @@
* ARB_draw_buffers extensions. This design has significant advantages over
* the older wglGraphicsBuffer and glxGraphicsBuffer:
*
- * * Can export depth and stencil. * Supports auxiliary bitplanes. *
- * Supports non-power-of-two padding. * Supports tracking of host window
- * size. * Supports cumulative render-to-texture. * Faster than pbuffers. *
- * Can render onto a texture without clearing it first. * Supports
- * multisample antialiased rendering.
+ * - Can export depth and stencil.
+ * - Supports auxiliary bitplanes.
+ * - Supports non-power-of-two padding.
+ * - Supports tracking of host window size.
+ * - Supports cumulative render-to-texture.
+ * - Faster than pbuffers.
+ * - Can render onto a texture without clearing it first.
+ * - Supports multisample antialiased rendering.
*
* Some of these deserve a little explanation. Auxiliary bitplanes are
* additional bitplanes above and beyond the normal depth,stencil,color. One
@@ -45,7 +48,6 @@
* EXT_framebuffer_blit to allow for multisample antialiasing these offscreen
* render targets. If these extensions are unavailable the buffer will render
* as if multisamples is 0.
- *
*/
class EXPCL_GL CLP(GraphicsBuffer) : public GraphicsBuffer {
public:
diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
index d850364fe1..61480ec021 100644
--- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
+++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
@@ -7420,6 +7420,7 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
}
gtc->_has_storage = true;
+ gtc->_simple_loaded = false;
gtc->_uses_mipmaps = uses_mipmaps;
gtc->_internal_format = internal_format;
gtc->_width = width;
@@ -13135,7 +13136,7 @@ apply_sampler(GLuint unit, const SamplerState &sampler, CLP(TextureContext) *gtc
}
}
- if (sampler.uses_mipmaps() && !gtc->_uses_mipmaps && !gl_ignore_mipmaps) {
+ if (sampler.uses_mipmaps() && !gtc->_uses_mipmaps && !gtc->_simple_loaded && !gl_ignore_mipmaps) {
// The texture wasn't created with mipmaps, but we are trying to sample it
// with mipmaps. We will need to reload it.
GLCAT.info()
@@ -13178,10 +13179,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
async_reload_texture(gtc);
has_image = _supports_compressed_texture ? tex->has_ram_image() : tex->has_uncompressed_ram_image();
if (!has_image) {
- if (gtc->was_simple_image_modified()) {
- return upload_simple_texture(gtc);
- }
- return true;
+ return upload_simple_texture(gtc);
}
}
}
@@ -13551,6 +13549,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
}
gtc->_has_storage = true;
+ gtc->_simple_loaded = false;
gtc->_immutable = true;
gtc->_uses_mipmaps = uses_mipmaps;
gtc->_internal_format = internal_format;
@@ -13598,6 +13597,7 @@ upload_texture(CLP(TextureContext) *gtc, bool force, bool uses_mipmaps) {
if (success) {
if (needs_reload) {
gtc->_has_storage = true;
+ gtc->_simple_loaded = false;
gtc->_uses_mipmaps = uses_mipmaps;
gtc->_internal_format = internal_format;
gtc->_width = width;
@@ -14190,7 +14190,8 @@ upload_simple_texture(CLP(TextureContext) *gtc) {
#endif
GLenum external_format = GL_BGRA;
- const unsigned char *image_ptr = tex->get_simple_ram_image();
+ CPTA_uchar image = tex->get_simple_ram_image();
+ const unsigned char *image_ptr = image.p();
if (image_ptr == nullptr) {
return false;
}
@@ -14233,7 +14234,17 @@ upload_simple_texture(CLP(TextureContext) *gtc) {
width, height, 0,
external_format, component_type, image_ptr);
- gtc->mark_simple_loaded();
+ gtc->_has_storage = true;
+ gtc->_simple_loaded = true;
+ gtc->_immutable = false;
+ gtc->_uses_mipmaps = false;
+ gtc->_internal_format = internal_format;
+ gtc->_width = width;
+ gtc->_height = height;
+ gtc->_depth = 1;
+ gtc->update_data_size_bytes(width * height * 4);
+
+ gtc->mark_loaded();
report_my_gl_errors();
return true;
diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h
index 8aecf8c852..c0af1f5228 100644
--- a/panda/src/glstuff/glGraphicsStateGuardian_src.h
+++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h
@@ -722,7 +722,6 @@ protected:
typedef pmap DirectionalLights;
DirectionalLights _dlights;
- int _pass_number;
GLuint _geom_display_list;
GLuint _current_vbuffer_index;
GLuint _current_ibuffer_index;
diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx
index 38461838f6..60a137921b 100644
--- a/panda/src/glstuff/glShaderContext_src.cxx
+++ b/panda/src/glstuff/glShaderContext_src.cxx
@@ -2911,10 +2911,13 @@ update_shader_texture_bindings(ShaderContext *prev) {
// Bindless texturing wasn't supported or didn't work, so let's just bind
// the texture normally.
+ // Note that simple RAM images are always 2-D for now, so to avoid errors,
+ // we must load the real texture if this is not for a sampler2D.
+ bool force = (spec._desired_type != Texture::TT_2d_texture);
#ifndef OPENGLES
if (multi_bind) {
// Multi-bind case.
- if (!_glgsg->update_texture(gtc, false)) {
+ if (!_glgsg->update_texture(gtc, force)) {
textures[i] = 0;
} else {
gtc->set_active(true);
@@ -2934,7 +2937,7 @@ update_shader_texture_bindings(ShaderContext *prev) {
{
// Non-multibind case.
_glgsg->set_active_texture_stage(i);
- if (!_glgsg->update_texture(gtc, false)) {
+ if (!_glgsg->update_texture(gtc, force)) {
continue;
}
_glgsg->apply_texture(gtc);
diff --git a/panda/src/glstuff/glTextureContext_src.I b/panda/src/glstuff/glTextureContext_src.I
index 33bda9a58f..51db7538ba 100644
--- a/panda/src/glstuff/glTextureContext_src.I
+++ b/panda/src/glstuff/glTextureContext_src.I
@@ -29,6 +29,7 @@ CLP(TextureContext)(CLP(GraphicsStateGuardian) *glgsg,
_handle_resident = false;
#endif
_has_storage = false;
+ _simple_loaded = false;
_immutable = false;
_uses_mipmaps = false;
_generate_mipmaps = false;
diff --git a/panda/src/glstuff/glTextureContext_src.cxx b/panda/src/glstuff/glTextureContext_src.cxx
index e36f8f756e..37bad3ae3a 100644
--- a/panda/src/glstuff/glTextureContext_src.cxx
+++ b/panda/src/glstuff/glTextureContext_src.cxx
@@ -82,6 +82,7 @@ reset_data() {
_handle_resident = false;
#endif
_has_storage = false;
+ _simple_loaded = false;
_immutable = false;
#ifndef OPENGLES_1
diff --git a/panda/src/glstuff/glTextureContext_src.h b/panda/src/glstuff/glTextureContext_src.h
index 667e356d60..41a89cb32b 100644
--- a/panda/src/glstuff/glTextureContext_src.h
+++ b/panda/src/glstuff/glTextureContext_src.h
@@ -64,6 +64,7 @@ public:
// or glTexStorage2D() call. If none of these have changed, we can reload
// the texture image with a glTexSubImage2D().
bool _has_storage;
+ bool _simple_loaded;
bool _immutable;
bool _uses_mipmaps;
bool _generate_mipmaps;
diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx
index b093925367..df0990f69f 100644
--- a/panda/src/gobj/config_gobj.cxx
+++ b/panda/src/gobj/config_gobj.cxx
@@ -366,6 +366,23 @@ ConfigVariableDouble simple_image_threshold
"simple images. Generally the value should be considerably "
"less than 1."));
+ConfigVariableInt texture_reload_num_threads
+ ("texture-reload-num-threads", 1,
+ PRC_DESC("The number of threads that will be started by the Texture class "
+ "to reload textures asynchronously. These threads will only be "
+ "started if the asynchronous interface is used, and if threading "
+ "support is compiled into Panda. The default is one thread, "
+ "which allows textures to be loaded one at a time in a single "
+ "asychronous thread. You can set this higher, particularly if "
+ "you have many CPU's available, to allow loading multiple models "
+ "simultaneously."));
+
+ConfigVariableEnum texture_reload_thread_priority
+ ("texture-reload-thread-priority", TP_normal,
+ PRC_DESC("The default thread priority to assign to the threads created for "
+ "asynchronous texture loading. The default is 'normal'; you may "
+ "also specify 'low', 'high', or 'urgent'."));
+
ConfigVariableInt geom_cache_size
("geom-cache-size", 5000,
PRC_DESC("Specifies the maximum number of entries in the cache "
@@ -503,7 +520,7 @@ PRC_DESC("If this is nonzero, it represents an artificial delay, "
"in seconds, that is imposed on every asynchronous load attempt "
"(within the thread). Its purpose is to help debug errors that "
"may occur when an asynchronous load is delayed. The "
- "delay is per-model, and all aync loads will be queued "
+ "delay is per-model, and all async loads will be queued "
"up behind the delay--it is as if the time it takes to read a "
"file is increased by this amount per read."));
diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h
index f44df52a3c..1713a56751 100644
--- a/panda/src/gobj/config_gobj.h
+++ b/panda/src/gobj/config_gobj.h
@@ -24,6 +24,7 @@
#include "configVariableString.h"
#include "configVariableList.h"
#include "autoTextureScale.h"
+#include "threadPriority.h"
NotifyCategoryDecl(gobj, EXPCL_PANDA_GOBJ, EXPTP_PANDA_GOBJ);
NotifyCategoryDecl(shader, EXPCL_PANDA_GOBJ, EXPTP_PANDA_GOBJ);
@@ -62,6 +63,8 @@ extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_auto_power_2;
extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only;
extern EXPCL_PANDA_GOBJ ConfigVariableInt simple_image_size;
extern EXPCL_PANDA_GOBJ ConfigVariableDouble simple_image_threshold;
+extern EXPCL_PANDA_GOBJ ConfigVariableInt texture_reload_num_threads;
+extern EXPCL_PANDA_GOBJ ConfigVariableEnum texture_reload_thread_priority;
extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_size;
extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_min_frames;
diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I
index 223a86aab5..6409ef8f26 100644
--- a/panda/src/gobj/texture.I
+++ b/panda/src/gobj/texture.I
@@ -28,7 +28,6 @@ make_copy() const {
CDWriter cdata_tex(tex->_cycler, true);
cdata_tex->inc_properties_modified();
cdata_tex->inc_image_modified();
- cdata_tex->inc_simple_image_modified();
return tex;
}
@@ -232,7 +231,6 @@ clear_image() {
do_clear_ram_image(cdata);
do_clear_simple_ram_image(cdata);
cdata->inc_image_modified();
- cdata->inc_simple_image_modified();
}
/**
@@ -1766,6 +1764,12 @@ INLINE void Texture::
set_simple_ram_image(CPTA_uchar image, int x_size, int y_size) {
CDWriter cdata(_cycler, true);
do_set_simple_ram_image(cdata, image, x_size, y_size);
+
+ // If we don't have a RAM image currently, we need to update this, to let the
+ // GSG know that it needs to update the simple image it is currently using.
+ if (!do_has_ram_image(cdata)) {
+ cdata->inc_image_modified();
+ }
}
/**
@@ -1797,16 +1801,6 @@ get_image_modified() const {
return cdata->_image_modified;
}
-/**
- * Returns a sequence number which is guaranteed to change at least every time
- * the texture's "simple" image data is modified.
- */
-INLINE UpdateSeq Texture::
-get_simple_image_modified() const {
- CDReader cdata(_cycler);
- return cdata->_simple_image_modified;
-}
-
/**
* Specifies the power-of-2 texture-scaling mode that will be applied to this
* particular texture when it is next loaded from disk. See
@@ -2466,14 +2460,6 @@ inc_image_modified() {
_modified_pages[0]._modified = _image_modified;
}
-/**
- *
- */
-INLINE void Texture::CData::
-inc_simple_image_modified() {
- ++_simple_image_modified;
-}
-
/**
*
*/
diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx
index c8c649bc36..d065d2dcd8 100644
--- a/panda/src/gobj/texture.cxx
+++ b/panda/src/gobj/texture.cxx
@@ -30,6 +30,7 @@
#include "pnmImage.h"
#include "pnmReader.h"
#include "pfmFile.h"
+#include "pnmFileTypeRegistry.h"
#include "virtualFileSystem.h"
#include "datagramInputFile.h"
#include "datagramOutputFile.h"
@@ -42,6 +43,7 @@
#include "streamReader.h"
#include "texturePeeker.h"
#include "convert_srgb.h"
+#include "asyncTaskManager.h"
#ifdef HAVE_SQUISH
#include
@@ -1018,6 +1020,69 @@ load_related(const InternalName *suffix) const {
return res;
}
+/**
+ * Schedules a background task that reloads the the Texture from its disk file
+ * if there is not currently a RAM image (or uncompressed RAM image, if
+ * allow_compression is false).
+ *
+ * A higher priority value indicates that this texture should be reloaded sooner
+ * than textures with a lower priority value. If the reload hasn't taken place
+ * yet, you can call this again to update the priority value.
+ *
+ * If someone else reloads the texture using an explicit call to reload() while
+ * an async reload request is pending, the async reload request is cancelled.
+ */
+PT(AsyncFuture) Texture::
+async_ensure_ram_image(bool allow_compression, int priority) {
+ CDLockedReader cdata(_cycler);
+ if (allow_compression ? do_has_ram_image(cdata) : do_has_uncompressed_ram_image(cdata)) {
+ // We already have a RAM image.
+ PT(AsyncFuture) fut = new AsyncFuture;
+ fut->set_result(nullptr);
+ return fut;
+ }
+ if (!do_can_reload(cdata)) {
+ // We don't have a filename to load from. This is an error.
+ return nullptr;
+ }
+
+ AsyncTask *task = cdata->_reload_task;
+ if (task != nullptr) {
+ // This texture is already queued to be reloaded. Don't queue it again,
+ // just make sure the priority is updated, and return.
+ task->set_priority(std::max(task->get_priority(), priority));
+ return (AsyncFuture *)task;
+ }
+
+ CDWriter cdataw(_cycler, cdata, true);
+
+ string task_name = string("reload:") + get_name();
+ AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr();
+ static PT(AsyncTaskChain) chain = task_mgr->make_task_chain("texture_reload");
+ chain->set_num_threads(texture_reload_num_threads);
+ chain->set_thread_priority(texture_reload_thread_priority);
+
+ double delay = async_load_delay;
+
+ // This texture has not yet been queued to be reloaded. Queue it up now.
+ task = new FunctionAsyncTask(task_name, [=](AsyncTask *task) {
+ if (delay != 0.0) {
+ Thread::sleep(delay);
+ }
+ if (allow_compression) {
+ get_ram_image();
+ } else {
+ get_uncompressed_ram_image();
+ }
+ return AsyncTask::DS_done;
+ });
+ task->set_task_chain("texture_reload");
+ task->set_priority(priority);
+ task_mgr->add(task);
+ cdataw->_reload_task = task;
+ return (AsyncFuture *)task;
+}
+
/**
* Replaces the current system-RAM image with the new data, converting it
* first if necessary from the indicated component-order format. See
@@ -1313,7 +1378,12 @@ new_simple_ram_image(int x_size, int y_size) {
cdata->_simple_ram_image._image = PTA_uchar::empty_array(expected_page_size);
cdata->_simple_ram_image._page_size = expected_page_size;
cdata->_simple_image_date_generated = (int32_t)time(nullptr);
- cdata->inc_simple_image_modified();
+
+ // If we don't have a RAM image currently, we need to update this, to let the
+ // GSG know that it needs to update the simple image it is currently using.
+ if (!do_has_ram_image(cdata)) {
+ cdata->inc_image_modified();
+ }
return cdata->_simple_ram_image._image;
}
@@ -5212,11 +5282,19 @@ do_write_one(CData *cdata, const Filename &fullpath, int z, int n) {
success = pfm.write(fullpath);
} else {
// Writing a normal, integer texture.
+ PNMFileType *type =
+ PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension(fullpath);
+ if (type == nullptr) {
+ gobj_cat.error()
+ << "Texture::write() - couldn't determine type from extension: " << fullpath << endl;
+ return false;
+ }
+
PNMImage pnmimage;
if (!do_store_one(cdata, pnmimage, z, n)) {
return false;
}
- success = pnmimage.write(fullpath);
+ success = pnmimage.write(fullpath, type);
}
if (!success) {
@@ -5452,7 +5530,6 @@ unlocked_ensure_ram_image(bool allow_compression) {
cdataw->_component_type = cdata_tex->_component_type;
cdataw->inc_properties_modified();
- cdataw->inc_image_modified();
}
cdataw->_keep_ram_image = cdata_tex->_keep_ram_image;
@@ -5462,9 +5539,7 @@ unlocked_ensure_ram_image(bool allow_compression) {
nassertr(_reloading, nullptr);
_reloading = false;
- // We don't generally increment the cdata->_image_modified semaphore,
- // because this is just a reload, and presumably the image hasn't changed
- // (unless we hit the if condition above).
+ cdataw->inc_image_modified();
_cvar.notify_all();
@@ -5602,6 +5677,12 @@ do_reload_ram_image(CData *cdata, bool allow_compression) {
cache->store(record);
}
}
+
+ // Remove any pending asynchronous reload operation.
+ if (cdata->_reload_task != nullptr) {
+ cdata->_reload_task->remove();
+ cdata->_reload_task = nullptr;
+ }
}
/**
@@ -6928,7 +7009,6 @@ do_clear(CData *cdata) {
cdata->inc_properties_modified();
cdata->inc_image_modified();
- cdata->inc_simple_image_modified();
}
/**
@@ -7617,7 +7697,6 @@ do_set_simple_ram_image(CData *cdata, CPTA_uchar image, int x_size, int y_size)
cdata->_simple_ram_image._image = image.cast_non_const();
cdata->_simple_ram_image._page_size = image.size();
cdata->_simple_image_date_generated = (int32_t)time(nullptr);
- cdata->inc_simple_image_modified();
}
/**
@@ -7712,11 +7791,6 @@ do_clear_simple_ram_image(CData *cdata) {
cdata->_simple_ram_image._image.clear();
cdata->_simple_ram_image._page_size = 0;
cdata->_simple_image_date_generated = 0;
-
- // We allow this exception: we update the _simple_image_modified here, since
- // no one really cares much about that anyway, and it's convenient to do it
- // here.
- cdata->inc_simple_image_modified();
}
/**
@@ -10342,10 +10416,12 @@ make_this_from_bam(const FactoryParams ¶ms) {
// object to read all of the attributes from the bam stream.
Texture *dummy = this;
AutoTextureScale auto_texture_scale = ATS_unspecified;
+ bool has_simple_ram_image = false;
{
CDWriter cdata_dummy(dummy->_cycler, true);
dummy->do_fillin_body(cdata_dummy, scan, manager);
auto_texture_scale = cdata_dummy->_auto_texture_scale;
+ has_simple_ram_image = !cdata_dummy->_simple_ram_image._image.empty();
}
if (filename.empty()) {
@@ -10378,6 +10454,61 @@ make_this_from_bam(const FactoryParams ¶ms) {
case TT_1d_texture:
case TT_2d_texture:
case TT_1d_texture_array:
+ // If we don't want to preload textures, and we already have a simple
+ // RAM image (or don't need one), we don't need to load it from disk.
+ // We do check for it in the texture pool first, though, in case it has
+ // already been loaded.
+ if ((options.get_texture_flags() & LoaderOptions::TF_preload) == 0 &&
+ (has_simple_ram_image || (options.get_texture_flags() & LoaderOptions::TF_preload_simple) == 0)) {
+ if (alpha_filename.empty()) {
+ me = TexturePool::get_texture(filename, primary_file_num_channels,
+ has_read_mipmaps);
+ } else {
+ me = TexturePool::get_texture(filename, alpha_filename,
+ primary_file_num_channels,
+ alpha_file_channel,
+ has_read_mipmaps);
+ }
+ if (me != nullptr && me->get_texture_type() == texture_type) {
+ // We can use this.
+ break;
+ }
+
+ // We don't have a texture, but we didn't need to preload it, so we
+ // can just use this one. We just need to know where we can find it
+ // when we do need to reload it.
+ Filename fullpath = filename;
+ Filename alpha_fullpath = alpha_filename;
+ const DSearchPath &model_path = get_model_path();
+ if (vfs->resolve_filename(fullpath, model_path) &&
+ (alpha_fullpath.empty() || vfs->resolve_filename(alpha_fullpath, model_path))) {
+ me = dummy;
+ me->set_name(name);
+
+ {
+ CDWriter cdata_me(me->_cycler, true);
+ cdata_me->_filename = filename;
+ cdata_me->_alpha_filename = alpha_filename;
+ cdata_me->_fullpath = fullpath;
+ cdata_me->_alpha_fullpath = alpha_fullpath;
+ cdata_me->_primary_file_num_channels = primary_file_num_channels;
+ cdata_me->_alpha_file_channel = alpha_file_channel;
+ cdata_me->_texture_type = texture_type;
+ cdata_me->_loaded_from_image = true;
+ cdata_me->_has_read_mipmaps = has_read_mipmaps;
+ }
+
+ // To manage the reference count, explicitly ref it now, then unref
+ // it in the finalize callback.
+ me->ref();
+ manager->register_finalize(me);
+
+ // Do add it to the cache now, so that future uses of this same
+ // texture are unified.
+ TexturePool::add_texture(me);
+ return me;
+ }
+ }
if (alpha_filename.empty()) {
me = TexturePool::load_texture(filename, primary_file_num_channels,
has_read_mipmaps, options);
@@ -10474,7 +10605,6 @@ do_fillin_body(CData *cdata, DatagramIterator &scan, BamReader *manager) {
cdata->_simple_ram_image._image = image;
cdata->_simple_ram_image._page_size = u_size;
- cdata->inc_simple_image_modified();
}
if (manager->get_file_minor_ver() >= 45) {
@@ -10672,7 +10802,6 @@ CData(const Texture::CData ©) {
_properties_modified = copy._properties_modified;
_image_modified = copy._image_modified;
- _simple_image_modified = copy._simple_image_modified;
_modified_pages = copy._modified_pages;
}
diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h
index 0254db323a..de3d90e5ea 100644
--- a/panda/src/gobj/texture.h
+++ b/panda/src/gobj/texture.h
@@ -45,7 +45,7 @@
#include "bamCacheRecord.h"
#include "pnmImage.h"
#include "pfmFile.h"
-#include "asyncFuture.h"
+#include "asyncTask.h"
class TextureContext;
class FactoryParams;
@@ -448,6 +448,7 @@ PUBLISHED:
MAKE_PROPERTY(expected_ram_image_size, get_expected_ram_image_size);
MAKE_PROPERTY(expected_ram_page_size, get_expected_ram_page_size);
+ PT(AsyncFuture) async_ensure_ram_image(bool allow_compression = true, int priority = 0);
INLINE CPTA_uchar get_ram_image();
INLINE CompressionMode get_ram_image_compression() const;
INLINE CPTA_uchar get_uncompressed_ram_image();
@@ -520,10 +521,8 @@ PUBLISHED:
INLINE UpdateSeq get_properties_modified() const;
INLINE UpdateSeq get_image_modified() const;
- INLINE UpdateSeq get_simple_image_modified() const;
MAKE_PROPERTY(properties_modified, get_properties_modified);
MAKE_PROPERTY(image_modified, get_image_modified);
- MAKE_PROPERTY(simple_image_modified, get_simple_image_modified);
SparseArray get_image_modified_pages(UpdateSeq since, int n = 0) const;
@@ -784,6 +783,7 @@ protected:
void do_set_pad_size(CData *cdata, int x, int y, int z);
virtual bool do_can_reload(const CData *cdata) const;
bool do_reload(CData *cdata);
+ AsyncFuture *do_async_ensure_ram_image(const CData *cdata, bool allow_compression, int priority);
INLINE AutoTextureScale do_get_auto_texture_scale(const CData *cdata) const;
@@ -959,7 +959,6 @@ protected:
INLINE void inc_properties_modified();
INLINE void inc_image_modified();
void inc_image_page_modified(int z);
- INLINE void inc_simple_image_modified();
Filename _filename;
Filename _alpha_filename;
@@ -1028,10 +1027,11 @@ protected:
UpdateSeq _properties_modified;
UpdateSeq _image_modified;
- UpdateSeq _simple_image_modified;
ModifiedPageRanges _modified_pages;
+ PT(AsyncTask) _reload_task;
+
public:
static TypeHandle get_class_type() {
return _type_handle;
diff --git a/panda/src/gobj/textureContext.I b/panda/src/gobj/textureContext.I
index a4be4a4935..70836cdb5b 100644
--- a/panda/src/gobj/textureContext.I
+++ b/panda/src/gobj/textureContext.I
@@ -67,15 +67,6 @@ was_image_modified() const {
return _image_modified != get_texture()->get_image_modified();
}
-/**
- * Returns true if the texture's "simple" image has been modified since the
- * last time mark_simple_loaded() was called.
- */
-INLINE bool TextureContext::
-was_simple_image_modified() const {
- return _simple_image_modified != get_texture()->get_simple_image_modified();
-}
-
/**
* Returns true if the given page of the texture image has been modified since
* the last time mark_loaded() was called.
@@ -103,15 +94,6 @@ get_image_modified() const {
return _image_modified;
}
-/**
- * Returns a sequence number which is guaranteed to change at least every time
- * the texture's "simple" image data is modified.
- */
-INLINE UpdateSeq TextureContext::
-get_simple_image_modified() const {
- return _simple_image_modified;
-}
-
/**
* Returns a SparseArray indicating which pages of the texture have been
* modified since the last call to mark_loaded().
@@ -146,20 +128,6 @@ mark_loaded() {
set_resident(true);
}
-/**
- * Should be called after the texture's "simple" image has been loaded into
- * graphics memory.
- */
-INLINE void TextureContext::
-mark_simple_loaded() {
- _properties_modified = get_texture()->get_properties_modified();
- _simple_image_modified = get_texture()->get_simple_image_modified();
- update_modified(std::max(_properties_modified, _simple_image_modified));
-
- // The texture's not exactly resident now, but some part of it is.
- set_resident(true);
-}
-
/**
* Should be called after the texture has been forced out of texture memory.
*/
@@ -167,7 +135,6 @@ INLINE void TextureContext::
mark_unloaded() {
_properties_modified = UpdateSeq::old();
_image_modified = UpdateSeq::old();
- _simple_image_modified = UpdateSeq::old();
update_modified(UpdateSeq::old());
set_resident(false);
diff --git a/panda/src/gobj/textureContext.h b/panda/src/gobj/textureContext.h
index b531c49504..de828781e2 100644
--- a/panda/src/gobj/textureContext.h
+++ b/panda/src/gobj/textureContext.h
@@ -43,19 +43,16 @@ PUBLISHED:
INLINE bool was_modified() const;
INLINE bool was_properties_modified() const;
INLINE bool was_image_modified() const;
- INLINE bool was_simple_image_modified() const;
INLINE bool was_image_page_modified(int z, int n) const;
INLINE UpdateSeq get_properties_modified() const;
INLINE UpdateSeq get_image_modified() const;
- INLINE UpdateSeq get_simple_image_modified() const;
INLINE SparseArray get_image_modified_pages(int n = 0) const;
public:
INLINE void update_data_size_bytes(size_t new_data_size_bytes);
INLINE void mark_loaded();
- INLINE void mark_simple_loaded();
INLINE void mark_unloaded();
INLINE void mark_needs_reload();
@@ -66,7 +63,6 @@ private:
int _view;
UpdateSeq _properties_modified;
UpdateSeq _image_modified;
- UpdateSeq _simple_image_modified;
public:
static TypeHandle get_class_type() {
diff --git a/panda/src/gobj/texturePeeker.h b/panda/src/gobj/texturePeeker.h
index 2894e349cc..011766b1cc 100644
--- a/panda/src/gobj/texturePeeker.h
+++ b/panda/src/gobj/texturePeeker.h
@@ -89,7 +89,6 @@ private:
int _y_size;
int _z_size;
int _is_cube;
- int _unused1;
int _pixel_width;
Texture::Format _format;
Texture::ComponentType _component_type;
diff --git a/panda/src/gobj/texturePool.I b/panda/src/gobj/texturePool.I
index 70836469fc..2cbb7da4b7 100644
--- a/panda/src/gobj/texturePool.I
+++ b/panda/src/gobj/texturePool.I
@@ -33,6 +33,29 @@ verify_texture(const Filename &filename) {
return load_texture(filename) != nullptr;
}
+/**
+ * Returns the texture that has already been previously loaded, or NULL
+ * otherwise.
+ */
+INLINE Texture *TexturePool::
+get_texture(const Filename &filename, int primary_file_num_channels,
+ bool read_mipmaps) {
+ return get_global_ptr()->ns_get_texture(filename, primary_file_num_channels,
+ read_mipmaps);
+}
+
+/**
+ * Returns the texture that has already been previously loaded, or NULL
+ * otherwise.
+ */
+INLINE Texture *TexturePool::
+get_texture(const Filename &filename, const Filename &alpha_filename,
+ int primary_file_num_channels, int alpha_file_channel,
+ bool read_mipmaps) {
+ return get_global_ptr()->ns_get_texture(filename, primary_file_num_channels,
+ read_mipmaps);
+}
+
/**
* Loads the given filename up into a texture, if it has not already been
* loaded, and returns the new texture. If a texture with the same filename
diff --git a/panda/src/gobj/texturePool.cxx b/panda/src/gobj/texturePool.cxx
index b4ccbe11f6..755f937c67 100644
--- a/panda/src/gobj/texturePool.cxx
+++ b/panda/src/gobj/texturePool.cxx
@@ -273,6 +273,62 @@ ns_has_texture(const Filename &orig_filename) {
return false;
}
+/**
+ * The nonstatic implementation of get_texture().
+ */
+Texture *TexturePool::
+ns_get_texture(const Filename &orig_filename, int primary_file_num_channels,
+ bool read_mipmaps) {
+ LookupKey key;
+ key._primary_file_num_channels = primary_file_num_channels;
+ {
+ MutexHolder holder(_lock);
+ resolve_filename(key._fullpath, orig_filename, read_mipmaps, LoaderOptions());
+
+ Textures::const_iterator ti;
+ ti = _textures.find(key);
+ if (ti != _textures.end()) {
+ // This texture was previously loaded.
+ Texture *tex = (*ti).second;
+ nassertr(!tex->get_fullpath().empty(), tex);
+ return tex;
+ }
+ }
+
+ return nullptr;
+}
+
+/**
+ * The nonstatic implementation of get_texture().
+ */
+Texture *TexturePool::
+ns_get_texture(const Filename &orig_filename,
+ const Filename &orig_alpha_filename,
+ int primary_file_num_channels,
+ int alpha_file_channel,
+ bool read_mipmaps) {
+ LookupKey key;
+ key._primary_file_num_channels = primary_file_num_channels;
+ key._alpha_file_channel = alpha_file_channel;
+ {
+ MutexHolder holder(_lock);
+ LoaderOptions options;
+ resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
+ resolve_filename(key._alpha_fullpath, orig_alpha_filename, read_mipmaps, options);
+
+ Textures::const_iterator ti;
+ ti = _textures.find(key);
+ if (ti != _textures.end()) {
+ // This texture was previously loaded.
+ Texture *tex = (*ti).second;
+ nassertr(!tex->get_fullpath().empty(), tex);
+ return tex;
+ }
+ }
+
+ return nullptr;
+}
+
/**
* The nonstatic implementation of load_texture().
*/
diff --git a/panda/src/gobj/texturePool.h b/panda/src/gobj/texturePool.h
index e89dd62a5d..997017ee2e 100644
--- a/panda/src/gobj/texturePool.h
+++ b/panda/src/gobj/texturePool.h
@@ -38,6 +38,14 @@ class EXPCL_PANDA_GOBJ TexturePool {
PUBLISHED:
INLINE static bool has_texture(const Filename &filename);
INLINE static bool verify_texture(const Filename &filename);
+ INLINE static Texture *get_texture(const Filename &filename,
+ int primary_file_num_channels = 0,
+ bool read_mipmaps = false);
+ INLINE static Texture *get_texture(const Filename &filename,
+ const Filename &alpha_filename,
+ int primary_file_num_channels = 0,
+ int alpha_file_channel = 0,
+ bool read_mipmaps = false);
BLOCKING INLINE static Texture *load_texture(const Filename &filename,
int primary_file_num_channels = 0,
bool read_mipmaps = false,
@@ -109,6 +117,14 @@ private:
TexturePool();
bool ns_has_texture(const Filename &orig_filename);
+ Texture *ns_get_texture(const Filename &filename,
+ int primary_file_num_channels = 0,
+ bool read_mipmaps = false);
+ Texture *ns_get_texture(const Filename &filename,
+ const Filename &alpha_filename,
+ int primary_file_num_channels = 0,
+ int alpha_file_channel = 0,
+ bool read_mipmaps = false);
Texture *ns_load_texture(const Filename &orig_filename,
int primary_file_num_channels,
bool read_mipmaps,
diff --git a/panda/src/gobj/textureReloadRequest.cxx b/panda/src/gobj/textureReloadRequest.cxx
index b63acb6090..af6454c33a 100644
--- a/panda/src/gobj/textureReloadRequest.cxx
+++ b/panda/src/gobj/textureReloadRequest.cxx
@@ -22,28 +22,34 @@ TypeHandle TextureReloadRequest::_type_handle;
AsyncTask::DoneStatus TextureReloadRequest::
do_task() {
// Don't reload the texture if it doesn't need it.
- if (_texture->was_image_modified(_pgo)) {
- double delay = async_load_delay;
- if (delay != 0.0) {
- Thread::sleep(delay);
- }
+ if (!_texture->was_image_modified(_pgo) &&
+ (_allow_compressed ? _texture->has_ram_image() : _texture->has_uncompressed_ram_image())) {
+ return DS_done;
+ }
- if (_texture->was_image_modified(_pgo)) {
- if (_allow_compressed) {
- _texture->get_ram_image();
- } else {
- _texture->get_uncompressed_ram_image();
- }
+ double delay = async_load_delay;
+ if (delay != 0.0) {
+ Thread::sleep(delay);
- // Now that we've loaded the texture, we should ensure it actually gets
- // prepared--even if it's no longer visible in the frame--or it may
- // become a kind of a leak (if the texture is never rendered again on
- // this GSG, we'll just end up carrying the texture memory in RAM
- // forever, instead of dumping it as soon as it gets prepared).
- _pgo->enqueue_texture(_texture);
+ if (!_texture->was_image_modified(_pgo) &&
+ (_allow_compressed ? _texture->has_ram_image() : _texture->has_uncompressed_ram_image())) {
+ return DS_done;
}
}
+ if (_allow_compressed) {
+ _texture->get_ram_image();
+ } else {
+ _texture->get_uncompressed_ram_image();
+ }
+
+ // Now that we've loaded the texture, we should ensure it actually gets
+ // prepared--even if it's no longer visible in the frame--or it may become a
+ // kind of a leak (if the texture is never rendered again on this GSG, we'll
+ // just end up carrying the texture memory in RAM forever, instead of dumping
+ // it as soon as it gets prepared).
+ _pgo->enqueue_texture(_texture);
+
// Don't continue the task; we're done.
return DS_done;
}
diff --git a/panda/src/gobj/textureReloadRequest.h b/panda/src/gobj/textureReloadRequest.h
index 625ac3ce3e..1010cd7ec5 100644
--- a/panda/src/gobj/textureReloadRequest.h
+++ b/panda/src/gobj/textureReloadRequest.h
@@ -27,6 +27,8 @@
* force the texture's image to be re-read from disk. It is used by
* GraphicsStateGuardian::async_reload_texture(), when get_incomplete_render()
* is true.
+ *
+ * @deprecated Use Texture::async_ensure_ram_image() instead.
*/
class EXPCL_PANDA_GOBJ TextureReloadRequest : public AsyncTask {
public:
diff --git a/panda/src/grutil/pipeOcclusionCullTraverser.cxx b/panda/src/grutil/pipeOcclusionCullTraverser.cxx
index 2a7f27a878..62828319b3 100644
--- a/panda/src/grutil/pipeOcclusionCullTraverser.cxx
+++ b/panda/src/grutil/pipeOcclusionCullTraverser.cxx
@@ -290,7 +290,7 @@ bool PipeOcclusionCullTraverser::
is_in_view(CullTraverserData &data) {
_next_query = nullptr;
- if (!CullTraverser::is_in_view(data)) {
+ if (!data.is_in_view(get_camera_mask())) {
return false;
}
if (!_live) {
diff --git a/panda/src/grutil/rigidBodyCombiner.cxx b/panda/src/grutil/rigidBodyCombiner.cxx
index fb2c3959ab..76ec13a538 100644
--- a/panda/src/grutil/rigidBodyCombiner.cxx
+++ b/panda/src/grutil/rigidBodyCombiner.cxx
@@ -145,8 +145,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
}
// Render the internal scene only--this is the optimized scene.
- CullTraverserData next_data(data, _internal_root);
- trav->traverse(next_data);
+ trav->traverse_down(data, _internal_root);
// Do not directly render the nodes beneath this node.
return false;
diff --git a/panda/src/grutil/shaderTerrainMesh.cxx b/panda/src/grutil/shaderTerrainMesh.cxx
index 4683ee6ba7..4a2281a941 100644
--- a/panda/src/grutil/shaderTerrainMesh.cxx
+++ b/panda/src/grutil/shaderTerrainMesh.cxx
@@ -107,6 +107,7 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
_update_enabled(true),
_heightfield_tex(nullptr)
{
+ set_renderable();
}
/**
@@ -440,14 +441,7 @@ void ShaderTerrainMesh::do_create_chunk_geom() {
}
/**
- * @copydoc PandaNode::is_renderable()
- */
-bool ShaderTerrainMesh::is_renderable() const {
- return true;
-}
-
-/**
- * @copydoc PandaNode::is_renderable()
+ * @copydoc PandaNode::safe_to_flatten()
*/
bool ShaderTerrainMesh::safe_to_flatten() const {
return false;
diff --git a/panda/src/grutil/shaderTerrainMesh.h b/panda/src/grutil/shaderTerrainMesh.h
index 9b08e12c0d..a0b8541e85 100644
--- a/panda/src/grutil/shaderTerrainMesh.h
+++ b/panda/src/grutil/shaderTerrainMesh.h
@@ -85,7 +85,6 @@ PUBLISHED:
public:
// Methods derived from PandaNode
- virtual bool is_renderable() const;
virtual bool safe_to_flatten() const;
virtual bool safe_to_combine() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
diff --git a/panda/src/linmath/lquaternion_src.cxx b/panda/src/linmath/lquaternion_src.cxx
index cd48827eea..430b9971dc 100644
--- a/panda/src/linmath/lquaternion_src.cxx
+++ b/panda/src/linmath/lquaternion_src.cxx
@@ -106,7 +106,6 @@ set_hpr(const FLOATNAME(LVecBase3) &hpr, CoordinateSystem cs) {
v = FLOATNAME(LVector3)::right(cs);
a = deg_2_rad(hpr[1] * 0.5f);
csincos(a, &s, &c);
- s = csin(a);
quat_p.set(c, v[0] * s, v[1] * s, v[2] * s);
v = FLOATNAME(LVector3)::forward(cs);
a = deg_2_rad(hpr[2] * 0.5f);
diff --git a/panda/src/linmath/lvecBase2_ext_src.I b/panda/src/linmath/lvecBase2_ext_src.I
index 5d731fec7a..5b36e339ec 100644
--- a/panda/src/linmath/lvecBase2_ext_src.I
+++ b/panda/src/linmath/lvecBase2_ext_src.I
@@ -357,5 +357,45 @@ __ceil__(PyObject *self) const {
return py_vec;
}
+/**
+ *
+ */
+INLINE_LINMATH int Extension::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+ if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+ PyErr_SetString(PyExc_BufferError,
+ "Object is not writable.");
+ return -1;
+ }
+
+ static const char format[2] = {FLOATTOKEN, 0};
+ static const Py_ssize_t shape = FLOATNAME(LVecBase2)::num_components;
+
+ Py_INCREF(self);
+
+ view->buf = (void *)_this->get_data();
+ view->obj = self;
+ view->len = 2 * sizeof(FLOATTYPE);
+ view->readonly = 1;
+ view->itemsize = sizeof(FLOATTYPE);
+ view->format = nullptr;
+ if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+ view->format = (char *)format;
+ }
+ view->ndim = 1;
+ view->shape = nullptr;
+ if ((flags & PyBUF_ND) == PyBUF_ND) {
+ view->shape = (Py_ssize_t *)&shape;
+ }
+ view->strides = nullptr;
+ if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+ view->strides = &view->itemsize;
+ }
+ view->suboffsets = nullptr;
+ view->internal = nullptr;
+
+ return 0;
+}
+
#undef PYNUMBER_FLOATTYPE
#undef PY_AS_FLOATTYPE
diff --git a/panda/src/linmath/lvecBase2_ext_src.h b/panda/src/linmath/lvecBase2_ext_src.h
index dad3807548..372c805517 100644
--- a/panda/src/linmath/lvecBase2_ext_src.h
+++ b/panda/src/linmath/lvecBase2_ext_src.h
@@ -34,6 +34,8 @@ public:
INLINE_LINMATH PyObject *__round__(PyObject *self) const;
INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+ INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
};
#include "lvecBase2_ext_src.I"
diff --git a/panda/src/linmath/lvecBase2_src.h b/panda/src/linmath/lvecBase2_src.h
index 9e6af9170f..7f451b67b3 100644
--- a/panda/src/linmath/lvecBase2_src.h
+++ b/panda/src/linmath/lvecBase2_src.h
@@ -160,6 +160,8 @@ PUBLISHED:
INLINE_LINMATH void write_datagram(Datagram &destination) const;
INLINE_LINMATH void read_datagram(DatagramIterator &source);
+ EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
public:
// The underlying implementation is via the Eigen library, if available.
diff --git a/panda/src/linmath/lvecBase3_ext_src.I b/panda/src/linmath/lvecBase3_ext_src.I
index 85486df43d..c59add2465 100644
--- a/panda/src/linmath/lvecBase3_ext_src.I
+++ b/panda/src/linmath/lvecBase3_ext_src.I
@@ -370,5 +370,45 @@ __ceil__(PyObject *self) const {
return py_vec;
}
+/**
+ *
+ */
+INLINE_LINMATH int Extension::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+ if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+ PyErr_SetString(PyExc_BufferError,
+ "Object is not writable.");
+ return -1;
+ }
+
+ static const char format[2] = {FLOATTOKEN, 0};
+ static const Py_ssize_t shape = FLOATNAME(LVecBase3)::num_components;
+
+ Py_INCREF(self);
+
+ view->buf = (void *)_this->get_data();
+ view->obj = self;
+ view->len = 3 * sizeof(FLOATTYPE);
+ view->readonly = 1;
+ view->itemsize = sizeof(FLOATTYPE);
+ view->format = nullptr;
+ if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+ view->format = (char *)format;
+ }
+ view->ndim = 1;
+ view->shape = nullptr;
+ if ((flags & PyBUF_ND) == PyBUF_ND) {
+ view->shape = (Py_ssize_t *)&shape;
+ }
+ view->strides = nullptr;
+ if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+ view->strides = &view->itemsize;
+ }
+ view->suboffsets = nullptr;
+ view->internal = nullptr;
+
+ return 0;
+}
+
#undef PYNUMBER_FLOATTYPE
#undef PY_AS_FLOATTYPE
diff --git a/panda/src/linmath/lvecBase3_ext_src.h b/panda/src/linmath/lvecBase3_ext_src.h
index 6d3d9c85a9..e5e509147e 100644
--- a/panda/src/linmath/lvecBase3_ext_src.h
+++ b/panda/src/linmath/lvecBase3_ext_src.h
@@ -34,6 +34,8 @@ public:
INLINE_LINMATH PyObject *__round__(PyObject *self) const;
INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+ INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
};
#include "lvecBase3_ext_src.I"
diff --git a/panda/src/linmath/lvecBase3_src.h b/panda/src/linmath/lvecBase3_src.h
index 64785a4747..8594f1b921 100644
--- a/panda/src/linmath/lvecBase3_src.h
+++ b/panda/src/linmath/lvecBase3_src.h
@@ -181,6 +181,8 @@ PUBLISHED:
INLINE_LINMATH void write_datagram(Datagram &destination) const;
INLINE_LINMATH void read_datagram(DatagramIterator &source);
+ EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
public:
// The underlying implementation is via the Eigen library, if available.
diff --git a/panda/src/linmath/lvecBase4_ext_src.I b/panda/src/linmath/lvecBase4_ext_src.I
index b18012cbdc..d1be8a5316 100644
--- a/panda/src/linmath/lvecBase4_ext_src.I
+++ b/panda/src/linmath/lvecBase4_ext_src.I
@@ -388,5 +388,45 @@ __ceil__(PyObject *self) const {
return py_vec;
}
+/**
+ *
+ */
+INLINE_LINMATH int Extension::
+__getbuffer__(PyObject *self, Py_buffer *view, int flags) const {
+ if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
+ PyErr_SetString(PyExc_BufferError,
+ "Object is not writable.");
+ return -1;
+ }
+
+ static const char format[2] = {FLOATTOKEN, 0};
+ static const Py_ssize_t shape = FLOATNAME(LVecBase4)::num_components;
+
+ Py_INCREF(self);
+
+ view->buf = (void *)_this->get_data();
+ view->obj = self;
+ view->len = 4 * sizeof(FLOATTYPE);
+ view->readonly = 1;
+ view->itemsize = sizeof(FLOATTYPE);
+ view->format = nullptr;
+ if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
+ view->format = (char *)format;
+ }
+ view->ndim = 1;
+ view->shape = nullptr;
+ if ((flags & PyBUF_ND) == PyBUF_ND) {
+ view->shape = (Py_ssize_t *)&shape;
+ }
+ view->strides = nullptr;
+ if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
+ view->strides = &view->itemsize;
+ }
+ view->suboffsets = nullptr;
+ view->internal = nullptr;
+
+ return 0;
+}
+
#undef PYNUMBER_FLOATTYPE
#undef PY_AS_FLOATTYPE
diff --git a/panda/src/linmath/lvecBase4_ext_src.h b/panda/src/linmath/lvecBase4_ext_src.h
index da9dcd3d4b..b21e1b7ab7 100644
--- a/panda/src/linmath/lvecBase4_ext_src.h
+++ b/panda/src/linmath/lvecBase4_ext_src.h
@@ -34,6 +34,8 @@ public:
INLINE_LINMATH PyObject *__round__(PyObject *self) const;
INLINE_LINMATH PyObject *__floor__(PyObject *self) const;
INLINE_LINMATH PyObject *__ceil__(PyObject *self) const;
+
+ INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
};
#include "lvecBase4_ext_src.I"
diff --git a/panda/src/linmath/lvecBase4_src.h b/panda/src/linmath/lvecBase4_src.h
index e5a23374a4..129b427c2d 100644
--- a/panda/src/linmath/lvecBase4_src.h
+++ b/panda/src/linmath/lvecBase4_src.h
@@ -187,6 +187,8 @@ PUBLISHED:
INLINE_LINMATH void write_datagram(Datagram &destination) const;
INLINE_LINMATH void read_datagram(DatagramIterator &source);
+ EXTENSION(INLINE_LINMATH int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
+
public:
// The underlying implementation is via the Eigen library, if available.
diff --git a/panda/src/parametrics/ropeNode.cxx b/panda/src/parametrics/ropeNode.cxx
index 4daf80e8ea..5d49c2dda3 100644
--- a/panda/src/parametrics/ropeNode.cxx
+++ b/panda/src/parametrics/ropeNode.cxx
@@ -71,6 +71,7 @@ RopeNode(const std::string &name) :
PandaNode(name)
{
set_cull_callback();
+ set_renderable();
}
/**
@@ -162,17 +163,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool RopeNode::
-is_renderable() const {
- return true;
-}
-
/**
*
*/
diff --git a/panda/src/parametrics/ropeNode.h b/panda/src/parametrics/ropeNode.h
index e4647e304d..4cb8542dc0 100644
--- a/panda/src/parametrics/ropeNode.h
+++ b/panda/src/parametrics/ropeNode.h
@@ -45,7 +45,6 @@ public:
virtual bool safe_to_transform() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
PUBLISHED:
enum RenderMode {
diff --git a/panda/src/parametrics/sheetNode.cxx b/panda/src/parametrics/sheetNode.cxx
index 734082f577..620a35512f 100644
--- a/panda/src/parametrics/sheetNode.cxx
+++ b/panda/src/parametrics/sheetNode.cxx
@@ -70,6 +70,7 @@ SheetNode(const std::string &name) :
PandaNode(name)
{
set_cull_callback();
+ set_renderable();
}
/**
@@ -140,17 +141,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool SheetNode::
-is_renderable() const {
- return true;
-}
-
/**
*
*/
diff --git a/panda/src/parametrics/sheetNode.h b/panda/src/parametrics/sheetNode.h
index 97a8d80a71..1d27d840a8 100644
--- a/panda/src/parametrics/sheetNode.h
+++ b/panda/src/parametrics/sheetNode.h
@@ -43,7 +43,6 @@ public:
virtual bool safe_to_transform() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
PUBLISHED:
INLINE void set_surface(NurbsSurfaceEvaluator *surface);
diff --git a/panda/src/pgraph/config_pgraph.h b/panda/src/pgraph/config_pgraph.h
index f1bec0168e..59551133aa 100644
--- a/panda/src/pgraph/config_pgraph.h
+++ b/panda/src/pgraph/config_pgraph.h
@@ -30,7 +30,7 @@ NotifyCategoryDecl(pgraph, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
NotifyCategoryDecl(loader, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
NotifyCategoryDecl(portal, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
-extern ConfigVariableBool fake_view_frustum_cull;
+extern EXPCL_PANDA_PGRAPH ConfigVariableBool fake_view_frustum_cull;
extern ConfigVariableBool clip_plane_cull;
extern ConfigVariableBool allow_portal_cull;
extern ConfigVariableBool debug_portal_cull;
diff --git a/panda/src/pgraph/cullPlanes.cxx b/panda/src/pgraph/cullPlanes.cxx
index fafbae6cfa..7cfd67f30b 100644
--- a/panda/src/pgraph/cullPlanes.cxx
+++ b/panda/src/pgraph/cullPlanes.cxx
@@ -79,26 +79,33 @@ xform(const LMatrix4 &mat) const {
* will be added to the state, unless those ClipPlanes are also listed in
* off_attrib.
*/
-CPT(CullPlanes) CullPlanes::
-apply_state(const CullTraverser *trav, const CullTraverserData *data,
+void CullPlanes::
+apply_state(CPT(CullPlanes) &planes,
+ const CullTraverser *trav, const CullTraverserData *data,
const ClipPlaneAttrib *net_attrib,
const ClipPlaneAttrib *off_attrib,
- const OccluderEffect *node_effect) const {
+ const OccluderEffect *node_effect) {
if (net_attrib == nullptr && node_effect == nullptr) {
- return this;
+ return;
}
PT(CullPlanes) new_planes;
- if (get_ref_count() == 1) {
- new_planes = (CullPlanes *)this;
- } else {
- new_planes = new CullPlanes(*this);
+ if (planes != nullptr) {
+ if (planes->get_ref_count() == 1) {
+ new_planes = (CullPlanes *)planes.p();
+ } else {
+ new_planes = new CullPlanes(*planes);
+ }
}
CPT(TransformState) net_transform = nullptr;
if (net_attrib != nullptr) {
int num_on_planes = net_attrib->get_num_on_planes();
+ if (num_on_planes > 0 && new_planes.is_null()) {
+ new_planes = new CullPlanes;
+ }
+
for (int i = 0; i < num_on_planes; ++i) {
NodePath clip_plane = net_attrib->get_on_plane(i);
Planes::const_iterator pi = new_planes->_planes.find(clip_plane);
@@ -128,13 +135,17 @@ apply_state(const CullTraverser *trav, const CullTraverserData *data,
const Lens *lens = scene->get_lens();
int num_on_occluders = node_effect->get_num_on_occluders();
+ if (num_on_occluders > 0 && new_planes.is_null()) {
+ new_planes = new CullPlanes;
+ }
+
for (int i = 0; i < num_on_occluders; ++i) {
NodePath occluder = node_effect->get_on_occluder(i);
Occluders::const_iterator oi = new_planes->_occluders.find(occluder);
if (oi == new_planes->_occluders.end()) {
// Here's a new occluder; consider adding it to the list.
OccluderNode *occluder_node = DCAST(OccluderNode, occluder.node());
- nassertr(occluder_node->get_num_vertices() == 4, new_planes);
+ nassertv(occluder_node->get_num_vertices() == 4);
CPT(TransformState) occluder_transform = occluder.get_transform(scene->get_cull_center());
@@ -247,7 +258,7 @@ apply_state(const CullTraverser *trav, const CullTraverserData *data,
// existing occluder volumes.
bool is_enclosed = false;
Occluders::const_iterator oi;
- for (oi = _occluders.begin(); oi != _occluders.end(); ++oi) {
+ for (oi = new_planes->_occluders.begin(); oi != new_planes->_occluders.end(); ++oi) {
int occluder_result = (*oi).second->contains(occluder_gbv);
if ((occluder_result & BoundingVolume::IF_all) != 0) {
is_enclosed = true;
@@ -292,14 +303,14 @@ apply_state(const CullTraverser *trav, const CullTraverserData *data,
if (show_occluder_volumes) {
// Draw the frustum for visualization.
- nassertr(net_transform != nullptr, new_planes);
+ nassertv(net_transform != nullptr);
trav->draw_bounding_volume(frustum, data->get_internal_transform(trav));
}
}
}
}
- return new_planes;
+ planes = std::move(new_planes);
}
/**
diff --git a/panda/src/pgraph/cullPlanes.h b/panda/src/pgraph/cullPlanes.h
index 9480854853..8688e30054 100644
--- a/panda/src/pgraph/cullPlanes.h
+++ b/panda/src/pgraph/cullPlanes.h
@@ -53,11 +53,12 @@ public:
static CPT(CullPlanes) make_empty();
CPT(CullPlanes) xform(const LMatrix4 &mat) const;
- CPT(CullPlanes) apply_state(const CullTraverser *trav,
- const CullTraverserData *data,
- const ClipPlaneAttrib *net_attrib,
- const ClipPlaneAttrib *off_attrib,
- const OccluderEffect *node_effect) const;
+ static void apply_state(CPT(CullPlanes) &planes,
+ const CullTraverser *trav,
+ const CullTraverserData *data,
+ const ClipPlaneAttrib *net_attrib,
+ const ClipPlaneAttrib *off_attrib,
+ const OccluderEffect *node_effect);
CPT(CullPlanes) do_cull(int &result, CPT(RenderState) &state,
const GeometricBoundingVolume *node_gbv) const;
diff --git a/panda/src/pgraph/cullTraverser.I b/panda/src/pgraph/cullTraverser.I
index 98bc781b50..7b243bc7c7 100644
--- a/panda/src/pgraph/cullTraverser.I
+++ b/panda/src/pgraph/cullTraverser.I
@@ -183,6 +183,14 @@ get_effective_incomplete_render() const {
return _effective_incomplete_render;
}
+/**
+ * Returns true if fake view frustum culling is active.
+ */
+INLINE bool CullTraverser::
+get_fake_view_frustum_cull() const {
+ return _fake_view_frustum_cull;
+}
+
/**
* Flushes the PStatCollectors used during traversal.
*/
@@ -195,53 +203,106 @@ flush_level() {
}
/**
- * This is implemented inline to reduce recursion.
+ * Calls traverse_down on each child.
*/
INLINE void CullTraverser::
-do_traverse(CullTraverserData &data) {
- if (is_in_view(data)) {
- if (pgraph_cat.is_spam()) {
- pgraph_cat.spam()
- << "\n" << data.get_node_path()
- << " " << data._draw_mask << "\n";
- }
-
- PandaNodePipelineReader *node_reader = data.node_reader();
- int fancy_bits = node_reader->get_fancy_bits();
-
- if (fancy_bits == 0 && data._cull_planes->is_empty()) {
- // Nothing interesting in this node; just move on.
-
- } else {
- // Something in this node is worth taking a closer look.
- const RenderEffects *node_effects = node_reader->get_effects();
- if (node_effects->has_show_bounds()) {
- // If we should show the bounding volume for this node, make it up
- // now.
- show_bounds(data, node_effects->has_show_tight_bounds());
- }
-
- data.apply_transform_and_state(this);
-
- const FogAttrib *fog = (const FogAttrib *)
- node_reader->get_state()->get_attrib(FogAttrib::get_class_slot());
-
- if (fog != nullptr && fog->get_fog() != nullptr) {
- // If we just introduced a FogAttrib here, call adjust_to_camera()
- // now. This maybe isn't the perfect time to call it, but it's good
- // enough; and at this time we have all the information we need for
- // it.
- fog->get_fog()->adjust_to_camera(get_camera_transform());
- }
-
- if (fancy_bits & PandaNode::FB_cull_callback) {
- PandaNode *node = data.node();
- if (!node->cull_callback(this, data)) {
- return;
- }
- }
- }
-
- traverse_below(data);
+traverse_below(CullTraverserData &data) {
+ PandaNodePipelineReader *node_reader = data.node_reader();
+ PandaNode::Children children = node_reader->get_children();
+ node_reader->release();
+ int num_children = children.get_num_children();
+ for (int i = 0; i < num_children; ++i) {
+ traverse_down(data, children.get_child_connection(i), data._state);
}
}
+
+/**
+ * Traverses a child of the given node/data.
+ */
+INLINE void CullTraverser::
+traverse_down(const CullTraverserData &data, PandaNode *node) {
+ traverse_down(data, node, data._net_transform, data._state);
+}
+
+/**
+ * Traverses down into the given node/data, overriding the current transform
+ * and state. Note that the state and transform on the given node are still
+ * applied.
+ */
+INLINE void CullTraverser::
+traverse_down(const CullTraverserData &data, PandaNode *node,
+ const TransformState *net_transform, const RenderState *state) {
+ PandaNodePipelineReader node_reader(node, data._node_reader.get_current_thread());
+
+ int result = data.is_child_in_view(node_reader, _camera_mask);
+ if (result == BoundingVolume::IF_no_intersection) {
+#ifdef NDEBUG
+ return;
+#else
+ if (UNLIKELY(_fake_view_frustum_cull)) {
+ do_fake_cull(data, node, net_transform, state);
+ }
+ return;
+#endif
+ }
+
+ GeometricBoundingVolume *view_frustum = nullptr;
+ if ((result & BoundingVolume::IF_all) == 0 && !node_reader.is_final()) {
+ view_frustum = data._view_frustum;
+ }
+
+ CullTraverserData next_data(data, std::move(node_reader), net_transform, state, view_frustum);
+ if (data._cull_planes != nullptr) {
+ const GeometricBoundingVolume *node_gbv = node_reader.get_bounds()->as_geometric_bounding_volume();
+ if (!next_data.apply_cull_planes(data._cull_planes, node_gbv)) {
+ return;
+ }
+ }
+
+ do_traverse(next_data);
+}
+
+/**
+ * Traverses down into the given node, as though it were a child node of the
+ * current node.
+ */
+INLINE void CullTraverser::
+traverse_down(const CullTraverserData &data, const PandaNode::DownConnection &child) {
+ traverse_down(data, child, data._state);
+}
+
+/**
+ * Traverses down into the given node, as though it were a child node of the
+ * current node, except that the current state is ignored and replaced with the
+ * given state. The state on the child node is still applied.
+ */
+INLINE void CullTraverser::
+traverse_down(const CullTraverserData &data, const PandaNode::DownConnection &child, const RenderState *state) {
+ int result = data.is_child_in_view(child, _camera_mask);
+ if (result == BoundingVolume::IF_no_intersection) {
+#ifdef NDEBUG
+ return;
+#else
+ if (UNLIKELY(_fake_view_frustum_cull)) {
+ do_fake_cull(data, child.get_child(), data._net_transform, state);
+ }
+ return;
+#endif
+ }
+
+ PandaNodePipelineReader node_reader(child.get_child(), data._node_reader.get_current_thread());
+
+ GeometricBoundingVolume *view_frustum = nullptr;
+ if ((result & BoundingVolume::IF_all) == 0 && !node_reader.is_final()) {
+ view_frustum = data._view_frustum;
+ }
+
+ CullTraverserData next_data(data, std::move(node_reader), data._net_transform, state, view_frustum);
+
+ if (data._cull_planes != nullptr &&
+ !next_data.apply_cull_planes(data._cull_planes, child.get_bounds())) {
+ return;
+ }
+
+ do_traverse(next_data);
+}
diff --git a/panda/src/pgraph/cullTraverser.cxx b/panda/src/pgraph/cullTraverser.cxx
index eee63e026b..1183707240 100644
--- a/panda/src/pgraph/cullTraverser.cxx
+++ b/panda/src/pgraph/cullTraverser.cxx
@@ -17,6 +17,7 @@
#include "transformState.h"
#include "renderState.h"
#include "colorAttrib.h"
+#include "textureAttrib.h"
#include "renderModeAttrib.h"
#include "cullFaceAttrib.h"
#include "depthOffsetAttrib.h"
@@ -48,14 +49,15 @@ TypeHandle CullTraverser::_type_handle;
CullTraverser::
CullTraverser() :
_gsg(nullptr),
- _current_thread(Thread::get_current_thread())
+ _current_thread(Thread::get_current_thread()),
+ _camera_mask(DrawMask::all_on()),
+ _fake_view_frustum_cull(false),
+ _has_tag_state_key(false),
+ _initial_state(RenderState::make_empty()),
+ _cull_handler(nullptr),
+ _portal_clipper(nullptr),
+ _effective_incomplete_render(false)
{
- _camera_mask = DrawMask::all_on();
- _has_tag_state_key = false;
- _initial_state = RenderState::make_empty();
- _cull_handler = nullptr;
- _portal_clipper = nullptr;
- _effective_incomplete_render = true;
}
/**
@@ -67,6 +69,7 @@ CullTraverser(const CullTraverser ©) :
_current_thread(copy._current_thread),
_scene_setup(copy._scene_setup),
_camera_mask(copy._camera_mask),
+ _fake_view_frustum_cull(copy._fake_view_frustum_cull),
_has_tag_state_key(copy._has_tag_state_key),
_tag_state_key(copy._tag_state_key),
_initial_state(copy._initial_state),
@@ -99,6 +102,10 @@ set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsg,
_effective_incomplete_render = _gsg->get_incomplete_render() && dr_incomplete_render;
_view_frustum = scene_setup->get_view_frustum();
+
+#ifndef NDEBUG
+ _fake_view_frustum_cull = fake_view_frustum_cull;
+#endif
}
/**
@@ -132,7 +139,9 @@ traverse(const NodePath &root) {
_initial_state, _view_frustum,
_current_thread);
- traverse(data);
+ if (data.is_in_view(_camera_mask)) {
+ do_traverse(data);
+ }
// Finally add the lines to be drawn
if (debug_portal_cull) {
@@ -143,45 +152,68 @@ traverse(const NodePath &root) {
NodePath cull_center = _scene_setup->get_cull_center();
CPT(TransformState) transform = cull_center.get_transform(root);
- CullTraverserData my_data(data, portal_viewer._previous);
- my_data._net_transform = my_data._net_transform->compose(transform);
- traverse(my_data);
+ CullTraverserData my_data(data, portal_viewer._previous,
+ data._net_transform->compose(transform),
+ data._state, data._view_frustum);
+ if (my_data.is_in_view(_camera_mask)) {
+ do_traverse(my_data);
+ }
} else {
CullTraverserData data(root, TransformState::make_identity(),
_initial_state, _view_frustum,
_current_thread);
- do_traverse(data);
+ if (data.is_in_view(_camera_mask)) {
+ do_traverse(data);
+ }
}
}
/**
- * Traverses from the next node with the given data, which has been
- * constructed with the node but has not yet been converted into the node's
- * space.
+ * Internal method called by traverse() and traverse_down(). Traverses the
+ * given node, assuming it has already been checked with is_in_view().
*/
void CullTraverser::
-traverse(CullTraverserData &data) {
- do_traverse(data);
-}
+do_traverse(CullTraverserData &data) {
+#ifndef NDEBUG
+ if (UNLIKELY(pgraph_cat.is_spam())) {
+ pgraph_cat.spam()
+ << "\n" << data.get_node_path()
+ << " " << data._draw_mask << "\n";
+ }
+#endif
-/**
- * Traverses all the children of the indicated node, with the given data,
- * which has been converted into the node's space.
- */
-void CullTraverser::
-traverse_below(CullTraverserData &data) {
- _nodes_pcollector.add_level(1);
- PandaNodePipelineReader *node_reader = data.node_reader();
PandaNode *node = data.node();
+ PandaNodePipelineReader *node_reader = data.node_reader();
+ int fancy_bits = node_reader->get_fancy_bits();
- if (!data.is_this_node_hidden(_camera_mask)) {
+ if ((fancy_bits & ~PandaNode::FB_renderable) == 0 && data._cull_planes == nullptr) {
+ // Nothing interesting in this node; just move on.
+ }
+ else {
+ // Something in this node is worth taking a closer look.
+ if (fancy_bits & PandaNode::FB_show_bounds) {
+ // If we should show the bounding volume for this node, make it up
+ // now.
+ show_bounds(data, (fancy_bits & PandaNode::FB_show_tight_bounds) != 0);
+ }
+
+ data.apply_transform_and_state(this);
+
+ if (fancy_bits & PandaNode::FB_cull_callback) {
+ if (!node->cull_callback(this, data)) {
+ return;
+ }
+ }
+ }
+
+ if ((fancy_bits & PandaNode::FB_renderable) != 0 &&
+ !data.is_this_node_hidden(_camera_mask)) {
node->add_for_draw(this, data);
// Check for a decal effect.
- const RenderEffects *node_effects = node_reader->get_effects();
- if (node_effects->has_decal()) {
+ if (fancy_bits & PandaNode::FB_decal) {
// If we *are* implementing decals with DepthOffsetAttribs, apply it
// now, so that each child of this node gets offset by a tiny amount.
data._state = data._state->compose(get_depth_offset_state());
@@ -195,22 +227,15 @@ traverse_below(CullTraverserData &data) {
}
}
+ _nodes_pcollector.add_level(1);
+
// Now visit all the node's children.
PandaNode::Children children = node_reader->get_children();
node_reader->release();
int num_children = children.get_num_children();
- if (!node->has_selective_visibility()) {
- for (int i = 0; i < num_children; ++i) {
- CullTraverserData next_data(data, children.get_child(i));
- do_traverse(next_data);
- }
- } else {
- int i = node->get_first_visible_child();
- while (i < num_children) {
- CullTraverserData next_data(data, children.get_child(i));
- do_traverse(next_data);
- i = node->get_next_visible_child(i);
- }
+ for (int i = 0; i < num_children; ++i) {
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+ traverse_down(data, child, data._state);
}
}
@@ -246,13 +271,28 @@ draw_bounding_volume(const BoundingVolume *vol,
}
/**
- * Returns true if the current node is fully or partially within the viewing
- * area and should be drawn, or false if it (and all of its children) should
- * be pruned.
+ * Implements fake-view-frustum-cull.
*/
-bool CullTraverser::
-is_in_view(CullTraverserData &data) {
- return data.is_in_view(_camera_mask);
+void CullTraverser::
+do_fake_cull(const CullTraverserData &data, PandaNode *child,
+ const TransformState *net_transform, const RenderState *state) {
+#ifndef NDEBUG
+ // Once someone asks for this pointer, we hold its reference count and never
+ // free it.
+ static CPT(RenderState) fake_view_frustum_cull_state;
+ if (fake_view_frustum_cull_state == nullptr) {
+ fake_view_frustum_cull_state = RenderState::make
+ (ColorAttrib::make_flat(LColor(1.0f, 0.0f, 0.0f, 1.0f)),
+ TextureAttrib::make_all_off(),
+ RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
+ RenderState::get_max_priority());
+ }
+
+ CullTraverserData next_data(data, child, net_transform,
+ state->compose(fake_view_frustum_cull_state),
+ nullptr);
+ do_traverse(next_data);
+#endif
}
/**
@@ -503,7 +543,7 @@ compute_point(const BoundingSphere *sphere,
* Returns a RenderState for rendering the outside surfaces of the bounding
* volume visualizations.
*/
-CPT(RenderState) CullTraverser::
+const RenderState *CullTraverser::
get_bounds_outer_viz_state() {
// Once someone asks for this pointer, we hold its reference count and never
// free it.
@@ -521,7 +561,7 @@ get_bounds_outer_viz_state() {
* Returns a RenderState for rendering the inside surfaces of the bounding
* volume visualizations.
*/
-CPT(RenderState) CullTraverser::
+const RenderState *CullTraverser::
get_bounds_inner_viz_state() {
// Once someone asks for this pointer, we hold its reference count and never
// free it.
@@ -538,7 +578,7 @@ get_bounds_inner_viz_state() {
/**
* Returns a RenderState for increasing the DepthOffset by one.
*/
-CPT(RenderState) CullTraverser::
+const RenderState *CullTraverser::
get_depth_offset_state() {
// Once someone asks for this pointer, we hold its reference count and never
// free it.
diff --git a/panda/src/pgraph/cullTraverser.h b/panda/src/pgraph/cullTraverser.h
index b8ecbe084b..a243a165cc 100644
--- a/panda/src/pgraph/cullTraverser.h
+++ b/panda/src/pgraph/cullTraverser.h
@@ -76,22 +76,33 @@ PUBLISHED:
INLINE PortalClipper *get_portal_clipper() const;
INLINE bool get_effective_incomplete_render() const;
-
- void traverse(const NodePath &root);
- void traverse(CullTraverserData &data);
- virtual void traverse_below(CullTraverserData &data);
-
- virtual void end_traverse();
+ INLINE bool get_fake_view_frustum_cull() const;
INLINE static void flush_level();
+ void traverse(const NodePath &root);
+ void do_traverse(CullTraverserData &data);
+ INLINE void traverse_below(CullTraverserData &data);
+
+ virtual void end_traverse();
+
void draw_bounding_volume(const BoundingVolume *vol,
const TransformState *internal_transform) const;
-protected:
- INLINE void do_traverse(CullTraverserData &data);
+public:
+ INLINE void traverse_down(const CullTraverserData &data, PandaNode *child);
+ INLINE void traverse_down(const CullTraverserData &data, PandaNode *child,
+ const TransformState *net_transform,
+ const RenderState *state);
+ INLINE void traverse_down(const CullTraverserData &data,
+ const PandaNode::DownConnection &child);
+ INLINE void traverse_down(const CullTraverserData &data,
+ const PandaNode::DownConnection &child,
+ const RenderState *state);
- virtual bool is_in_view(CullTraverserData &data);
+ void do_fake_cull(const CullTraverserData &data, PandaNode *child,
+ const TransformState *net_transform,
+ const RenderState *state);
public:
// Statistics
@@ -106,14 +117,15 @@ private:
PT(Geom) make_tight_bounds_viz(PandaNode *node) const;
static LVertex compute_point(const BoundingSphere *sphere,
PN_stdfloat latitude, PN_stdfloat longitude);
- static CPT(RenderState) get_bounds_outer_viz_state();
- static CPT(RenderState) get_bounds_inner_viz_state();
- static CPT(RenderState) get_depth_offset_state();
+ static const RenderState *get_bounds_outer_viz_state();
+ static const RenderState *get_bounds_inner_viz_state();
+ static const RenderState *get_depth_offset_state();
GraphicsStateGuardianBase *_gsg;
Thread *_current_thread;
PT(SceneSetup) _scene_setup;
DrawMask _camera_mask;
+ bool _fake_view_frustum_cull;
bool _has_tag_state_key;
std::string _tag_state_key;
CPT(RenderState) _initial_state;
diff --git a/panda/src/pgraph/cullTraverserData.I b/panda/src/pgraph/cullTraverserData.I
index cc5126dc07..ff456b01cb 100644
--- a/panda/src/pgraph/cullTraverserData.I
+++ b/panda/src/pgraph/cullTraverserData.I
@@ -17,22 +17,18 @@
INLINE CullTraverserData::
CullTraverserData(const NodePath &start,
const TransformState *net_transform,
- const RenderState *state,
+ CPT(RenderState) state,
GeometricBoundingVolume *view_frustum,
Thread *current_thread) :
_next(nullptr),
_start(start._head),
_node_reader(start.node(), current_thread),
_net_transform(net_transform),
- _state(state),
+ _state(std::move(state)),
_view_frustum(view_frustum),
- _cull_planes(CullPlanes::make_empty()),
_draw_mask(DrawMask::all_on()),
_portal_depth(0)
{
- // Only update the bounding volume if we're going to end up needing it.
- bool check_bounds = (view_frustum != nullptr);
- _node_reader.check_cached(check_bounds);
}
/**
@@ -40,24 +36,46 @@ CullTraverserData(const NodePath &start,
* node down in the traversal.
*/
INLINE CullTraverserData::
-CullTraverserData(const CullTraverserData &parent, PandaNode *child) :
+CullTraverserData(const CullTraverserData &parent, PandaNode *child,
+ const TransformState *net_transform,
+ CPT(RenderState) state,
+ GeometricBoundingVolume *view_frustum) :
_next(&parent),
#ifdef _DEBUG
_start(nullptr),
#endif
_node_reader(child, parent._node_reader.get_current_thread()),
- _net_transform(parent._net_transform),
- _state(parent._state),
- _view_frustum(parent._view_frustum),
- _cull_planes(parent._cull_planes),
+ _net_transform(net_transform),
+ _state(std::move(state)),
+ _view_frustum(view_frustum),
+ _instances(parent._instances),
+ _draw_mask(parent._draw_mask),
+ _portal_depth(parent._portal_depth)
+{
+}
+
+/**
+ * This constructor creates a CullTraverserData object that reflects the next
+ * node down in the traversal.
+ */
+INLINE CullTraverserData::
+CullTraverserData(const CullTraverserData &parent,
+ PandaNodePipelineReader &&node_reader,
+ const TransformState *net_transform,
+ CPT(RenderState) state,
+ GeometricBoundingVolume *view_frustum) :
+ _next(&parent),
+#ifdef _DEBUG
+ _start(nullptr),
+#endif
+ _node_reader(std::move(node_reader)),
+ _net_transform(net_transform),
+ _state(std::move(state)),
+ _view_frustum(view_frustum),
_instances(parent._instances),
_draw_mask(parent._draw_mask),
_portal_depth(parent._portal_depth)
{
- // Only update the bounding volume if we're going to end up needing it.
- bool check_bounds = !_cull_planes->is_empty() ||
- (_view_frustum != nullptr);
- _node_reader.check_cached(check_bounds);
}
/**
@@ -125,32 +143,82 @@ get_net_transform(const CullTraverser *) const {
}
/**
- * Returns true if the current node is within the view frustum, false
- * otherwise. If the node's bounding volume falls completely within the view
- * frustum, this will also reset the view frustum pointer, saving some work
- * for future nodes.
+ * Returns intersection flags if the current node may be within the view
+ * frustum, 0 otherwise.
*/
-INLINE bool CullTraverserData::
-is_in_view(const DrawMask &camera_mask) {
- if (_node_reader.get_transform()->is_invalid()) {
- // If the transform is invalid, forget it.
- return false;
- }
+INLINE int CullTraverserData::
+is_in_view(const DrawMask &camera_mask) const {
+ bool check_bounds = (_view_frustum != nullptr || _cull_planes != nullptr);
+ _node_reader.check_cached(check_bounds);
if (!_node_reader.compare_draw_mask(_draw_mask, camera_mask)) {
// If there are no draw bits in common with the camera, the node is out.
- return false;
+ return BoundingVolume::IF_no_intersection;
}
- if (_view_frustum == nullptr &&
- _cull_planes->is_empty()) {
- // If the transform is valid, but we don't have a frustum or any clip
- // planes or occluders, it's always in.
- return true;
+ if (_node_reader.get_transform()->is_invalid()) {
+ // If the transform is invalid, forget it.
+ return BoundingVolume::IF_no_intersection;
}
- // Otherwise, compare the bounding volume to the frustum.
- return is_in_view_impl();
+ if (_view_frustum != nullptr) {
+ const GeometricBoundingVolume *node_gbv = _node_reader.get_bounds()->as_geometric_bounding_volume();
+ nassertr(node_gbv != nullptr, BoundingVolume::IF_no_intersection);
+
+ return _view_frustum->contains(node_gbv);
+ }
+
+ return BoundingVolume::IF_possible;
+}
+
+/**
+ * Returns intersection flags if the given child of the current node may be
+ * within the view frustum, 0 otherwise.
+ */
+INLINE int CullTraverserData::
+is_child_in_view(const PandaNodePipelineReader &node_reader, const DrawMask &camera_mask) const {
+ bool check_bounds = (_view_frustum != nullptr || _cull_planes != nullptr);
+ node_reader.check_cached(check_bounds);
+
+ if (!node_reader.compare_draw_mask(_draw_mask, camera_mask)) {
+ // If there are no draw bits in common with the camera, the node is out.
+ return BoundingVolume::IF_no_intersection;
+ }
+
+ if (_node_reader.get_transform()->is_invalid()) {
+ // If the transform is invalid, forget it.
+ return BoundingVolume::IF_no_intersection;
+ }
+
+ if (_view_frustum != nullptr) {
+ const GeometricBoundingVolume *node_gbv = node_reader.get_bounds()->as_geometric_bounding_volume();
+ nassertr(node_gbv != nullptr, BoundingVolume::IF_no_intersection);
+
+ return _view_frustum->contains(node_gbv);
+ }
+ return BoundingVolume::IF_possible;
+}
+
+/**
+ * Returns intersection flags if the given child of the current node may be
+ * within the view frustum, 0 otherwise.
+ */
+INLINE int CullTraverserData::
+is_child_in_view(const PandaNode::DownConnection &child, const DrawMask &camera_mask) const {
+ // No need to call check_cached here, since we presumably already called it
+ // on the parent.
+ if (!child.compare_draw_mask(_draw_mask, camera_mask)) {
+ // If there are no draw bits in common with the camera, the node is out.
+ return BoundingVolume::IF_no_intersection;
+ }
+
+ if (_view_frustum != nullptr) {
+ const GeometricBoundingVolume *node_gbv = child.get_bounds();
+ nassertr(node_gbv != nullptr, BoundingVolume::IF_no_intersection);
+
+ return _view_frustum->contains(node_gbv);
+ }
+ return BoundingVolume::IF_possible;
}
/**
diff --git a/panda/src/pgraph/cullTraverserData.cxx b/panda/src/pgraph/cullTraverserData.cxx
index 12f71ef12f..cd1a535899 100644
--- a/panda/src/pgraph/cullTraverserData.cxx
+++ b/panda/src/pgraph/cullTraverserData.cxx
@@ -15,9 +15,6 @@
#include "cullTraverser.h"
#include "config_pgraph.h"
#include "pandaNode.h"
-#include "colorAttrib.h"
-#include "textureAttrib.h"
-#include "renderModeAttrib.h"
#include "clipPlaneAttrib.h"
#include "boundingPlane.h"
#include "billboardEffect.h"
@@ -64,10 +61,28 @@ apply_transform_and_state(CullTraverser *trav) {
}
if (clip_plane_cull) {
- _cull_planes = _cull_planes->apply_state(trav, this,
- (const ClipPlaneAttrib *)node_state->get_attrib(ClipPlaneAttrib::get_class_slot()),
- (const ClipPlaneAttrib *)_node_reader.get_off_clip_planes(),
- (const OccluderEffect *)node_effects->get_effect(OccluderEffect::get_class_type()));
+ const ClipPlaneAttrib *cpa = (const ClipPlaneAttrib *)
+ node_state->get_attrib(ClipPlaneAttrib::get_class_slot());
+ const OccluderEffect *occluders = (const OccluderEffect *)
+ node_effects->get_effect(OccluderEffect::get_class_type());
+
+ if (cpa != nullptr || occluders != nullptr) {
+ CullPlanes::apply_state(_cull_planes, trav, this, cpa,
+ (const ClipPlaneAttrib *)_node_reader.get_off_clip_planes(),
+ occluders);
+ }
+ }
+
+ const FogAttrib *fog_attr;
+ if (node_state->get_attrib(fog_attr)) {
+ Fog *fog = fog_attr->get_fog();
+ if (fog != nullptr) {
+ // If we just introduced a FogAttrib here, call adjust_to_camera()
+ // now. This maybe isn't the perfect time to call it, but it's good
+ // enough; and at this time we have all the information we need for
+ // it.
+ fog->adjust_to_camera(trav->get_camera_transform());
+ }
}
}
@@ -88,35 +103,90 @@ apply_transform(const TransformState *node_transform) {
_net_transform = _net_transform->compose(node_transform);
- if ((_view_frustum != nullptr) ||
- (!_cull_planes->is_empty())) {
+ if (_view_frustum != nullptr || _cull_planes != nullptr) {
// We need to move the viewing frustums into the node's coordinate space
// by applying the node's inverse transform.
- if (node_transform->is_singular()) {
- // But we can't invert a singular transform! Instead of trying, we'll
- // just give up on frustum culling from this point down.
- _view_frustum = nullptr;
- _cull_planes = CullPlanes::make_empty();
-
- } else {
- CPT(TransformState) inv_transform =
- node_transform->invert_compose(TransformState::make_identity());
-
+ const LMatrix4 *inverse_mat = node_transform->get_inverse_mat();
+ if (inverse_mat != nullptr) {
// Copy the bounding volumes for the frustums so we can transform
// them.
if (_view_frustum != nullptr) {
_view_frustum = _view_frustum->make_copy()->as_geometric_bounding_volume();
nassertv(_view_frustum != nullptr);
- _view_frustum->xform(inv_transform->get_mat());
+ _view_frustum->xform(*inverse_mat);
}
- _cull_planes = _cull_planes->xform(inv_transform->get_mat());
+ if (_cull_planes != nullptr) {
+ _cull_planes = _cull_planes->xform(*inverse_mat);
+ }
+ }
+ else {
+ // But we can't invert a singular transform! Instead of trying, we'll
+ // just give up on frustum culling from this point down.
+ pgraph_cat.warning()
+ << "Singular transformation detected on node: " << get_node_path() << "\n";
+ _view_frustum = nullptr;
+ _cull_planes = nullptr;
}
}
}
}
+/**
+ * Returns intersection flags if any of the children under the current node are
+ * in view if first transformed by the given transform, false otherwise.
+ */
+bool CullTraverserData::
+is_instance_in_view(const TransformState *instance_transform, const DrawMask &camera_mask) const {
+ PT(GeometricBoundingVolume) view_frustum_p;
+ const GeometricBoundingVolume *view_frustum = nullptr;
+
+ if (_view_frustum != nullptr) {
+ if (!instance_transform->is_identity()) {
+ // We need to move the viewing frustums into the node's coordinate space
+ // by applying the node's inverse transform.
+ const LMatrix4 *inverse_mat = instance_transform->get_inverse_mat();
+ if (inverse_mat != nullptr) {
+ // Copy the bounding volumes for the frustums so we can transform them.
+ view_frustum_p = _view_frustum->make_copy()->as_geometric_bounding_volume();
+ nassertr(view_frustum_p != nullptr, false);
+
+ view_frustum_p->xform(*inverse_mat);
+ view_frustum = view_frustum_p;
+ } else {
+ // Don't render instances with a singular transformation.
+ return false;
+ }
+ } else {
+ view_frustum = _view_frustum;
+ }
+ }
+
+ PandaNode::Children children = _node_reader.get_children();
+ int num_children = children.get_num_children();
+ for (int i = 0; i < num_children; ++i) {
+ const PandaNode::DownConnection &child = children.get_child_connection(i);
+
+ if (!child.compare_draw_mask(_draw_mask, camera_mask)) {
+ // If there are no draw bits in common with the camera, the node is out.
+ continue;
+ }
+
+ if (view_frustum == nullptr) {
+ return true;
+ }
+
+ const GeometricBoundingVolume *node_gbv = child.get_bounds();
+ nassertd(node_gbv != nullptr) continue;
+
+ if (view_frustum->contains(node_gbv)) {
+ return true;
+ }
+ }
+ return false;
+}
+
/**
* The private, recursive implementation of get_node_path(), this returns the
* NodePathComponent representing the NodePath.
@@ -152,127 +222,37 @@ r_get_node_path() const {
}
/**
- * The private implementation of is_in_view().
+ * Applies the cull planes. Returns true if the node should still be rendered,
+ * false if it should be culled.
*/
bool CullTraverserData::
-is_in_view_impl() {
- const GeometricBoundingVolume *node_gbv = nullptr;
-
- if (_view_frustum != nullptr) {
- node_gbv = _node_reader.get_bounds()->as_geometric_bounding_volume();
- nassertr(node_gbv != nullptr, false);
-
- int result = _view_frustum->contains(node_gbv);
-
- if (pgraph_cat.is_spam()) {
- pgraph_cat.spam()
- << get_node_path() << " cull result = " << std::hex << result << std::dec << "\n";
- }
-
- if (result == BoundingVolume::IF_no_intersection) {
- // No intersection at all. Cull.
-#ifdef NDEBUG
- return false;
-#else
- if (!fake_view_frustum_cull) {
- return false;
- }
-
- // If we have fake view-frustum culling enabled, instead of actually
- // culling an object we simply force it to be drawn in red wireframe.
- _view_frustum = nullptr;
- _state = _state->compose(get_fake_view_frustum_cull_state());
-#endif
-
- } else if ((result & BoundingVolume::IF_all) != 0) {
- // The node and its descendents are completely enclosed within the
- // frustum. No need to cull further.
- _view_frustum = nullptr;
-
- } else {
- // The node is partially, but not completely, within the viewing
- // frustum.
- if (_node_reader.is_final()) {
- // Normally we'd keep testing child bounding volumes as we continue
- // down. But this node has the "final" flag, so the user is claiming
- // that there is some important reason we should consider everything
- // visible at this point. So be it.
- _view_frustum = nullptr;
- }
- }
- }
-
- if (!_cull_planes->is_empty()) {
- if (node_gbv == nullptr) {
- node_gbv = _node_reader.get_bounds()->as_geometric_bounding_volume();
- nassertr(node_gbv != nullptr, false);
- }
-
+apply_cull_planes(const CullPlanes *planes, const GeometricBoundingVolume *node_gbv) {
+ if (!planes->is_empty()) {
// Also cull against the current clip planes.
int result;
- _cull_planes = _cull_planes->do_cull(result, _state, node_gbv);
+ CPT(CullPlanes) new_planes = planes->do_cull(result, _state, node_gbv);
if (pgraph_cat.is_spam()) {
pgraph_cat.spam()
<< get_node_path() << " cull planes cull result = " << std::hex
<< result << std::dec << "\n";
- _cull_planes->write(pgraph_cat.spam(false));
- }
-
- if (_node_reader.is_final()) {
- // Even though the node may be partially within the clip planes, do no
- // more culling against them below this node.
- _cull_planes = CullPlanes::make_empty();
-
- if (pgraph_cat.is_spam()) {
- pgraph_cat.spam()
- << get_node_path() << " is_final, cull planes disabled, state:\n";
- _state->write(pgraph_cat.spam(false), 2);
- }
+ new_planes->write(pgraph_cat.spam(false));
}
if (result == BoundingVolume::IF_no_intersection) {
// No intersection at all. Cull.
-#ifdef NDEBUG
return false;
-#else
- if (!fake_view_frustum_cull) {
- return false;
- }
- _cull_planes = CullPlanes::make_empty();
- _state = _state->compose(get_fake_view_frustum_cull_state());
-#endif
-
- } else if ((result & BoundingVolume::IF_all) != 0) {
- // The node and its descendents are completely in front of all of the
+ }
+ else if ((result & BoundingVolume::IF_all) != 0) {
+ // The node and its descendants are completely in front of all of the
// clip planes and occluders. The do_cull() call should therefore have
// removed all of the clip planes and occluders.
- nassertr(_cull_planes->is_empty(), true);
+ nassertr(new_planes->is_empty(), true);
+ }
+ else if (!_node_reader.is_final() && !new_planes->is_empty()) {
+ _cull_planes = std::move(new_planes);
}
}
return true;
}
-
-/**
- * Returns a RenderState for rendering stuff in red wireframe, strictly for
- * the fake_view_frustum_cull effect.
- */
-const RenderState *CullTraverserData::
-get_fake_view_frustum_cull_state() {
-#ifdef NDEBUG
- return nullptr;
-#else
- // Once someone asks for this pointer, we hold its reference count and never
- // free it.
- static CPT(RenderState) state;
- if (state == nullptr) {
- state = RenderState::make
- (ColorAttrib::make_flat(LColor(1.0f, 0.0f, 0.0f, 1.0f)),
- TextureAttrib::make_all_off(),
- RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
- RenderState::get_max_priority());
- }
- return state;
-#endif
-}
diff --git a/panda/src/pgraph/cullTraverserData.h b/panda/src/pgraph/cullTraverserData.h
index cdb8dd2a94..9fe4755e34 100644
--- a/panda/src/pgraph/cullTraverserData.h
+++ b/panda/src/pgraph/cullTraverserData.h
@@ -42,11 +42,19 @@ class EXPCL_PANDA_PGRAPH CullTraverserData {
public:
INLINE CullTraverserData(const NodePath &start,
const TransformState *net_transform,
- const RenderState *state,
+ CPT(RenderState) state,
GeometricBoundingVolume *view_frustum,
Thread *current_thread);
INLINE CullTraverserData(const CullTraverserData &parent,
- PandaNode *child);
+ PandaNode *child,
+ const TransformState *net_transform,
+ CPT(RenderState) state,
+ GeometricBoundingVolume *view_frustum);
+ INLINE CullTraverserData(const CullTraverserData &parent,
+ PandaNodePipelineReader &&node_reader,
+ const TransformState *net_transform,
+ CPT(RenderState) state,
+ GeometricBoundingVolume *view_frustum);
PUBLISHED:
INLINE PandaNode *node() const;
@@ -62,14 +70,21 @@ PUBLISHED:
INLINE CPT(TransformState) get_internal_transform(const CullTraverser *trav) const;
INLINE const TransformState *get_net_transform(const CullTraverser *trav) const;
- INLINE bool is_in_view(const DrawMask &camera_mask);
+ INLINE int is_in_view(const DrawMask &camera_mask) const;
INLINE bool is_this_node_hidden(const DrawMask &camera_mask) const;
+ bool apply_cull_planes(const CullPlanes *planes, const GeometricBoundingVolume *node_gbv);
+
void apply_transform_and_state(CullTraverser *trav);
void apply_transform(const TransformState *node_transform);
MAKE_PROPERTY(node_path, get_node_path);
+public:
+ bool is_instance_in_view(const TransformState *instance_transform, const DrawMask &camera_mask) const;
+ INLINE int is_child_in_view(const PandaNodePipelineReader &node_reader, const DrawMask &camera_mask) const;
+ INLINE int is_child_in_view(const PandaNode::DownConnection &child, const DrawMask &camera_mask) const;
+
private:
// We store a chain leading all the way to the root, so that we can compose
// a NodePath. We may be able to eliminate this requirement in the future.
@@ -88,9 +103,6 @@ public:
private:
PT(NodePathComponent) r_get_node_path() const;
-
- bool is_in_view_impl();
- static const RenderState *get_fake_view_frustum_cull_state();
};
/* okcircular */
diff --git a/panda/src/pgraph/depthBiasAttrib.h b/panda/src/pgraph/depthBiasAttrib.h
index 75fcdd3dc9..fea972bd81 100644
--- a/panda/src/pgraph/depthBiasAttrib.h
+++ b/panda/src/pgraph/depthBiasAttrib.h
@@ -37,6 +37,8 @@ class FactoryParams;
* with a value of 1 beneath another DepthBiasAttrib with a value of 2
* presents a net offset of 3. (A DepthBiasAttrib will not, however,
* combine with any other DepthBiasAttribs with a lower override parameter.)
+ *
+ * @since 1.11.0
*/
class EXPCL_PANDA_PGRAPH DepthBiasAttrib : public RenderAttrib {
private:
diff --git a/panda/src/pgraph/geomNode.cxx b/panda/src/pgraph/geomNode.cxx
index fa4a81beac..b7e9842beb 100644
--- a/panda/src/pgraph/geomNode.cxx
+++ b/panda/src/pgraph/geomNode.cxx
@@ -59,6 +59,8 @@ GeomNode(const std::string &name) :
// GeomNodes have a certain set of bits on by default.
set_into_collide_mask(get_default_collide_mask());
+
+ set_renderable();
}
/**
@@ -397,6 +399,24 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
geom_state = state_munger->munge_state(geom_state);
}
+ // As well as the shaders.
+ const ShaderAttrib *sa;
+ if (geom_state->get_attrib(sa)) {
+ Shader *shader = (Shader *)sa->get_shader();
+ if (shader != nullptr) {
+ prepared_objects->enqueue_shader(shader);
+ }
+ else if (sa->auto_shader()) {
+ gsg->ensure_generated_shader(geom_state);
+ }
+ else if (munger->is_of_type(StateMunger::get_class_type())) {
+ // Premunge the state for the fixed-function pipeline.
+ StateMunger *state_munger = (StateMunger *)munger.p();
+ geom_state = state_munger->munge_state(geom_state);
+ }
+ // TODO: prepare the shader inputs.
+ }
+
// And now prepare each of the textures.
const TextureAttrib *ta;
if (geom_state->get_attrib(ta)) {
@@ -409,16 +429,6 @@ r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
}
}
}
-
- // As well as the shaders.
- const ShaderAttrib *sa;
- if (geom_state->get_attrib(sa)) {
- Shader *shader = (Shader *)sa->get_shader();
- if (shader != nullptr) {
- prepared_objects->enqueue_shader(shader);
- }
- // TODO: prepare the shader inputs.
- }
}
PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread);
@@ -482,17 +492,6 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
return next_transform;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool GeomNode::
-is_renderable() const {
- return true;
-}
-
/**
* Adds the node's contents to the CullResult we are building up during the
* cull traversal, so that it will be drawn at render time. For most nodes
@@ -561,7 +560,7 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
// Cull this Geom.
continue;
}
- if (!data._cull_planes->is_empty()) {
+ if (data._cull_planes != nullptr) {
// Also cull the Geom against the cull planes.
CPT(BoundingVolume) geom_volume = geom->get_bounds(current_thread);
const GeometricBoundingVolume *geom_gbv =
diff --git a/panda/src/pgraph/geomNode.h b/panda/src/pgraph/geomNode.h
index 1402d07b5a..c6e28516cb 100644
--- a/panda/src/pgraph/geomNode.h
+++ b/panda/src/pgraph/geomNode.h
@@ -50,7 +50,6 @@ public:
bool &found_any,
const TransformState *transform,
Thread *current_thread) const;
- virtual bool is_renderable() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
virtual CollideMask get_legal_collide_mask() const;
diff --git a/panda/src/pgraph/instancedNode.cxx b/panda/src/pgraph/instancedNode.cxx
index 567b31316e..c1a51281ea 100644
--- a/panda/src/pgraph/instancedNode.cxx
+++ b/panda/src/pgraph/instancedNode.cxx
@@ -218,32 +218,29 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
instances = new_list;
}
- if (data._view_frustum != nullptr || !data._cull_planes->is_empty()) {
- // Culling is on, so we need to figure out which instances are visible.
- Children children = data.node_reader()->get_children();
- data.node_reader()->release();
-
- // Keep track of which instances should be culled away.
+ if (data._view_frustum != nullptr || data._cull_planes != nullptr) {
+ // Culling is on, so we need to figure out which instances should be culled.
BitArray culled_instances;
culled_instances.set_range(0, instances->size());
for (size_t ii = 0; ii < instances->size(); ++ii) {
- CullTraverserData instance_data(data);
- instance_data.apply_transform((*instances)[ii].get_transform());
+ if (data.is_instance_in_view((*instances)[ii].get_transform(), trav->get_camera_mask())) {
+ culled_instances.clear_bit(ii);
+ }
+ }
- for (size_t ci = 0; ci < children.size(); ++ci) {
- CullTraverserData child_data(instance_data, children.get_child(ci));
- if (child_data.is_in_view(trav->get_camera_mask())) {
- // Yep, the instance is in view.
- culled_instances.clear_bit(ii);
- break;
- }
+ if (!culled_instances.is_zero() && trav->get_fake_view_frustum_cull()) {
+ // The culled instances are drawn with the fake-view-frustum-cull effect.
+ data._instances = instances->without(culled_instances ^ BitArray::range(0, instances->size()));
+
+ Children children = data.node_reader()->get_children();
+ int num_children = children.get_num_children();
+ for (int i = 0; i < num_children; ++i) {
+ trav->do_fake_cull(data, children.get_child(i), data._net_transform, data._state);
}
}
instances = instances->without(culled_instances);
- } else {
- data.node_reader()->release();
}
if (instances->empty()) {
@@ -259,15 +256,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
data._cull_planes = CullPlanes::make_empty();
return true;
-
- /*
- for (const InstanceList::Instance &instance : *instances) {
- CullTraverserData instance_data(data);
- instance_data.apply_transform(instance.get_transform());
- trav->traverse_below(instance_data);
- }
- return false;
- */
}
/**
diff --git a/panda/src/pgraph/occluderNode.cxx b/panda/src/pgraph/occluderNode.cxx
index 09f8bbd038..a1db42c3c3 100644
--- a/panda/src/pgraph/occluderNode.cxx
+++ b/panda/src/pgraph/occluderNode.cxx
@@ -55,6 +55,7 @@ OccluderNode(const std::string &name) :
PandaNode(name)
{
set_cull_callback();
+ set_renderable();
// OccluderNodes are hidden by default.
set_overall_hidden(true);
set_double_sided(false);
@@ -156,18 +157,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool OccluderNode::
-is_renderable() const {
- return true;
-}
-
-
/**
* Writes a brief description of the node to the indicated output stream.
* This is invoked by the << operator. It may be overridden in derived
diff --git a/panda/src/pgraph/occluderNode.h b/panda/src/pgraph/occluderNode.h
index ebc5db9bc3..484c38753d 100644
--- a/panda/src/pgraph/occluderNode.h
+++ b/panda/src/pgraph/occluderNode.h
@@ -42,7 +42,6 @@ public:
virtual void xform(const LMatrix4 &mat);
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void output(std::ostream &out) const;
diff --git a/panda/src/pgraph/pandaNode.I b/panda/src/pgraph/pandaNode.I
index 8cbea5c871..bf961413c5 100644
--- a/panda/src/pgraph/pandaNode.I
+++ b/panda/src/pgraph/pandaNode.I
@@ -773,6 +773,59 @@ get_sort() const {
return _sort;
}
+/**
+ * Returns the net collide mask of this child. Requires that update_cached()
+ * has been called on the parent.
+ */
+INLINE CollideMask PandaNode::DownConnection::
+get_net_collide_mask() const {
+ return _net_collide_mask;
+}
+
+/**
+ * Returns the external bounding volume of this child. Requires that
+ * update_cached() has been called on the parent.
+ */
+INLINE const GeometricBoundingVolume *PandaNode::DownConnection::
+get_bounds() const {
+ return _external_bounds;
+}
+
+/**
+ * Compares the running draw mask computed during a traversal with this child's
+ * net draw masks. Returns true if the child should be traversed into, or
+ * false if there is nothing at this level or below that will be visible to
+ * the indicated camera_mask.
+ */
+INLINE bool PandaNode::DownConnection::
+compare_draw_mask(DrawMask running_draw_mask, DrawMask camera_mask) const {
+ // As a special case, if net_draw_show_mask is all 0, it means either that
+ // all nodes under this node are hidden to all cameras, or that none of them
+ // are renderable nodes (or some combination). In either case, we might as
+ // well short-circuit.
+ if (_net_draw_show_mask.is_zero()) {
+ return false;
+ }
+
+ DrawMask net_draw_control_mask, net_draw_show_mask;
+ net_draw_control_mask = _net_draw_control_mask;
+ net_draw_show_mask = _net_draw_show_mask;
+
+ // Now the bits that are not in net_draw_control_mask--that is, those bits
+ // that are not changed by any of the nodes at this level and below--are
+ // taken from running_draw_mask, which is inherited from above. On the
+ // other hand, the bits that *are* in net_draw_control_mask--those bits that
+ // are changed by any of the nodes at this level and below--are taken from
+ // net_draw_show_mask, which is propagated upwards from below.
+
+ // This way, we will traverse into this node if it has any children which
+ // want to be visited by the traversal, but we will avoid traversing into it
+ // if all of its children are hidden to this camera.
+ DrawMask compare_mask = (running_draw_mask & ~net_draw_control_mask) | (net_draw_show_mask & net_draw_control_mask);
+
+ return !((compare_mask & PandaNode::_overall_bit).is_zero()) && !((compare_mask & camera_mask).is_zero());
+}
+
/**
*
*/
@@ -1351,6 +1404,18 @@ get_child(int n) const {
return (*down)[n].get_child();
}
+/**
+ * Returns the nth child node of this node. See get_num_children(). Also see
+ * get_children(), if your intention is to iterate through the complete list
+ * of children; get_children() is preferable in this case.
+ */
+INLINE const PandaNode::DownConnection &PandaNodePipelineReader::
+get_child_connection(int n) const {
+ CPT(PandaNode::Down) down = _cdata->get_down();
+ //nassertr(n >= 0 && n < (int)down->size(), nullptr);
+ return (*down)[n];
+}
+
/**
* Returns the sort index of the nth child node of this node (that is, the
* number that was passed to add_child()). See get_num_children().
diff --git a/panda/src/pgraph/pandaNode.cxx b/panda/src/pgraph/pandaNode.cxx
index fabae9a272..9e13b3edfe 100644
--- a/panda/src/pgraph/pandaNode.cxx
+++ b/panda/src/pgraph/pandaNode.cxx
@@ -27,6 +27,8 @@
#include "config_mathutil.h"
#include "lightReMutexHolder.h"
#include "graphicsStateGuardianBase.h"
+#include "decalEffect.h"
+#include "showBoundsEffect.h"
using std::ostream;
using std::ostringstream;
@@ -387,46 +389,6 @@ cull_callback(CullTraverser *, CullTraverserData &) {
return true;
}
-/**
- * Should be overridden by derived classes to return true if this kind of node
- * has some restrictions on the set of children that should be rendered. Node
- * with this property include LODNodes, SwitchNodes, and SequenceNodes.
- *
- * If this function returns true, get_first_visible_child() and
- * get_next_visible_child() will be called to walk through the list of
- * children during cull, instead of iterating through the entire list. This
- * method is called after cull_callback(), so cull_callback() may be
- * responsible for the decisions as to which children are visible at the
- * moment.
- */
-bool PandaNode::
-has_selective_visibility() const {
- return false;
-}
-
-/**
- * Returns the index number of the first visible child of this node, or a
- * number >= get_num_children() if there are no visible children of this node.
- * This is called during the cull traversal, but only if
- * has_selective_visibility() has already returned true. See
- * has_selective_visibility().
- */
-int PandaNode::
-get_first_visible_child() const {
- return 0;
-}
-
-/**
- * Returns the index number of the next visible child of this node following
- * the indicated child, or a number >= get_num_children() if there are no more
- * visible children of this node. See has_selective_visibility() and
- * get_first_visible_child().
- */
-int PandaNode::
-get_next_visible_child(int n) const {
- return n + 1;
-}
-
/**
* Should be overridden by derived classes to return true if this kind of node
* has the special property that just one of its children is visible at any
@@ -460,7 +422,8 @@ get_visible_child() const {
*/
bool PandaNode::
is_renderable() const {
- return false;
+ CDReader cdata(_cycler);
+ return (cdata->_fancy_bits & FB_renderable) != 0;
}
/**
@@ -1003,7 +966,15 @@ set_effect(const RenderEffect *effect) {
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = cdata->_effects->add_effect(effect);
+
cdata->set_fancy_bit(FB_effects, true);
+ if (effect->get_type() == DecalEffect::get_class_type()) {
+ cdata->set_fancy_bit(FB_decal, true);
+ }
+ else if (effect->get_type() == ShowBoundsEffect::get_class_type()) {
+ cdata->set_fancy_bit(FB_show_bounds, true);
+ cdata->set_fancy_bit(FB_show_tight_bounds, ((const ShowBoundsEffect *)effect)->get_tight());
+ }
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
@@ -1018,7 +989,14 @@ clear_effect(TypeHandle type) {
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = cdata->_effects->remove_effect(type);
+
cdata->set_fancy_bit(FB_effects, !cdata->_effects->is_empty());
+ if (type == DecalEffect::get_class_type()) {
+ cdata->set_fancy_bit(FB_decal, false);
+ }
+ else if (type == ShowBoundsEffect::get_class_type()) {
+ cdata->set_fancy_bit(FB_show_bounds | FB_show_tight_bounds, false);
+ }
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
@@ -1067,6 +1045,9 @@ set_effects(const RenderEffects *effects, Thread *current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = effects;
cdata->set_fancy_bit(FB_effects, !effects->is_empty());
+ cdata->set_fancy_bit(FB_decal, effects->has_decal());
+ cdata->set_fancy_bit(FB_show_bounds, effects->has_show_bounds());
+ cdata->set_fancy_bit(FB_show_tight_bounds, effects->has_show_tight_bounds());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
@@ -1402,7 +1383,8 @@ copy_all_properties(PandaNode *other) {
}
static const int change_bits = (FB_transform | FB_state | FB_effects |
- FB_tag | FB_draw_mask);
+ FB_tag | FB_draw_mask | FB_decal |
+ FB_show_bounds | FB_show_tight_bounds);
cdataw->_fancy_bits =
(cdataw->_fancy_bits & ~change_bits) |
(cdatar->_fancy_bits & change_bits);
@@ -2536,6 +2518,24 @@ disable_cull_callback() {
mark_bam_modified();
}
+/**
+ * Called by a derived class to indicate that there is some value to visiting
+ * this particular node during the cull traversal for any camera. This will be
+ * used to optimize the result of get_net_draw_show_mask(), so that any subtrees
+ * that contain only nodes for which is_renderable() is false need not be
+ * visited. It also indicates that add_for_draw() should be called if the
+ * object is determined to be in view of the camera.
+ */
+void PandaNode::
+set_renderable() {
+ Thread *current_thread = Thread::get_current_thread();
+ OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
+ CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
+ cdata->set_fancy_bit(FB_renderable, true);
+ }
+ CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
+}
+
/**
* The private implementation of remove_child(), for a particular pipeline
* stage.
@@ -3272,7 +3272,7 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
// Start with a clean slate.
CollideMask net_collide_mask = cdata->_into_collide_mask;
DrawMask net_draw_control_mask, net_draw_show_mask;
- bool renderable = is_renderable();
+ bool renderable = (cdata->_fancy_bits & FB_renderable) != 0;
if (renderable) {
// If this node is itself renderable, it contributes to the net draw
@@ -3292,16 +3292,22 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
off_clip_planes = ClipPlaneAttrib::make();
}
- // Also get the list of the node's children.
- Children children(cdata);
-
int num_vertices = cdata->_internal_vertices;
+ // Also get the list of the node's children. When the cdataw destructs, it
+ // will also release the lock, since we've got all the data we need from the
+ // node.
+ PT(Down) down;
+ {
+ CDStageWriter cdataw(_cycler, pipeline_stage, cdata);
+ down = cdataw->modify_down();
+ }
+
// Now that we've got all the data we need from the node, we can release
// the lock.
- _cycler.release_read_stage(pipeline_stage, cdata.take_pointer());
+ //_cycler.release_read_stage(pipeline_stage, cdata.take_pointer());
- int num_children = children.get_num_children();
+ int num_children = down->size();
// We need to keep references to the bounding volumes, since in a threaded
// environment the pointers might go away while we're working (since we're
@@ -3334,7 +3340,8 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
// Now expand those contents to include all of our children.
for (int i = 0; i < num_children; ++i) {
- PandaNode *child = children.get_child(i);
+ DownConnection &connection = (*down)[i];
+ PandaNode *child = connection.get_child();
const ClipPlaneAttrib *orig_cp = DCAST(ClipPlaneAttrib, off_clip_planes);
@@ -3348,7 +3355,9 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
// Child needs update.
CDStageWriter child_cdataw = child->update_cached(update_bounds, pipeline_stage, child_cdata);
- net_collide_mask |= child_cdataw->_net_collide_mask;
+ CollideMask child_collide_mask = child_cdataw->_net_collide_mask;
+ net_collide_mask |= child_collide_mask;
+ connection._net_collide_mask = child_collide_mask;
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
@@ -3425,11 +3434,18 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
child_volumes[child_volumes_i++] = child_cdataw->_external_bounds;
}
num_vertices += child_cdataw->_nested_vertices;
+
+ connection._external_bounds = child_cdataw->_external_bounds->as_geometric_bounding_volume();
}
+ connection._net_draw_control_mask = child_control_mask;
+ connection._net_draw_show_mask = child_show_mask;
+
} else {
// Child is good.
- net_collide_mask |= child_cdata->_net_collide_mask;
+ CollideMask child_collide_mask = child_cdata->_net_collide_mask;
+ net_collide_mask |= child_collide_mask;
+ connection._net_collide_mask = child_collide_mask;
// See comments in similar block above.
if (drawmask_cat.is_debug()) {
@@ -3477,7 +3493,12 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
child_volumes[child_volumes_i++] = child_cdata->_external_bounds;
}
num_vertices += child_cdata->_nested_vertices;
+
+ connection._external_bounds = child_cdata->_external_bounds->as_geometric_bounding_volume();
}
+
+ connection._net_draw_control_mask = child_control_mask;
+ connection._net_draw_show_mask = child_show_mask;
}
}
@@ -3865,6 +3886,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
set_fancy_bit(FB_state, !_state->is_empty());
set_fancy_bit(FB_effects, !_effects->is_empty());
set_fancy_bit(FB_tag, !_tag_data.is_empty());
+ set_fancy_bit(FB_decal, _effects->has_decal());
+ set_fancy_bit(FB_show_bounds, _effects->has_show_bounds());
+ set_fancy_bit(FB_show_tight_bounds, _effects->has_show_tight_bounds());
// Mark the bounds stale.
++_next_update;
diff --git a/panda/src/pgraph/pandaNode.h b/panda/src/pgraph/pandaNode.h
index d4254f315c..d078839b7e 100644
--- a/panda/src/pgraph/pandaNode.h
+++ b/panda/src/pgraph/pandaNode.h
@@ -46,6 +46,7 @@
#include "lightReMutex.h"
#include "extension.h"
#include "simpleHashMap.h"
+#include "geometricBoundingVolume.h"
class NodePathComponent;
class CullTraverser;
@@ -97,12 +98,9 @@ public:
Thread *current_thread = Thread::get_current_thread()) const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool has_selective_visibility() const;
- virtual int get_first_visible_child() const;
- virtual int get_next_visible_child(int n) const;
virtual bool has_single_child_visibility() const;
virtual int get_visible_child() const;
- virtual bool is_renderable() const;
+ virtual bool is_renderable() const final; //CHANGED: see set_renderable()
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
PUBLISHED:
@@ -326,6 +324,10 @@ PUBLISHED:
FB_tag = 0x0010,
FB_draw_mask = 0x0020,
FB_cull_callback = 0x0040,
+ FB_renderable = 0x0080,
+ FB_decal = 0x0100,
+ FB_show_bounds = 0x0200,
+ FB_show_tight_bounds = 0x0400,
};
INLINE int get_fancy_bits(Thread *current_thread = Thread::get_current_thread()) const;
@@ -371,6 +373,8 @@ protected:
void set_cull_callback();
void disable_cull_callback();
+ void set_renderable();
+
public:
virtual void r_prepare_scene(GraphicsStateGuardianBase *gsg,
const RenderState *node_state,
@@ -459,11 +463,26 @@ public:
INLINE void set_child(PandaNode *child);
INLINE int get_sort() const;
+ INLINE CollideMask get_net_collide_mask() const;
+ INLINE const GeometricBoundingVolume *get_bounds() const;
+
+ INLINE bool compare_draw_mask(DrawMask running_draw_mask,
+ DrawMask camera_mask) const;
+
private:
// Child pointers are reference counted. That way, holding a pointer to
// the root of a subgraph keeps the entire subgraph around.
PT(PandaNode) _child;
int _sort;
+
+ // These values are cached here so that a traverser can efficiently check
+ // whether a node is in view before recursing.
+ CollideMask _net_collide_mask;
+ CPT(GeometricBoundingVolume) _external_bounds;
+ DrawMask _net_draw_control_mask;
+ DrawMask _net_draw_show_mask;
+
+ friend class PandaNode;
};
private:
@@ -718,6 +737,10 @@ PUBLISHED:
INLINE PandaNode *get_child(size_t n) const;
INLINE int get_child_sort(size_t n) const;
+ INLINE const DownConnection &get_child_connection(size_t n) const {
+ return (*_down)[n];
+ }
+
PUBLISHED:
INLINE PandaNode *operator [](size_t n) const { return get_child(n); }
INLINE size_t size() const { return get_num_children(); }
@@ -865,6 +888,7 @@ public:
INLINE int get_num_children() const;
INLINE PandaNode *get_child(int n) const;
+ INLINE const PandaNode::DownConnection &get_child_connection(int n) const;
INLINE int get_child_sort(int n) const;
INLINE int find_child(PandaNode *node) const;
diff --git a/panda/src/pgraph/planeNode.cxx b/panda/src/pgraph/planeNode.cxx
index a54e385e60..dbae5ff960 100644
--- a/panda/src/pgraph/planeNode.cxx
+++ b/panda/src/pgraph/planeNode.cxx
@@ -65,6 +65,7 @@ PlaneNode(const std::string &name, const LPlane &plane) :
_clip_effect(~0)
{
set_cull_callback();
+ set_renderable();
// PlaneNodes are hidden by default.
set_overall_hidden(true);
@@ -148,17 +149,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool PlaneNode::
-is_renderable() const {
- return true;
-}
-
/**
* Returns a newly-allocated BoundingVolume that represents the internal
* contents of the node. Should be overridden by PandaNode classes that
diff --git a/panda/src/pgraph/planeNode.h b/panda/src/pgraph/planeNode.h
index 2b58225abd..2a77d68bc2 100644
--- a/panda/src/pgraph/planeNode.h
+++ b/panda/src/pgraph/planeNode.h
@@ -46,7 +46,6 @@ public:
virtual void xform(const LMatrix4 &mat);
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
PUBLISHED:
INLINE void set_plane(const LPlane &plane);
@@ -65,6 +64,11 @@ PUBLISHED:
INLINE void set_clip_effect(int clip_effect);
INLINE int get_clip_effect() const;
+ MAKE_PROPERTY(plane, get_plane, set_plane);
+ MAKE_PROPERTY(viz_scale, get_viz_scale, set_viz_scale);
+ MAKE_PROPERTY(priority, get_priority, set_priority);
+ MAKE_PROPERTY(clip_effect, get_clip_effect, set_clip_effect);
+
public:
INLINE static UpdateSeq get_sort_seq();
diff --git a/panda/src/pgraph/portalNode.cxx b/panda/src/pgraph/portalNode.cxx
index c7ad0444fe..09f854b0d9 100644
--- a/panda/src/pgraph/portalNode.cxx
+++ b/panda/src/pgraph/portalNode.cxx
@@ -48,6 +48,7 @@ PortalNode(const std::string &name) :
_flags(0)
{
set_cull_callback();
+ set_renderable();
_visible = false;
_open = true;
@@ -307,18 +308,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool PortalNode::
-is_renderable() const {
- return true;
-}
-
-
/**
* Writes a brief description of the node to the indicated output stream.
* This is invoked by the << operator. It may be overridden in derived
diff --git a/panda/src/pgraph/portalNode.h b/panda/src/pgraph/portalNode.h
index de78847dab..602dd96139 100644
--- a/panda/src/pgraph/portalNode.h
+++ b/panda/src/pgraph/portalNode.h
@@ -45,7 +45,6 @@ public:
virtual void enable_clipping_planes();
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void output(std::ostream &out) const;
diff --git a/panda/src/pgraph/showBoundsEffect.h b/panda/src/pgraph/showBoundsEffect.h
index e0f8a7950f..58ed65e1bc 100644
--- a/panda/src/pgraph/showBoundsEffect.h
+++ b/panda/src/pgraph/showBoundsEffect.h
@@ -25,7 +25,7 @@ class FactoryParams;
* this node. This is generally used only during development to help identify
* bounding volume issues.
*/
-class EXPCL_PANDA_PGRAPH ShowBoundsEffect : public RenderEffect {
+class EXPCL_PANDA_PGRAPH ShowBoundsEffect final : public RenderEffect {
private:
INLINE ShowBoundsEffect();
diff --git a/panda/src/pgraph/transformState.I b/panda/src/pgraph/transformState.I
index 437c625bc4..6097430e91 100644
--- a/panda/src/pgraph/transformState.I
+++ b/panda/src/pgraph/transformState.I
@@ -40,6 +40,31 @@ get_hash() const {
return _hash;
}
+/**
+ * Constructs an identity transform.
+ */
+INLINE CPT(TransformState) TransformState::
+make_identity() {
+ if (UNLIKELY(_states_lock == nullptr)) {
+ init_states();
+ }
+
+ return _identity_state;
+}
+
+/**
+ * Constructs an invalid transform; for instance, the result of inverting a
+ * singular matrix.
+ */
+INLINE CPT(TransformState) TransformState::
+make_invalid() {
+ if (UNLIKELY(_states_lock == nullptr)) {
+ init_states();
+ }
+
+ return _invalid_state;
+}
+
/**
* Makes a new TransformState with the specified components.
*/
@@ -204,7 +229,7 @@ make_pos_rotate_scale2d(const LVecBase2 &pos, PN_stdfloat rotate,
*/
INLINE bool TransformState::
is_identity() const {
- return ((_flags & F_is_identity) != 0);
+ return this == _identity_state;
}
/**
@@ -214,7 +239,7 @@ is_identity() const {
*/
INLINE bool TransformState::
is_invalid() const {
- return ((_flags & F_is_invalid) != 0);
+ return this == _invalid_state;
}
/**
@@ -472,6 +497,20 @@ get_mat() const {
return _mat;
}
+/**
+ * Returns the inverse of the matrix, or nullptr if it's singular.
+ */
+INLINE const LMatrix4 *TransformState::
+get_inverse_mat() const {
+ if (!is_singular()) {
+ nassertr(_inv_mat != nullptr, nullptr);
+ return _inv_mat;
+ }
+ else {
+ return nullptr;
+ }
+}
+
/**
* Returns the pos component of the 2-d transform. It is an error to call
* this if has_pos() or is_2d() returned false.
@@ -817,8 +856,8 @@ INLINE void TransformState::
check_hash() const {
// This pretends to be a const function, even though it's not, because it
// only updates a transparent cache value.
- if ((_flags & F_hash_known) == 0) {
- ((TransformState *)this)->calc_hash();
+ if (_hash == H_unknown) {
+ calc_hash();
}
}
@@ -898,15 +937,6 @@ check_mat() const {
}
}
-/**
- * Computes the hash value.
- */
-INLINE void TransformState::
-calc_hash() {
- LightMutexHolder holder(_lock);
- do_calc_hash();
-}
-
/**
* Derives the components from the matrix, if possible.
*/
diff --git a/panda/src/pgraph/transformState.cxx b/panda/src/pgraph/transformState.cxx
index aac37c306b..f02e77203a 100644
--- a/panda/src/pgraph/transformState.cxx
+++ b/panda/src/pgraph/transformState.cxx
@@ -51,18 +51,17 @@ CacheStats TransformState::_cache_stats;
TypeHandle TransformState::_type_handle;
/**
- * Actually, this could be a private constructor, since no one inherits from
- * TransformState, but gcc gives us a spurious warning if all constructors are
- * private.
+ *
*/
TransformState::
-TransformState() : _lock("TransformState") {
+TransformState() :
+ _flags(F_is_identity | F_singular_known | F_is_2d),
+ _lock("TransformState") {
+
if (_states_lock == nullptr) {
init_states();
}
- _saved_entry = -1;
- _flags = F_is_identity | F_singular_known | F_is_2d;
- _inv_mat = nullptr;
+
_cache_stats.add_num_states(1);
#ifdef DO_MEMORY_USAGE
@@ -232,43 +231,13 @@ operator == (const TransformState &other) const {
}
}
-/**
- * Constructs an identity transform.
- */
-CPT(TransformState) TransformState::
-make_identity() {
- // The identity state is asked for so often, we make it a special case and
- // store a pointer forever once we find it the first time.
- if (_identity_state == nullptr) {
- TransformState *state = new TransformState;
- _identity_state = return_unique(state);
- }
-
- return _identity_state;
-}
-
-/**
- * Constructs an invalid transform; for instance, the result of inverting a
- * singular matrix.
- */
-CPT(TransformState) TransformState::
-make_invalid() {
- if (_invalid_state == nullptr) {
- TransformState *state = new TransformState;
- state->_flags = F_is_invalid | F_singular_known | F_is_singular | F_components_known | F_mat_known;
- _invalid_state = return_unique(state);
- }
-
- return _invalid_state;
-}
-
/**
* Makes a new TransformState with the specified components.
*/
CPT(TransformState) TransformState::
make_pos_hpr_scale_shear(const LVecBase3 &pos, const LVecBase3 &hpr,
const LVecBase3 &scale, const LVecBase3 &shear) {
- nassertr(!(pos.is_nan() || hpr.is_nan() || scale.is_nan() || shear.is_nan()) , make_invalid());
+ nassertr(!(pos.is_nan() || hpr.is_nan() || scale.is_nan() || shear.is_nan()), make_invalid());
// Make a special-case check for the identity transform.
if (pos == LVecBase3(0.0f, 0.0f, 0.0f) &&
hpr == LVecBase3(0.0f, 0.0f, 0.0f) &&
@@ -293,7 +262,7 @@ make_pos_hpr_scale_shear(const LVecBase3 &pos, const LVecBase3 &hpr,
CPT(TransformState) TransformState::
make_pos_quat_scale_shear(const LVecBase3 &pos, const LQuaternion &quat,
const LVecBase3 &scale, const LVecBase3 &shear) {
- nassertr(!(pos.is_nan() || quat.is_nan() || scale.is_nan() || shear.is_nan()) , make_invalid());
+ nassertr(!(pos.is_nan() || quat.is_nan() || scale.is_nan() || shear.is_nan()), make_invalid());
// Make a special-case check for the identity transform.
if (pos == LVecBase3(0.0f, 0.0f, 0.0f) &&
quat == LQuaternion::ident_quat() &&
@@ -336,7 +305,7 @@ CPT(TransformState) TransformState::
make_pos_rotate_scale_shear2d(const LVecBase2 &pos, PN_stdfloat rotate,
const LVecBase2 &scale,
PN_stdfloat shear) {
- nassertr(!(pos.is_nan() || cnan(rotate) || scale.is_nan() || cnan(shear)) , make_invalid());
+ nassertr(!(pos.is_nan() || cnan(rotate) || scale.is_nan() || cnan(shear)), make_invalid());
// Make a special-case check for the identity transform.
if (pos == LVecBase2(0.0f, 0.0f) &&
rotate == 0.0f &&
@@ -700,7 +669,7 @@ invert_compose(const TransformState *other) const {
if (other == this) {
// a->invert_compose(a) always produces identity.
- return make_identity();
+ return _identity_state;
}
if (!transform_cache) {
@@ -1421,6 +1390,40 @@ init_states() {
_states_lock = new LightReMutex("TransformState::_states_lock");
_cache_stats.init();
nassertv(Thread::get_current_thread() == Thread::get_main_thread());
+
+ // The identity and invalid states are asked for so often, we make them a
+ // special case and store a pointer forever.
+ {
+ TransformState *state = new TransformState;
+ state->_pos.set(0.0f, 0.0f, 0.0f);
+ state->_scale.set(1.0f, 1.0f, 1.0f);
+ state->_shear.set(0.0f, 0.0f, 0.0f);
+ state->_hpr.set(0.0f, 0.0f, 0.0f);
+ state->_quat.set(1.0f, 0.0f, 0.0f, 0.0f);
+ state->_norm_quat.set(1.0f, 0.0f, 0.0f, 0.0f);
+ state->_mat.set(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ state->_inv_mat = new LMatrix4(state->_mat);
+ state->_hash = H_identity;
+ state->_flags = F_is_identity | F_singular_known | F_components_known
+ | F_has_components | F_mat_known | F_quat_known | F_hpr_known
+ | F_uniform_scale | F_identity_scale | F_is_2d
+ | F_norm_quat_known;
+ state->cache_ref();
+ state->_saved_entry = _states.store(state, nullptr);
+ _identity_state = state;
+ }
+ {
+ TransformState *state = new TransformState;
+ state->_hash = H_invalid;
+ state->_flags = F_is_singular | F_singular_known | F_components_known
+ | F_mat_known | F_is_invalid;
+ state->cache_ref();
+ state->_saved_entry = _states.store(state, nullptr);
+ _invalid_state = state;
+ }
}
/**
@@ -1604,7 +1607,7 @@ do_invert_compose(const TransformState *other) const {
// First, invert our own transform.
if (scale == 0.0f) {
((TransformState *)this)->_flags |= F_is_singular | F_singular_known;
- return make_invalid();
+ return _invalid_state;
}
scale = 1.0f / scale;
quat.invert_in_place();
@@ -1633,7 +1636,7 @@ do_invert_compose(const TransformState *other) const {
// First, invert our own transform.
if (scale == 0.0f) {
((TransformState *)this)->_flags |= F_is_singular | F_singular_known;
- return make_invalid();
+ return _invalid_state;
}
scale = 1.0f / scale;
quat.invert_in_place();
@@ -1657,7 +1660,7 @@ do_invert_compose(const TransformState *other) const {
pgraph_cat.warning()
<< "Unexpected singular matrix found for " << *this << "\n";
} else {
- nassertr(_inv_mat != nullptr, make_invalid());
+ nassertr(_inv_mat != nullptr, _invalid_state);
LMatrix4 new_mat;
new_mat.multiply(other->get_mat(), *_inv_mat);
if (!new_mat.almost_equal(result->get_mat(), 0.1)) {
@@ -1676,12 +1679,12 @@ do_invert_compose(const TransformState *other) const {
}
if (is_singular()) {
- return make_invalid();
+ return _invalid_state;
}
// Now that is_singular() has returned false, we can assume that _inv_mat
// has been allocated and filled in.
- nassertr(_inv_mat != nullptr, make_invalid());
+ nassertr(_inv_mat != nullptr, _invalid_state);
if (is_2d() && other->is_2d()) {
const LMatrix4 &i = *_inv_mat;
@@ -1999,54 +2002,61 @@ remove_cache_pointers() {
* Computes a suitable hash value for phash_map.
*/
void TransformState::
-do_calc_hash() {
+calc_hash() const {
+ // It's OK not to grab the lock here, because (1) this does not depend on
+ // cached values (only given components are considered), and (2) the hash
+ // itself is set atomically.
PStatTimer timer(_transform_hash_pcollector);
- _hash = 0;
+ AtomicAdjust::Integer hash = 0;
static const int significant_flags =
(F_is_invalid | F_is_identity | F_components_given | F_hpr_given | F_is_2d);
int flags = (_flags & significant_flags);
- _hash = int_hash::add_hash(_hash, flags);
+ hash = int_hash::add_hash(hash, flags);
- if ((_flags & (F_is_invalid | F_is_identity)) == 0) {
- // Only bother to put the rest of the stuff in the hash if the transform
- // is not invalid or empty.
+ nassertv((flags & (F_is_invalid | F_is_identity)) == 0);
- if ((_flags & F_components_given) != 0) {
- // If the transform was specified componentwise, hash it componentwise.
- _hash = _pos.add_hash(_hash);
- if ((_flags & F_hpr_given) != 0) {
- _hash = _hpr.add_hash(_hash);
+ // Only bother to put the rest of the stuff in the hash if the transform
+ // is not invalid or empty.
- } else if ((_flags & F_quat_given) != 0) {
- _hash = _quat.add_hash(_hash);
- }
+ if ((_flags & F_components_given) != 0) {
+ // If the transform was specified componentwise, hash it componentwise.
+ hash = _pos.add_hash(hash);
+ if ((_flags & F_hpr_given) != 0) {
+ hash = _hpr.add_hash(hash);
+ }
+ else if ((_flags & F_quat_given) != 0) {
+ hash = _quat.add_hash(hash);
+ }
- _hash = _scale.add_hash(_hash);
- _hash = _shear.add_hash(_hash);
+ hash = _scale.add_hash(hash);
+ hash = _shear.add_hash(hash);
+ }
+ else {
+ // Otherwise, hash the matrix . . .
+ if (_uniquify_matrix) {
+ // . . . but only if the user thinks that's worthwhile.
+ check_mat();
+ hash = _mat.add_hash(hash);
+ }
+ else {
+ // Otherwise, hash the pointer only--any two different matrix-based
+ // TransformStates are considered to be different, even if their
+ // matrices have the same values.
- } else {
- // Otherwise, hash the matrix . . .
- if (_uniquify_matrix) {
- // . . . but only if the user thinks that's worthwhile.
- if ((_flags & F_mat_known) == 0) {
- // Calculate the matrix without doubly-locking.
- do_calc_mat();
- }
- _hash = _mat.add_hash(_hash);
-
- } else {
- // Otherwise, hash the pointer only--any two different matrix-based
- // TransformStates are considered to be different, even if their
- // matrices have the same values.
-
- _hash = pointer_hash::add_hash(_hash, this);
- }
+ hash = pointer_hash::add_hash(hash, this);
}
}
- _flags |= F_hash_known;
+ if (hash == H_unknown || hash == H_identity || hash == H_invalid) {
+ // Arbitrarily offset the hash not to conflict with these special values.
+ hash += 0x10000;
+ }
+
+ // We don't care if some other thread set this in the meantime, since every
+ // thread should have computed the same hash.
+ AtomicAdjust::set(_hash, hash);
}
/**
@@ -2063,7 +2073,7 @@ calc_singular() {
PStatTimer timer(_transform_calc_pcollector);
- nassertv((_flags & F_is_invalid) == 0);
+ nassertv((_flags & (F_is_invalid | F_is_identity)) == 0);
// We determine if a matrix is singular by attempting to invert it (and we
// save the result of this invert operation for a subsequent
@@ -2100,39 +2110,30 @@ do_calc_components() {
PStatTimer timer(_transform_calc_pcollector);
- nassertv((_flags & F_is_invalid) == 0);
- if ((_flags & F_is_identity) != 0) {
- _scale.set(1.0f, 1.0f, 1.0f);
- _shear.set(0.0f, 0.0f, 0.0f);
- _hpr.set(0.0f, 0.0f, 0.0f);
- _quat = LQuaternion::ident_quat();
- _pos.set(0.0f, 0.0f, 0.0f);
- _flags |= F_has_components | F_components_known | F_hpr_known | F_quat_known | F_uniform_scale | F_identity_scale;
+ nassertv((_flags & (F_is_invalid | F_is_identity)) == 0);
+
+ // If we don't have components and we're not identity, the only other
+ // explanation is that we were constructed via a matrix.
+ nassertv((_flags & F_mat_known) != 0);
+
+ if ((_flags & F_mat_known) == 0) {
+ do_calc_mat();
+ }
+ bool possible = decompose_matrix(_mat, _scale, _shear, _hpr, _pos);
+ if (!possible) {
+ // Some matrices can't be decomposed into scale, hpr, pos. In this
+ // case, we now know that we cannot compute the components; but the
+ // closest approximations are stored, at least.
+ _flags |= F_components_known | F_hpr_known;
} else {
- // If we don't have components and we're not identity, the only other
- // explanation is that we were constructed via a matrix.
- nassertv((_flags & F_mat_known) != 0);
-
- if ((_flags & F_mat_known) == 0) {
- do_calc_mat();
- }
- bool possible = decompose_matrix(_mat, _scale, _shear, _hpr, _pos);
- if (!possible) {
- // Some matrices can't be decomposed into scale, hpr, pos. In this
- // case, we now know that we cannot compute the components; but the
- // closest approximations are stored, at least.
- _flags |= F_components_known | F_hpr_known;
-
- } else {
- // Otherwise, we do have the components, or at least the hpr.
- _flags |= F_has_components | F_components_known | F_hpr_known;
- check_uniform_scale();
- }
-
- // However, we can always get at least the pos.
- _mat.get_row3(_pos, 3);
+ // Otherwise, we do have the components, or at least the hpr.
+ _flags |= F_has_components | F_components_known | F_hpr_known;
+ check_uniform_scale();
}
+
+ // However, we can always get at least the pos.
+ _mat.get_row3(_pos, 3);
}
/**
@@ -2148,7 +2149,7 @@ do_calc_hpr() {
PStatTimer timer(_transform_calc_pcollector);
- nassertv((_flags & F_is_invalid) == 0);
+ nassertv((_flags & (F_is_invalid | F_is_identity)) == 0);
if ((_flags & F_components_known) == 0) {
do_calc_components();
}
@@ -2174,7 +2175,7 @@ calc_quat() {
PStatTimer timer(_transform_calc_pcollector);
- nassertv((_flags & F_is_invalid) == 0);
+ nassertv((_flags & (F_is_invalid | F_is_identity)) == 0);
if ((_flags & F_components_known) == 0) {
do_calc_components();
}
@@ -2214,20 +2215,16 @@ do_calc_mat() {
PStatTimer timer(_transform_calc_pcollector);
- nassertv((_flags & F_is_invalid) == 0);
- if ((_flags & F_is_identity) != 0) {
- _mat = LMatrix4::ident_mat();
+ nassertv((_flags & (F_is_invalid | F_is_identity)) == 0);
- } else {
- // If we don't have a matrix and we're not identity, the only other
- // explanation is that we were constructed via components.
- nassertv((_flags & F_components_known) != 0);
- if ((_flags & F_hpr_known) == 0) {
- do_calc_hpr();
- }
-
- compose_matrix(_mat, _scale, _shear, get_hpr(), _pos);
+ // If we don't have a matrix and we're not identity, the only other
+ // explanation is that we were constructed via components.
+ nassertv((_flags & F_components_known) != 0);
+ if ((_flags & F_hpr_known) == 0) {
+ do_calc_hpr();
}
+
+ compose_matrix(_mat, _scale, _shear, get_hpr(), _pos);
_flags |= F_mat_known;
}
@@ -2341,7 +2338,18 @@ make_from_bam(const FactoryParams ¶ms) {
parse_params(params, scan, manager);
state->fillin(scan, manager);
- manager->register_change_this(change_this, state);
+
+ if (state->_flags & F_is_identity) {
+ delete state;
+ return (TypedWritable *)_identity_state.p();
+ }
+ else if (state->_flags & F_is_invalid) {
+ delete state;
+ return (TypedWritable *)_invalid_state.p();
+ }
+ else {
+ manager->register_change_this(change_this, state);
+ }
return state;
}
diff --git a/panda/src/pgraph/transformState.h b/panda/src/pgraph/transformState.h
index 3e835fcebf..288d26b315 100644
--- a/panda/src/pgraph/transformState.h
+++ b/panda/src/pgraph/transformState.h
@@ -52,7 +52,7 @@ class FactoryParams;
* And instead of modifying a TransformState object, create a new one.
*/
class EXPCL_PANDA_PGRAPH TransformState final : public NodeCachedReferenceCount {
-protected:
+private:
TransformState();
public:
@@ -69,8 +69,8 @@ PUBLISHED:
bool operator == (const TransformState &other) const;
INLINE size_t get_hash() const;
- static CPT(TransformState) make_identity();
- static CPT(TransformState) make_invalid();
+ INLINE static CPT(TransformState) make_identity();
+ INLINE static CPT(TransformState) make_invalid();
INLINE static CPT(TransformState) make_pos(const LVecBase3 &pos);
INLINE static CPT(TransformState) make_hpr(const LVecBase3 &hpr);
INLINE static CPT(TransformState) make_quat(const LQuaternion &quat);
@@ -142,6 +142,7 @@ PUBLISHED:
INLINE PN_stdfloat get_uniform_scale() const;
INLINE const LVecBase3 &get_shear() const;
INLINE const LMatrix4 &get_mat() const;
+ INLINE const LMatrix4 *get_inverse_mat() const;
INLINE LVecBase2 get_pos2d() const;
INLINE PN_stdfloat get_rotate2d() const;
@@ -267,7 +268,7 @@ private:
// This iterator records the entry corresponding to this TransformState
// object in the above global set. We keep the index around so we can
// remove it when the TransformState destructs.
- int _saved_entry;
+ int _saved_entry = -1;
// This data structure manages the job of caching the composition of two
// TransformStates. It's complicated because we have to be sure to remove
@@ -325,8 +326,7 @@ private:
INLINE void check_quat() const;
INLINE void check_norm_quat() const;
INLINE void check_mat() const;
- INLINE void calc_hash();
- void do_calc_hash();
+ void calc_hash() const;
void calc_singular();
INLINE void calc_components();
void do_calc_components();
@@ -364,15 +364,20 @@ private:
F_has_nonzero_shear = 0x00004000,
F_is_destructing = 0x00008000,
F_is_2d = 0x00010000,
- F_hash_known = 0x00020000,
F_norm_quat_known = 0x00040000,
};
LPoint3 _pos;
LVecBase3 _hpr, _scale, _shear;
LQuaternion _quat, _norm_quat;
LMatrix4 _mat;
- LMatrix4 *_inv_mat;
- size_t _hash;
+ LMatrix4 *_inv_mat = nullptr;
+
+ enum HashValue : AtomicAdjust::Integer {
+ H_unknown = 0,
+ H_identity = 1,
+ H_invalid = 2,
+ };
+ mutable AtomicAdjust::Integer _hash = H_unknown;
unsigned int _flags;
diff --git a/panda/src/pgraphnodes/callbackNode.cxx b/panda/src/pgraphnodes/callbackNode.cxx
index bf0a233f3c..2e555dfd1e 100644
--- a/panda/src/pgraphnodes/callbackNode.cxx
+++ b/panda/src/pgraphnodes/callbackNode.cxx
@@ -30,6 +30,7 @@ CallbackNode(const std::string &name) :
PandaNode(name)
{
PandaNode::set_cull_callback();
+ PandaNode::set_renderable();
// Set up a default, infinite bounding volume, unless the user tells us
// otherwise. Not sure if this is a great idea, because it means a naive
@@ -102,17 +103,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool CallbackNode::
-is_renderable() const {
- return true;
-}
-
/**
* Adds the node's contents to the CullResult we are building up during the
* cull traversal, so that it will be drawn at render time. For most nodes
diff --git a/panda/src/pgraphnodes/callbackNode.h b/panda/src/pgraphnodes/callbackNode.h
index 0a4d101903..f658137ddc 100644
--- a/panda/src/pgraphnodes/callbackNode.h
+++ b/panda/src/pgraphnodes/callbackNode.h
@@ -44,7 +44,6 @@ public:
virtual bool safe_to_combine() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
virtual void output(std::ostream &out) const;
diff --git a/panda/src/pgraphnodes/computeNode.cxx b/panda/src/pgraphnodes/computeNode.cxx
index 560e1a8365..a2f0ca5f15 100644
--- a/panda/src/pgraphnodes/computeNode.cxx
+++ b/panda/src/pgraphnodes/computeNode.cxx
@@ -32,6 +32,7 @@ ComputeNode(const std::string &name) :
_dispatcher(new ComputeNode::Dispatcher)
{
set_internal_bounds(new OmniBoundingVolume);
+ set_renderable();
}
/**
@@ -65,17 +66,6 @@ safe_to_combine() const {
return false;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool ComputeNode::
-is_renderable() const {
- return true;
-}
-
/**
* Adds the node's contents to the CullResult we are building up during the
* cull traversal, so that it will be drawn at render time. For most nodes
diff --git a/panda/src/pgraphnodes/computeNode.h b/panda/src/pgraphnodes/computeNode.h
index 475aca2355..e1d0f6f073 100644
--- a/panda/src/pgraphnodes/computeNode.h
+++ b/panda/src/pgraphnodes/computeNode.h
@@ -47,7 +47,6 @@ public:
virtual PandaNode *make_copy() const;
virtual bool safe_to_combine() const;
- virtual bool is_renderable() const;
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
virtual void output(std::ostream &out) const;
diff --git a/panda/src/pgraphnodes/fadeLodNode.cxx b/panda/src/pgraphnodes/fadeLodNode.cxx
index f523d01795..265594279b 100644
--- a/panda/src/pgraphnodes/fadeLodNode.cxx
+++ b/panda/src/pgraphnodes/fadeLodNode.cxx
@@ -164,54 +164,37 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
nassertr(elapsed >= 0.0f && elapsed <= _fade_time, false);
+ Children children = get_children();
if (elapsed < half_fade_time) {
// FIRST HALF OF FADE Fade the new LOD in with z writing off Keep
// drawing the old LOD opaque with z writing on
- if (out_child >= 0 && out_child < get_num_children()) {
- PandaNode *child = get_child(out_child);
- if (child != nullptr) {
- CullTraverserData next_data_out(data, child);
- next_data_out._state =
- next_data_out._state->compose(get_fade_1_old_state());
- trav->traverse(next_data_out);
- }
+ if (out_child >= 0 && out_child < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(out_child);
+ trav->traverse_down(data, child,
+ data._state->compose(get_fade_1_old_state()));
}
- if (in_child >= 0 && in_child < get_num_children()) {
- PandaNode *child = get_child(in_child);
- if (child != nullptr) {
- CullTraverserData next_data_in(data, child);
-
- PN_stdfloat in_alpha = elapsed / half_fade_time;
- next_data_in._state =
- next_data_in._state->compose(get_fade_1_new_state(in_alpha));
- trav->traverse(next_data_in);
- }
+ if (in_child >= 0 && in_child < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(in_child);
+ PN_stdfloat in_alpha = elapsed / half_fade_time;
+ trav->traverse_down(data, child,
+ data._state->compose(get_fade_1_new_state(in_alpha)));
}
} else {
// SECOND HALF OF FADE: Fade out the old LOD with z write off and
// draw the opaque new LOD with z write on
- if (in_child >= 0 && in_child < get_num_children()) {
- PandaNode *child = get_child(in_child);
- if (child != nullptr) {
- CullTraverserData next_data_in(data, child);
- next_data_in._state =
- next_data_in._state->compose(get_fade_2_new_state());
- trav->traverse(next_data_in);
- }
+ if (in_child >= 0 && in_child < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(in_child);
+ trav->traverse_down(data, child,
+ data._state->compose(get_fade_2_new_state()));
}
- if (out_child >= 0 && out_child < get_num_children()) {
- PandaNode *child = get_child(out_child);
- if (child != nullptr) {
- CullTraverserData next_data_out(data, child);
-
- PN_stdfloat out_alpha = 1.0f - (elapsed - half_fade_time) / half_fade_time;
- next_data_out._state =
- next_data_out._state->compose(get_fade_2_old_state(out_alpha));
- trav->traverse(next_data_out);
- }
+ if (out_child >= 0 && out_child < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(out_child);
+ PN_stdfloat out_alpha = 1.0f - (elapsed - half_fade_time) / half_fade_time;
+ trav->traverse_down(data, child,
+ data._state->compose(get_fade_2_old_state(out_alpha)));
}
}
}
@@ -222,12 +205,10 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
// This is the normal case: we're not in the middle of a transition; we're
// just drawing one child of the LOD.
int index = ldata->_fade_in;
- if (index >= 0 && index < get_num_children()) {
- PandaNode *child = get_child(index);
- if (child != nullptr) {
- CullTraverserData next_data(data, child);
- trav->traverse(next_data);
- }
+ Children children = get_children();
+ if (index >= 0 && index < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ trav->traverse_down(data, child, data._state);
}
}
diff --git a/panda/src/pgraphnodes/lodNode.cxx b/panda/src/pgraphnodes/lodNode.cxx
index 2b66c8fa36..a5574dcaa8 100644
--- a/panda/src/pgraphnodes/lodNode.cxx
+++ b/panda/src/pgraphnodes/lodNode.cxx
@@ -146,7 +146,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
PN_stdfloat lod_scale = cdata->_lod_scale *
trav->get_scene()->get_camera_node()->get_lod_scale();
- int num_children = std::min(get_num_children(), (int)cdata->_switch_vector.size());
+ Children children = get_children();
+ int num_children = std::min(children.get_num_children(), cdata->_switch_vector.size());
if (data._instances == nullptr || cdata->_got_force_switch) {
LPoint3 center = cdata->_center * rel_transform->get_mat();
@@ -163,11 +164,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
if (in_range) {
// This switch level is in range. Draw its children.
- PandaNode *child = get_child(index);
- if (child != nullptr) {
- CullTraverserData next_data(data, child);
- trav->traverse(next_data);
- }
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ trav->traverse_down(data, child);
}
}
}
@@ -193,12 +191,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
CPT(InstanceList) instances = data._instances->without(in_range[index]);
if (!instances->empty()) {
// At least one instance is visible in this switch level.
- PandaNode *child = get_child(index);
- if (child != nullptr) {
- CullTraverserData next_data(data, child);
- next_data._instances = instances;
- trav->traverse(next_data);
- }
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ trav->traverse_down(data, child);
}
}
}
@@ -435,26 +429,23 @@ show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
if (in_range) {
// This switch level is in range. Draw its children in the funny
// wireframe mode.
- if (index < get_num_children()) {
- PandaNode *child = get_child(index);
- if (child != nullptr) {
- CullTraverserData next_data3(data, child);
- next_data3._state = next_data3._state->compose(sw.get_viz_model_state());
- trav->traverse(next_data3);
- }
+ Children children = get_children();
+ if (index < children.get_num_children()) {
+ const PandaNode::DownConnection &child = children.get_child_connection(index);
+ trav->traverse_down(data, child, data._state->compose(sw.get_viz_model_state()));
}
// And draw the spindle in this color.
- CullTraverserData next_data2(data, sw.get_spindle_viz());
- next_data2.apply_transform(viz_transform);
- trav->traverse(next_data2);
+ //CullTraverserData next_data2(data, sw.get_spindle_viz());
+ //next_data2.apply_transform(viz_transform);
+ //trav->traverse(next_data2);
}
// Draw the rings for this switch level. We do this after we have drawn
// the geometry and the spindle.
- CullTraverserData next_data(data, sw.get_ring_viz());
- next_data.apply_transform(viz_transform);
- trav->traverse(next_data);
+ //CullTraverserData next_data(data, sw.get_ring_viz());
+ //next_data.apply_transform(viz_transform);
+ //trav->traverse(next_data);
}
}
diff --git a/panda/src/pgraphnodes/nodeCullCallbackData.cxx b/panda/src/pgraphnodes/nodeCullCallbackData.cxx
index c1f0ac7464..5a9dbc9e0a 100644
--- a/panda/src/pgraphnodes/nodeCullCallbackData.cxx
+++ b/panda/src/pgraphnodes/nodeCullCallbackData.cxx
@@ -56,13 +56,6 @@ upcall() {
}
}
- // Now visit all the node's children.
- PandaNodePipelineReader *node_reader = _data.node_reader();
- PandaNode::Children children = node_reader->get_children();
- node_reader->release();
- int num_children = children.get_num_children();
- for (int i = 0; i < num_children; ++i) {
- CullTraverserData next_data(_data, children.get_child(i));
- _trav->traverse(next_data);
- }
+ // Now traverse below.
+ _trav->traverse_below(_data);
}
diff --git a/panda/src/pgraphnodes/selectiveChildNode.I b/panda/src/pgraphnodes/selectiveChildNode.I
index beafc0669d..46b40ba3a7 100644
--- a/panda/src/pgraphnodes/selectiveChildNode.I
+++ b/panda/src/pgraphnodes/selectiveChildNode.I
@@ -16,8 +16,7 @@
*/
INLINE SelectiveChildNode::
SelectiveChildNode(const std::string &name) :
- PandaNode(name),
- _selected_child(0)
+ PandaNode(name)
{
}
@@ -26,17 +25,6 @@ SelectiveChildNode(const std::string &name) :
*/
INLINE SelectiveChildNode::
SelectiveChildNode(const SelectiveChildNode ©) :
- PandaNode(copy),
- _selected_child(copy._selected_child)
+ PandaNode(copy)
{
}
-
-/**
- * Selects the indicated child for rendering. This is normally called during
- * the cull_callback() method, but it may be called at any time.
- */
-INLINE void SelectiveChildNode::
-select_child(int n) {
- nassertv(n >= 0);
- _selected_child = n;
-}
diff --git a/panda/src/pgraphnodes/selectiveChildNode.cxx b/panda/src/pgraphnodes/selectiveChildNode.cxx
index 0fb38560aa..cfe5e258b4 100644
--- a/panda/src/pgraphnodes/selectiveChildNode.cxx
+++ b/panda/src/pgraphnodes/selectiveChildNode.cxx
@@ -14,44 +14,3 @@
#include "selectiveChildNode.h"
TypeHandle SelectiveChildNode::_type_handle;
-
-
-/**
- * Should be overridden by derived classes to return true if this kind of node
- * has some restrictions on the set of children that should be rendered. Node
- * with this property include LODNodes, SwitchNodes, and SequenceNodes.
- *
- * If this function returns true, get_first_visible_child() and
- * get_next_visible_child() will be called to walk through the list of
- * children during cull, instead of iterating through the entire list. This
- * method is called after cull_callback(), so cull_callback() may be
- * responsible for the decisions as to which children are visible at the
- * moment.
- */
-bool SelectiveChildNode::
-has_selective_visibility() const {
- return true;
-}
-
-/**
- * Returns the index number of the first visible child of this node, or a
- * number >= get_num_children() if there are no visible children of this node.
- * This is called during the cull traversal, but only if
- * has_selective_visibility() has already returned true. See
- * has_selective_visibility().
- */
-int SelectiveChildNode::
-get_first_visible_child() const {
- return _selected_child;
-}
-
-/**
- * Returns the index number of the next visible child of this node following
- * the indicated child, or a number >= get_num_children() if there are no more
- * visible children of this node. See has_selective_visibility() and
- * get_first_visible_child().
- */
-int SelectiveChildNode::
-get_next_visible_child(int n) const {
- return get_num_children();
-}
diff --git a/panda/src/pgraphnodes/selectiveChildNode.h b/panda/src/pgraphnodes/selectiveChildNode.h
index 29b0d4581a..aa23a49f1d 100644
--- a/panda/src/pgraphnodes/selectiveChildNode.h
+++ b/panda/src/pgraphnodes/selectiveChildNode.h
@@ -21,6 +21,8 @@
/**
* A base class for nodes like LODNode and SequenceNode that select only one
* visible child at a time.
+ *
+ * This class is now vestigial.
*/
class EXPCL_PANDA_PGRAPHNODES SelectiveChildNode : public PandaNode {
PUBLISHED:
@@ -29,21 +31,6 @@ PUBLISHED:
protected:
INLINE SelectiveChildNode(const SelectiveChildNode ©);
-public:
- virtual bool has_selective_visibility() const;
- virtual int get_first_visible_child() const;
- virtual int get_next_visible_child(int n) const;
-
-protected:
- INLINE void select_child(int n);
-
-private:
- // Not sure if this should be cycled or not. It's not exactly thread-safe
- // not to cycle it, but it doesn't really need the full pipeline control.
- // It's probably a problem in the non-thread-safe design; need to rethink
- // the design a bit.
- int _selected_child;
-
public:
static TypeHandle get_class_type() {
return _type_handle;
diff --git a/panda/src/pgraphnodes/sequenceNode.cxx b/panda/src/pgraphnodes/sequenceNode.cxx
index 06f0019e6d..48614edbcd 100644
--- a/panda/src/pgraphnodes/sequenceNode.cxx
+++ b/panda/src/pgraphnodes/sequenceNode.cxx
@@ -88,21 +88,10 @@ make_copy() const {
* should be culled.
*/
bool SequenceNode::
-cull_callback(CullTraverser *, CullTraverserData &) {
- select_child(get_frame());
- return true;
-}
-
-/**
- * Returns the index number of the first visible child of this node, or a
- * number >= get_num_children() if there are no visible children of this node.
- * This is called during the cull traversal, but only if
- * has_selective_visibility() has already returned true. See
- * has_selective_visibility().
- */
-int SequenceNode::
-get_first_visible_child() const {
- return get_frame();
+cull_callback(CullTraverser *trav, CullTraverserData &data) {
+ const PandaNode::DownConnection &child = data.node_reader()->get_child_connection(get_frame());
+ trav->traverse_down(data, child);
+ return false;
}
/**
diff --git a/panda/src/pgraphnodes/sequenceNode.h b/panda/src/pgraphnodes/sequenceNode.h
index 96b7698131..ba67bbf18c 100644
--- a/panda/src/pgraphnodes/sequenceNode.h
+++ b/panda/src/pgraphnodes/sequenceNode.h
@@ -43,7 +43,6 @@ public:
virtual bool safe_to_combine_children() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual int get_first_visible_child() const;
virtual bool has_single_child_visibility() const;
virtual int get_visible_child() const;
diff --git a/panda/src/pgraphnodes/switchNode.cxx b/panda/src/pgraphnodes/switchNode.cxx
index d16afc56c4..1e3430cb2e 100644
--- a/panda/src/pgraphnodes/switchNode.cxx
+++ b/panda/src/pgraphnodes/switchNode.cxx
@@ -103,21 +103,10 @@ make_copy() const {
* should be culled.
*/
bool SwitchNode::
-cull_callback(CullTraverser *, CullTraverserData &) {
- select_child(get_visible_child());
- return true;
-}
-
-/**
- * Returns the index number of the first visible child of this node, or a
- * number >= get_num_children() if there are no visible children of this node.
- * This is called during the cull traversal, but only if
- * has_selective_visibility() has already returned true. See
- * has_selective_visibility().
- */
-int SwitchNode::
-get_first_visible_child() const {
- return get_visible_child();
+cull_callback(CullTraverser *trav, CullTraverserData &data) {
+ const PandaNode::DownConnection &child = data.node_reader()->get_child_connection(get_visible_child());
+ trav->traverse_down(data, child);
+ return false;
}
/**
diff --git a/panda/src/pgraphnodes/switchNode.h b/panda/src/pgraphnodes/switchNode.h
index 2df989cbd7..1fe3353a68 100644
--- a/panda/src/pgraphnodes/switchNode.h
+++ b/panda/src/pgraphnodes/switchNode.h
@@ -34,7 +34,6 @@ public:
virtual bool safe_to_combine_children() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual int get_first_visible_child() const;
virtual bool has_single_child_visibility() const;
PUBLISHED:
diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx
index 2fc40d546a..5922ac7f50 100644
--- a/panda/src/pgui/pgEntry.cxx
+++ b/panda/src/pgui/pgEntry.cxx
@@ -184,8 +184,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
}
// Now render the text.
- CullTraverserData next_data(data, _text_render_root.node());
- trav->traverse(next_data);
+ trav->traverse_down(data, _text_render_root.node());
// Now continue to render everything else below this node.
return true;
diff --git a/panda/src/pgui/pgItem.cxx b/panda/src/pgui/pgItem.cxx
index 09a3fbcaac..8229f434dc 100644
--- a/panda/src/pgui/pgItem.cxx
+++ b/panda/src/pgui/pgItem.cxx
@@ -67,6 +67,7 @@ PGItem(const string &name) :
_flags(0)
{
set_cull_callback();
+ set_renderable();
}
/**
@@ -278,25 +279,13 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
if (state_def_root != nullptr) {
// This item has a current state definition that we should use to render
// the item.
- CullTraverserData next_data(data, state_def_root);
- trav->traverse(next_data);
+ trav->traverse_down(data, state_def_root);
}
// Now continue to render everything else below this node.
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool PGItem::
-is_renderable() const {
- return true;
-}
-
/**
* Called when needed to recompute the node's _internal_bound object. Nodes
* that contain anything of substance should redefine this to do the right
diff --git a/panda/src/pgui/pgItem.h b/panda/src/pgui/pgItem.h
index 5467029d52..a27c08d46c 100644
--- a/panda/src/pgui/pgItem.h
+++ b/panda/src/pgui/pgItem.h
@@ -65,7 +65,6 @@ protected:
virtual void draw_mask_changed();
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
int &internal_vertices,
diff --git a/panda/src/pgui/pgSliderBar.cxx b/panda/src/pgui/pgSliderBar.cxx
index e666d2a496..d423549ccb 100644
--- a/panda/src/pgui/pgSliderBar.cxx
+++ b/panda/src/pgui/pgSliderBar.cxx
@@ -605,8 +605,10 @@ item_press(PGItem *item, const MouseWatcherParameter ¶m) {
ClockObject::get_global_clock()->get_frame_time() + scroll_initial_delay;
} else if (item == _thumb_button) {
- _scroll_button_held = nullptr;
- begin_drag();
+ if (((PGButton *)item)->has_click_button(param.get_button())) {
+ _scroll_button_held = nullptr;
+ begin_drag();
+ }
}
}
diff --git a/panda/src/pgui/pgTop.cxx b/panda/src/pgui/pgTop.cxx
index 3c0948ebda..f4d9f31fda 100644
--- a/panda/src/pgui/pgTop.cxx
+++ b/panda/src/pgui/pgTop.cxx
@@ -29,6 +29,10 @@ PGTop(const std::string &name) :
{
set_cull_callback();
+ // We flag the PGTop as renderable, even though it technically doesn't have
+ // anything to render, but we do need the traverser to visit it every frame.
+ set_renderable();
+
_start_sort = 0;
// A PGTop node normally has an infinite bounding volume. Screw culling.
@@ -111,19 +115,6 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return false;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool PGTop::
-is_renderable() const {
- // We flag the PGTop as renderable, even though it technically doesn't have
- // anything to render, but we do need the traverser to visit it every frame.
- return true;
-}
-
/**
* Sets the MouseWatcher pointer that the PGTop object registers its PG items
* with. This must be set before the PG items are active.
diff --git a/panda/src/pgui/pgTop.h b/panda/src/pgui/pgTop.h
index 1d5e739c8e..f5e6bac554 100644
--- a/panda/src/pgui/pgTop.h
+++ b/panda/src/pgui/pgTop.h
@@ -46,7 +46,6 @@ protected:
public:
virtual PandaNode *make_copy() const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
PUBLISHED:
void set_mouse_watcher(MouseWatcher *watcher);
diff --git a/panda/src/pipeline/cycleDataLockedStageReader.I b/panda/src/pipeline/cycleDataLockedStageReader.I
index a471aac0b7..6cee154bb7 100644
--- a/panda/src/pipeline/cycleDataLockedStageReader.I
+++ b/panda/src/pipeline/cycleDataLockedStageReader.I
@@ -111,7 +111,9 @@ INLINE CycleDataLockedStageReader::
template
INLINE const CycleDataType *CycleDataLockedStageReader::
operator -> () const {
+#ifdef _DEBUG
nassertr(_pointer != nullptr, _cycler->cheat());
+#endif
return _pointer;
}
@@ -122,7 +124,9 @@ operator -> () const {
template
INLINE CycleDataLockedStageReader::
operator const CycleDataType * () const {
+#ifdef _DEBUG
nassertr(_pointer != nullptr, _cycler->cheat());
+#endif
return _pointer;
}
diff --git a/panda/src/pipeline/genericThread.I b/panda/src/pipeline/genericThread.I
index 11634fd226..a7b8fbb047 100644
--- a/panda/src/pipeline/genericThread.I
+++ b/panda/src/pipeline/genericThread.I
@@ -15,31 +15,14 @@
* Replaces the function that is called when the thread runs.
*/
INLINE void GenericThread::
-set_function(GenericThread::ThreadFunc *function) {
- _function = function;
+set_function(std::function function) {
+ _function = std::move(function);
}
/**
* Returns the function that is called when the thread runs.
*/
-INLINE GenericThread::ThreadFunc *GenericThread::
+INLINE const std::function &GenericThread::
get_function() const {
return _function;
}
-
-/**
- * Replaces the void pointer that is passed to the thread function. This is
- * any arbitrary pointer; the thread object does no processing on it.
- */
-INLINE void GenericThread::
-set_user_data(void *user_data) {
- _user_data = user_data;
-}
-
-/**
- * Returns the void pointer that is passed to the thread function.
- */
-INLINE void *GenericThread::
-get_user_data() const {
- return _user_data;
-}
diff --git a/panda/src/pipeline/genericThread.cxx b/panda/src/pipeline/genericThread.cxx
index 64eb0b4814..3d5248667f 100644
--- a/panda/src/pipeline/genericThread.cxx
+++ b/panda/src/pipeline/genericThread.cxx
@@ -14,6 +14,8 @@
#include "genericThread.h"
#include "pnotify.h"
+#ifndef CPPPARSER
+
TypeHandle GenericThread::_type_handle;
/**
@@ -23,8 +25,6 @@ GenericThread::
GenericThread(const std::string &name, const std::string &sync_name) :
Thread(name, sync_name)
{
- _function = nullptr;
- _user_data = nullptr;
}
/**
@@ -33,8 +33,17 @@ GenericThread(const std::string &name, const std::string &sync_name) :
GenericThread::
GenericThread(const std::string &name, const std::string &sync_name, GenericThread::ThreadFunc *function, void *user_data) :
Thread(name, sync_name),
- _function(function),
- _user_data(user_data)
+ _function(std::bind(function, user_data))
+{
+}
+
+/**
+ *
+ */
+GenericThread::
+GenericThread(const std::string &name, const std::string &sync_name, std::function function) :
+ Thread(name, sync_name),
+ _function(std::move(function))
{
}
@@ -43,6 +52,8 @@ GenericThread(const std::string &name, const std::string &sync_name, GenericThre
*/
void GenericThread::
thread_main() {
- nassertv(_function != nullptr);
- (*_function)(_user_data);
+ nassertv(_function);
+ _function();
}
+
+#endif
diff --git a/panda/src/pipeline/genericThread.h b/panda/src/pipeline/genericThread.h
index 6e6a6ade6a..b13185af1d 100644
--- a/panda/src/pipeline/genericThread.h
+++ b/panda/src/pipeline/genericThread.h
@@ -17,6 +17,9 @@
#include "pandabase.h"
#include "thread.h"
+#ifndef CPPPARSER
+#include
+
/**
* A generic thread type that allows calling a C-style thread function without
* having to subclass.
@@ -27,19 +30,16 @@ public:
GenericThread(const std::string &name, const std::string &sync_name);
GenericThread(const std::string &name, const std::string &sync_name, ThreadFunc *function, void *user_data);
+ GenericThread(const std::string &name, const std::string &sync_name, std::function function);
- INLINE void set_function(ThreadFunc *function);
- INLINE ThreadFunc *get_function() const;
-
- INLINE void set_user_data(void *user_data);
- INLINE void *get_user_data() const;
+ INLINE void set_function(std::function function);
+ INLINE const std::function &get_function() const;
protected:
virtual void thread_main();
private:
- ThreadFunc *_function;
- void *_user_data;
+ std::function _function;
public:
static TypeHandle get_class_type() {
@@ -61,4 +61,6 @@ private:
#include "genericThread.I"
-#endif
+#endif // CPPPARSER
+
+#endif // GENERICTHREAD_H
diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx
index be904d7972..0a8d666432 100644
--- a/panda/src/text/textNode.cxx
+++ b/panda/src/text/textNode.cxx
@@ -61,6 +61,7 @@ PStatCollector TextNode::_text_generate_pcollector("*:Generate Text");
TextNode::
TextNode(const string &name) : PandaNode(name) {
set_cull_callback();
+ set_renderable();
if (text_small_caps) {
TextProperties::set_small_caps(true);
@@ -487,25 +488,13 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
PT(PandaNode) internal_geom = do_get_internal_geom();
if (internal_geom != nullptr) {
// Render the text with this node.
- CullTraverserData next_data(data, internal_geom);
- trav->traverse(next_data);
+ trav->traverse_down(data, internal_geom);
}
// Now continue to render everything else below this node.
return true;
}
-/**
- * Returns true if there is some value to visiting this particular node during
- * the cull traversal for any camera, false otherwise. This will be used to
- * optimize the result of get_net_draw_show_mask(), so that any subtrees that
- * contain only nodes for which is_renderable() is false need not be visited.
- */
-bool TextNode::
-is_renderable() const {
- return true;
-}
-
/**
* Called when needed to recompute the node's _internal_bound object. Nodes
* that contain anything of substance should redefine this to do the right
diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h
index 3ace43895e..e40f1c9030 100644
--- a/panda/src/text/textNode.h
+++ b/panda/src/text/textNode.h
@@ -286,7 +286,6 @@ public:
Thread *current_thread) const;
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
- virtual bool is_renderable() const;
virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
int &internal_vertices,
diff --git a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
index d8e80db919..8442cae415 100644
--- a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
+++ b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
@@ -2387,10 +2387,7 @@ upload_texture(TinyTextureContext *gtc, bool force, bool uses_mipmaps) {
// meantime load a temporary simple image in its place.
async_reload_texture(gtc);
if (!tex->has_ram_image()) {
- if (gtc->was_simple_image_modified()) {
- return upload_simple_texture(gtc);
- }
- return true;
+ return upload_simple_texture(gtc);
}
}
}
@@ -2558,7 +2555,7 @@ upload_simple_texture(TinyTextureContext *gtc) {
ZTextureLevel *dest = &gltex->levels[0];
memcpy(dest->pixmap, image_ptr, image_size);
- gtc->mark_simple_loaded();
+ gtc->mark_loaded();
return true;
}
diff --git a/pandatool/src/assimp/pandaLogger.cxx b/pandatool/src/assimp/pandaLogger.cxx
index c9897533cf..ee0a41324e 100644
--- a/pandatool/src/assimp/pandaLogger.cxx
+++ b/pandatool/src/assimp/pandaLogger.cxx
@@ -40,6 +40,15 @@ void PandaLogger::OnDebug(const char *message) {
}
}
+/**
+ *
+ */
+void PandaLogger::OnVerboseDebug(const char *message) {
+ if (assimp_cat.is_spam()) {
+ assimp_cat.spam() << message << "\n";
+ }
+}
+
/**
*
*/
diff --git a/pandatool/src/assimp/pandaLogger.h b/pandatool/src/assimp/pandaLogger.h
index dbd2165ce6..2fdbd44609 100644
--- a/pandatool/src/assimp/pandaLogger.h
+++ b/pandatool/src/assimp/pandaLogger.h
@@ -30,11 +30,17 @@ protected:
INLINE bool attachStream(Assimp::LogStream*, unsigned int) {
return false;
};
+ INLINE bool detachStream(Assimp::LogStream*, unsigned int) {
+ return false;
+ };
+
+ // Kept for compatibility with Assimp 4.x
INLINE bool detatchStream(Assimp::LogStream*, unsigned int) {
return false;
};
void OnDebug(const char *message);
+ void OnVerboseDebug(const char *message);
void OnError(const char *message);
void OnInfo(const char *message);
void OnWarn(const char *message);
diff --git a/pandatool/src/egg-qtess/isoPlacer.cxx b/pandatool/src/egg-qtess/isoPlacer.cxx
index fda96904e3..f37588f463 100644
--- a/pandatool/src/egg-qtess/isoPlacer.cxx
+++ b/pandatool/src/egg-qtess/isoPlacer.cxx
@@ -109,11 +109,13 @@ place(int count, pvector &iso_points) {
int i;
// Count up the average curvature.
+ /*
double avg_curve = 0.0;
for (i = 0; i < _maxi; i++) {
avg_curve += _cscore[i];
}
avg_curve /= (double)_maxi;
+ */
// Find all the local maxima in the curvature table. These are bend points.
typedef pvector BendPoints;
diff --git a/samples/networking/01-simple-connection/client.py b/samples/networking/01-simple-connection/client.py
new file mode 100644
index 0000000000..2f5d1cef33
--- /dev/null
+++ b/samples/networking/01-simple-connection/client.py
@@ -0,0 +1,123 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+ return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+ parent=base.a2dTopLeft, align=TextNode.ALeft,
+ pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+ return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+ parent=base.a2dBottomRight, align=TextNode.ARight,
+ fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst2 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+ title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc']
+
+ # a distributed object of our game.
+ self.distributedObject = None
+ self.aiDGameObect = None
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ # here we could create a reconnect task to try and connect again.
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Mark interest for zone 1, 2 and 3. There won't be anything in them,
+ # it's just to display how it works for this small example.
+ self.setInterestZones([1, 2, 3])
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ print("Client Ready")
+ base.messenger.send("client-ready")
+
+# Start the client
+GameClientRepository()
+
+base.run()
diff --git a/samples/networking/01-simple-connection/server.py b/samples/networking/01-simple-connection/server.py
new file mode 100644
index 0000000000..51926a0feb
--- /dev/null
+++ b/samples/networking/01-simple-connection/server.py
@@ -0,0 +1,31 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+
+base.run()
diff --git a/samples/networking/02-time-synced-connection/AIRepository.py b/samples/networking/02-time-synced-connection/AIRepository.py
new file mode 100644
index 0000000000..0b462f0557
--- /dev/null
+++ b/samples/networking/02-time-synced-connection/AIRepository.py
@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+ def __init__(self):
+ """ The AI Repository usually lives on a server and is responsible for
+ server side logic that will handle game objects """
+
+ # List of all dc files that are of interest to this AI Repository
+ dcFileNames = ['../direct.dc']
+
+ # Initialize the repository. We pass it the dc files and as this is an
+ # AI repository the dcSuffix AI. This will make sure any later calls to
+ # createDistributedObject will use the correct version.
+ # The connectMethod
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ dcSuffix = 'AI',
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # doesn't use http you should change it accordingly. Make sure to pass
+ # the connectMethod to the ClientRepository.__init__ call too.
+ # Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def connectFailure(self, statusCode, statusString):
+ """ something went wrong """
+ print("Couldn't connect. Make sure to run server.py first!")
+ raise(StandardError, statusString)
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+ # The Client Repository will throw this event as soon as it has a doID
+ # range and would be able to create distributed objects
+ self.accept('createReady', self.gotCreateReady)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ exit()
+
+ def gotCreateReady(self):
+ """ Now we're ready to go! """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore('createReady')
+
+ # Create a Distributed Object by name. This will look up the object in
+ # the dc files passed to the repository earlier
+ self.timeManager = self.createDistributedObject(
+ className = 'TimeManagerAI', # The Name of the Class we want to initialize
+ zoneId = 1) # The Zone this Object will live in
+
+ print("AI Repository Ready")
+
+ def deallocateChannel(self, doID):
+ """ This method will be called whenever a client disconnects from the
+ server. The given doID is the ID of the client who left us. """
+ print("Client left us: ", doID)
diff --git a/samples/networking/02-time-synced-connection/client.py b/samples/networking/02-time-synced-connection/client.py
new file mode 100644
index 0000000000..23ed1b3a0b
--- /dev/null
+++ b/samples/networking/02-time-synced-connection/client.py
@@ -0,0 +1,136 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+ return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+ parent=base.a2dTopLeft, align=TextNode.ALeft,
+ pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+ return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+ parent=base.a2dBottomRight, align=TextNode.ARight,
+ fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+ title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc']
+
+ # a distributed object of our game.
+ self.distributedObject = None
+ self.aiDGameObect = None
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ # we could create a reconnect task to try and connect again.
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ """ Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ print("Client Ready")
+ base.messenger.send("client-ready")
+
+# Start the client
+client = GameClientRepository()
+
+base.run()
diff --git a/samples/networking/02-time-synced-connection/server.py b/samples/networking/02-time-synced-connection/server.py
new file mode 100644
index 0000000000..6f587de4cf
--- /dev/null
+++ b/samples/networking/02-time-synced-connection/server.py
@@ -0,0 +1,34 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+AIRepository()
+
+base.run()
diff --git a/samples/networking/03-distributed-node/AIDGameObject.py b/samples/networking/03-distributed-node/AIDGameObject.py
new file mode 100644
index 0000000000..9c4aab012b
--- /dev/null
+++ b/samples/networking/03-distributed-node/AIDGameObject.py
@@ -0,0 +1,29 @@
+from direct.distributed.DistributedObject import DistributedObject
+
+class AIDGameObject(DistributedObject):
+ """ This class is a DirectObject which will be created and managed by the
+ AI Repository. """
+
+ def __init__(self, cr):
+ DistributedObject.__init__(self, cr)
+
+ def announceGenerate(self):
+ """ The AI has created this object, so we send it's distributed object ID
+ over to the client. That way the client can actually grab the object
+ and use it to communicate with the AI. Alternatively store it in the
+ Client Repository in self.cr """
+ base.messenger.send(self.cr.uniqueName('AIDGameObjectGenerated'), [self.doId])
+ # call the base class method
+ DistributedObject.announceGenerate(self)
+
+ def d_requestDataFromAI(self):
+ """ Request some data from the AI and passing it some data from us. """
+ data = ("Some Data", 1, -1.25)
+ print("Sending game data:", data)
+ self.sendUpdate('messageRoundtripToAI', [data])
+
+ def messageRoundtripToClient(self, data):
+ """ Here we expect the answer from the AI from a previous
+ messageRoundtripToAI call """
+ print("Got Data:", data)
+ print("Roundtrip message complete")
diff --git a/samples/networking/03-distributed-node/AIDGameObjectAI.py b/samples/networking/03-distributed-node/AIDGameObjectAI.py
new file mode 100644
index 0000000000..4fda4364fb
--- /dev/null
+++ b/samples/networking/03-distributed-node/AIDGameObjectAI.py
@@ -0,0 +1,25 @@
+from direct.distributed.DistributedObjectAI import DistributedObjectAI
+
+class AIDGameObjectAI(DistributedObjectAI):
+ def __init__(self, aiRepository):
+ DistributedObjectAI.__init__(self, aiRepository)
+
+ def messageRoundtripToAI(self, data):
+ """ The client sent us some data to process. So work with it and send
+ changed data back to the requesting client """
+ requesterId = self.air.getAvatarIdFromSender()
+ print("Got client data:", data, "from client with ID", requesterId)
+
+ # do something with the data
+ aiChangedData = (
+ data[0] + " from the AI",
+ data[1] + 1,
+ data[2])
+
+ print("Sending modified game data back:", aiChangedData)
+ self.d_messageRoundtripToClient(aiChangedData, requesterId)
+
+ def d_messageRoundtripToClient(self, data, requesterId):
+ """ Send the given data to the requesting client """
+ print("Send message to back to:", requesterId)
+ self.sendUpdateToAvatarId(requesterId, 'messageRoundtripToClient', [data])
diff --git a/samples/networking/03-distributed-node/AIRepository.py b/samples/networking/03-distributed-node/AIRepository.py
new file mode 100644
index 0000000000..a51dca16f9
--- /dev/null
+++ b/samples/networking/03-distributed-node/AIRepository.py
@@ -0,0 +1,86 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from AIDGameObjectAI import AIDGameObjectAI
+
+class AIRepository(ClientRepository):
+ def __init__(self):
+ """ The AI Repository usually lives on a server and is responsible for
+ server side logic that will handle game objects """
+
+ # List of all dc files that are of interest to this AI Repository
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # Initialize the repository. We pass it the dc files and as this is an
+ # AI repository the dcSuffix AI. This will make sure any later calls to
+ # createDistributedObject will use the correct version.
+ # The connectMethod
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ dcSuffix = 'AI',
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # doesn't use http you should change it accordingly. Make sure to pass
+ # the connectMethod to the ClientRepository.__init__ call too.
+ # Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def connectFailure(self, statusCode, statusString):
+ """ something went wrong """
+ print("Couldn't connect. Make sure to run server.py first!")
+ raise(StandardError, statusString)
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+ # The Client Repository will throw this event as soon as it has a doID
+ # range and would be able to create distributed objects
+ self.accept('createReady', self.gotCreateReady)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ exit()
+
+ def gotCreateReady(self):
+ """ Now we're ready to go! """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore('createReady')
+
+ # Create a Distributed Object by name. This will look up the object in
+ # the dc files passed to the repository earlier
+ self.timeManager = self.createDistributedObject(
+ className = 'TimeManagerAI', # The Name of the Class we want to initialize
+ zoneId = 1) # The Zone this Object will live in
+
+ self.gameDistObject = self.createDistributedObject(
+ className = 'AIDGameObjectAI',
+ zoneId = 2)
+
+ print("AI Repository Ready")
+
+ def deallocateChannel(self, doID):
+ """ This method will be called whenever a client disconnects from the
+ server. The given doID is the ID of the client who left us. """
+ print("Client left us: ", doID)
diff --git a/samples/networking/03-distributed-node/ClientRepository.py b/samples/networking/03-distributed-node/ClientRepository.py
new file mode 100644
index 0000000000..f413cf6ca8
--- /dev/null
+++ b/samples/networking/03-distributed-node/ClientRepository.py
@@ -0,0 +1,130 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from DGameObject import DGameObject
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # a distributed object of our game.
+ self.distributedObject = None
+ self.aiDGameObect = None
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ """ Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ self.join()
+
+ print("Client Ready")
+
+ def join(self):
+ """ Join a game/room/whatever """
+ self.accept(self.uniqueName('AIDGameObjectGenerated'), self.aiDGameObectGenerated)
+
+ # set our intersted zones to let the client see all distributed obects
+ # in those zones
+ self.setInterestZones([1, 2])
+
+ # Manifest a object on the server. The object will have our "base" doId.
+ self.distributedObject = DGameObject(self)
+ self.createDistributedObject(
+ distObj = self.distributedObject,
+ zoneId = 2)
+
+ base.messenger.send("client-joined")
+ print("Joined")
+
+ def aiDGameObectGenerated(self, doId):
+ print("AIDGameObect was generated")
+ self.aiDGameObect = self.doId2do[doId]
+
+ def sendGameData(self):
+ if not self.distributedObject: return
+
+ print("send game data")
+
+ # send a message to the server
+ self.distributedObject.d_sendGameData()
+
+ def sendRoundtripToAI(self):
+ if not self.aiDGameObect: return
+
+ print("Initiate roundtrip message to AI Server")
+
+ self.aiDGameObect.d_requestDataFromAI()
diff --git a/samples/networking/03-distributed-node/DGameObject.py b/samples/networking/03-distributed-node/DGameObject.py
new file mode 100644
index 0000000000..c3eb37a320
--- /dev/null
+++ b/samples/networking/03-distributed-node/DGameObject.py
@@ -0,0 +1,15 @@
+from direct.distributed.DistributedObject import DistributedObject
+
+class DGameObject(DistributedObject):
+ def __init__(self, cr):
+ DistributedObject.__init__(self, cr)
+
+ def sendGameData(self, data):
+ """ Method that can be called from the clients with an sendUpdate call """
+ print(data)
+
+ def d_sendGameData(self):
+ """ A method to send an update message to the server. The d_ stands
+ for distributed """
+ # send the message to the server
+ self.sendUpdate('sendGameData', [('ValueA', 123, 1.25)])
diff --git a/samples/networking/03-distributed-node/ServerRepository.py b/samples/networking/03-distributed-node/ServerRepository.py
new file mode 100644
index 0000000000..59da3a6aa4
--- /dev/null
+++ b/samples/networking/03-distributed-node/ServerRepository.py
@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
diff --git a/samples/networking/03-distributed-node/client.py b/samples/networking/03-distributed-node/client.py
new file mode 100644
index 0000000000..23ed1b3a0b
--- /dev/null
+++ b/samples/networking/03-distributed-node/client.py
@@ -0,0 +1,136 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+ return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+ parent=base.a2dTopLeft, align=TextNode.ALeft,
+ pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+ return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+ parent=base.a2dBottomRight, align=TextNode.ARight,
+ fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+ title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc']
+
+ # a distributed object of our game.
+ self.distributedObject = None
+ self.aiDGameObect = None
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ # we could create a reconnect task to try and connect again.
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ """ Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ print("Client Ready")
+ base.messenger.send("client-ready")
+
+# Start the client
+client = GameClientRepository()
+
+base.run()
diff --git a/samples/networking/03-distributed-node/sample.dc b/samples/networking/03-distributed-node/sample.dc
new file mode 100755
index 0000000000..651d79b35c
--- /dev/null
+++ b/samples/networking/03-distributed-node/sample.dc
@@ -0,0 +1,17 @@
+import DGameObject
+import AIDGameObject/AI
+
+struct gameDataModel {
+ string value_a;
+ uint8 value_b;
+ int8 value_c/100;
+}
+
+dclass DGameObject: DistributedObject {
+ sendGameData(gameDataModel data) broadcast;
+};
+
+dclass AIDGameObject: DistributedObject {
+ messageRoundtripToAI(gameDataModel data) p2p;
+ messageRoundtripToClient(gameDataModel data) p2p;
+}
diff --git a/samples/networking/03-distributed-node/server.py b/samples/networking/03-distributed-node/server.py
new file mode 100644
index 0000000000..0aab404a46
--- /dev/null
+++ b/samples/networking/03-distributed-node/server.py
@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()
diff --git a/samples/networking/04-distributed-model/AIRepository.py b/samples/networking/04-distributed-model/AIRepository.py
new file mode 100644
index 0000000000..9e46f024d3
--- /dev/null
+++ b/samples/networking/04-distributed-model/AIRepository.py
@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+ def __init__(self):
+ """ The AI Repository usually lives on a server and is responsible for
+ server side logic that will handle game objects """
+
+ # List of all dc files that are of interest to this AI Repository
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # Initialize the repository. We pass it the dc files and as this is an
+ # AI repository the dcSuffix AI. This will make sure any later calls to
+ # createDistributedObject will use the correct version.
+ # The connectMethod
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ dcSuffix = 'AI',
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # doesn't use http you should change it accordingly. Make sure to pass
+ # the connectMethod to the ClientRepository.__init__ call too.
+ # Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def connectFailure(self, statusCode, statusString):
+ """ something went wrong """
+ print("Couldn't connect. Make sure to run server.py first!")
+ raise(StandardError, statusString)
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+ # The Client Repository will throw this event as soon as it has a doID
+ # range and would be able to create distributed objects
+ self.accept('createReady', self.gotCreateReady)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ exit()
+
+ def gotCreateReady(self):
+ """ Now we're ready to go! """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore('createReady')
+
+ # Create a Distributed Object by name. This will look up the object in
+ # the dc files passed to the repository earlier
+ self.timeManager = self.createDistributedObject(
+ className = 'TimeManagerAI', # The Name of the Class we want to initialize
+ zoneId = 1) # The Zone this Object will live in
+
+ print("AI Repository Ready")
+
+ def deallocateChannel(self, doID):
+ """ This method will be called whenever a client disconnects from the
+ server. The given doID is the ID of the client who left us. """
+ print("Client left us: ", doID)
diff --git a/samples/networking/04-distributed-model/ClientRepository.py b/samples/networking/04-distributed-model/ClientRepository.py
new file mode 100644
index 0000000000..f2b5ec7c82
--- /dev/null
+++ b/samples/networking/04-distributed-model/ClientRepository.py
@@ -0,0 +1,116 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from random import random
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ """ Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ self.join()
+
+ print("Client Ready")
+
+ def join(self):
+ """ Join a game/room/whatever """
+ # set our intersted zones to let the client see all distributed obects
+ # in those zones
+ self.setInterestZones([1, 2])
+
+ # Manifest a object on the server. The object will have our "base" doId.
+ self.myDistributedModel = self.createDistributedObject(
+ className = "DModel",
+ zoneId = 2)
+
+ x = random()
+ z = random()
+ # set position for this local client
+ self.myDistributedModel.setPos(x, 10, z)
+ # make sure already connected clients will get notified of the
+ # position by calling the distributred (d_*) version of the method
+ self.myDistributedModel.d_setPos(x, 10, z)
+
+ base.messenger.send("client-joined")
+ print("Joined")
+
+ def modelReady(self, doId):
+ print("AIDGameObect was generated")
+ self.aiDGameObect = self.doId2do[doId]
diff --git a/samples/networking/04-distributed-model/DModel.py b/samples/networking/04-distributed-model/DModel.py
new file mode 100644
index 0000000000..047e95ab85
--- /dev/null
+++ b/samples/networking/04-distributed-model/DModel.py
@@ -0,0 +1,61 @@
+from direct.distributed.DistributedNode import DistributedNode
+
+class DModel(DistributedNode):
+ def __init__(self, cr):
+ DistributedNode.__init__(self, cr)
+
+ # Load up the visible representation of this avatar.
+ self.model = loader.loadModel('smiley.egg')
+ self.model.reparentTo(self)
+
+ def announceGenerate(self):
+ """ This method is called after generate(), after all of the
+ required fields have been filled in. At the time of this call,
+ the distributed object is ready for use. """
+
+ DistributedNode.announceGenerate(self)
+
+ # Now that the object has been fully manifested, we can parent
+ # it into the scene.
+ self.reparentTo(render)
+
+ def disable(self):
+ """ This method is called when the object is removed from the
+ scene, for instance because it left the zone. It is balanced
+ against generate(): for each generate(), there will be a
+ corresponding disable(). Everything that was done in
+ generate() or announceGenerate() should be undone in disable().
+
+ After a disable(), the object might be cached in memory in case
+ it will eventually reappear. The DistributedObject should be
+ prepared to receive another generate() for an object that has
+ already received disable().
+
+ Note that the above is only strictly true for *cacheable*
+ objects. Most objects are, by default, non-cacheable; you
+ have to call obj.setCacheable(True) (usually in the
+ constructor) to make it cacheable. Until you do this, your
+ non-cacheable object will always receive a delete() whenever
+ it receives a disable(), and it will never be stored in a
+ cache.
+ """
+
+ # Take it out of the scene graph.
+ self.detachNode()
+
+ DistributedNode.disable(self)
+
+ def delete(self):
+ """ This method is called after disable() when the object is to
+ be completely removed, for instance because the other user
+ logged off. We will not expect to see this object again; it
+ will not be cached. This is stronger than disable(), and the
+ object may remove any structures it needs to in order to allow
+ it to be completely deleted from memory. This balances against
+ __init__(): every DistributedObject that is created will
+ eventually get delete() called for it exactly once. """
+
+ # Clean out self.model, so we don't have a circular reference.
+ self.model = None
+
+ DistributedNode.delete(self)
diff --git a/samples/networking/04-distributed-model/ServerRepository.py b/samples/networking/04-distributed-model/ServerRepository.py
new file mode 100644
index 0000000000..59da3a6aa4
--- /dev/null
+++ b/samples/networking/04-distributed-model/ServerRepository.py
@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
diff --git a/samples/networking/04-distributed-model/client.py b/samples/networking/04-distributed-model/client.py
new file mode 100644
index 0000000000..0a8373b469
--- /dev/null
+++ b/samples/networking/04-distributed-model/client.py
@@ -0,0 +1,43 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ClientRepository import GameClientRepository
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+client = GameClientRepository()
+
+base.accept("escape", exit)
+
+
+
+
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+ return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+ parent=base.a2dTopLeft, align=TextNode.ALeft,
+ pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+ return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+ parent=base.a2dBottomRight, align=TextNode.ARight,
+ fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+ title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-joined", setConnectedMessage)
+
+# start the client
+base.run()
diff --git a/samples/networking/04-distributed-model/sample.dc b/samples/networking/04-distributed-model/sample.dc
new file mode 100755
index 0000000000..2a0e86b154
--- /dev/null
+++ b/samples/networking/04-distributed-model/sample.dc
@@ -0,0 +1,3 @@
+import DModel
+
+dclass DModel: DistributedNode { }
diff --git a/samples/networking/04-distributed-model/server.py b/samples/networking/04-distributed-model/server.py
new file mode 100644
index 0000000000..0aab404a46
--- /dev/null
+++ b/samples/networking/04-distributed-model/server.py
@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()
diff --git a/samples/networking/05-small-chat/AIRepository.py b/samples/networking/05-small-chat/AIRepository.py
new file mode 100644
index 0000000000..9e46f024d3
--- /dev/null
+++ b/samples/networking/05-small-chat/AIRepository.py
@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+ def __init__(self):
+ """ The AI Repository usually lives on a server and is responsible for
+ server side logic that will handle game objects """
+
+ # List of all dc files that are of interest to this AI Repository
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # Initialize the repository. We pass it the dc files and as this is an
+ # AI repository the dcSuffix AI. This will make sure any later calls to
+ # createDistributedObject will use the correct version.
+ # The connectMethod
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ dcSuffix = 'AI',
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # doesn't use http you should change it accordingly. Make sure to pass
+ # the connectMethod to the ClientRepository.__init__ call too.
+ # Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def connectFailure(self, statusCode, statusString):
+ """ something went wrong """
+ print("Couldn't connect. Make sure to run server.py first!")
+ raise(StandardError, statusString)
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+ # The Client Repository will throw this event as soon as it has a doID
+ # range and would be able to create distributed objects
+ self.accept('createReady', self.gotCreateReady)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ exit()
+
+ def gotCreateReady(self):
+ """ Now we're ready to go! """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore('createReady')
+
+ # Create a Distributed Object by name. This will look up the object in
+ # the dc files passed to the repository earlier
+ self.timeManager = self.createDistributedObject(
+ className = 'TimeManagerAI', # The Name of the Class we want to initialize
+ zoneId = 1) # The Zone this Object will live in
+
+ print("AI Repository Ready")
+
+ def deallocateChannel(self, doID):
+ """ This method will be called whenever a client disconnects from the
+ server. The given doID is the ID of the client who left us. """
+ print("Client left us: ", doID)
diff --git a/samples/networking/05-small-chat/ClientRepository.py b/samples/networking/05-small-chat/ClientRepository.py
new file mode 100644
index 0000000000..3dd82bd88a
--- /dev/null
+++ b/samples/networking/05-small-chat/ClientRepository.py
@@ -0,0 +1,104 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from message import Message
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ ''' This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. '''
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ ''' Something went wrong '''
+ # we could create a reconnect task to try and connect again.
+ exit()
+
+ def connectSuccess(self):
+ ''' Successfully connected. But we still can't really do
+ anything until we've got the doID range. '''
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ ''' Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. '''
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ ''' Ready to enter the world. Expand our interest to include
+ any other zones '''
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ # create a instance of the message class
+ msg = Message(self)
+ # and create the distributed Object with it
+ self.createDistributedObject(
+ distObj = msg, zoneId = 1)
+ # save the created Distributed Object
+ # in self.msg for later usage
+ self.msg = msg
+
+ def sendMessage(self, msgText):
+ '''Function to call the send function of the message class,
+ which sends the Message over the Network to the other users'''
+
+ sentText = "{}: {}".format(self.doIdBase, msgText)
+
+ self.msg.b_sendText(sentText)
diff --git a/samples/networking/05-small-chat/client.py b/samples/networking/05-small-chat/client.py
new file mode 100755
index 0000000000..4a76cb26ed
--- /dev/null
+++ b/samples/networking/05-small-chat/client.py
@@ -0,0 +1,85 @@
+from direct.showbase.ShowBase import ShowBase
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from message import Message
+from ClientRepository import GameClientRepository
+from direct.gui.DirectGui import DirectButton, DirectEntry, DirectFrame
+from direct.gui import DirectGuiGlobals as DGG
+
+# initialize the engine
+base = ShowBase()
+
+client = GameClientRepository()
+
+# Setup the GUI
+
+# this frame will contain all our GUI elements
+frameMain = DirectFrame(frameColor = (0, 0, 0, 1))
+
+def clearText():
+ ''' Write an empty string in the textbox '''
+ txt_msg.enterText('')
+
+def setDefaultText():
+ ''' Write the default message in the textbox '''
+ txt_msg.enterText('Your Message')
+
+def send(args=None):
+ ''' Send the text written in the message textbox to the clients '''
+ client.sendMessage(txt_msg.get())
+ txt_msg.enterText('')
+
+# the Textbox where we write our Messages, which we
+# want to send over to the other users
+txt_msg = DirectEntry(
+ initialText = 'Your Message',
+ cursorKeys = True,
+ pos = (-0.9,0,-0.9),
+ scale = 0.1,
+ width = 14,
+ focusInCommand = clearText,
+ focusOutCommand = setDefaultText,
+ command = send,
+ parent = frameMain)
+
+# a button to initiate the sending of the message
+btn_send = DirectButton(
+ text = 'Send',
+ pos = (0.75,0,-0.9),
+ scale = 0.15,
+ command = send,
+ parent = frameMain)
+
+# This will show all sent messages
+txt_messages = DirectEntry(
+ cursorKeys = False,
+ pos = (-0.8,0,0.5),
+ scale = 0.1,
+ width = 14,
+ numLines = 10,
+ state = DGG.DISABLED,
+ parent = frameMain)
+
+# Function which will write the given text in the
+# textbox, where we store all send and recieved messages.
+# This Function will only write the last 10 messages and
+# cut of the erlier messages
+def setText(messageText):
+ # get all messages from the textbox
+ parts = txt_messages.get().split('\n')
+ if len(parts) >= 10:
+ cutParts = ''
+ # as the textbox can only hold 10 lines cut out the first entry
+ for i in range(1,len(parts)):
+ cutParts += parts[i] + '\n'
+ txt_messages.enterText(cutParts + messageText)
+ else:
+ txt_messages.enterText(txt_messages.get() + '\n' + messageText)
+
+# create a DirectObject instance, which will then catch the events and
+# handle them with the given functions
+base.accept('setText', setText)
+base.accept('escape', exit)
+
+# start the application
+base.run()
diff --git a/samples/networking/05-small-chat/message.py b/samples/networking/05-small-chat/message.py
new file mode 100755
index 0000000000..879935e37e
--- /dev/null
+++ b/samples/networking/05-small-chat/message.py
@@ -0,0 +1,25 @@
+from direct.distributed.DistributedObject import DistributedObject
+from direct.showbase.MessengerGlobal import messenger
+
+class Message(DistributedObject):
+ def __init__(self, clientRepo):
+ DistributedObject.__init__(self, clientRepo)
+
+ def sendText(self, messageText):
+ """Function which is caled for local changes only"""
+ # send an event, which will set the text on the
+ #print "got a message"
+ messenger.send("setText", [messageText])
+
+ def d_sendText(self, messageText):
+ """Function which is caled to send the message over the network
+ therfore the d_ suffix stands for distributed"""
+ #print "send message %s" % messageText
+ self.sendUpdate("sendText", [messageText])
+
+ def b_sendText(self, messageText):
+ """Function which combines the local and distributed functionality,
+ so the sendText and d_sendText functions are called.
+ The b_ suffix stands for both"""
+ self.sendText(messageText)
+ self.d_sendText(messageText)
diff --git a/samples/networking/05-small-chat/sample.dc b/samples/networking/05-small-chat/sample.dc
new file mode 100755
index 0000000000..08c6f4505f
--- /dev/null
+++ b/samples/networking/05-small-chat/sample.dc
@@ -0,0 +1,5 @@
+from message import Message
+
+dclass Message: DistributedObject {
+ sendText(string messageText) broadcast;
+}
diff --git a/samples/networking/05-small-chat/server.py b/samples/networking/05-small-chat/server.py
new file mode 100644
index 0000000000..874e41ec79
--- /dev/null
+++ b/samples/networking/05-small-chat/server.py
@@ -0,0 +1,34 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+AIRepository()
+
+base.run()
diff --git a/samples/networking/06-simple-avatar/AIRepository.py b/samples/networking/06-simple-avatar/AIRepository.py
new file mode 100644
index 0000000000..9e46f024d3
--- /dev/null
+++ b/samples/networking/06-simple-avatar/AIRepository.py
@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+ def __init__(self):
+ """ The AI Repository usually lives on a server and is responsible for
+ server side logic that will handle game objects """
+
+ # List of all dc files that are of interest to this AI Repository
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # Initialize the repository. We pass it the dc files and as this is an
+ # AI repository the dcSuffix AI. This will make sure any later calls to
+ # createDistributedObject will use the correct version.
+ # The connectMethod
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ dcSuffix = 'AI',
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # doesn't use http you should change it accordingly. Make sure to pass
+ # the connectMethod to the ClientRepository.__init__ call too.
+ # Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def connectFailure(self, statusCode, statusString):
+ """ something went wrong """
+ print("Couldn't connect. Make sure to run server.py first!")
+ raise(StandardError, statusString)
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+ # The Client Repository will throw this event as soon as it has a doID
+ # range and would be able to create distributed objects
+ self.accept('createReady', self.gotCreateReady)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ exit()
+
+ def gotCreateReady(self):
+ """ Now we're ready to go! """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore('createReady')
+
+ # Create a Distributed Object by name. This will look up the object in
+ # the dc files passed to the repository earlier
+ self.timeManager = self.createDistributedObject(
+ className = 'TimeManagerAI', # The Name of the Class we want to initialize
+ zoneId = 1) # The Zone this Object will live in
+
+ print("AI Repository Ready")
+
+ def deallocateChannel(self, doID):
+ """ This method will be called whenever a client disconnects from the
+ server. The given doID is the ID of the client who left us. """
+ print("Client left us: ", doID)
diff --git a/samples/networking/06-simple-avatar/ClientRepository.py b/samples/networking/06-simple-avatar/ClientRepository.py
new file mode 100644
index 0000000000..145242260b
--- /dev/null
+++ b/samples/networking/06-simple-avatar/ClientRepository.py
@@ -0,0 +1,103 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from DistributedSmoothActor import DistributedSmoothActor
+
+class GameClientRepository(ClientRepository):
+
+ def __init__(self):
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # a distributed object of our game.
+ self.distributedObject = None
+ self.aiDGameObect = None
+
+ ClientRepository.__init__(
+ self,
+ dcFileNames = dcFileNames,
+ threadedNet = True)
+
+ # Set the same port as configured on the server to be able to connect
+ # to it
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # Set the IP or hostname of the server we want to connect to
+ hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+ # Build the URL from the server hostname and port. If your server
+ # uses another protocol then http you should change it accordingly.
+ # Make sure to pass the connectMethod to the ClientRepository.__init__
+ # call too. Available connection methods are:
+ # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+ self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+ # Attempt a connection to the server
+ self.connect([self.url],
+ successCallback = self.connectSuccess,
+ failureCallback = self.connectFailure)
+
+ def lostConnection(self):
+ """ This should be overridden by a derived class to handle an
+ unexpectedly lost connection to the gameserver. """
+ # Handle the disconnection from the server. This can be a reconnect,
+ # simply exiting the application or anything else.
+ exit()
+
+ def connectFailure(self, statusCode, statusString):
+ """ Something went wrong """
+ exit()
+
+ def connectSuccess(self):
+ """ Successfully connected. But we still can't really do
+ anything until we've got the doID range. """
+
+ # Make sure we have interest in the by the AIRepository defined
+ # TimeManager zone, so we always see it even if we switch to
+ # another zone.
+ self.setInterestZones([1])
+
+ # We must wait for the TimeManager to be fully created and
+ # synced before we can enter another zone and wait for the
+ # game object. The uniqueName is important that we get the
+ # correct, our sync message from the TimeManager and not
+ # accidentaly a message from another client
+ self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+ def syncReady(self):
+ """ Now we've got the TimeManager manifested, and we're in
+ sync with the server time. Now we can enter the world. Check
+ to see if we've received our doIdBase yet. """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if self.haveCreateAuthority():
+ # we already have one
+ self.gotCreateReady()
+ else:
+ # Not yet, keep waiting a bit longer.
+ self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+ def gotCreateReady(self):
+ """ Ready to enter the world. Expand our interest to include
+ any other zones """
+
+ # This method checks whether we actually have a valid doID range
+ # to create distributed objects yet
+ if not self.haveCreateAuthority():
+ # Not ready yet.
+ return
+
+ # we are ready now, so ignore further createReady events
+ self.ignore(self.uniqueName('createReady'))
+
+ self.join()
+
+ print("Client Ready")
+
+ def join(self):
+ """ Join a game/room/whatever """
+ # set our intersted zones to let the client see all distributed obects
+ # in those zones
+ self.setInterestZones([1, 2])
+
+ base.messenger.send('client-joined')
+ print("Joined")
diff --git a/samples/networking/06-simple-avatar/DistributedSmoothActor.py b/samples/networking/06-simple-avatar/DistributedSmoothActor.py
new file mode 100644
index 0000000000..516f43c9f7
--- /dev/null
+++ b/samples/networking/06-simple-avatar/DistributedSmoothActor.py
@@ -0,0 +1,50 @@
+from direct.distributed.DistributedSmoothNode import DistributedSmoothNode
+from panda3d.core import NodePath
+
+from direct.actor.Actor import Actor
+
+class DistributedSmoothActor(DistributedSmoothNode, Actor):
+ def __init__(self, cr):
+ Actor.__init__(self, "models/ralph",
+ {"run": "models/ralph-run",
+ "walk": "models/ralph-walk"})
+ DistributedSmoothNode.__init__(self, cr)
+ self.setCacheable(1)
+ self.setScale(.2)
+
+ def generate(self):
+ DistributedSmoothNode.generate(self)
+ self.activateSmoothing(True, False)
+ self.startSmooth()
+
+ def announceGenerate(self):
+ DistributedSmoothNode.announceGenerate(self)
+ self.reparentTo(render)
+
+ def disable(self):
+ # remove all anims, on all parts and all lods
+ self.stopSmooth()
+ if (not self.isEmpty()):
+ Actor.unloadAnims(self, None, None, None)
+ DistributedSmoothNode.disable(self)
+
+ def delete(self):
+ try:
+ self.DistributedActor_deleted
+ except:
+ self.DistributedActor_deleted = 1
+ DistributedSmoothNode.delete(self)
+ Actor.delete(self)
+
+ def start(self):
+ # Let the DistributedSmoothNode take care of broadcasting the
+ # position updates several times a second.
+ self.startPosHprBroadcast()
+
+ def loop(self, animName):
+ self.sendUpdate("loop", [animName])
+ return Actor.loop(self, animName)
+
+ def pose(self, animName, frame):
+ self.sendUpdate("pose", [animName, frame])
+ return Actor.pose(self, animName, frame)
diff --git a/samples/networking/06-simple-avatar/ServerRepository.py b/samples/networking/06-simple-avatar/ServerRepository.py
new file mode 100644
index 0000000000..59da3a6aa4
--- /dev/null
+++ b/samples/networking/06-simple-avatar/ServerRepository.py
@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+ """The server repository class"""
+ def __init__(self):
+ """initialise the server class"""
+
+ # get the port number from the configuration file
+ # if it doesn't exist, we use 4400 as the default
+ tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+ # list of all needed .dc files
+ dcFileNames = ['../direct.dc', 'sample.dc']
+
+ # initialise a threaded server on this machine with
+ # the port number and the dc filenames
+ ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
diff --git a/samples/networking/06-simple-avatar/client.py b/samples/networking/06-simple-avatar/client.py
new file mode 100644
index 0000000000..c19cd6b2d4
--- /dev/null
+++ b/samples/networking/06-simple-avatar/client.py
@@ -0,0 +1,151 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import KeyboardButton, NodePath, PandaNode
+
+# import our own repositories
+from ClientRepository import GameClientRepository
+from DistributedSmoothActor import DistributedSmoothActor
+
+# initialize the engine
+base = ShowBase()
+
+base.disableMouse()
+
+class Avatar:
+ def __init__(self, cr):
+ self.cr = cr
+ self.ralph = DistributedSmoothActor(self.cr)
+
+ self.cr.createDistributedObject(
+ distObj = self.ralph,
+ zoneId = 2)
+
+ # Create a floater object, which floats 2 units above ralph. We
+ # use this as a target for the camera to look at.
+
+ self.floater = NodePath(PandaNode("floater"))
+ self.floater.reparentTo(self.ralph)
+ self.floater.setZ(2.0)
+
+ # We will use this for checking if keyboard keys are pressed
+ self.isDown = base.mouseWatcherNode.isButtonDown
+
+ taskMgr.add(self.move, "moveTask")
+
+ # Set up the camera
+ base.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
+
+ # start the avatar
+ self.ralph.start()
+
+ # Accepts arrow keys to move either the player or the menu cursor,
+ # Also deals with grid checking and collision detection
+ def move(self, task):
+
+ # Get the time that elapsed since last frame. We multiply this with
+ # the desired speed in order to find out with which distance to move
+ # in order to achieve that desired speed.
+ dt = globalClock.getDt()
+
+ # If the camera-left key is pressed, move camera left.
+ # If the camera-right key is pressed, move camera right.
+
+ if self.isDown(KeyboardButton.asciiKey(b"j")):
+ base.camera.setX(base.camera, -20 * dt)
+ if self.isDown(KeyboardButton.asciiKey(b"k")):
+ base.camera.setX(base.camera, +20 * dt)
+
+ # If a move-key is pressed, move ralph in the specified direction.
+
+ if self.isDown(KeyboardButton.asciiKey(b"a")):
+ self.ralph.setH(self.ralph.getH() + 300 * dt)
+ if self.isDown(KeyboardButton.asciiKey(b"d")):
+ self.ralph.setH(self.ralph.getH() - 300 * dt)
+ if self.isDown(KeyboardButton.asciiKey(b"w")):
+ self.ralph.setY(self.ralph, -20 * dt)
+ if self.isDown(KeyboardButton.asciiKey(b"s")):
+ self.ralph.setY(self.ralph, +10 * dt)
+
+ # update distributed position and rotation
+ #self.ralph.setDistPos(self.ralph.getX(), self.ralph.getY(), self.ralph.getZ())
+ #self.ralph.setDistHpr(self.ralph.getH(), self.ralph.getP(), self.ralph.getR())
+
+ # If ralph is moving, loop the run animation.
+ # If he is standing still, stop the animation.
+ currentAnim = self.ralph.getCurrentAnim()
+
+ if self.isDown(KeyboardButton.asciiKey(b"w")):
+ if currentAnim != "run":
+ self.ralph.loop("run")
+ elif self.isDown(KeyboardButton.asciiKey(b"s")):
+ # Play the walk animation backwards.
+ if currentAnim != "walk":
+ self.ralph.loop("walk")
+ self.ralph.setPlayRate(-1.0, "walk")
+ elif self.isDown(KeyboardButton.asciiKey(b"a")) or self.isDown(KeyboardButton.asciiKey(b"d")):
+ if currentAnim != "walk":
+ self.ralph.loop("walk")
+ self.ralph.setPlayRate(1.0, "walk")
+ else:
+ if currentAnim is not None:
+ self.ralph.stop()
+ self.ralph.pose("walk", 5)
+ self.isMoving = False
+
+ # If the camera is too far from ralph, move it closer.
+ # If the camera is too close to ralph, move it farther.
+
+ camvec = self.ralph.getPos() - base.camera.getPos()
+ camvec.setZ(0)
+ camdist = camvec.length()
+ camvec.normalize()
+ if camdist > 10.0:
+ base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10))
+ camdist = 10.0
+ if camdist < 5.0:
+ base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist))
+ camdist = 5.0
+
+ # The camera should look in ralph's direction,
+ # but it should also try to stay horizontal, so look at
+ # a floater which hovers above ralph's head.
+ base.camera.lookAt(self.floater)
+
+ return task.cont
+
+base.accept("escape", exit)
+
+# initialize the client
+client = GameClientRepository()
+
+
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+ return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+ parent=base.a2dTopLeft, align=TextNode.ALeft,
+ pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+ return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+ parent=base.a2dBottomRight, align=TextNode.ARight,
+ fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "W|A|S|D: Move avatar)")
+inst2 = addInstructions(0.12, "esc: Close the client")
+inst3 = addInstructions(0.24, "See console output")
+
+def clientJoined():
+ title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+ # Setup our avatar
+ Avatar(client)
+
+base.accept("client-joined", clientJoined)
+
+# start the client
+base.run()
diff --git a/samples/networking/06-simple-avatar/models/ralph-run.egg.pz b/samples/networking/06-simple-avatar/models/ralph-run.egg.pz
new file mode 100644
index 0000000000..e50ff41d24
Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph-run.egg.pz differ
diff --git a/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz b/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz
new file mode 100644
index 0000000000..955352549c
Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz differ
diff --git a/samples/networking/06-simple-avatar/models/ralph.egg.pz b/samples/networking/06-simple-avatar/models/ralph.egg.pz
new file mode 100644
index 0000000000..5749c11650
Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph.egg.pz differ
diff --git a/samples/networking/06-simple-avatar/models/ralph.jpg b/samples/networking/06-simple-avatar/models/ralph.jpg
new file mode 100644
index 0000000000..198b4f5424
Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph.jpg differ
diff --git a/samples/networking/06-simple-avatar/sample.dc b/samples/networking/06-simple-avatar/sample.dc
new file mode 100755
index 0000000000..1a4800bc29
--- /dev/null
+++ b/samples/networking/06-simple-avatar/sample.dc
@@ -0,0 +1,6 @@
+import DistributedSmoothActor
+
+dclass DistributedSmoothActor: DistributedSmoothNode {
+ loop(string animName) broadcast;
+ pose(string animName, int16 frame) broadcast;
+}
diff --git a/samples/networking/06-simple-avatar/server.py b/samples/networking/06-simple-avatar/server.py
new file mode 100644
index 0000000000..0aab404a46
--- /dev/null
+++ b/samples/networking/06-simple-avatar/server.py
@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()
diff --git a/samples/networking/direct.dc b/samples/networking/direct.dc
new file mode 100755
index 0000000000..7e272a7344
--- /dev/null
+++ b/samples/networking/direct.dc
@@ -0,0 +1,91 @@
+// This is a sample dc file for some of the classes defined within the
+// direct source tree. It is suggested that you copy this file into
+// your own project (or load it from the direct source tree) and build
+// on it with your own dc file for your own classes.
+
+keyword broadcast;
+keyword ram;
+keyword p2p;
+
+from direct.distributed import DistributedObject/AI
+from direct.distributed import TimeManager/AI
+from direct.distributed import DistributedNode/AI
+from direct.distributed import DistributedSmoothNode/AI
+
+struct BarrierData {
+ uint16 context;
+ string name;
+ uint32 avIds[];
+};
+
+// The most fundamental class
+dclass DistributedObject {
+ // These are used to support DistributedObjectAI.beginBarrier() and
+ // the matching DistributedObject.doneBarrier(). If you don't call
+ // these functions, you don't care about these distributed methods.
+ // (Actually, you probably don't care anyway.)
+ setBarrierData(BarrierData data[]) broadcast ram;
+ setBarrierReady(uint16 context);
+ setLocation(uint32 parentId, uint32 zoneId) broadcast ram;
+};
+
+dclass TimeManager: DistributedObject {
+ requestServerTime(uint8 context) p2p;
+ serverTime(uint8 context, int32 timestamp);
+};
+
+dclass DistributedNode: DistributedObject {
+ setX(int16 / 10) broadcast ram;
+ setY(int16 / 10) broadcast ram;
+ setZ(int16 / 10) broadcast ram;
+ setH(int16 % 360 / 10) broadcast ram;
+ setP(int16 % 360 / 10) broadcast ram;
+ setR(int16 % 360 / 10) broadcast ram;
+
+ setPos: setX, setY, setZ;
+ setHpr: setH, setP, setR;
+ setPosHpr: setX, setY, setZ, setH, setP, setR;
+ setXY: setX, setY;
+ setXZ: setX, setZ;
+ setXYH: setX, setY, setH;
+ setXYZH: setX, setY, setZ, setH;
+};
+
+dclass DistributedSmoothNode: DistributedNode {
+ // Component set pos and hpr functions.
+
+ setComponentL(uint64) broadcast ram;
+ setComponentX(int16 / 10) broadcast ram;
+ setComponentY(int16 / 10) broadcast ram;
+ setComponentZ(int16 / 10) broadcast ram;
+ setComponentH(int16 % 360 / 10) broadcast ram;
+ setComponentP(int16 % 360 / 10) broadcast ram;
+ setComponentR(int16 % 360 / 10) broadcast ram;
+ setComponentT(int16 timestamp) broadcast ram;
+
+ // Composite set pos and hpr functions. These map to combinations
+ // of one or more of the above components. They all include
+ // setComponentT(), which must be called last.
+ setSmStop: setComponentT;
+ setSmH: setComponentH, setComponentT;
+ setSmZ: setComponentZ, setComponentT;
+ setSmXY: setComponentX, setComponentY, setComponentT;
+ setSmXZ: setComponentX, setComponentZ, setComponentT;
+ setSmPos: setComponentX, setComponentY, setComponentZ, setComponentT;
+ setSmHpr: setComponentH, setComponentP, setComponentR, setComponentT;
+ setSmXYH: setComponentX, setComponentY, setComponentH, setComponentT;
+ setSmXYZH: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentT;
+ setSmPosHpr: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
+ // special update if L (being location, such as zoneId) changes, send everything, intended to
+ // keep position and 'location' in sync
+ setSmPosHprL: setComponentL, setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
+
+ clearSmoothing(int8 bogus) broadcast;
+
+ suggestResync(uint32 avId, int16 timestampA, int16 timestampB,
+ int32 serverTimeSec, uint16 serverTimeUSec,
+ uint16 / 100 uncertainty);
+ returnResync(uint32 avId, int16 timestampB,
+ int32 serverTimeSec, uint16 serverTimeUSec,
+ uint16 / 100 uncertainty);
+};
diff --git a/tests/linmath/test_lvector2.py b/tests/linmath/test_lvector2.py
index bbf658dead..84bda15444 100644
--- a/tests/linmath/test_lvector2.py
+++ b/tests/linmath/test_lvector2.py
@@ -143,3 +143,11 @@ def test_vec2_floordiv(type):
v = type(i)
v //= -j
assert v.x == i // -j
+
+
+def test_vec2_buffer():
+ v = Vec2(1.5, -10.0)
+ m = memoryview(v)
+ assert len(m) == 2
+ assert m[0] == 1.5
+ assert m[1] == -10.0
diff --git a/tests/linmath/test_lvector3.py b/tests/linmath/test_lvector3.py
index 1b6dc02995..e12a1650f0 100644
--- a/tests/linmath/test_lvector3.py
+++ b/tests/linmath/test_lvector3.py
@@ -128,3 +128,12 @@ def test_vec3_floordiv(type):
v = type(i)
v //= -j
assert v.x == i // -j
+
+
+def test_vec3_buffer():
+ v = Vec3(0.5, 2.0, -10.0)
+ m = memoryview(v)
+ assert len(m) == 3
+ assert m[0] == 0.5
+ assert m[1] == 2.0
+ assert m[2] == -10.0
diff --git a/tests/linmath/test_lvector4.py b/tests/linmath/test_lvector4.py
index 8e651472a6..060953fbc8 100644
--- a/tests/linmath/test_lvector4.py
+++ b/tests/linmath/test_lvector4.py
@@ -144,3 +144,13 @@ def test_vec4_floordiv(type):
v = type(i)
v //= -j
assert v.x == i // -j
+
+
+def test_vec4_buffer():
+ v = Vec4(0, 0.5, 2.0, -4.0)
+ m = memoryview(v)
+ assert len(m) == 4
+ assert m[0] == 0
+ assert m[1] == 0.5
+ assert m[2] == 2.0
+ assert m[3] == -4.0
diff --git a/tests/pgraph/test_transforms.py b/tests/pgraph/test_transforms.py
new file mode 100644
index 0000000000..d4ac4d2fdb
--- /dev/null
+++ b/tests/pgraph/test_transforms.py
@@ -0,0 +1,68 @@
+from panda3d.core import TransformState, Mat4, Mat3
+
+
+def test_transform_identity():
+ state = TransformState.make_identity()
+
+ assert state.is_identity()
+ assert not state.is_invalid()
+ assert not state.is_singular()
+ assert state.is_2d()
+
+ assert state.has_components()
+ assert not state.components_given()
+ assert not state.hpr_given()
+ assert not state.quat_given()
+ assert state.has_pos()
+ assert state.has_hpr()
+ assert state.has_quat()
+ assert state.has_scale()
+ assert state.has_identity_scale()
+ assert state.has_uniform_scale()
+ assert state.has_shear()
+ assert not state.has_nonzero_shear()
+ assert state.has_mat()
+
+ assert state.get_pos() == (0, 0, 0)
+ assert state.get_hpr() == (0, 0, 0)
+ assert state.get_quat() == (1, 0, 0, 0)
+ assert state.get_norm_quat() == (1, 0, 0, 0)
+ assert state.get_scale() == (1, 1, 1)
+ assert state.get_uniform_scale() == 1
+ assert state.get_shear() == (0, 0, 0)
+ assert state.get_mat() == Mat4.ident_mat()
+
+ assert state.get_pos2d() == (0, 0)
+ assert state.get_rotate2d() == 0
+ assert state.get_scale2d() == (1, 1)
+ assert state.get_shear2d() == 0
+ assert state.get_mat3() == Mat3.ident_mat()
+
+ state2 = TransformState.make_identity()
+ assert state.this == state2.this
+
+
+def test_transform_invalid():
+ state = TransformState.make_invalid()
+
+ assert not state.is_identity()
+ assert state.is_invalid()
+ assert state.is_singular()
+ assert not state.is_2d()
+
+ assert not state.has_components()
+ assert not state.components_given()
+ assert not state.hpr_given()
+ assert not state.quat_given()
+ assert not state.has_pos()
+ assert not state.has_hpr()
+ assert not state.has_quat()
+ assert not state.has_scale()
+ assert not state.has_identity_scale()
+ assert not state.has_uniform_scale()
+ assert not state.has_shear()
+ assert not state.has_nonzero_shear()
+ assert not state.has_mat()
+
+ state2 = TransformState.make_invalid()
+ assert state.this == state2.this