From ac6a1a78747643b67fd4ece31c88f1c47b6f17b3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 22 Nov 2020 17:44:59 +0100 Subject: [PATCH] collide: Expose CollisionPolygon's setup_points and verify_points to Python Closes #1035 --- makepanda/makepanda.py | 2 + panda/src/collide/CMakeLists.txt | 7 +- panda/src/collide/collisionPolygon.h | 6 +- panda/src/collide/collisionPolygon_ext.cxx | 86 +++++++++++++++++++ panda/src/collide/collisionPolygon_ext.h | 43 ++++++++++ panda/src/collide/p3collide_ext_composite.cxx | 1 + tests/collide/test_collision_polygon.py | 45 ++++++++++ 7 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 panda/src/collide/collisionPolygon_ext.cxx create mode 100644 panda/src/collide/collisionPolygon_ext.h create mode 100644 panda/src/collide/p3collide_ext_composite.cxx create mode 100644 tests/collide/test_collision_polygon.py diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 9b710019a7..3715258132 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3829,6 +3829,7 @@ OPTS=['DIR:panda/src/collide'] IGATEFILES=GetDirectoryContents('panda/src/collide', ["*.h", "*_composite*.cxx"]) TargetAdd('libp3collide.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3collide.in', opts=['IMOD:panda3d.core', 'ILIB:libp3collide', 'SRCDIR:panda/src/collide']) +PyTargetAdd('p3collide_ext_composite.obj', opts=OPTS, input='p3collide_ext_composite.cxx') # # DIRECTORY: panda/src/parametrics/ @@ -4073,6 +4074,7 @@ PyTargetAdd('core.pyd', input='p3event_pythonTask.obj') PyTargetAdd('core.pyd', input='p3gobj_ext_composite.obj') PyTargetAdd('core.pyd', input='p3pgraph_ext_composite.obj') PyTargetAdd('core.pyd', input='p3display_ext_composite.obj') +PyTargetAdd('core.pyd', input='p3collide_ext_composite.obj') PyTargetAdd('core.pyd', input='core_module.obj') if not GetLinkAllStatic() and GetTarget() != 'emscripten': diff --git a/panda/src/collide/CMakeLists.txt b/panda/src/collide/CMakeLists.txt index 63ac4163e8..1aef0fda80 100644 --- a/panda/src/collide/CMakeLists.txt +++ b/panda/src/collide/CMakeLists.txt @@ -65,11 +65,16 @@ set(P3COLLIDE_SOURCES config_collide.cxx ) +set(P3COLLIDE_IGATEEXT + collisionPolygon_ext.cxx + collisionPolygon_ext.h +) + composite_sources(p3collide P3COLLIDE_SOURCES) add_component_library(p3collide SYMBOL BUILDING_PANDA_COLLIDE ${P3COLLIDE_HEADERS} ${P3COLLIDE_SOURCES}) target_link_libraries(p3collide p3tform) -target_interrogate(p3collide ALL) +target_interrogate(p3collide ALL EXTENSIONS ${P3COLLIDE_IGATEEXT}) if(NOT BUILD_METALIBS) install(TARGETS p3collide diff --git a/panda/src/collide/collisionPolygon.h b/panda/src/collide/collisionPolygon.h index 1aa740b15c..cb62afaa2d 100644 --- a/panda/src/collide/collisionPolygon.h +++ b/panda/src/collide/collisionPolygon.h @@ -59,6 +59,9 @@ PUBLISHED: bool is_valid() const; bool is_concave() const; + EXTENSION(static bool verify_points(PyObject *points)); + EXTENSION(void setup_points(PyObject *points)); + PUBLISHED: MAKE_SEQ_PROPERTY(points, get_num_points, get_point); MAKE_PROPERTY(valid, is_valid); @@ -71,6 +74,8 @@ public: const CullTraverserData &data, bool bounds_only) const; + void setup_points(const LPoint3 *begin, const LPoint3 *end); + virtual PStatCollector &get_volume_pcollector(); virtual PStatCollector &get_test_pcollector(); @@ -128,7 +133,6 @@ private: PN_stdfloat dist_to_polygon(const LPoint2 &p, LPoint2 &edge_p, const Points &points) const; void project(const LVector3 &axis, PN_stdfloat ¢er, PN_stdfloat &extent) const; - void setup_points(const LPoint3 *begin, const LPoint3 *end); INLINE LPoint2 to_2d(const LVecBase3 &point3d) const; INLINE void calc_to_3d_mat(LMatrix4 &to_3d_mat) const; INLINE void rederive_to_3d_mat(LMatrix4 &to_3d_mat) const; diff --git a/panda/src/collide/collisionPolygon_ext.cxx b/panda/src/collide/collisionPolygon_ext.cxx new file mode 100644 index 0000000000..398db4fa7b --- /dev/null +++ b/panda/src/collide/collisionPolygon_ext.cxx @@ -0,0 +1,86 @@ +/** + * 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 collisionPolygon_ext.cxx + * @author Derzsi Daniel + * @date 2020-10-13 + */ + +#include "collisionPolygon_ext.h" + +#ifdef HAVE_PYTHON + +#include "collisionPolygon.h" + +#ifdef STDFLOAT_DOUBLE +extern struct Dtool_PyTypedObject Dtool_LPoint3d; +#else +extern struct Dtool_PyTypedObject Dtool_LPoint3f; +#endif + +/** + * Verifies that the indicated Python list of points will define a + * CollisionPolygon. + */ +bool Extension:: +verify_points(PyObject *points) { + const pvector vec = convert_points(points); + const LPoint3 *verts_begin = &vec[0]; + const LPoint3 *verts_end = verts_begin + vec.size(); + + return CollisionPolygon::verify_points(verts_begin, verts_end); +} + +/** + * Initializes this CollisionPolygon with the given Python list of + * points. + */ +void Extension:: +setup_points(PyObject *points) { + const pvector vec = convert_points(points); + const LPoint3 *verts_begin = &vec[0]; + const LPoint3 *verts_end = verts_begin + vec.size(); + + _this->setup_points(verts_begin, verts_end); +} + +/** + * Converts a Python sequence to a list of LPoint3 objects. + */ +pvector Extension:: +convert_points(PyObject *points) { + pvector vec; + PyObject *seq = PySequence_Fast(points, "function expects a sequence"); + + if (!seq) { + return vec; + } + + PyObject **items = PySequence_Fast_ITEMS(seq); + Py_ssize_t len = PySequence_Fast_GET_SIZE(seq); + void *ptr; + + vec.reserve(len); + + for (Py_ssize_t i = 0; i < len; ++i) { +#ifdef STDFLOAT_DOUBLE + if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3d)) { +#else + if (ptr = DtoolInstance_UPCAST(items[i], Dtool_LPoint3f)) { +#endif + vec.push_back(*(LPoint3 *)ptr); + } else { + collide_cat.warning() << "Argument must be of LPoint3 type.\n"; + } + } + + Py_DECREF(seq); + return vec; +} + +#endif diff --git a/panda/src/collide/collisionPolygon_ext.h b/panda/src/collide/collisionPolygon_ext.h new file mode 100644 index 0000000000..ed2043066d --- /dev/null +++ b/panda/src/collide/collisionPolygon_ext.h @@ -0,0 +1,43 @@ +/** + * 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 collisionPolygon_ext.h + * @author Derzsi Daniel + * @date 2020-10-13 + */ + +#ifndef COLLISIONPOLYGON_EXT_H +#define COLLISIONPOLYGON_EXT_H + +#include "pandabase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "collisionPolygon.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for CollisionPolygon, which are called + * instead of any C++ methods with the same prototype. + * + * @since 1.11.0 + */ +template<> +class Extension : public ExtensionBase { +public: + static bool verify_points(PyObject *points); + void setup_points(PyObject *points); + +private: + static pvector convert_points(PyObject *points); +}; + +#endif // HAVE_PYTHON + +#endif diff --git a/panda/src/collide/p3collide_ext_composite.cxx b/panda/src/collide/p3collide_ext_composite.cxx new file mode 100644 index 0000000000..4e61af1ace --- /dev/null +++ b/panda/src/collide/p3collide_ext_composite.cxx @@ -0,0 +1 @@ +#include "collisionPolygon_ext.cxx" diff --git a/tests/collide/test_collision_polygon.py b/tests/collide/test_collision_polygon.py new file mode 100644 index 0000000000..4939123a7d --- /dev/null +++ b/tests/collide/test_collision_polygon.py @@ -0,0 +1,45 @@ +from panda3d import core + + +def test_collision_polygon_verify_not_enough_points(): + # Less than 3 points cannot create a polygon + assert not core.CollisionPolygon.verify_points([]) + assert not core.CollisionPolygon.verify_points([core.LPoint3(1, 0, 0)]) + assert not core.CollisionPolygon.verify_points([core.LPoint3(1, 0, 0), core.LPoint3(0, 0, 1)]) + + +def test_collision_polygon_verify_repeating_points(): + # Repeating points cannot create a polygon + assert not core.CollisionPolygon.verify_points([core.LPoint3(1, 0, 0), core.LPoint3(1, 0, 0), core.LPoint3(0, 0, 1)]) + assert not core.CollisionPolygon.verify_points([core.LPoint3(3, 6, 1), core.LPoint3(1, 3, 5), core.LPoint3(9, 1, 2), core.LPoint3(1, 3, 5)]) + + +def test_collision_polygon_verify_colinear_points(): + # Colinear points cannot create a polygon + assert not core.CollisionPolygon.verify_points([core.LPoint3(1, 2, 3), core.LPoint3(2, 3, 4), core.LPoint3(3, 4, 5)]) + assert not core.CollisionPolygon.verify_points([core.LPoint3(2, 1, 1), core.LPoint3(3, 2, 1), core.LPoint3(4, 3, 1)]) + + +def test_collision_polygon_verify_points(): + # Those should be regular, colinear points + assert core.CollisionPolygon.verify_points([core.LPoint3(1, 0, 0), core.LPoint3(0, 1, 0), core.LPoint3(0, 0, 1)]) + assert core.CollisionPolygon.verify_points([core.LPoint3(10, 2, 8), core.LPoint3(7, 1, 3), core.LPoint3(5, 9, 6)]) + assert core.CollisionPolygon.verify_points([core.LPoint3(3, -8, -7), core.LPoint3(9, 10, 8), core.LPoint3(7, 0, 10), core.LPoint3(-6, -2, 3)]) + assert core.CollisionPolygon.verify_points([core.LPoint3(-1, -3, -5), core.LPoint3(10, 3, -10), core.LPoint3(-10, 10, -4), core.LPoint3(0, 1, -4), core.LPoint3(-9, -2, 0)]) + + +def test_collision_polygon_setup_points(): + # Create empty collision polygon + polygon = core.CollisionPolygon(core.LVecBase3(0, 0, 0), core.LVecBase3(0, 0, 0), core.LVecBase3(0, 0, 0)) + assert not polygon.is_valid() + + # Test our setup method against a few test cases + for points in [ + [core.LPoint3(-1, -3, -5), core.LPoint3(10, 3, -10), core.LPoint3(-10, 10, -4), core.LPoint3(0, 1, -4), core.LPoint3(-9, -2, 0)], + [core.LPoint3(3, -8, -7), core.LPoint3(9, 10, 8), core.LPoint3(7, 0, 10), core.LPoint3(-6, -2, 3)], + [core.LPoint3(1, 0, 0), core.LPoint3(0, 1, 0), core.LPoint3(0, 0, 1)], + [core.LPoint3(10, 2, 8), core.LPoint3(7, 1, 3), core.LPoint3(5, 9, 6)] + ]: + polygon.setup_points(points) + assert polygon.is_valid() + assert polygon.get_num_points() == len(points)