Panda3D
collisionHandlerPusher.cxx
1 // Filename: collisionHandlerPusher.cxx
2 // Created by: drose (16Mar02)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "collisionHandlerPusher.h"
16 #include "collisionNode.h"
17 #include "collisionEntry.h"
18 #include "collisionPolygon.h"
19 #include "config_collide.h"
20 #include "dcast.h"
21 #include "epvector.h"
22 
23 TypeHandle CollisionHandlerPusher::_type_handle;
24 
25 ///////////////////////////////////////////////////////////////////
26 // Class : ShoveData
27 // Description : The ShoveData class is used within
28 // CollisionHandlerPusher::handle_entries(), to track
29 // multiple shoves onto a given collider. It's not
30 // exported outside this file.
31 ////////////////////////////////////////////////////////////////////
32 class ShoveData {
33 public:
34  LVector3 _vector;
35  PN_stdfloat _length;
36  bool _valid;
37  CollisionEntry *_entry;
38 };
39 
40 ////////////////////////////////////////////////////////////////////
41 // Function: CollisionHandlerPusher::Constructor
42 // Access: Public
43 // Description:
44 ////////////////////////////////////////////////////////////////////
45 CollisionHandlerPusher::
46 CollisionHandlerPusher() {
47  _horizontal = pushers_horizontal;
48 }
49 
50 ////////////////////////////////////////////////////////////////////
51 // Function: CollisionHandlerPusher::Destructor
52 // Access: Public, Virtual
53 // Description:
54 ////////////////////////////////////////////////////////////////////
55 CollisionHandlerPusher::
56 ~CollisionHandlerPusher() {
57 }
58 
59 ////////////////////////////////////////////////////////////////////
60 // Function: CollisionHandlerPusher::handle_entries
61 // Access: Protected, Virtual
62 // Description: Called by the parent class after all collisions have
63 // been detected, this manages the various collisions
64 // and moves around the nodes as necessary.
65 //
66 // The return value is normally true, but it may be
67 // false to indicate the CollisionTraverser should
68 // disable this handler from being called in the future.
69 ////////////////////////////////////////////////////////////////////
70 bool CollisionHandlerPusher::
71 handle_entries() {
72  bool okflag = true;
73 
74  FromEntries::const_iterator fi;
75  for (fi = _from_entries.begin(); fi != _from_entries.end(); ++fi) {
76  const NodePath &from_node_path = (*fi).first;
77  const Entries &entries = (*fi).second;
78 
79  Colliders::iterator ci;
80  ci = _colliders.find(from_node_path);
81  if (ci == _colliders.end()) {
82  // Hmm, someone added a CollisionNode to a traverser and gave
83  // it this CollisionHandler pointer--but they didn't tell us
84  // about the node.
85  collide_cat.error()
86  << "CollisionHandlerPusher doesn't know about "
87  << from_node_path << ", disabling.\n";
88  okflag = false;
89  } else {
90  ColliderDef &def = (*ci).second;
91  {
92  // How to apply multiple shoves from different solids onto the
93  // same collider? One's first intuition is to vector sum all
94  // the shoves. However, this causes problems when two parallel
95  // walls shove on the collider, because we end up with a double
96  // shove. We hack around this by testing if two shove vectors
97  // share nearly the same direction, and if so, we keep only the
98  // longer of the two.
99 
100  typedef epvector<ShoveData> Shoves;
101  Shoves shoves;
102 
103  Entries::const_iterator ei;
104  for (ei = entries.begin(); ei != entries.end(); ++ei) {
105  CollisionEntry *entry = (*ei);
106  nassertr(entry != (CollisionEntry *)NULL, false);
107  nassertr(from_node_path == entry->get_from_node_path(), false);
108 
109  LPoint3 surface_point;
110  LVector3 normal;
111  LPoint3 interior_point;
112 
113  if (!entry->get_all(def._target, surface_point, normal, interior_point)) {
114 #ifndef NDEBUG
115  if (collide_cat.is_debug()) {
116  collide_cat.debug()
117  << "Cannot shove on " << from_node_path << " for collision into "
118  << entry->get_into_node_path() << "; no normal/depth information.\n";
119  }
120 #endif
121  } else {
122  // Shove it just enough to clear the volume.
123  if (!surface_point.almost_equal(interior_point)) {
124  if (_horizontal) {
125  normal[2] = 0.0f;
126  }
127  // Just to be on the safe size, we normalize the normal
128  // vector, even though it really ought to be unit-length
129  // already (unless we just forced it horizontal, above).
130  normal.normalize();
131 
132  ShoveData sd;
133  sd._vector = normal;
134  sd._length = (surface_point - interior_point).length();
135  sd._valid = true;
136  sd._entry = entry;
137 
138  #ifndef NDEBUG
139  if (collide_cat.is_debug()) {
140  collide_cat.debug()
141  << "Shove on " << from_node_path << " from "
142  << entry->get_into_node_path() << ": " << sd._vector
143  << " times " << sd._length << "\n";
144  }
145  #endif
146 
147  shoves.push_back(sd);
148  }
149  }
150  }
151 
152  if (!shoves.empty()) {
153  // Now we look for two shoves that are largely in the same
154  // direction, so we can combine them into a single shove of
155  // the same magnitude; we also check for two shoves at 90
156  // degrees, so we can detect whether we are hitting an inner
157  // or an outer corner.
158 
159  Shoves::iterator si;
160  for (si = shoves.begin(); si != shoves.end(); ++si) {
161  ShoveData &sd = (*si);
162  Shoves::iterator sj;
163  for (sj = shoves.begin(); sj != si; ++sj) {
164  ShoveData &sd2 = (*sj);
165  if (sd2._valid) {
166  PN_stdfloat d = sd._vector.dot(sd2._vector);
167  if (collide_cat.is_debug()) {
168  collide_cat.debug()
169  << "Considering dot product " << d << "\n";
170  }
171 
172  if (d > 0.9) {
173  // These two shoves are largely in the same direction;
174  // save the larger of the two.
175  if (sd2._length < sd._length) {
176  sd2._valid = false;
177  } else {
178  sd._valid = false;
179  }
180  } else {
181  // These two shoves are not in the same direction.
182  // If they are both from polygons that are a child
183  // of the same node, try to determine the shape of
184  // the corner (convex or concave).
185  const CollisionSolid *s1 = sd._entry->get_into();
186  const CollisionSolid *s2 = sd2._entry->get_into();
187  if (s1 != (CollisionSolid *)NULL &&
188  s2 != (CollisionSolid *)NULL &&
189  s1->is_exact_type(CollisionPolygon::get_class_type()) &&
190  s2->is_exact_type(CollisionPolygon::get_class_type()) &&
191  sd._entry->get_into_node_path() ==
192  sd2._entry->get_into_node_path()) {
193  const CollisionPolygon *p1 = DCAST(CollisionPolygon, s1);
194  const CollisionPolygon *p2 = DCAST(CollisionPolygon, s2);
195  if (p1->dist_to_plane(p2->get_collision_origin()) < 0 &&
196  p2->dist_to_plane(p1->get_collision_origin()) < 0) {
197  // Each polygon is behind the other one. That
198  // means we have a convex corner, and therefore
199  // we should discard one of the shoves (or the
200  // user will get stuck coming at a convex
201  // corner).
202  if (collide_cat.is_debug()) {
203  collide_cat.debug()
204  << "Discarding shove from convex corner.\n";
205  }
206 
207  // This time, unlike the case of two parallel
208  // walls above, we discard the larger of the two
209  // shoves, not the smaller. This is because as
210  // we slide off the convex corner, the wall we
211  // are sliding away from will get a bigger and
212  // bigger shove--and we need to keep ignoring
213  // the same wall as we slide.
214  if (sd2._length < sd._length) {
215  sd._valid = false;
216  } else {
217  sd2._valid = false;
218  }
219  }
220  }
221  }
222  }
223  }
224  }
225 
226  // Now we can determine the net shove.
227  LVector3 net_shove(0.0f, 0.0f, 0.0f);
228  LVector3 force_normal(0.0f, 0.0f, 0.0f);
229  for (si = shoves.begin(); si != shoves.end(); ++si) {
230  const ShoveData &sd = (*si);
231  if (sd._valid) {
232  net_shove += sd._vector * sd._length;
233  force_normal += sd._vector;
234  }
235  }
236 
237  #ifndef NDEBUG
238  if (collide_cat.is_debug()) {
239  collide_cat.debug()
240  << "Net shove on " << from_node_path << " is: "
241  << net_shove << "\n";
242  }
243  #endif
244 
245  // This is the part where the node actually gets moved:
246  CPT(TransformState) trans = def._target.get_transform();
247  LVecBase3 pos = trans->get_pos();
248  pos += net_shove * trans->get_mat();
249  def._target.set_transform(trans->set_pos(pos));
250  def.updated_transform();
251 
252  // We call this to allow derived classes to do other
253  // fix-ups as they see fit:
254  apply_net_shove(def, net_shove, force_normal);
255  apply_linear_force(def, force_normal);
256  }
257  }
258  }
259  }
260 
261  return okflag;
262 }
263 
264 ////////////////////////////////////////////////////////////////////
265 // Function: CollisionHandlerPusher::apply_net_shove
266 // Access: Protected, Virtual
267 // Description: This is an optional hook for derived classes to do
268 // some work with the ColliderDef and the force vector.
269 ////////////////////////////////////////////////////////////////////
270 void CollisionHandlerPusher::
271 apply_net_shove(ColliderDef &def, const LVector3 &net_shove,
272  const LVector3 &force_normal) {
273 }
274 
275 ////////////////////////////////////////////////////////////////////
276 // Function: CollisionHandlerPusher::apply_linear_force
277 // Access: Protected, Virtual
278 // Description: This is an optional hook for derived classes to do
279 // some work with the ColliderDef and the force vector.
280 ////////////////////////////////////////////////////////////////////
281 void CollisionHandlerPusher::
282 apply_linear_force(ColliderDef &def, const LVector3 &force_normal) {
283 }
bool get_all(const NodePath &space, LPoint3 &surface_point, LVector3 &surface_normal, LPoint3 &interior_point) const
Simultaneously transforms the surface point, surface normal, and interior point of the collision into...
This is the base class for all three-component vectors and points.
Definition: lvecBase3.h:105
bool is_exact_type(TypeHandle handle) const
Returns true if the current object is the indicated type exactly.
Definition: typedObject.I:74
NodePath get_into_node_path() const
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
virtual LPoint3 get_collision_origin() const
Returns the point in space deemed to be the "origin" of the solid for collision purposes.
The abstract base class for all things that can collide with other things in the world, and all the things they can collide with (except geometry).
The ShoveData class is used within CollisionHandlerPusher::handle_entries(), to track multiple shoves...
This is a three-component vector distance (as opposed to a three-component point, which represents a ...
Definition: lvector3.h:100
This is a three-component point in space (as opposed to a three-component vector, which represents a ...
Definition: lpoint3.h:99
NodePath find(const string &path) const
Searches for a node below the referenced node that matches the indicated string.
Definition: nodePath.cxx:434
const CollisionSolid * get_into() const
Returns the CollisionSolid pointer for the particular solid was collided into.
Defines a single collision event.
NodePath get_from_node_path() const
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
bool normalize()
Normalizes the vector in place.
Definition: lvecBase3.h:783
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:165