Panda3D
Loading...
Searching...
No Matches
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
22TypeHandle CollisionHandlerFluidPusher::_type_handle;
23
24/**
25 *
26 */
27CollisionHandlerFluidPusher::
28CollisionHandlerFluidPusher() {
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 */
59bool CollisionHandlerFluidPusher::
60handle_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}
Defines a single collision event.
get_t
returns time value for this collision relative to other CollisionEntries
void reset_collided()
prepare for another collision test
get_into
Returns the CollisionSolid pointer for the particular solid was collided into.
bool has_into() const
Returns true if the "into" solid is, in fact, a CollisionSolid, and its pointer is known (in which ca...
get_from_node_path
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
bool collided() const
returns true if this represents an actual collision as opposed to a potential collision,...
get_into_node_path
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
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...
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() .
virtual void add_entry(CollisionEntry *entry)
Called between a begin_group() .
A spherical collision volume or object.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition nodePath.h:159
LVector3 get_pos_delta() const
Returns the delta vector from this node's position in the previous frame (according to set_prev_trans...
LPoint3 get_pos() const
Retrieves the translation component of the transform.
NodePath find(const std::string &path) const
Searches for a node below the referenced node that matches the indicated string.
Definition nodePath.cxx:315
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:882
const TransformState * get_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the complete transform object set on this node.
Definition nodePath.cxx:794
Indicates a coordinate-system transform on vertices.
get_mat
Returns the matrix that describes the transform.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.