From 3c4f666509b862e157b38d9fb3eae176e2eccb8d Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 27 Jul 2020 13:38:47 +0200 Subject: [PATCH 01/13] direct: Improvements to API reference, better cross-linking --- direct/src/directbase/DirectStart.py | 2 +- direct/src/showbase/Loader.py | 18 +++++++++--------- direct/src/showbase/Messenger.py | 2 +- direct/src/showbase/ShowBase.py | 11 ++++++----- direct/src/showbase/ShowBaseGlobal.py | 15 ++++++++++----- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/direct/src/directbase/DirectStart.py b/direct/src/directbase/DirectStart.py index 8b6b5b9539..ea51d43fc9 100644 --- a/direct/src/directbase/DirectStart.py +++ b/direct/src/directbase/DirectStart.py @@ -4,7 +4,7 @@ opening a graphical window and setting up the scene graph. This example demonstrates its use: import direct.directbase.DirectStart - run() + base.run() While it may be considered useful for quick prototyping in the interactive Python shell, using it in applications is not considered good style. diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 80aa5f09e2..fd6424d917 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -24,14 +24,14 @@ class Loader(DirectObject): _loadedPythonFileTypes = False - class Callback: + class _Callback: """Returned by loadModel when used asynchronously. This class is modelled after Future, and can be awaited.""" # This indicates that this class behaves like a Future. _asyncio_future_blocking = False - class ResultAwaiter(object): + class _ResultAwaiter(object): """Reinvents generators because of PEP 479, sigh. See #513.""" __slots__ = 'requestList', 'index' @@ -126,9 +126,9 @@ class Loader(DirectObject): self._asyncio_future_blocking = True if self.gotList: - return self.ResultAwaiter([self]) + return self._ResultAwaiter([self]) else: - return self.ResultAwaiter(self.requestList) + return self._ResultAwaiter(self.requestList) def __aiter__(self): """ This allows using `async for` to iterate asynchronously over @@ -138,7 +138,7 @@ class Loader(DirectObject): requestList = self.requestList assert requestList is not None, "Request was cancelled." - return self.ResultAwaiter(requestList) + return self._ResultAwaiter(requestList) # special methods def __init__(self, base): @@ -308,7 +308,7 @@ class Loader(DirectObject): # requested models have been loaded, we'll invoke the # callback (passing it the models on the parameter list). - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for modelPath in modelList: request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions) @@ -476,7 +476,7 @@ class Loader(DirectObject): # requested models have been saved, we'll invoke the # callback (passing it the models on the parameter list). - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for modelPath, node in modelList: request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node) @@ -1013,7 +1013,7 @@ class Loader(DirectObject): # requested sounds have been loaded, we'll invoke the # callback (passing it the sounds on the parameter list). - cb = Loader.Callback(self, len(soundList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(soundList), gotList, callback, extraArgs) for i, soundPath in enumerate(soundList): request = AudioLoadRequest(manager, soundPath, positional) request.setDoneEvent(self.hook) @@ -1078,7 +1078,7 @@ class Loader(DirectObject): callback = self.__asyncFlattenDone gotList = True - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for model in modelList: request = ModelFlattenRequest(model.node()) diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py index c8d72cb6b4..e4ba5ccda8 100644 --- a/direct/src/showbase/Messenger.py +++ b/direct/src/showbase/Messenger.py @@ -1,5 +1,5 @@ """This defines the Messenger class, which is responsible for most of the -event handling that happens on the Python side. +:ref:`event handling ` that happens on the Python side. """ __all__ = ['Messenger'] diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 984ec8f8f2..1eb66ad94d 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1,4 +1,4 @@ -""" This module contains ShowBase, an application framework responsible +""" This module contains `.ShowBase`, an application framework responsible for opening a graphical display, setting up input devices and creating the scene graph. @@ -19,14 +19,15 @@ Built-in global variables Some key variables used in all Panda3D scripts are actually attributes of the ShowBase instance. When creating an instance of this class, it will write many of these variables to the built-in scope of the Python interpreter, so that -they are accessible to any Python module. +they are accessible to any Python module, without the need fors extra imports. While these are handy for prototyping, we do not recommend using them in bigger projects, as it can make the code confusing to read to other Python developers, to whom it may not be obvious where these variables are originating. -Some of these built-in variables are documented further in the -:mod:`~direct.showbase.ShowBaseGlobal` module. +Refer to the :mod:`builtins` page for a listing of the variables written to the +built-in scope. + """ __all__ = ['ShowBase', 'WindowControls'] @@ -2022,7 +2023,7 @@ class ShowBase(DirectObject.DirectObject): def enableAllAudio(self): """ Reenables the SFX and music managers that were active at the time - `disableAllAudio() was called. Meant to be called when the app regains + `disableAllAudio()` was called. Meant to be called when the app regains audio focus. """ self.AppHasAudioFocus = 1 diff --git a/direct/src/showbase/ShowBaseGlobal.py b/direct/src/showbase/ShowBaseGlobal.py index eedffdd1ad..eeef44d4b2 100644 --- a/direct/src/showbase/ShowBaseGlobal.py +++ b/direct/src/showbase/ShowBaseGlobal.py @@ -2,9 +2,14 @@ :class:`~.ShowBase.ShowBase` instance, as an alternative to using the builtin scope. -Note that you cannot directly import `base` from this module since ShowBase -may not have been created yet; instead, ShowBase dynamically adds itself to -this module's scope when instantiated.""" +Many of the variables contained in this module are also automatically written +to the :mod:`builtins` module when ShowBase is instantiated, making them +available to any Python code. Importing them from this module instead can make +it easier to see where these variables are coming from. + +Note that you cannot directly import :data:`~builtins.base` from this module +since ShowBase may not have been created yet; instead, ShowBase dynamically +adds itself to this module's scope when instantiated.""" __all__ = [] @@ -17,8 +22,8 @@ from . import DConfig as config __dev__ = config.GetBool('want-dev', __debug__) -#: The global instance of the :class:`~panda3d.core.VirtualFileSystem`, as -#: obtained using :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`. +#: The global instance of the :ref:`virtual-file-system`, as obtained using +#: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`. vfs = VirtualFileSystem.getGlobalPtr() #: The default Panda3D output stream for notifications and logging, as From 32143055bdd868757e2379f82b53c3a59aa9bbfb Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 4 Aug 2020 11:08:51 +0200 Subject: [PATCH 02/13] PythonUtil: Fix usage of reduce() for Python 3 Backport to 1.10.7; see #980 --- direct/src/showbase/PythonUtil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 4c9afa634b..e92025a775 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -44,6 +44,7 @@ from panda3d.core import ConfigVariableBool if sys.version_info >= (3, 0): import builtins + from functools import reduce xrange = range else: import __builtin__ as builtins From 9c4cb28805edf550fac9d8f4f87e568bbbf9a154 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 11 Aug 2020 09:38:41 +0200 Subject: [PATCH 03/13] mathutil: error instead of infinite loop if triangulation failed Fixes #985 --- panda/src/mathutil/triangulator.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/mathutil/triangulator.cxx b/panda/src/mathutil/triangulator.cxx index da29f022be..f179b24e83 100644 --- a/panda/src/mathutil/triangulator.cxx +++ b/panda/src/mathutil/triangulator.cxx @@ -150,7 +150,11 @@ triangulate() { } */ + int attempts = 0; + while (construct_trapezoids(num_segments) != 0) { + nassertv_always(attempts++ < 100); + // If there's an error, re-shuffle the index and try again. Randomizer randomizer; for (i = 0; i < num_segments; ++i) { From 8178fb21f334657a57f25d707cc592d68d29aa9b Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 11 Aug 2020 09:40:34 +0200 Subject: [PATCH 04/13] direct: don't rely on vfs being present in builtins --- direct/src/distributed/ConnectionRepository.py | 1 + direct/src/distributed/ServerRepository.py | 1 + direct/src/particles/ParticleEffect.py | 1 + 3 files changed, 3 insertions(+) diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index 348a4390b6..d77802c7ff 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -260,6 +260,7 @@ class ConnectionRepository( searchPath = getModelPath().getValue() for dcFileName in dcFileNames: pathname = Filename(dcFileName) + vfs = VirtualFileSystem.getGlobalPtr() vfs.resolveFilename(pathname, searchPath) readResult = dcFile.read(pathname) if not readResult: diff --git a/direct/src/distributed/ServerRepository.py b/direct/src/distributed/ServerRepository.py index ac0f2c6856..6e0ef837a0 100644 --- a/direct/src/distributed/ServerRepository.py +++ b/direct/src/distributed/ServerRepository.py @@ -225,6 +225,7 @@ class ServerRepository: searchPath = getModelPath().getValue() for dcFileName in dcFileNames: pathname = Filename(dcFileName) + vfs = VirtualFileSystem.getGlobalPtr() vfs.resolveFilename(pathname, searchPath) readResult = dcFile.read(pathname) if not readResult: diff --git a/direct/src/particles/ParticleEffect.py b/direct/src/particles/ParticleEffect.py index f2c2141614..82f0904715 100644 --- a/direct/src/particles/ParticleEffect.py +++ b/direct/src/particles/ParticleEffect.py @@ -201,6 +201,7 @@ class ParticleEffect(NodePath): f.write('self.addForceGroup(%s)\n' % target) def loadConfig(self, filename): + vfs = VirtualFileSystem.getGlobalPtr() data = vfs.readFile(filename, 1) data = data.replace(b'\r', b'') try: From 1f05d37b209470c179b6d00f888ac39ffd14bcf4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 20 Mar 2020 02:44:59 +0200 Subject: [PATCH 05/13] pgui: Ignore PGEntry tab keypress Closes #885 --- panda/src/pgui/pgEntry.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx index b6a86754d7..c729a23038 100644 --- a/panda/src/pgui/pgEntry.cxx +++ b/panda/src/pgui/pgEntry.cxx @@ -203,6 +203,11 @@ press(const MouseWatcherParameter ¶m, bool background) { ButtonHandle button = param.get_button(); + if (button == KeyboardButton::tab()) { + // Tab. Ignore the entry. + return; + } + if (button == MouseButton::one() || button == MouseButton::two() || button == MouseButton::three() || From dfa1eb78cd744f39c028660e1c3a857f9be4b5a9 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Aug 2020 10:58:09 +0200 Subject: [PATCH 06/13] dist: Fix replacePaths just prepending "linecache" before paths --- direct/src/dist/FreezeTool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index f7b2ea9b91..237deb9cf3 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -1299,7 +1299,8 @@ class Freezer: for moduleName, module in list(self.mf.modules.items()): if module.__code__: origPathname = module.__code__.co_filename - replace_paths.append((origPathname, moduleName)) + if origPathname: + replace_paths.append((origPathname, moduleName)) self.mf.replace_paths = replace_paths # Now that we have built up the replacement mapping, go back From 2cb377920404e6c9e1e63c0f5be3d04ee0f232ab Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 16 Aug 2020 14:43:43 -0700 Subject: [PATCH 07/13] build_apps: Use FreezeTool.__replacePaths() to cleanup tracebacks This alters paths in tracebacks to contain just module names instead of full, absolute paths. This makes tracebacks easier to read and leaks less information about the build machine. Closes #991 --- direct/src/dist/FreezeTool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index 237deb9cf3..f2c39e88b4 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -1695,6 +1695,8 @@ class Freezer: def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}, log_append=False): + self.__replacePaths() + # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." From c7341bec264fe461b5ee85ecd2c768b06e6fa38f Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Aug 2020 11:07:33 +0200 Subject: [PATCH 08/13] tests: Fix Filename test to match paths case-insensitively --- tests/dtoolutil/test_filename.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dtoolutil/test_filename.py b/tests/dtoolutil/test_filename.py index d24d9d52d7..698ed0e69f 100644 --- a/tests/dtoolutil/test_filename.py +++ b/tests/dtoolutil/test_filename.py @@ -21,4 +21,4 @@ def test_filename_ctor_pathlib(): path = pathlib.Path(__file__) fn = Filename(path) - assert fn.to_os_specific_w() == str(path) + assert fn.to_os_specific_w().lower() == str(path).lower() From 9f0fc0a59460963add136674e89a2fa4be5f51e6 Mon Sep 17 00:00:00 2001 From: LD Date: Tue, 11 Aug 2020 21:36:39 +0200 Subject: [PATCH 09/13] dtoolbase: Fix bad double-prec NaN/inf detection in release build Fixes #987 Closes #988 --- dtool/src/dtoolbase/cmath.I | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dtool/src/dtoolbase/cmath.I b/dtool/src/dtoolbase/cmath.I index d353a99ce4..b303671fbb 100644 --- a/dtool/src/dtoolbase/cmath.I +++ b/dtool/src/dtoolbase/cmath.I @@ -351,7 +351,7 @@ cnan(double v) { #if __FINITE_MATH_ONLY__ // GCC's isnan breaks when using -ffast-math. union { double d; uint64_t x; } u = { v }; - return ((u.x << 1) > 0xff70000000000000ull); + return ((u.x << 1) > 0xffe0000000000000ull); #elif !defined(_WIN32) return std::isnan(v); #else @@ -383,7 +383,7 @@ cinf(double v) { #if __FINITE_MATH_ONLY__ // GCC's isinf breaks when using -ffast-math. union { double d; uint64_t x; } u = { v }; - return ((u.x << 1) == 0xff70000000000000ull); + return ((u.x << 1) == 0xffe0000000000000ull); #elif !defined(_WIN32) return std::isinf(v); #else From be24266715460a9a9a60899ff635fc17272c63ef Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Aug 2020 11:43:34 +0200 Subject: [PATCH 10/13] tests: Copy some linmath/gobj tests over from master branch --- tests/gobj/test_lenses.py | 77 ++++++++++++++++++++++++++++++ tests/linmath/test_lvector2.py | 64 +++++++++++++++++++++++++ tests/linmath/test_lvector3.py | 74 +++++++++++++++++++++++++++++ tests/linmath/test_lvector4.py | 87 ++++++++++++++++++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 tests/gobj/test_lenses.py create mode 100644 tests/linmath/test_lvector2.py create mode 100644 tests/linmath/test_lvector3.py create mode 100644 tests/linmath/test_lvector4.py diff --git a/tests/gobj/test_lenses.py b/tests/gobj/test_lenses.py new file mode 100644 index 0000000000..a51e497c8e --- /dev/null +++ b/tests/gobj/test_lenses.py @@ -0,0 +1,77 @@ +from panda3d.core import PerspectiveLens, Point3, Point2 + + +def test_perspectivelens_extrude(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(0.5, 100) + + near = Point3() + far = Point3() + + assert lens.extrude((0, 0), near, far) + assert near.almost_equal((0, 0.5, 0), 0.001) + assert far.almost_equal((0, 100, 0), 0.1) + + assert lens.extrude((-1, -1), near, far) + assert near.almost_equal((-0.5, 0.5, -0.5), 0.001) + assert far.almost_equal((-100, 100, -100), 0.1) + + assert lens.extrude((1, 0), near, far) + assert near.almost_equal((0.5, 0.5, 0), 0.001) + assert far.almost_equal((100, 100, 0), 0.1) + + +def test_perspectivelens_extrude_depth(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(0.5, 100) + + point = Point3() + + assert lens.extrude_depth((0, 0, -1), point) + assert point.almost_equal((0, 0.5, 0), 0.001) + + assert lens.extrude_depth((0, 0, 1), point) + assert point.almost_equal((0, 100, 0), 0.001) + + assert lens.extrude_depth((-1, -1, -1), point) + assert point.almost_equal((-0.5, 0.5, -0.5), 0.001) + + assert lens.extrude_depth((-1, -1, 1), point) + assert point.almost_equal((-100, 100, -100), 0.1) + + assert lens.extrude_depth((1, 0, -1), point) + assert point.almost_equal((0.5, 0.5, 0), 0.001) + + assert lens.extrude_depth((1, 0, 1), point) + assert point.almost_equal((100, 100, 0), 0.1) + + +def test_perspectivelens_project(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(0.5, 100) + + point = Point2() + + assert not lens.project((0, 0, 0), point) + assert not lens.project((-1, 0.5, 0), point) + + assert lens.project((0, 0.5, 0), point) + assert point.almost_equal((0, 0), 0.001) + + assert lens.project((0, 100, 0), point) + assert point.almost_equal((0, 0), 0.001) + + assert lens.project((-0.5, 0.5, -0.5), point) + assert point.almost_equal((-1, -1), 0.001) + + assert lens.project((-100, 100, -100), point) + assert point.almost_equal((-1, -1), 0.001) + + assert lens.project((0.5, 0.5, 0), point) + assert point.almost_equal((1, 0), 0.001) + + assert lens.project((100, 100, 0), point) + assert point.almost_equal((1, 0), 0.001) diff --git a/tests/linmath/test_lvector2.py b/tests/linmath/test_lvector2.py new file mode 100644 index 0000000000..f7609ac5d2 --- /dev/null +++ b/tests/linmath/test_lvector2.py @@ -0,0 +1,64 @@ +from panda3d.core import Vec2, Vec3, Vec4, Vec2F, Vec2D + + +def test_vec2_creation(): + assert Vec2(x=1, y=2) == Vec2(1, 2) == Vec2((1, 2)) + + +def test_vec2_getter_setter(): + original_vector = Vec2(2, 3) + + assert original_vector.x == 2 + assert original_vector.y == 3 + + original_vector.x = 1 + original_vector.y = 3 + + assert original_vector == Vec2(1, 3) + + original_vector[0] = 3 + original_vector[1] = 1 + + assert original_vector == Vec2(3, 1) + + original_vector.set_x(-8) + original_vector.set_y(6) + + assert original_vector.x == -8 + assert original_vector.y == 6 + + +def test_vec2_sum(): + original_vector = Vec2(2, 3) + + assert original_vector + original_vector == Vec2(4, 6) + assert original_vector + 3 == Vec2(5, 6) + + +def test_vec2_power(): + assert Vec2(2, -3) ** 2 == Vec2(4, 9) + + +def test_vec2_len(): + assert len(Vec2(2, -3)) == 2 + + +def test_vec2_swizzle_mask(): + original_vector = Vec2(3, 5) + + assert original_vector.yx == Vec2(5, 3) + assert original_vector.xy == original_vector + + +def test_vec2_str(): + assert str(Vec2F(2, 3)) == "LVector2f(2, 3)" + assert str(Vec2D(2, 3)) == "LVector2d(2, 3)" + + +def test_vec2_compare(): + assert Vec2(1, 2).compare_to(Vec2(1, 2)) == 0 + + assert Vec2(1, 0).compare_to(Vec2(1, 0)) == 0 + assert Vec2(1, 0).compare_to(Vec2(0, 1)) == 1 + assert Vec2(0, 1).compare_to(Vec2(1, 0)) == -1 + assert Vec2(0, 1).compare_to(Vec2(0, 1)) == 0 diff --git a/tests/linmath/test_lvector3.py b/tests/linmath/test_lvector3.py new file mode 100644 index 0000000000..016c2e8420 --- /dev/null +++ b/tests/linmath/test_lvector3.py @@ -0,0 +1,74 @@ +from panda3d.core import Vec2, Vec3, Vec3F, Vec3D + + +def test_vec3_creation(): + assert Vec3(x=1, y=2, z=1) == Vec3(1, 2, 1) == Vec3((1, 2, 1)) + + +def test_vec3_getter_setter(): + original_vector = Vec3(2, 3, 7) + + assert original_vector.x == 2 + assert original_vector.y == 3 + assert original_vector.z == 7 + + original_vector.x = 1 + original_vector.y = 3 + original_vector.z = 5 + + assert original_vector == Vec3(1, 3, 5) + + original_vector[0] = 3 + original_vector[1] = 1 + original_vector[2] = 1 + + assert original_vector == Vec3(3, 1, 1) + + original_vector.set_x(-8) + original_vector.set_y(6) + original_vector.set_z(10) + + assert original_vector.x == -8 + assert original_vector.y == 6 + assert original_vector.z == 10 + + +def test_vec3_sum(): + original_vector = Vec3(2, 3, -2) + + assert original_vector + original_vector == Vec3(4, 6, -4) + assert original_vector + 3 == Vec3(5, 6, 1) + + +def test_vec3_power(): + assert Vec3(2, -3, 2) ** 2 == Vec3(4, 9, 4) + + +def test_vec3_len(): + assert len(Vec3(2, -3, 10)) == 3 + + +def test_vec3_swizzle_mask(): + original_vector = Vec3(3, 5, 1) + + assert original_vector.xy == Vec2(3, 5) + assert original_vector.zxy == Vec3(1, 3, 5) + + +def test_vec3_str(): + assert str(Vec3F(2, 3, 1)) == "LVector3f(2, 3, 1)" + assert str(Vec3D(2, 3, 1)) == "LVector3d(2, 3, 1)" + + +def test_vec3_compare(): + assert Vec3(1, 2, 3).compare_to(Vec3(1, 2, 3)) == 0 + + assert Vec3(1, 0, 0).compare_to(Vec3(1, 0, 0)) == 0 + assert Vec3(1, 0, 0).compare_to(Vec3(0, 1, 0)) == 1 + assert Vec3(1, 0, 0).compare_to(Vec3(0, 0, 1)) == 1 + assert Vec3(0, 1, 0).compare_to(Vec3(1, 0, 0)) == -1 + assert Vec3(0, 1, 0).compare_to(Vec3(0, 1, 0)) == 0 + assert Vec3(0, 1, 0).compare_to(Vec3(0, 0, 1)) == 1 + assert Vec3(0, 0, 1).compare_to(Vec3(1, 0, 0)) == -1 + assert Vec3(0, 0, 1).compare_to(Vec3(0, 1, 0)) == -1 + assert Vec3(0, 0, 1).compare_to(Vec3(0, 0, 1)) == 0 diff --git a/tests/linmath/test_lvector4.py b/tests/linmath/test_lvector4.py new file mode 100644 index 0000000000..5a65c7842c --- /dev/null +++ b/tests/linmath/test_lvector4.py @@ -0,0 +1,87 @@ +from panda3d.core import Vec2, Vec3, Vec4, Vec4F, Vec4D + + +def test_vec4_creation(): + assert Vec4(x=1, y=2, z=1, w=7) == Vec4(1, 2, 1, 7) == Vec4((1, 2, 1, 7)) + + +def test_vec4_getter_setter(): + original_vector = Vec4(2, 3, 7, 9) + + assert original_vector.x == 2 + assert original_vector.y == 3 + assert original_vector.z == 7 + assert original_vector.w == 9 + + original_vector.x = 1 + original_vector.y = 3 + original_vector.z = 5 + original_vector.w = -8 + + assert original_vector == Vec4(1, 3, 5, -8) + + original_vector[0] = 3 + original_vector[1] = 1 + original_vector[2] = 1 + original_vector[3] = -2 + + assert original_vector == Vec4(3, 1, 1, -2) + + original_vector.set_x(-8) + original_vector.set_y(6) + original_vector.set_z(10) + original_vector.set_w(30) + + assert original_vector.x == -8 + assert original_vector.y == 6 + assert original_vector.z == 10 + assert original_vector.w == 30 + + +def test_vec4_sum(): + original_vector = Vec4(2, 3, -2, 1) + + assert original_vector + original_vector == Vec4(4, 6, -4, 2) + assert original_vector + 3 == Vec4(5, 6, 1, 4) + + +def test_vec4_power(): + assert Vec4(2, -3, 2, -1) ** 2 == Vec4(4, 9, 4, 1) + + +def test_vec4_len(): + assert len(Vec4(2, -3, 10, 30)) == 4 + + +def test_vec4_swizzle_mask(): + original_vector = Vec4(3, 5, 1, 0) + + assert original_vector.xy == Vec2(3, 5) + assert original_vector.zxy == Vec3(1, 3, 5) + assert original_vector.zxyw == Vec4(1, 3, 5, 0) + + +def test_vec4_str(): + assert str(Vec4F(2, 3, 1, 9)) == "LVector4f(2, 3, 1, 9)" + assert str(Vec4D(2, 3, 1, 9)) == "LVector4d(2, 3, 1, 9)" + + +def test_vec4_compare(): + assert Vec4(1, 2, 3, 4).compare_to(Vec4(1, 2, 3, 4)) == 0 + + assert Vec4(1, 0, 0, 0).compare_to(Vec4(1, 0, 0, 0)) == 0 + assert Vec4(1, 0, 0, 0).compare_to(Vec4(0, 1, 0, 0)) == 1 + assert Vec4(1, 0, 0, 0).compare_to(Vec4(0, 0, 1, 0)) == 1 + assert Vec4(1, 0, 0, 0).compare_to(Vec4(0, 0, 0, 1)) == 1 + assert Vec4(0, 1, 0, 0).compare_to(Vec4(1, 0, 0, 0)) == -1 + assert Vec4(0, 1, 0, 0).compare_to(Vec4(0, 1, 0, 0)) == 0 + assert Vec4(0, 1, 0, 0).compare_to(Vec4(0, 0, 1, 0)) == 1 + assert Vec4(0, 1, 0, 0).compare_to(Vec4(0, 0, 0, 1)) == 1 + assert Vec4(0, 0, 1, 0).compare_to(Vec4(1, 0, 0, 0)) == -1 + assert Vec4(0, 0, 1, 0).compare_to(Vec4(0, 1, 0, 0)) == -1 + assert Vec4(0, 0, 1, 0).compare_to(Vec4(0, 0, 1, 0)) == 0 + assert Vec4(0, 0, 1, 0).compare_to(Vec4(0, 0, 0, 1)) == 1 + assert Vec4(0, 0, 0, 1).compare_to(Vec4(1, 0, 0, 0)) == -1 + assert Vec4(0, 0, 0, 1).compare_to(Vec4(0, 1, 0, 0)) == -1 + assert Vec4(0, 0, 0, 1).compare_to(Vec4(0, 0, 1, 0)) == -1 + assert Vec4(0, 0, 0, 1).compare_to(Vec4(0, 0, 0, 1)) == 0 From 9ac1a4f7cb3697e74e561d6d7f0d02131b55fd33 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Aug 2020 12:14:55 +0200 Subject: [PATCH 11/13] tests: Add some unit tests for #987 --- tests/gobj/test_lenses.py | 24 +++++++++++++++++++++++- tests/linmath/test_lvector2.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/gobj/test_lenses.py b/tests/gobj/test_lenses.py index a51e497c8e..c5c6961f5c 100644 --- a/tests/gobj/test_lenses.py +++ b/tests/gobj/test_lenses.py @@ -1,4 +1,4 @@ -from panda3d.core import PerspectiveLens, Point3, Point2 +from panda3d.core import PerspectiveLens, Point3, Point2, CS_zup_right def test_perspectivelens_extrude(): @@ -75,3 +75,25 @@ def test_perspectivelens_project(): assert lens.project((100, 100, 0), point) assert point.almost_equal((1, 0), 0.001) + + +def test_perspectivelens_far_inf(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(2, float("inf")) + lens.coordinate_system = CS_zup_right + + mat = lens.get_projection_mat() + assert mat[1][2] == 1 + assert mat[3][2] == -4 + + +def test_perspectivelens_near_inf(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(float("inf"), 2) + lens.coordinate_system = CS_zup_right + + mat = lens.get_projection_mat() + assert mat[1][2] == -1 + assert mat[3][2] == 4 diff --git a/tests/linmath/test_lvector2.py b/tests/linmath/test_lvector2.py index f7609ac5d2..4dd4571d11 100644 --- a/tests/linmath/test_lvector2.py +++ b/tests/linmath/test_lvector2.py @@ -62,3 +62,31 @@ def test_vec2_compare(): assert Vec2(1, 0).compare_to(Vec2(0, 1)) == 1 assert Vec2(0, 1).compare_to(Vec2(1, 0)) == -1 assert Vec2(0, 1).compare_to(Vec2(0, 1)) == 0 + + +def test_vec2_nan(): + nan = float("nan") + inf = float("inf") + assert not Vec2F(0, 0).is_nan() + assert not Vec2F(1, 0).is_nan() + assert Vec2F(nan, 0).is_nan() + assert Vec2F(0, nan).is_nan() + assert Vec2F(nan, nan).is_nan() + assert Vec2F(-nan, 0).is_nan() + assert Vec2F(-nan, nan).is_nan() + assert Vec2F(inf, nan).is_nan() + assert not Vec2F(inf, 0).is_nan() + assert not Vec2F(inf, inf).is_nan() + assert not Vec2F(-inf, 0).is_nan() + + assert not Vec2D(0, 0).is_nan() + assert not Vec2D(1, 0).is_nan() + assert Vec2D(nan, 0).is_nan() + assert Vec2D(0, nan).is_nan() + assert Vec2D(nan, nan).is_nan() + assert Vec2D(-nan, 0).is_nan() + assert Vec2D(-nan, nan).is_nan() + assert Vec2D(inf, nan).is_nan() + assert not Vec2D(inf, 0).is_nan() + assert not Vec2D(inf, inf).is_nan() + assert not Vec2D(-inf, 0).is_nan() From dd77bd4cdff10b97d78208dbb12290fd0519c2b9 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 17 Aug 2020 03:27:03 -0700 Subject: [PATCH 12/13] shadergen: Make use of material alpha if present (#925) The material alpha overrides alpha values from flat color or vertex colors. This follows what the fixed-function pipeline does. Fixes #912 --- panda/src/pgraphnodes/shaderGenerator.cxx | 4 +- tests/display/test_color_buffer.py | 70 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index 455f4d4be6..f0e14baa06 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -1485,7 +1485,9 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) { // Combine in alpha, which bypasses lighting calculations. Use of lerp // here is a workaround for a radeon driver bug. if (key._calc_primary_alpha) { - if (key._color_type == ColorAttrib::T_vertex) { + if (key._material_flags & Material::F_diffuse) { + text << "\t result.a = attr_material[1].w;\n"; + } else if (key._color_type == ColorAttrib::T_vertex) { text << "\t result.a = l_color.a;\n"; } else if (key._color_type == ColorAttrib::T_flat) { text << "\t result.a = attr_color.a;\n"; diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py index d95913b0f7..d59b86a0cf 100644 --- a/tests/display/test_color_buffer.py +++ b/tests/display/test_color_buffer.py @@ -46,6 +46,16 @@ def material_attrib(request): return core.MaterialAttrib.make(mat) +@pytest.fixture(scope='session') +def light_attrib(): + light = core.AmbientLight('amb') + light.color = (1, 1, 1, 1) + light_attrib = core.LightAttrib.make() + light_attrib = light_attrib.add_on_light(core.NodePath(light)) + + return light_attrib + + @pytest.fixture(scope='module', params=[False, True], ids=["srgb:off", "srgb:on"]) def color_region(request, graphics_pipe): """Creates and returns a DisplayRegion with a depth buffer.""" @@ -291,3 +301,63 @@ def test_scaled_color_off_vertex(color_region, shader_attrib, material_attrib): result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR) assert result.almost_equal(TEST_COLOR_SCALE, FUZZ) + +def test_color_transparency(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_flat(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + core.ColorAttrib.make_flat(TEST_COLOR), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_vertex(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + core.ColorAttrib.make_vertex(), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state, vertex_color=(1, 1, 1, 0.5)) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_no_light(color_region, shader_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(1.0, 0.1) From 73b5316c6c7a08f614d0cd336721a55f46bc192d Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 2 Apr 2019 21:25:08 +0200 Subject: [PATCH 13/13] gobj: add margin to float comparison in lens.project() Fixes unit tests in double-precision build. Cherry-picked from fa53e013cbc795dcccc57bc8503ca4788408fb86 --- panda/src/gobj/lens.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/gobj/lens.cxx b/panda/src/gobj/lens.cxx index f66f2ba6b9..6fabc0cf8f 100644 --- a/panda/src/gobj/lens.cxx +++ b/panda/src/gobj/lens.cxx @@ -1221,8 +1221,8 @@ do_project(const CData *cdata, const LPoint3 &point3d, LPoint3 &point2d) const { point2d.set(full[0] * recip_full3, full[1] * recip_full3, full[2] * recip_full3); return (full[3] > 0.0f) && - (point2d[0] >= -1.0f) && (point2d[0] <= 1.0f) && - (point2d[1] >= -1.0f) && (point2d[1] <= 1.0f); + (point2d[0] >= -1.0f - NEARLY_ZERO(PN_stdfloat)) && (point2d[0] <= 1.0f + NEARLY_ZERO(PN_stdfloat)) && + (point2d[1] >= -1.0f - NEARLY_ZERO(PN_stdfloat)) && (point2d[1] <= 1.0f + NEARLY_ZERO(PN_stdfloat)); } /**