27#define clamp(x, x_min, x_max) std::max(std::min(x, x_max), x_min)
37PStatCollector BulletWorld::_pstat_simulation(
"App:Bullet:DoPhysics:Simulation");
38PStatCollector BulletWorld::_pstat_p2b(
"App:Bullet:DoPhysics:SyncP2B");
39PStatCollector BulletWorld::_pstat_b2p(
"App:Bullet:DoPhysics:SyncB2P");
50 for (
size_t i = 0; i < 32; ++i) {
51 _filter_cb2._collide[i].
clear();
52 _filter_cb2._collide[i].
set_bit(i);
56 btScalar dx(bullet_sap_extents);
57 btVector3 extents(dx, dx, dx);
59 switch (bullet_broadphase_algorithm) {
60 case BA_sweep_and_prune:
61 _broadphase =
new btAxisSweep3(extents, extents, 1024);
63 case BA_dynamic_aabb_tree:
64 _broadphase =
new btDbvtBroadphase();
67 bullet_cat.error() <<
"no proper broadphase algorithm!" << endl;
69 nassertv(_broadphase);
72 _configuration =
new btSoftBodyRigidBodyCollisionConfiguration();
73 nassertv(_configuration);
76 _dispatcher =
new btCollisionDispatcher(_configuration);
77 nassertv(_dispatcher);
80 _solver =
new btSequentialImpulseConstraintSolver;
84 _world =
new btSoftRigidDynamicsWorld(_dispatcher, _broadphase, _solver, _configuration);
86 nassertv(_world->getPairCache());
88 _world->setWorldUserInfo(
this);
89 _world->setGravity(btVector3(0.0f, 0.0f, 0.0f));
92 _world->getPairCache()->setInternalGhostPairCallback(&_ghost_cb);
95 _filter_algorithm = bullet_filter_algorithm;
96 switch (_filter_algorithm) {
98 _filter_cb = &_filter_cb1;
101 _filter_cb = &_filter_cb2;
104 _filter_cb = &_filter_cb3;
107 bullet_cat.error() <<
"no proper filter algorithm!" << endl;
108 _filter_cb =
nullptr;
111 _world->getPairCache()->setOverlapFilterCallback(_filter_cb);
114 _tick_callback_obj =
nullptr;
117 _info.m_dispatcher = _dispatcher;
118 _info.m_broadphase = _broadphase;
119 _info.m_gravity.setValue(0.0f, 0.0f, 0.0f);
120 _info.m_sparsesdf.Initialize();
123 btGImpactCollisionAlgorithm::registerAlgorithm(_dispatcher);
126 _world->getDispatchInfo().m_enableSPU =
true;
127 _world->getDispatchInfo().m_useContinuous =
true;
128 _world->getSolverInfo().m_splitImpulse = bullet_split_impulse;
129 _world->getSolverInfo().m_numIterations = bullet_solver_iterations;
160 if (node != _debug) {
161 if (_debug !=
nullptr) {
162 _debug->_debug_stale =
false;
163 _debug->_debug_world =
nullptr;
167 _world->setDebugDrawer(&(_debug->_drawer));
178 if (_debug !=
nullptr) {
179 _debug->_debug_stale =
false;
180 _debug->_debug_world =
nullptr;
181 _world->setDebugDrawer(
nullptr);
190set_gravity(
const LVector3 &gravity) {
193 _world->setGravity(LVecBase3_to_btVector3(gravity));
194 _info.m_gravity.setValue(gravity.get_x(), gravity.get_y(), gravity.get_z());
201set_gravity(PN_stdfloat gx, PN_stdfloat gy, PN_stdfloat gz) {
204 _world->setGravity(btVector3((btScalar)gx, (btScalar)gy, (btScalar)gz));
205 _info.m_gravity.setValue((btScalar)gx, (btScalar)gy, (btScalar)gz);
211const LVector3 BulletWorld::
215 return btVector3_to_LVector3(_world->getGravity());
222do_physics(PN_stdfloat dt,
int max_substeps, PN_stdfloat stepsize) {
225 _pstat_physics.start();
227 int num_substeps = clamp(
int(dt / stepsize), 1, max_substeps);
231 do_sync_p2b(dt, num_substeps);
235 _pstat_simulation.start();
236 int n = _world->stepSimulation((btScalar)dt, max_substeps, (btScalar)stepsize);
237 _pstat_simulation.stop();
242 _info.m_sparsesdf.GarbageCollect(bullet_gc_lifetime);
247 _debug->do_sync_b2p(_world);
250 _pstat_physics.stop();
259do_sync_p2b(PN_stdfloat dt,
int num_substeps) {
266 softbody->do_sync_p2b();
270 ghost->do_sync_p2b();
274 character->do_sync_p2b(dt, num_substeps);
289 softbody->do_sync_b2p();
293 ghost->do_sync_b2p();
297 character->do_sync_b2p();
301 vehicle->do_sync_b2p();
312 if (object->
is_of_type(BulletGhostNode::get_class_type())) {
315 else if (object->
is_of_type(BulletRigidBodyNode::get_class_type())) {
318 else if (object->
is_of_type(BulletSoftBodyNode::get_class_type())) {
321 else if (object->
is_of_type(BulletBaseCharacterControllerNode::get_class_type())) {
324 else if (object->
is_of_type(BulletVehicle::get_class_type())) {
327 else if (object->
is_of_type(BulletConstraint::get_class_type())) {
331 bullet_cat->error() <<
"not a bullet world object!" << endl;
342 if (object->
is_of_type(BulletGhostNode::get_class_type())) {
345 else if (object->
is_of_type(BulletRigidBodyNode::get_class_type())) {
348 else if (object->
is_of_type(BulletSoftBodyNode::get_class_type())) {
351 else if (object->
is_of_type(BulletBaseCharacterControllerNode::get_class_type())) {
354 else if (object->
is_of_type(BulletVehicle::get_class_type())) {
357 else if (object->
is_of_type(BulletConstraint::get_class_type())) {
361 bullet_cat->error() <<
"not a bullet world object!" << endl;
372 do_attach_rigid_body(node);
382 do_remove_rigid_body(node);
392 do_attach_soft_body(node);
402 do_remove_soft_body(node);
412 do_attach_ghost(node);
422 do_remove_ghost(node);
432 do_attach_character(node);
442 do_remove_character(node);
452 do_attach_vehicle(vehicle);
462 do_remove_vehicle(vehicle);
473 do_attach_constraint(constraint, linked_collision);
483 do_remove_constraint(constraint);
494 btRigidBody *ptr = btRigidBody::upcast(node->get_object());
496 BulletRigidBodies::iterator found;
498 found = find(_bodies.begin(), _bodies.end(), ptnode);
500 if (found == _bodies.end()) {
501 _bodies.push_back(node);
502 _world->addRigidBody(ptr);
505 bullet_cat.warning() <<
"rigid body already attached" << endl;
517 btRigidBody *ptr = btRigidBody::upcast(node->get_object());
519 BulletRigidBodies::iterator found;
521 found = find(_bodies.begin(), _bodies.end(), ptnode);
523 if (found == _bodies.end()) {
524 bullet_cat.warning() <<
"rigid body not attached" << endl;
527 _bodies.erase(found);
528 _world->removeRigidBody(ptr);
540 btSoftBody *ptr = btSoftBody::upcast(node->get_object());
543 short group = btBroadphaseProxy::DefaultFilter;
544 short mask = btBroadphaseProxy::AllFilter;
546 BulletSoftBodies::iterator found;
548 found = find(_softbodies.begin(), _softbodies.end(), ptnode);
550 if (found == _softbodies.end()) {
551 _softbodies.push_back(node);
552 _world->addSoftBody(ptr, group, mask);
555 bullet_cat.warning() <<
"soft body already attached" << endl;
567 btSoftBody *ptr = btSoftBody::upcast(node->get_object());
569 BulletSoftBodies::iterator found;
571 found = find(_softbodies.begin(), _softbodies.end(), ptnode);
573 if (found == _softbodies.end()) {
574 bullet_cat.warning() <<
"soft body not attached" << endl;
577 _softbodies.erase(found);
578 _world->removeSoftBody(ptr);
603 short group = btBroadphaseProxy::SensorTrigger;
604 short mask = btBroadphaseProxy::AllFilter
605 & ~btBroadphaseProxy::StaticFilter
606 & ~btBroadphaseProxy::SensorTrigger;
608 btGhostObject *ptr = btGhostObject::upcast(node->get_object());
610 BulletGhosts::iterator found;
612 found = find(_ghosts.begin(), _ghosts.end(), ptnode);
614 if (found == _ghosts.end()) {
615 _ghosts.push_back(node);
616 _world->addCollisionObject(ptr, group, mask);
619 bullet_cat.warning() <<
"ghost already attached" << endl;
631 btGhostObject *ptr = btGhostObject::upcast(node->get_object());
633 BulletGhosts::iterator found;
635 found = find(_ghosts.begin(), _ghosts.end(), ptnode);
637 if (found == _ghosts.end()) {
638 bullet_cat.warning() <<
"ghost not attached" << endl;
641 _ghosts.erase(found);
642 _world->removeCollisionObject(ptr);
654 BulletCharacterControllers::iterator found;
656 found = find(_characters.begin(), _characters.end(), ptnode);
658 if (found == _characters.end()) {
659 _characters.push_back(node);
661 _world->addCollisionObject(node->get_ghost(),
662 btBroadphaseProxy::CharacterFilter,
663 btBroadphaseProxy::StaticFilter|btBroadphaseProxy::DefaultFilter);
665 _world->addCharacter(node->get_character());
668 bullet_cat.warning() <<
"character already attached" << endl;
680 BulletCharacterControllers::iterator found;
682 found = find(_characters.begin(), _characters.end(), ptnode);
684 if (found == _characters.end()) {
685 bullet_cat.warning() <<
"character not attached" << endl;
688 _characters.erase(found);
689 _world->removeCollisionObject(node->get_ghost());
690 _world->removeCharacter(node->get_character());
702 BulletVehicles::iterator found;
704 found = find(_vehicles.begin(), _vehicles.end(), ptvehicle);
706 if (found == _vehicles.end()) {
707 _vehicles.push_back(vehicle);
708 _world->addVehicle(vehicle->get_vehicle());
711 bullet_cat.warning() <<
"vehicle already attached" << endl;
725 BulletVehicles::iterator found;
727 found = find(_vehicles.begin(), _vehicles.end(), ptvehicle);
729 if (found == _vehicles.end()) {
730 bullet_cat.warning() <<
"vehicle not attached" << endl;
733 _vehicles.erase(found);
734 _world->removeVehicle(vehicle->get_vehicle());
746 nassertv(constraint);
748 BulletConstraints::iterator found;
750 found = find(_constraints.begin(), _constraints.end(), ptconstraint);
752 if (found == _constraints.end()) {
753 _constraints.push_back(constraint);
754 _world->addConstraint(constraint->ptr(), linked_collision);
757 bullet_cat.warning() <<
"constraint already attached" << endl;
767 nassertv(constraint);
769 BulletConstraints::iterator found;
771 found = find(_constraints.begin(), _constraints.end(), ptconstraint);
773 if (found == _constraints.end()) {
774 bullet_cat.warning() <<
"constraint not attached" << endl;
777 _constraints.erase(found);
778 _world->removeConstraint(constraint->ptr());
786get_num_rigid_bodies()
const {
789 return _bodies.size();
796get_rigid_body(
int idx)
const {
799 nassertr(idx >= 0 && idx < (
int)_bodies.size(),
nullptr);
807get_num_soft_bodies()
const {
810 return _softbodies.size();
817get_soft_body(
int idx)
const {
820 nassertr(idx >= 0 && idx < (
int)_softbodies.size(),
nullptr);
821 return _softbodies[idx];
828get_num_ghosts()
const {
831 return _ghosts.size();
838get_ghost(
int idx)
const {
841 nassertr(idx >= 0 && idx < (
int)_ghosts.size(),
nullptr);
849get_num_characters()
const {
852 return _characters.size();
859get_character(
int idx)
const {
862 nassertr(idx >= 0 && idx < (
int)_characters.size(),
nullptr);
863 return _characters[idx];
870get_num_vehicles()
const {
873 return _vehicles.size();
880get_vehicle(
int idx)
const {
883 nassertr(idx >= 0 && idx < (
int)_vehicles.size(),
nullptr);
884 return _vehicles[idx];
891get_num_constraints()
const {
894 return _constraints.size();
901get_constraint(
int idx)
const {
904 nassertr(idx >= 0 && idx < (
int)_constraints.size(),
nullptr);
905 return _constraints[idx];
912get_num_manifolds()
const {
915 return _world->getDispatcher()->getNumManifolds();
922ray_test_closest(
const LPoint3 &from_pos,
const LPoint3 &to_pos,
const CollideMask &mask)
const {
928 const btVector3 from = LVecBase3_to_btVector3(from_pos);
929 const btVector3 to = LVecBase3_to_btVector3(to_pos);
932 _world->rayTest(from, to, cb);
940ray_test_all(
const LPoint3 &from_pos,
const LPoint3 &to_pos,
const CollideMask &mask)
const {
946 const btVector3 from = LVecBase3_to_btVector3(from_pos);
947 const btVector3 to = LVecBase3_to_btVector3(to_pos);
950 _world->rayTest(from, to, cb);
965 const btConvexShape *convex = (
const btConvexShape *) shape->ptr();
971 const btVector3 from_pos = LVecBase3_to_btVector3(from_ts.
get_pos());
972 const btVector3 to_pos = LVecBase3_to_btVector3(to_ts.
get_pos());
973 const btTransform from_trans = LMatrix4_to_btTrans(from_ts.
get_mat());
974 const btTransform to_trans = LMatrix4_to_btTrans(to_ts.
get_mat());
977 _world->convexSweepTest(convex, from_trans, to_trans, cb, penetration);
989 nassertr(node0,
false);
990 nassertr(node1,
false);
991 nassertr(_filter_cb,
false);
993 btCollisionObject *obj0 = get_collision_object(node0);
994 btCollisionObject *obj1 = get_collision_object(node1);
996 nassertr(obj0,
false);
997 nassertr(obj1,
false);
999 btBroadphaseProxy *proxy0 = obj0->getBroadphaseHandle();
1000 btBroadphaseProxy *proxy1 = obj1->getBroadphaseHandle();
1002 nassertr(proxy0,
false);
1003 nassertr(proxy1,
false);
1005 return _filter_cb->needBroadphaseCollision(proxy0, proxy1);
1021 btCollisionObject *obj = get_collision_object(node);
1026#if BT_BULLET_VERSION >= 281
1028 cb.use_filter(_filter_cb, obj->getBroadphaseHandle());
1032 _world->contactTest(obj, cb);
1047 btCollisionObject *obj0 = get_collision_object(node0);
1048 btCollisionObject *obj1 = get_collision_object(node1);
1054 _world->contactPairTest(obj0, obj1, cb);
1064get_manifold(
int idx)
const {
1067 nassertr(idx < _dispatcher->getNumManifolds(),
nullptr);
1069 btPersistentManifold *ptr = _dispatcher->getManifoldByIndexInternal(idx);
1077__get_manifold(
int idx)
const {
1082 btPersistentManifold *ptr = _dispatcher->getManifoldByIndexInternal(idx);
1089btCollisionObject *BulletWorld::
1092 if (node->
is_of_type(BulletRigidBodyNode::get_class_type())) {
1095 else if (node->
is_of_type(BulletGhostNode::get_class_type())) {
1098 else if (node->
is_of_type(BulletBaseCharacterControllerNode::get_class_type())) {
1101 else if (node->
is_of_type(BulletSoftBodyNode::get_class_type())) {
1112set_group_collision_flag(
unsigned int group1,
unsigned int group2,
bool enable) {
1115 if (_filter_algorithm != FA_groups_mask) {
1116 bullet_cat.warning() <<
"filter algorithm is not 'groups-mask'" << endl;
1119 _filter_cb2._collide[group1].
set_bit_to(group2, enable);
1120 _filter_cb2._collide[group2].
set_bit_to(group1, enable);
1127get_group_collision_flag(
unsigned int group1,
unsigned int group2)
const {
1130 return _filter_cb2._collide[group1].
get_bit(group2);
1137set_force_update_all_aabbs(
bool force) {
1139 _world->setForceUpdateAllAabbs(force);
1146get_force_update_all_aabbs()
const {
1148 return _world->getForceUpdateAllAabbs();
1158 _world->getSolverInfo().m_solverMode |= SOLVER_DISABLE_VELOCITY_DEPENDENT_FRICTION_DIRECTION;
1159 _world->getSolverInfo().m_solverMode |= SOLVER_USE_2_FRICTION_DIRECTIONS;
1160 _world->getSolverInfo().m_solverMode |= SOLVER_ENABLE_FRICTION_DIRECTION_CACHING;
1162 bullet_contact_added_callback = obj;
1169clear_contact_added_callback() {
1172 _world->getSolverInfo().m_solverMode &= ~SOLVER_DISABLE_VELOCITY_DEPENDENT_FRICTION_DIRECTION;
1173 _world->getSolverInfo().m_solverMode &= ~SOLVER_USE_2_FRICTION_DIRECTIONS;
1174 _world->getSolverInfo().m_solverMode &= ~SOLVER_ENABLE_FRICTION_DIRECTION_CACHING;
1176 bullet_contact_added_callback =
nullptr;
1186 nassertv(obj !=
nullptr);
1187 _tick_callback_obj = obj;
1188 _world->setInternalTickCallback(&BulletWorld::tick_callback,
this, is_pretick);
1195clear_tick_callback() {
1198 _tick_callback_obj =
nullptr;
1199 _world->setInternalTickCallback(
nullptr);
1206tick_callback(btDynamicsWorld *world, btScalar timestep) {
1208 nassertv(world->getWorldUserInfo());
1230 nassertv(obj !=
nullptr);
1232 if (_filter_algorithm != FA_callback) {
1233 bullet_cat.warning() <<
"filter algorithm is not 'callback'" << endl;
1236 _filter_cb3._filter_callback_obj = obj;
1243clear_filter_callback() {
1246 _filter_cb3._filter_callback_obj =
nullptr;
1252bool BulletWorld::btFilterCallback1::
1253needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1)
const {
1255 btCollisionObject *obj0 = (btCollisionObject *) proxy0->m_clientObject;
1256 btCollisionObject *obj1 = (btCollisionObject *) proxy1->m_clientObject;
1258 nassertr(obj0,
false);
1259 nassertr(obj1,
false);
1264 nassertr(node0,
false);
1265 nassertr(node1,
false);
1270 return (mask0 & mask1) != 0;
1276bool BulletWorld::btFilterCallback2::
1277needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1)
const {
1279 btCollisionObject *obj0 = (btCollisionObject *) proxy0->m_clientObject;
1280 btCollisionObject *obj1 = (btCollisionObject *) proxy1->m_clientObject;
1282 nassertr(obj0,
false);
1283 nassertr(obj1,
false);
1288 nassertr(node0,
false);
1289 nassertr(node1,
false);
1296 for (
size_t i = 0; i < 32; ++i) {
1298 if ((_collide[i] & mask1) != 0)
1310bool BulletWorld::btFilterCallback3::
1311needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1)
const {
1313 nassertr(_filter_callback_obj,
false);
1315 btCollisionObject *obj0 = (btCollisionObject *) proxy0->m_clientObject;
1316 btCollisionObject *obj1 = (btCollisionObject *) proxy1->m_clientObject;
1318 nassertr(obj0,
false);
1319 nassertr(obj1,
false);
1324 nassertr(node0,
false);
1325 nassertr(node1,
false);
1328 _filter_callback_obj->do_callback(&cbdata);
1329 return cbdata.get_collide();
1336operator << (ostream &out, BulletWorld::BroadphaseAlgorithm algorithm) {
1338 switch (algorithm) {
1339 case BulletWorld::BA_sweep_and_prune:
1340 return out <<
"sap";
1342 case BulletWorld::BA_dynamic_aabb_tree:
1343 return out <<
"aabb";
1346 return out <<
"**invalid BulletWorld::BroadphaseAlgorithm(" << (int)algorithm <<
")**";
1353operator >> (istream &in, BulletWorld::BroadphaseAlgorithm &algorithm) {
1357 if (word ==
"sap") {
1358 algorithm = BulletWorld::BA_sweep_and_prune;
1360 else if (word ==
"aabb") {
1361 algorithm = BulletWorld::BA_dynamic_aabb_tree;
1365 <<
"Invalid BulletWorld::BroadphaseAlgorithm: " << word <<
"\n";
1366 algorithm = BulletWorld::BA_dynamic_aabb_tree;
1376operator << (ostream &out, BulletWorld::FilterAlgorithm algorithm) {
1378 switch (algorithm) {
1379 case BulletWorld::FA_mask:
1380 return out <<
"mask";
1381 case BulletWorld::FA_groups_mask:
1382 return out <<
"groups-mask";
1383 case BulletWorld::FA_callback:
1384 return out <<
"callback";
1386 return out <<
"**invalid BulletWorld::FilterAlgorithm(" << (int)algorithm <<
")**";
1393operator >> (istream &in, BulletWorld::FilterAlgorithm &algorithm) {
1397 if (word ==
"mask") {
1398 algorithm = BulletWorld::FA_mask;
1400 else if (word ==
"groups-mask") {
1401 algorithm = BulletWorld::FA_groups_mask;
1403 else if (word ==
"callback") {
1404 algorithm = BulletWorld::FA_callback;
1408 <<
"Invalid BulletWorld::FilterAlgorithm: " << word <<
"\n";
1409 algorithm = BulletWorld::FA_mask;
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_bit_to(int index, bool value)
Sets the nth bit either on or off, according to the indicated bool value.
void set_bit(int index)
Sets the nth bit on.
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
void clear()
Sets all the bits in the BitMask off.
Simulates a raycast vehicle which casts a ray per wheel at the ground as a cheap replacement for comp...
BulletRigidBodyNode * do_get_chassis()
Returns the chassis of this vehicle.
void attach_ghost(BulletGhostNode *node)
void remove_soft_body(BulletSoftBodyNode *node)
void attach_character(BulletBaseCharacterControllerNode *node)
void remove_character(BulletBaseCharacterControllerNode *node)
clear_debug_node
Removes a debug node that has been assigned to this BulletWorld.
BulletContactResult contact_test_pair(PandaNode *node0, PandaNode *node1) const
Performas a test if the two bodies given as parameters are in contact or not.
void remove_ghost(BulletGhostNode *node)
void remove_vehicle(BulletVehicle *vehicle)
BulletClosestHitSweepResult sweep_test_closest(BulletShape *shape, const TransformState &from_ts, const TransformState &to_ts, const CollideMask &mask=CollideMask::all_on(), PN_stdfloat penetration=0.0f) const
Performs a sweep test against all other shapes that match the given group mask.
void attach_vehicle(BulletVehicle *vehicle)
bool filter_test(PandaNode *node0, PandaNode *node1) const
Performs a test if two bodies should collide or not, based on the collision filter setting.
void attach_constraint(BulletConstraint *constraint, bool linked_collision=false)
Attaches a single constraint to a world.
void attach_rigid_body(BulletRigidBodyNode *node)
void attach_soft_body(BulletSoftBodyNode *node)
BulletContactResult contact_test(PandaNode *node, bool use_filter=false) const
Performas a test for all bodies which are currently in contact with the given body.
void remove_rigid_body(BulletRigidBodyNode *node)
void remove_constraint(BulletConstraint *constraint)
This is a generic object that can be assigned to a callback at various points in the rendering proces...
virtual void do_callback(CallbackData *cbdata)
This method called when the callback is triggered; it *replaces* the original function.
void release() const
Releases the lightMutex.
void acquire() const
Grabs the lightMutex if it is available.
Similar to MutexHolder, but for a light mutex.
This is a standard, non-reentrant mutex, similar to the Mutex class.
A lightweight class that represents a single element that may be timed and/or counted via stats.
A basic node of the scene graph or data graph.
get_into_collide_mask
Returns the "into" collide mask for this node.
TypeHandle is the identifier used to differentiate C++ class types.
This is an abstract class that all classes which use TypeHandle, and also provide virtual functions t...
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static BulletAllHitsRayResult empty()
Named constructor intended to be used for asserts with have to return a concrete value.
static BulletClosestHitRayResult empty()
Named constructor intended to be used for asserts with have to return a concrete value.
static BulletClosestHitSweepResult empty()
Named constructor intended to be used for asserts with have to return a concrete value.