mirror of
https://github.com/panda3d/panda3d.git
synced 2025-12-30 11:39:48 -06:00
collide: Add custom owner field to CollisionNode
This stores a weakref to a custom Python object, useful for being able to look up the game object corresponding to a particular collision node in an event without the downsides of Python tags Fixes #1703
This commit is contained in:
@@ -74,6 +74,8 @@ set(P3COLLIDE_IGATEEXT
|
||||
collisionHandlerPhysical_ext.h
|
||||
collisionHandlerQueue_ext.cxx
|
||||
collisionHandlerQueue_ext.h
|
||||
collisionNode_ext.cxx
|
||||
collisionNode_ext.h
|
||||
collisionPolygon_ext.cxx
|
||||
collisionPolygon_ext.h
|
||||
collisionTraverser_ext.cxx
|
||||
|
||||
@@ -166,3 +166,24 @@ INLINE CollideMask CollisionNode::
|
||||
get_default_collide_mask() {
|
||||
return default_collision_node_collide_mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the custom pointer set via set_owner().
|
||||
*/
|
||||
INLINE void *CollisionNode::
|
||||
get_owner() const {
|
||||
return _owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom pointer, together with an optional callback that will be
|
||||
* called when the node is deleted.
|
||||
*
|
||||
* The owner or the callback will not be copied along with the CollisionNode.
|
||||
*/
|
||||
INLINE void CollisionNode::
|
||||
set_owner(void *owner, OwnerCallback *callback) {
|
||||
clear_owner();
|
||||
_owner = owner;
|
||||
_owner_callback = callback;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ CollisionNode::
|
||||
CollisionNode(const std::string &name) :
|
||||
PandaNode(name),
|
||||
_from_collide_mask(get_default_collide_mask()),
|
||||
_collider_sort(0)
|
||||
_collider_sort(0),
|
||||
_owner(nullptr),
|
||||
_owner_callback(nullptr)
|
||||
{
|
||||
set_cull_callback();
|
||||
set_renderable();
|
||||
@@ -60,7 +62,9 @@ CollisionNode(const CollisionNode ©) :
|
||||
PandaNode(copy),
|
||||
_from_collide_mask(copy._from_collide_mask),
|
||||
_collider_sort(copy._collider_sort),
|
||||
_solids(copy._solids)
|
||||
_solids(copy._solids),
|
||||
_owner(nullptr),
|
||||
_owner_callback(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -69,6 +73,10 @@ CollisionNode(const CollisionNode ©) :
|
||||
*/
|
||||
CollisionNode::
|
||||
~CollisionNode() {
|
||||
if (_owner_callback != nullptr) {
|
||||
_owner_callback(_owner);
|
||||
_owner_callback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +259,18 @@ set_from_collide_mask(CollideMask mask) {
|
||||
_from_collide_mask = mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the owner that was previously set using set_owner().
|
||||
*/
|
||||
void CollisionNode::
|
||||
clear_owner() {
|
||||
if (_owner_callback != nullptr) {
|
||||
_owner_callback(_owner);
|
||||
}
|
||||
_owner = nullptr;
|
||||
_owner_callback = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when needed to recompute the node's _internal_bound object. Nodes
|
||||
* that contain anything of substance should redefine this to do the right
|
||||
|
||||
@@ -77,6 +77,22 @@ PUBLISHED:
|
||||
INLINE static CollideMask get_default_collide_mask();
|
||||
MAKE_PROPERTY(default_collide_mask, get_default_collide_mask);
|
||||
|
||||
public:
|
||||
typedef void (OwnerCallback)(void *);
|
||||
|
||||
INLINE void *get_owner() const;
|
||||
|
||||
#ifndef CPPPARSER
|
||||
INLINE void set_owner(void *owner, OwnerCallback *callback = nullptr);
|
||||
void clear_owner();
|
||||
#endif
|
||||
|
||||
EXTENSION(PyObject *get_owner() const);
|
||||
EXTENSION(void set_owner(PyObject *owner));
|
||||
|
||||
PUBLISHED:
|
||||
MAKE_PROPERTY(owner, get_owner, set_owner);
|
||||
|
||||
protected:
|
||||
virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
|
||||
int &internal_vertices,
|
||||
@@ -94,6 +110,9 @@ private:
|
||||
typedef pvector< COWPT(CollisionSolid) > Solids;
|
||||
Solids _solids;
|
||||
|
||||
void *_owner = nullptr;
|
||||
OwnerCallback *_owner_callback = nullptr;
|
||||
|
||||
friend class CollisionTraverser;
|
||||
|
||||
public:
|
||||
|
||||
62
panda/src/collide/collisionNode_ext.cxx
Normal file
62
panda/src/collide/collisionNode_ext.cxx
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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 collisionNode_ext.cxx
|
||||
* @author rdb
|
||||
* @date 2024-12-12
|
||||
*/
|
||||
|
||||
#include "collisionNode_ext.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "collisionNode.h"
|
||||
|
||||
/**
|
||||
* Returns the object previously set via set_owner(). If the object has been
|
||||
* destroyed, returns None.
|
||||
*/
|
||||
PyObject *Extension<CollisionNode>::
|
||||
get_owner() const {
|
||||
PyObject *owner = (PyObject *)_this->get_owner();
|
||||
|
||||
#if PY_VERSION_HEX >= 0x030D0000 // 3.13
|
||||
PyObject *strong_ref;
|
||||
int result = 0;
|
||||
if (owner != nullptr) {
|
||||
PyWeakref_GetRef(owner, &strong_ref);
|
||||
}
|
||||
if (result > 0) {
|
||||
return strong_ref;
|
||||
}
|
||||
else if (result == 0) {
|
||||
return Py_NewRef(Py_None);
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
return Py_NewRef(owner != nullptr ? PyWeakref_GetObject(owner) : Py_None);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a weak reference to the given object on the CollisionNode, for later
|
||||
* use in collision events and handlers.
|
||||
*/
|
||||
void Extension<CollisionNode>::
|
||||
set_owner(PyObject *owner) {
|
||||
if (owner != Py_None) {
|
||||
PyObject *ref = PyWeakref_NewRef(owner, nullptr);
|
||||
_this->set_owner(ref, [](void *obj) { Py_DECREF((PyObject *)obj); });
|
||||
} else {
|
||||
_this->clear_owner();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
40
panda/src/collide/collisionNode_ext.h
Normal file
40
panda/src/collide/collisionNode_ext.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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 collisionNode_ext.h
|
||||
* @author rdb
|
||||
* @date 2024-12-12
|
||||
*/
|
||||
|
||||
#ifndef COLLISIONNODE_EXT_H
|
||||
#define COLLISIONNODE_EXT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "collisionNode.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for CollisionNode, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
template<>
|
||||
class Extension<CollisionNode> : public ExtensionBase<CollisionNode> {
|
||||
public:
|
||||
PyObject *get_owner() const;
|
||||
void set_owner(PyObject *owner);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "collisionHandlerEvent_ext.cxx"
|
||||
#include "collisionHandlerPhysical_ext.cxx"
|
||||
#include "collisionHandlerQueue_ext.cxx"
|
||||
#include "collisionNode_ext.cxx"
|
||||
#include "collisionPolygon_ext.cxx"
|
||||
#include "collisionTraverser_ext.cxx"
|
||||
|
||||
@@ -884,6 +884,8 @@ PUBLISHED:
|
||||
INLINE void set_collide_mask(CollideMask new_mask, CollideMask bits_to_change = CollideMask::all_on(),
|
||||
TypeHandle node_type = TypeHandle::none());
|
||||
|
||||
EXTENSION(void set_collide_owner(PyObject *owner));
|
||||
|
||||
// Comparison methods
|
||||
INLINE bool operator == (const NodePath &other) const;
|
||||
INLINE bool operator != (const NodePath &other) const;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "typedWritable_ext.h"
|
||||
#include "shaderInput_ext.h"
|
||||
#include "shaderAttrib.h"
|
||||
#include "collisionNode.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
@@ -327,4 +328,62 @@ get_tight_bounds(const NodePath &other) const {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively assigns a weak reference to the given owner object to all
|
||||
* collision nodes at this level and below.
|
||||
*
|
||||
* You may pass in None to clear all owners below this level.
|
||||
*
|
||||
* Note that there is no corresponding get_collide_owner(), since there may be
|
||||
* multiple nodes below this level with different owners.
|
||||
*/
|
||||
void Extension<NodePath>::
|
||||
set_collide_owner(PyObject *owner) {
|
||||
if (owner != Py_None) {
|
||||
PyObject *ref = PyWeakref_NewRef(owner, nullptr);
|
||||
if (ref != nullptr) {
|
||||
r_set_collide_owner(_this->node(), ref);
|
||||
Py_DECREF(ref);
|
||||
}
|
||||
} else {
|
||||
r_clear_collide_owner(_this->node());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive implementation of set_collide_owner. weakref must be a weak ref
|
||||
* object.
|
||||
*/
|
||||
void Extension<NodePath>::
|
||||
r_set_collide_owner(PandaNode *node, PyObject *weakref) {
|
||||
if (node->is_collision_node()) {
|
||||
CollisionNode *cnode = (CollisionNode *)node;
|
||||
cnode->set_owner(Py_NewRef(weakref),
|
||||
[](void *obj) { Py_DECREF((PyObject *)obj); });
|
||||
}
|
||||
|
||||
PandaNode::Children cr = node->get_children();
|
||||
int num_children = cr.get_num_children();
|
||||
for (int i = 0; i < num_children; i++) {
|
||||
r_set_collide_owner(cr.get_child(i), weakref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive implementation of set_collide_owner(None).
|
||||
*/
|
||||
void Extension<NodePath>::
|
||||
r_clear_collide_owner(PandaNode *node) {
|
||||
if (node->is_collision_node()) {
|
||||
CollisionNode *cnode = (CollisionNode *)node;
|
||||
cnode->clear_owner();
|
||||
}
|
||||
|
||||
PandaNode::Children cr = node->get_children();
|
||||
int num_children = cr.get_num_children();
|
||||
for (int i = 0; i < num_children; i++) {
|
||||
r_clear_collide_owner(cr.get_child(i));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
@@ -54,6 +54,12 @@ public:
|
||||
void set_shader_inputs(PyObject *args, PyObject *kwargs);
|
||||
|
||||
PyObject *get_tight_bounds(const NodePath &other = NodePath()) const;
|
||||
|
||||
void set_collide_owner(PyObject *owner);
|
||||
|
||||
private:
|
||||
static void r_set_collide_owner(PandaNode *node, PyObject *weakref);
|
||||
static void r_clear_collide_owner(PandaNode *node);
|
||||
};
|
||||
|
||||
BEGIN_PUBLISH
|
||||
|
||||
46
tests/collide/test_collision_node.py
Normal file
46
tests/collide/test_collision_node.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from panda3d.core import *
|
||||
import sys
|
||||
|
||||
|
||||
class CustomObject:
|
||||
pass
|
||||
|
||||
|
||||
def test_collision_node_owner():
|
||||
owner = CustomObject()
|
||||
initial_rc = sys.getrefcount(owner)
|
||||
|
||||
node = CollisionNode("node")
|
||||
assert node.owner is None
|
||||
|
||||
node.owner = owner
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
assert node.owner is owner
|
||||
|
||||
node.owner = owner
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
assert node.owner is owner
|
||||
|
||||
node.owner = None
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
assert node.owner is None
|
||||
|
||||
del node
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
|
||||
# Assign owner and then delete node
|
||||
node = CollisionNode("node")
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
node.owner = owner
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
del node
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
|
||||
# Delete owner and see what happens to the node
|
||||
node = CollisionNode("node")
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
node.owner = owner
|
||||
assert sys.getrefcount(owner) == initial_rc
|
||||
del owner
|
||||
assert node.owner is None
|
||||
|
||||
@@ -312,3 +312,47 @@ def test_nodepath_replace_texture_none():
|
||||
assert path2.get_texture() == tex1
|
||||
path1.replace_texture(tex1, None)
|
||||
assert path2.get_texture() is None
|
||||
|
||||
|
||||
def test_nodepath_set_collide_owner():
|
||||
from panda3d.core import NodePath, CollisionNode
|
||||
|
||||
class CustomOwner:
|
||||
pass
|
||||
|
||||
owner1 = CustomOwner()
|
||||
owner2 = CustomOwner()
|
||||
owner3 = CustomOwner()
|
||||
|
||||
root = NodePath("root")
|
||||
model1 = root.attach_new_node("model1")
|
||||
collider1 = model1.attach_new_node(CollisionNode("collider1"))
|
||||
collider2 = model1.attach_new_node(CollisionNode("collider2"))
|
||||
model2 = root.attach_new_node("model2")
|
||||
collider3 = model2.attach_new_node(CollisionNode("collider3"))
|
||||
|
||||
root.set_collide_owner(owner1)
|
||||
assert collider1.node().owner is owner1
|
||||
assert collider2.node().owner is owner1
|
||||
assert collider3.node().owner is owner1
|
||||
|
||||
model1.set_collide_owner(None)
|
||||
assert collider1.node().owner is None
|
||||
assert collider2.node().owner is None
|
||||
assert collider3.node().owner is owner1
|
||||
|
||||
collider2.set_collide_owner(owner2)
|
||||
assert collider1.node().owner is None
|
||||
assert collider2.node().owner is owner2
|
||||
assert collider3.node().owner is owner1
|
||||
|
||||
del owner1
|
||||
assert collider1.node().owner is None
|
||||
assert collider2.node().owner is owner2
|
||||
assert collider3.node().owner is None
|
||||
|
||||
root.set_collide_owner(owner3)
|
||||
model2.set_collide_owner(owner2)
|
||||
assert collider1.node().owner is owner3
|
||||
assert collider2.node().owner is owner3
|
||||
assert collider3.node().owner is owner2
|
||||
|
||||
Reference in New Issue
Block a user