Panda3D
collisionHandlerFluidPusher.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file collisionHandlerFluidPusher.cxx
10  * @author drose
11  * @date 2002-03-16
12  */
13 
15 #include "collisionNode.h"
16 #include "collisionEntry.h"
17 #include "collisionPolygon.h"
18 #include "collisionSphere.h"
19 #include "config_collide.h"
20 #include "dcast.h"
21 
22 TypeHandle CollisionHandlerFluidPusher::_type_handle;
23 
24 /**
25  *
26  */
27 CollisionHandlerFluidPusher::
28 CollisionHandlerFluidPusher() {
29  _wants_all_potential_collidees = true;
30 }
31 
32 /**
33  * Called between a begin_group() .. end_group() sequence for each collision
34  * that is detected.
35  */
38  nassertv(entry != nullptr);
39  // skip over CollisionHandlerPhysical::add_entry, since it filters out
40  // collidees by orientation; our collider can change direction mid-frame, so
41  // it may collide with something that would have been filtered out
43 
44  // filter out non-tangibles
45  if (entry->get_from()->is_tangible() &&
46  (!entry->has_into() || entry->get_into()->is_tangible())) {
47 
48  _from_entries[entry->get_from_node_path()].push_back(entry);
49  if (entry->collided()) {
50  _has_contact = true;
51  }
52  }
53 }
54 
55 /**
56  * Calculates a reasonable final position for a collider given a set of
57  * collidees
58  */
59 bool CollisionHandlerFluidPusher::
60 handle_entries() {
61  /*
62  This pusher repeatedly calculates the first collision, calculates a new
63  trajectory based on that collision, and repeats until the original motion is
64  exhausted or the collider becomes "stuck". This solves the "acute collisions"
65  problem where colliders could bounce their way through to the other side
66  of a wall.
67 
68  Pseudocode:
69 
70  INPUTS
71  PosA = collider's previous position
72  PosB = collider's current position
73  M = movement vector (PosB - PosA)
74  BV = bounding sphere that includes collider at PosA and PosB
75  CS = 'collision set', all 'collidables' within BV (collision polys, capsules, etc)
76 
77  VARIABLES
78  N = movement vector since most recent collision (or start of frame)
79  SCS = 'sub collision set', all collidables that could still be collided with
80  C = single collider currently being collided with
81  PosX = new position given movement along N interrupted by collision with C
82 
83  OUTPUTS
84  final position is PosX
85 
86  1. N = M, SCS = CS, PosX = PosB
87  2. compute, using SCS and N, which collidable C is the first collision
88  3. if no collision found, DONE
89  4. if movement in direction M is now blocked, then
90  PosX = initial point of contact with C along N, DONE
91  5. calculate PosX (and new N) assuming that there will be no more collisions
92  6. remove C from SCS (assumes that you can't collide against a solid more than once per frame)
93  7. go to 2
94  */
95  bool okflag = true;
96 
97  // if all we got was potential collisions, don't bother
98  if (!_has_contact) {
99  return okflag;
100  }
101 
102  // for every fluid mover being pushed...
103  FromEntries::iterator fei;
104  for (fei = _from_entries.begin(); fei != _from_entries.end(); ++fei) {
105  NodePath from_node_path = fei->first;
106  Entries *orig_entries = &fei->second;
107 
108  Colliders::iterator ci;
109  ci = _colliders.find(from_node_path);
110  if (ci == _colliders.end()) {
111  // Hmm, someone added a CollisionNode to a traverser and gave it this
112  // CollisionHandler pointer--but they didn't tell us about the node.
113  collide_cat.error()
114  << "CollisionHandlerFluidPusher doesn't know about "
115  << from_node_path << ", disabling.\n";
116  okflag = false;
117  } else {
118  ColliderDef &def = (*ci).second;
119 
120  // we do our math in this node's space
121  NodePath wrt_node(*_root);
122 
123  // extract the collision entries into a vector that we can safely modify
124  Entries entries(*orig_entries);
125 
126  // this is the original position delta for the entire frame, before
127  // collision response
128  LVector3 M(from_node_path.get_pos_delta(wrt_node));
129  // this is used to track position deltas every time we collide against a
130  // solid
131  LVector3 N(M);
132 
133  const LPoint3 orig_pos(from_node_path.get_pos(wrt_node));
134  CPT(TransformState) prev_trans(from_node_path.get_prev_transform(wrt_node));
135  const LPoint3 orig_prev_pos(prev_trans->get_pos());
136 
137  // currently we only support spheres as the collider
138  const CollisionSphere *sphere;
139  DCAST_INTO_R(sphere, entries.front()->get_from(), false);
140 
141  from_node_path.set_pos(wrt_node, 0,0,0);
142  LPoint3 sphere_offset = (sphere->get_center() *
143  from_node_path.get_transform(wrt_node)->get_mat());
144  from_node_path.set_pos(wrt_node, orig_pos);
145 
146  // this will hold the final calculated position at each iteration
147  LPoint3 candidate_final_pos(orig_pos);
148  // this holds the position before reacting to collisions
149  LPoint3 uncollided_pos(candidate_final_pos);
150 
151  // unit vector facing back into original direction of motion
152  LVector3 reverse_vec(-M);
153  reverse_vec.normalize();
154 
155  // unit vector pointing out to the right relative to the direction of
156  // motion, looking into the direction of motion
157  //const LVector3 right_unit(LVector3::up().cross(reverse_vec));
158 
159  // iterate until the mover runs out of movement or gets stuck
160  while (true) {
161  const CollisionEntry *C = nullptr;
162  // find the first (earliest) collision
163  Entries::const_iterator cei;
164  for (cei = entries.begin(); cei != entries.end(); ++cei) {
165  const CollisionEntry *entry = (*cei);
166  nassertr(entry != nullptr, false);
167  if (entry->collided() && ((C == nullptr) || (entry->get_t() < C->get_t()))) {
168  nassertr(from_node_path == entry->get_from_node_path(), false);
169  C = entry;
170  }
171  }
172 
173  // if no collisions, we're done
174  if (C == nullptr) {
175  break;
176  }
177 
178  // move back to initial contact position
179  LPoint3 contact_pos;
180  LVector3 contact_normal;
181 
182  if (!C->get_all_contact_info(wrt_node, contact_pos, contact_normal)) {
183  collide_cat.warning()
184  << "Cannot shove on " << from_node_path << " for collision into "
185  << C->get_into_node_path() << "; no contact pos/normal information.\n";
186  break;
187  }
188  // calculate the position of the target node at the point of contact
189  contact_pos -= sphere_offset;
190 
191  uncollided_pos = candidate_final_pos;
192  candidate_final_pos = contact_pos;
193 
194  LVector3 proj_surface_normal(contact_normal);
195 
196  LVector3 norm_proj_surface_normal(proj_surface_normal);
197  norm_proj_surface_normal.normalize();
198 
199  LVector3 blocked_movement(uncollided_pos - contact_pos);
200 
201  PN_stdfloat push_magnitude(-blocked_movement.dot(proj_surface_normal));
202  if (push_magnitude < 0.0f) {
203  // don't ever push into plane
204  candidate_final_pos = contact_pos;
205  } else {
206  // calculate new position given that you collided with this thing
207  // project the final position onto the plane of the obstruction
208  candidate_final_pos = uncollided_pos + (norm_proj_surface_normal * push_magnitude);
209  }
210 
211  from_node_path.set_pos(wrt_node, candidate_final_pos);
212  CPT(TransformState) prev_trans(from_node_path.get_prev_transform(wrt_node));
213  prev_trans = prev_trans->set_pos(contact_pos);
214  from_node_path.set_prev_transform(wrt_node, prev_trans);
215 
216  /*{
217  const LPoint3 new_pos(from_node_path.get_pos(wrt_node));
218  CPT(TransformState) new_prev_trans(from_node_path.get_prev_transform(wrt_node));
219  const LPoint3 new_prev_pos(new_prev_trans->get_pos());
220  }*/
221 
222  // recalculate the position delta
223  N = from_node_path.get_pos_delta(wrt_node);
224 
225  // calculate new collisions given new movement vector
226  Entries::iterator ei;
227  Entries new_entries;
228  for (ei = entries.begin(); ei != entries.end(); ++ei) {
229  CollisionEntry *entry = (*ei);
230  nassertr(entry != nullptr, false);
231  // skip the one we just collided against
232  if (entry != C) {
233  entry->_from_node_path = from_node_path;
234  entry->reset_collided();
235  PT(CollisionEntry) result = entry->get_from()->test_intersection(**ei);
236  if (result != nullptr && result != nullptr) {
237  new_entries.push_back(result);
238  }
239  }
240  }
241  entries.swap(new_entries);
242  }
243 
244  // put things back where they were
245  from_node_path.set_pos(wrt_node, orig_pos);
246  // restore the appropriate previous position
247  prev_trans = from_node_path.get_prev_transform(wrt_node);
248  prev_trans = prev_trans->set_pos(orig_prev_pos);
249  from_node_path.set_prev_transform(wrt_node, prev_trans);
250 
251  LVector3 net_shove(candidate_final_pos - orig_pos);
252  LVector3 force_normal(net_shove);
253  force_normal.normalize();
254 
255  // This is the part where the node actually gets moved:
256  def._target.set_pos(wrt_node, candidate_final_pos);
257 
258  // We call this to allow derived classes to do other fix-ups as they see
259  // fit:
260  apply_net_shove(def, net_shove, force_normal);
261  apply_linear_force(def, force_normal);
262  }
263  }
264 
265  return okflag;
266 }
get_from_node_path
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
NodePath find(const std::string &path) const
Searches for a node below the referenced node that matches the indicated string.
Definition: nodePath.cxx:314
Indicates a coordinate-system transform on vertices.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool collided() const
returns true if this represents an actual collision as opposed to a potential collision,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A spherical collision volume or object.
bool has_into() const
Returns true if the "into" solid is, in fact, a CollisionSolid, and its pointer is known (in which ca...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_into_node_path
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
get_mat
Returns the matrix that describes the transform.
bool get_all_contact_info(const NodePath &space, LPoint3 &contact_pos, LVector3 &contact_normal) const
Simultaneously transforms the contact position and contact normal of the collision into the indicated...
LVector3 get_pos_delta() const
Returns the delta vector from this node's position in the previous frame (according to set_prev_trans...
Definition: nodePath.cxx:1007
get_t
returns time value for this collision relative to other CollisionEntries
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines a single collision event.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
LPoint3 get_pos() const
Retrieves the translation component of the transform.
Definition: nodePath.cxx:992
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_into
Returns the CollisionSolid pointer for the particular solid was collided into.
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
virtual void add_entry(CollisionEntry *entry)
Called between a begin_group() .
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
const TransformState * get_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the complete transform object set on this node.
Definition: nodePath.cxx:758
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
virtual void add_entry(CollisionEntry *entry)
Called between a begin_group() .
void reset_collided()
prepare for another collision test
const TransformState * get_prev_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the transform that has been set as this node's "previous" position.
Definition: nodePath.cxx:846