Panda3D
Loading...
Searching...
No Matches
collisionPlane.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 collisionPlane.cxx
10 * @author drose
11 * @date 2000-04-25
12 */
13
14#include "collisionPlane.h"
15#include "collisionHandler.h"
16#include "collisionEntry.h"
17#include "collisionSphere.h"
18#include "collisionLine.h"
19#include "collisionRay.h"
20#include "collisionSegment.h"
21#include "collisionCapsule.h"
22#include "collisionParabola.h"
23#include "config_collide.h"
24#include "pointerToArray.h"
25#include "geomNode.h"
26#include "geom.h"
27#include "datagram.h"
28#include "datagramIterator.h"
29#include "bamReader.h"
30#include "bamWriter.h"
31#include "boundingPlane.h"
32#include "geom.h"
33#include "geomTrifans.h"
34#include "geomLinestrips.h"
35#include "geomVertexWriter.h"
36
37PStatCollector CollisionPlane::_volume_pcollector("Collision Volumes:CollisionPlane");
38PStatCollector CollisionPlane::_test_pcollector("Collision Tests:CollisionPlane");
39TypeHandle CollisionPlane::_type_handle;
40
41/**
42 *
43 */
44CollisionSolid *CollisionPlane::
45make_copy() {
46 return new CollisionPlane(*this);
47}
48
49/**
50 * Transforms the solid by the indicated matrix.
51 */
53xform(const LMatrix4 &mat) {
54 _plane = _plane * mat;
56}
57
58/**
59 * Returns the point in space deemed to be the "origin" of the solid for
60 * collision purposes. The closest intersection point to this origin point is
61 * considered to be the most significant.
62 */
65 // No real sensible origin exists for a plane. We return 0, 0, 0, without
66 // even bothering to ensure that that point exists on the plane.
67 return LPoint3::origin();
68}
69
70/**
71 * Returns a PStatCollector that is used to count the number of bounding
72 * volume tests made against a solid of this type in a given frame.
73 */
76 return _volume_pcollector;
77}
78
79/**
80 * Returns a PStatCollector that is used to count the number of intersection
81 * tests made against a solid of this type in a given frame.
82 */
85 return _test_pcollector;
86}
87
88/**
89 *
90 */
91void CollisionPlane::
92output(std::ostream &out) const {
93 out << "cplane, (" << _plane << ")";
94}
95
96/**
97 *
98 */
99PT(BoundingVolume) CollisionPlane::
100compute_internal_bounds() const {
101 return new BoundingPlane(_plane);
102}
103
104/**
105 *
106 */
107PT(CollisionEntry) CollisionPlane::
108test_intersection_from_sphere(const CollisionEntry &entry) const {
109 const CollisionSphere *sphere;
110 DCAST_INTO_R(sphere, entry.get_from(), nullptr);
111
112 CPT(TransformState) wrt_space = entry.get_wrt_space();
113 const LMatrix4 &wrt_mat = wrt_space->get_mat();
114
115 LPoint3 from_center = sphere->get_center() * wrt_mat;
116 LVector3 from_radius_v =
117 LVector3(sphere->get_radius(), 0.0f, 0.0f) * wrt_mat;
118 PN_stdfloat from_radius = length(from_radius_v);
119
120 PN_stdfloat dist = dist_to_plane(from_center);
121 if (dist > from_radius) {
122 // No intersection.
123 return nullptr;
124 }
125
126 if (collide_cat.is_debug()) {
127 collide_cat.debug()
128 << "intersection detected from " << entry.get_from_node_path()
129 << " into " << entry.get_into_node_path() << "\n";
130 }
131 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
132
133 LVector3 normal = (
135 ? get_effective_normal() : get_normal();
136
137 new_entry->set_surface_normal(normal);
138 new_entry->set_surface_point(from_center - get_normal() * dist);
139 new_entry->set_interior_point(from_center - get_normal() * from_radius);
140
141 return new_entry;
142}
143
144/**
145 *
146 */
147PT(CollisionEntry) CollisionPlane::
148test_intersection_from_line(const CollisionEntry &entry) const {
149 const CollisionLine *line;
150 DCAST_INTO_R(line, entry.get_from(), nullptr);
151
152 CPT(TransformState) wrt_space = entry.get_wrt_space();
153 const LMatrix4 &wrt_mat = wrt_space->get_mat();
154
155 LPoint3 from_origin = line->get_origin() * wrt_mat;
156 LVector3 from_direction = line->get_direction() * wrt_mat;
157
158 PN_stdfloat t;
159 if (!_plane.intersects_line(t, from_origin, from_direction)) {
160 // No intersection. The line is parallel to the plane.
161
162 if (_plane.dist_to_plane(from_origin) > 0.0f) {
163 // The line is entirely in front of the plane.
164 return nullptr;
165 }
166
167 // The line is entirely behind the plane.
168 t = 0.0f;
169 }
170
171 if (collide_cat.is_debug()) {
172 collide_cat.debug()
173 << "intersection detected from " << entry.get_from_node_path()
174 << " into " << entry.get_into_node_path() << "\n";
175 }
176 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
177
178 LPoint3 into_intersection_point = from_origin + t * from_direction;
179
180 LVector3 normal =
182 ? get_effective_normal() : get_normal();
183
184 new_entry->set_surface_normal(normal);
185 new_entry->set_surface_point(into_intersection_point);
186
187 return new_entry;
188}
189
190/**
191 *
192 */
193PT(CollisionEntry) CollisionPlane::
194test_intersection_from_ray(const CollisionEntry &entry) const {
195 const CollisionRay *ray;
196 DCAST_INTO_R(ray, entry.get_from(), nullptr);
197
198 CPT(TransformState) wrt_space = entry.get_wrt_space();
199 const LMatrix4 &wrt_mat = wrt_space->get_mat();
200
201 LPoint3 from_origin = ray->get_origin() * wrt_mat;
202 LVector3 from_direction = ray->get_direction() * wrt_mat;
203
204 PN_stdfloat t;
205
206 if (_plane.dist_to_plane(from_origin) < 0.0f) {
207 // The origin of the ray is behind the plane, so we don't need to test
208 // further.
209 t = 0.0f;
210
211 } else {
212 if (!_plane.intersects_line(t, from_origin, from_direction)) {
213 // No intersection. The ray is parallel to the plane.
214 return nullptr;
215 }
216
217 if (t < 0.0f) {
218 // The intersection point is before the start of the ray, and so the ray
219 // is entirely in front of the plane.
220 return nullptr;
221 }
222 }
223
224 if (collide_cat.is_debug()) {
225 collide_cat.debug()
226 << "intersection detected from " << entry.get_from_node_path()
227 << " into " << entry.get_into_node_path() << "\n";
228 }
229 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
230
231 LPoint3 into_intersection_point = from_origin + t * from_direction;
232
233 LVector3 normal =
235 ? get_effective_normal() : get_normal();
236
237 new_entry->set_surface_normal(normal);
238 new_entry->set_surface_point(into_intersection_point);
239
240 return new_entry;
241}
242
243/**
244 *
245 */
246PT(CollisionEntry) CollisionPlane::
247test_intersection_from_segment(const CollisionEntry &entry) const {
248 const CollisionSegment *segment;
249 DCAST_INTO_R(segment, entry.get_from(), nullptr);
250
251 CPT(TransformState) wrt_space = entry.get_wrt_space();
252 const LMatrix4 &wrt_mat = wrt_space->get_mat();
253
254 LPoint3 from_a = segment->get_point_a() * wrt_mat;
255 LPoint3 from_b = segment->get_point_b() * wrt_mat;
256
257 PN_stdfloat dist_a = _plane.dist_to_plane(from_a);
258 PN_stdfloat dist_b = _plane.dist_to_plane(from_b);
259
260 if (dist_a >= 0.0f && dist_b >= 0.0f) {
261 // Entirely in front of the plane means no intersection.
262 return nullptr;
263 }
264
265 if (collide_cat.is_debug()) {
266 collide_cat.debug()
267 << "intersection detected from " << entry.get_from_node_path()
268 << " into " << entry.get_into_node_path() << "\n";
269 }
270 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
271
272 LVector3 normal = (has_effective_normal() && segment->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
273 new_entry->set_surface_normal(normal);
274
275 PN_stdfloat t;
276 LVector3 from_direction = from_b - from_a;
277 if (_plane.intersects_line(t, from_a, from_direction)) {
278 // It intersects the plane.
279 if (t >= 0.0f && t <= 1.0f) {
280 // Within the segment! Yay, that means we have a surface point.
281 new_entry->set_surface_point(from_a + t * from_direction);
282 }
283 }
284
285 if (dist_a < dist_b) {
286 // Point A penetrates deeper.
287 new_entry->set_interior_point(from_a);
288
289 } else if (dist_b < dist_a) {
290 // No, point B does.
291 new_entry->set_interior_point(from_b);
292
293 } else {
294 // Let's be fair and choose the center of the segment.
295 new_entry->set_interior_point((from_a + from_b) * 0.5);
296 }
297
298 return new_entry;
299}
300
301/**
302 *
303 */
304PT(CollisionEntry) CollisionPlane::
305test_intersection_from_capsule(const CollisionEntry &entry) const {
306 const CollisionCapsule *capsule;
307 DCAST_INTO_R(capsule, entry.get_from(), nullptr);
308
309 CPT(TransformState) wrt_space = entry.get_wrt_space();
310 const LMatrix4 &wrt_mat = wrt_space->get_mat();
311
312 LPoint3 from_a = capsule->get_point_a() * wrt_mat;
313 LPoint3 from_b = capsule->get_point_b() * wrt_mat;
314 LVector3 from_radius_v =
315 LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
316 PN_stdfloat from_radius = length(from_radius_v);
317
318 PN_stdfloat dist_a = _plane.dist_to_plane(from_a);
319 PN_stdfloat dist_b = _plane.dist_to_plane(from_b);
320
321 if (dist_a >= from_radius && dist_b >= from_radius) {
322 // Entirely in front of the plane means no intersection.
323 return nullptr;
324 }
325
326 if (collide_cat.is_debug()) {
327 collide_cat.debug()
328 << "intersection detected from " << entry.get_from_node_path()
329 << " into " << entry.get_into_node_path() << "\n";
330 }
331 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
332
333 LVector3 normal = (has_effective_normal() && capsule->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
334 new_entry->set_surface_normal(normal);
335
336 PN_stdfloat t;
337 LVector3 from_direction = from_b - from_a;
338 if (_plane.intersects_line(t, from_a, from_direction)) {
339 // It intersects the plane.
340 if (t >= 1.0f) {
341 new_entry->set_surface_point(from_b - get_normal() * dist_b);
342
343 } else if (t <= 0.0f) {
344 new_entry->set_surface_point(from_a - get_normal() * dist_a);
345
346 } else {
347 // Within the capsule! Yay, that means we have a surface point.
348 new_entry->set_surface_point(from_a + t * from_direction);
349 }
350 } else {
351 // If it's completely parallel, pretend it's colliding in the center of
352 // the capsule.
353 new_entry->set_surface_point(from_a + 0.5f * from_direction - get_normal() * dist_a);
354 }
355
356 if (IS_NEARLY_EQUAL(dist_a, dist_b)) {
357 // Let's be fair and choose the center of the capsule.
358 new_entry->set_interior_point(from_a + 0.5f * from_direction - get_normal() * from_radius);
359
360 } else if (dist_a < dist_b) {
361 // Point A penetrates deeper.
362 new_entry->set_interior_point(from_a - get_normal() * from_radius);
363
364 } else if (dist_b < dist_a) {
365 // No, point B does.
366 new_entry->set_interior_point(from_b - get_normal() * from_radius);
367 }
368
369 return new_entry;
370}
371
372/**
373 * This is part of the double-dispatch implementation of test_intersection().
374 * It is called when the "from" object is a parabola.
375 */
376PT(CollisionEntry) CollisionPlane::
377test_intersection_from_parabola(const CollisionEntry &entry) const {
378 const CollisionParabola *parabola;
379 DCAST_INTO_R(parabola, entry.get_from(), nullptr);
380
381 CPT(TransformState) wrt_space = entry.get_wrt_space();
382 const LMatrix4 &wrt_mat = wrt_space->get_mat();
383
384 // Convert the parabola into local coordinate space.
385 LParabola local_p(parabola->get_parabola());
386 local_p.xform(wrt_mat);
387
388 PN_stdfloat t;
389 if (_plane.dist_to_plane(local_p.calc_point(parabola->get_t1())) < 0.0f) {
390 // The first point in the parabola is behind the plane, so we don't need
391 // to test further.
392 t = parabola->get_t1();
393
394 } else {
395 PN_stdfloat t1, t2;
396 if (!get_plane().intersects_parabola(t1, t2, local_p)) {
397 // No intersection. The infinite parabola is entirely in front of the
398 // plane.
399 return nullptr;
400 }
401
402 if (t1 >= parabola->get_t1() && t1 <= parabola->get_t2()) {
403 if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
404 // Both intersection points are within our segment of the parabola.
405 // Choose the first of the two.
406 t = std::min(t1, t2);
407 } else {
408 // Only t1 is within our segment.
409 t = t1;
410 }
411
412 } else if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
413 // Only t2 is within our segment.
414 t = t2;
415
416 } else {
417 // Neither intersection point is within our segment.
418 return nullptr;
419 }
420 }
421
422 if (collide_cat.is_debug()) {
423 collide_cat.debug()
424 << "intersection detected from " << entry.get_from_node_path()
425 << " into " << entry.get_into_node_path() << "\n";
426 }
427 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
428
429 LPoint3 into_intersection_point = local_p.calc_point(t);
430
431 LVector3 normal = (has_effective_normal() && parabola->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
432
433 new_entry->set_surface_normal(normal);
434 new_entry->set_surface_point(into_intersection_point);
435
436 return new_entry;
437}
438
439/**
440 * This is part of the double-dispatch implementation of test_intersection().
441 * It is called when the "from" object is a box.
442 */
443PT(CollisionEntry) CollisionPlane::
444test_intersection_from_box(const CollisionEntry &entry) const {
445 const CollisionBox *box;
446 DCAST_INTO_R(box, entry.get_from(), nullptr);
447
448 CPT(TransformState) wrt_space = entry.get_wrt_space();
449 const LMatrix4 &wrt_mat = wrt_space->get_mat();
450
451 LPoint3 from_center = box->get_center() * wrt_mat;
452 LVector3 from_extents = box->get_dimensions() * 0.5f;
453 PN_stdfloat dist = _plane.dist_to_plane(from_center);
454
455 // Determine the basis vectors describing the box.
456 LVecBase3 box_x = wrt_mat.get_row3(0) * from_extents[0];
457 LVecBase3 box_y = wrt_mat.get_row3(1) * from_extents[1];
458 LVecBase3 box_z = wrt_mat.get_row3(2) * from_extents[2];
459
460 // Project the box onto the normal vector of the plane to determine whether
461 // there is a separating axis.
462 PN_stdfloat dx = box_x.dot(_plane.get_normal());
463 PN_stdfloat dy = box_y.dot(_plane.get_normal());
464 PN_stdfloat dz = box_z.dot(_plane.get_normal());
465 PN_stdfloat depth = dist - (cabs(dx) + cabs(dy) + cabs(dz));
466
467 if (depth > 0) {
468 // No collision.
469 return nullptr;
470 }
471
472 if (collide_cat.is_debug()) {
473 collide_cat.debug()
474 << "intersection detected from " << entry.get_from_node_path()
475 << " into " << entry.get_into_node_path() << "\n";
476 }
477 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
478
479 LVector3 normal = (has_effective_normal() && box->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
480 new_entry->set_surface_normal(normal);
481
482 // Determine which point on the cube will be the interior point. If the
483 // points are equally close, this chooses their center instead.
484 LPoint3 interior_point = from_center +
485 box_x * ((dx < 0) - (dx > 0)) +
486 box_y * ((dy < 0) - (dy > 0)) +
487 box_z * ((dz < 0) - (dz > 0));
488
489 // The surface point is the interior point projected onto the plane.
490 new_entry->set_surface_point(interior_point - get_normal() * depth);
491 new_entry->set_interior_point(interior_point);
492
493 return new_entry;
494}
495
496/**
497 * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this
498 * solid.
499 */
500void CollisionPlane::
501fill_viz_geom() {
502 if (collide_cat.is_debug()) {
503 collide_cat.debug()
504 << "Recomputing viz for " << *this << "\n";
505 }
506
507 // Since we can't represent an infinite plane, we'll have to be satisfied
508 // with drawing a big polygon. Choose four points on the plane to be the
509 // corners of the polygon.
510
511 // We must choose four points fairly reasonably spread apart on the plane.
512 // We'll start with a center point and one corner point, and then use cross
513 // products to find the remaining three corners of a square.
514
515 // The center point will be on the axis with the largest coefficent. The
516 // first corner will be diagonal in the other two dimensions.
517
518 LPoint3 cp;
519 LVector3 p1, p2, p3, p4;
520
521 LVector3 normal = get_normal();
522 PN_stdfloat D = _plane[3];
523
524 if (fabs(normal[0]) > fabs(normal[1]) &&
525 fabs(normal[0]) > fabs(normal[2])) {
526 // X has the largest coefficient.
527 cp.set(-D / normal[0], 0.0f, 0.0f);
528 p1 = LPoint3(-(normal[1] + normal[2] + D)/normal[0], 1.0f, 1.0f) - cp;
529
530 } else if (fabs(normal[1]) > fabs(normal[2])) {
531 // Y has the largest coefficient.
532 cp.set(0.0f, -D / normal[1], 0.0f);
533 p1 = LPoint3(1.0f, -(normal[0] + normal[2] + D)/normal[1], 1.0f) - cp;
534
535 } else {
536 // Z has the largest coefficient.
537 cp.set(0.0f, 0.0f, -D / normal[2]);
538 p1 = LPoint3(1.0f, 1.0f, -(normal[0] + normal[1] + D)/normal[2]) - cp;
539 }
540
541 p1.normalize();
542 p2 = cross(normal, p1);
543 p3 = cross(normal, p2);
544 p4 = cross(normal, p3);
545
546 static const double plane_scale = 10.0;
547
548 PT(GeomVertexData) vdata = new GeomVertexData
549 ("collision", GeomVertexFormat::get_v3(),
550 Geom::UH_static);
551 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
552
553 vertex.add_data3(cp + p1 * plane_scale);
554 vertex.add_data3(cp + p2 * plane_scale);
555 vertex.add_data3(cp + p3 * plane_scale);
556 vertex.add_data3(cp + p4 * plane_scale);
557
558 PT(GeomTrifans) body = new GeomTrifans(Geom::UH_static);
559 body->add_consecutive_vertices(0, 4);
560 body->close_primitive();
561
562 PT(GeomLinestrips) border = new GeomLinestrips(Geom::UH_static);
563 border->add_consecutive_vertices(0, 4);
564 border->add_vertex(0);
565 border->close_primitive();
566
567 PT(Geom) geom1 = new Geom(vdata);
568 geom1->add_primitive(body);
569
570 PT(Geom) geom2 = new Geom(vdata);
571 geom2->add_primitive(border);
572
573 _viz_geom->add_geom(geom1, get_solid_viz_state());
574 _viz_geom->add_geom(geom2, get_wireframe_viz_state());
575
576 _bounds_viz_geom->add_geom(geom1, get_solid_bounds_viz_state());
577 _bounds_viz_geom->add_geom(geom2, get_wireframe_bounds_viz_state());
578}
579
580/**
581 * Function to write the important information in the particular object to a
582 * Datagram
583 */
585write_datagram(BamWriter *manager, Datagram &me)
586{
588 _plane.write_datagram(me);
589}
590
591/**
592 * Function that reads out of the datagram (or asks manager to read) all of
593 * the data that is needed to re-create this object and stores it in the
594 * appropiate place
595 */
596void CollisionPlane::
597fillin(DatagramIterator& scan, BamReader* manager)
598{
599 CollisionSolid::fillin(scan, manager);
600 _plane.read_datagram(scan);
601}
602
603/**
604 * Factory method to generate a CollisionPlane object
605 */
608{
609 CollisionPlane *me = new CollisionPlane;
610 DatagramIterator scan;
611 BamReader *manager;
612
613 parse_params(params, scan, manager);
614 me->fillin(scan, manager);
615 return me;
616}
617
618/**
619 * Factory method to generate a CollisionPlane object
620 */
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void parse_params(const FactoryParams &params, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
Definition bamReader.I:275
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition bamReader.h:110
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition bamReader.I:177
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition bamWriter.h:63
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
Defines a single collision event.
get_from_node_path
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
get_into_node_path
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
get_t1
Returns the starting point on the parabola.
get_parabola
Returns the parabola specified by this solid.
virtual LPoint3 get_collision_origin() const
Returns the point in space deemed to be the "origin" of the solid for collision purposes.
virtual PStatCollector & get_volume_pcollector()
Returns a PStatCollector that is used to count the number of bounding volume tests made against a sol...
static void register_with_read_factory()
Factory method to generate a CollisionPlane object.
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
static TypedWritable * make_CollisionPlane(const FactoryParams &params)
Factory method to generate a CollisionPlane object.
virtual PStatCollector & get_test_pcollector()
Returns a PStatCollector that is used to count the number of intersection tests made against a solid ...
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
The abstract base class for all things that can collide with other things in the world,...
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
bool has_effective_normal() const
Returns true if a special normal was set by set_effective_normal(), false otherwise.
const LVector3 & get_effective_normal() const
Returns the normal that was set by set_effective_normal().
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
get_respect_effective_normal
See set_respect_effective_normal().
A class to retrieve the individual data elements previously stored in a Datagram.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
An instance of this class is passed to the Factory when requesting it to do its business and construc...
void register_factory(TypeHandle handle, CreateFunc *func, void *user_data=nullptr)
Registers a new kind of thing the Factory will be able to create.
Definition factory.I:73
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
A lightweight class that represents a single element that may be timed and/or counted via stats.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
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.
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.
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.
STTransform::operator CPT TransformState() const
This is used internally to convert an STTransform into a TransformState pointer.
Definition stTransform.I:98