38PStatCollector CollisionCapsule::_volume_pcollector(
"Collision Volumes:CollisionCapsule");
39PStatCollector CollisionCapsule::_test_pcollector(
"Collision Tests:CollisionCapsule");
47 return new CollisionCapsule(*
this);
55 return entry.
get_into()->test_intersection_from_capsule(entry);
62xform(
const LMatrix4 &mat) {
68 LVector3 radius_v = LVector3(_radius, 0.0f, 0.0f) * mat;
69 _radius = length(radius_v);
91 return _volume_pcollector;
100 return _test_pcollector;
106void CollisionCapsule::
107output(std::ostream &out)
const {
108 out <<
"capsule, a (" << _a <<
"), b (" << _b <<
"), r " << _radius;
115compute_internal_bounds()
const {
116 PT(BoundingVolume) bound = CollisionSolid::compute_internal_bounds();
118 if (bound->is_of_type(GeometricBoundingVolume::get_class_type())) {
119 GeometricBoundingVolume *gbound;
120 DCAST_INTO_R(gbound, bound, bound);
122 LVector3 vec = (_b - _a);
123 if (vec.normalize()) {
127 points[0] = _a - vec * _radius;
128 points[1] = _b + vec * _radius;
130 gbound->
around(points, points + 2);
135 BoundingSphere sphere(_a, _radius);
148 const CollisionSphere *sphere;
149 DCAST_INTO_R(sphere, entry.
get_from(),
nullptr);
154 const LMatrix4 &wrt_mat = wrt_space->get_mat();
156 LPoint3 from_a = sphere->get_center() * wrt_mat;
157 LPoint3 from_b = from_a;
159 LPoint3 contact_point;
160 PN_stdfloat actual_t = 0.0f;
162 if (wrt_prev_space != wrt_space) {
164 from_a = sphere->get_center() * wrt_prev_space->get_mat();
167 LVector3 from_direction = from_b - from_a;
169 LVector3 from_radius_v =
170 LVector3(sphere->get_radius(), 0.0f, 0.0f) * wrt_mat;
171 PN_stdfloat from_radius = length(from_radius_v);
174 if (!intersects_line(t1, t2, from_a, from_direction, from_radius)) {
179 if (t2 < 0.0 || t1 > 1.0) {
186 actual_t = std::min(1.0, std::max(0.0, t1));
187 contact_point = from_a + actual_t * (from_b - from_a);
189 if (collide_cat.is_debug()) {
194 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
196 LPoint3 into_intersection_point;
200 into_intersection_point = from_b;
204 into_intersection_point = from_a + t2 * from_direction;
206 set_intersection_point(new_entry, into_intersection_point, from_radius);
208 LPoint3 fake_contact_point;
209 LVector3 contact_normal;
210 calculate_surface_point_and_normal(contact_point,
214 new_entry->set_contact_pos(contact_point);
215 new_entry->set_contact_normal(contact_normal);
216 new_entry->set_t(actual_t);
226 const CollisionLine *line;
227 DCAST_INTO_R(line, entry.
get_from(),
nullptr);
230 const LMatrix4 &wrt_mat = wrt_space->get_mat();
232 LPoint3 from_origin = line->get_origin() * wrt_mat;
233 LVector3 from_direction = line->get_direction() * wrt_mat;
236 if (!intersects_line(t1, t2, from_origin, from_direction, 0.0f)) {
241 if (collide_cat.is_debug()) {
246 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
248 LPoint3 into_intersection_point = from_origin + t1 * from_direction;
249 set_intersection_point(new_entry, into_intersection_point, 0.0);
255 LVector3 normal = into_intersection_point * _inv_mat;
256 if (normal[1] > _length) {
258 normal[1] -= _length;
259 }
else if (normal[1] > 0.0f) {
263 normal = normalize(normal * _mat);
264 new_entry->set_surface_normal(normal);
275 const CollisionRay *ray;
276 DCAST_INTO_R(ray, entry.
get_from(),
nullptr);
279 const LMatrix4 &wrt_mat = wrt_space->get_mat();
281 LPoint3 from_origin = ray->get_origin() * wrt_mat;
282 LVector3 from_direction = ray->get_direction() * wrt_mat;
285 if (!intersects_line(t1, t2, from_origin, from_direction, 0.0f)) {
295 if (collide_cat.is_debug()) {
300 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
302 LPoint3 into_intersection_point;
306 into_intersection_point = from_origin;
309 into_intersection_point = from_origin + t1 * from_direction;
311 set_intersection_point(new_entry, into_intersection_point, 0.0);
317 LVector3 normal = into_intersection_point * _inv_mat;
318 if (normal[1] > _length) {
320 normal[1] -= _length;
321 }
else if (normal[1] > 0.0f) {
325 normal = normalize(normal * _mat);
326 new_entry->set_surface_normal(normal);
336test_intersection_from_segment(
const CollisionEntry &entry)
const {
337 const CollisionSegment *segment;
338 DCAST_INTO_R(segment, entry.
get_from(),
nullptr);
341 const LMatrix4 &wrt_mat = wrt_space->get_mat();
343 LPoint3 from_a = segment->get_point_a() * wrt_mat;
344 LPoint3 from_b = segment->get_point_b() * wrt_mat;
345 LVector3 from_direction = from_b - from_a;
348 if (!intersects_line(t1, t2, from_a, from_direction, 0.0f)) {
353 if (t2 < 0.0 || t1 > 1.0) {
359 if (collide_cat.is_debug()) {
364 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
366 LPoint3 into_intersection_point;
370 into_intersection_point = from_a;
374 into_intersection_point = from_a + t1 * from_direction;
376 set_intersection_point(new_entry, into_intersection_point, 0.0);
382 LVector3 normal = into_intersection_point * _inv_mat;
383 if (normal[1] > _length) {
385 normal[1] -= _length;
386 }
else if (normal[1] > 0.0f) {
390 normal = normalize(normal * _mat);
391 new_entry->set_surface_normal(normal);
401test_intersection_from_capsule(
const CollisionEntry &entry)
const {
402 const CollisionCapsule *capsule;
403 DCAST_INTO_R(capsule, entry.
get_from(),
nullptr);
406 LVector3 into_direction = _b - into_a;
409 const LMatrix4 &wrt_mat = wrt_space->get_mat();
411 LPoint3 from_a = capsule->get_point_a() * wrt_mat;
412 LPoint3 from_b = capsule->get_point_b() * wrt_mat;
413 LVector3 from_direction = from_b - from_a;
415 LVector3 from_radius_v =
416 LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
417 PN_stdfloat from_radius = length(from_radius_v);
420 double into_t, from_t;
421 calc_closest_segment_points(into_t, from_t,
422 into_a, into_direction,
423 from_a, from_direction);
424 LPoint3 into_closest = into_a + into_direction * into_t;
425 LPoint3 from_closest = from_a + from_direction * from_t;
428 LVector3 closest_vec = from_closest - into_closest;
429 PN_stdfloat distance = closest_vec.length();
430 if (distance > _radius + from_radius) {
434 if (collide_cat.is_debug()) {
439 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
444 LVector3 surface_normal = closest_vec * (1.0 / distance);
446 new_entry->set_surface_point(into_closest + surface_normal * _radius);
447 new_entry->set_interior_point(from_closest - surface_normal * from_radius);
451 }
else if (distance != 0) {
452 new_entry->set_surface_normal(surface_normal);
456 set_intersection_point(new_entry, into_closest, 0);
466test_intersection_from_parabola(
const CollisionEntry &entry)
const {
467 const CollisionParabola *parabola;
468 DCAST_INTO_R(parabola, entry.
get_from(),
nullptr);
471 const LMatrix4 &wrt_mat = wrt_space->get_mat();
475 local_p.xform(wrt_mat);
478 if (!intersects_parabola(t, local_p, parabola->
get_t1(), parabola->
get_t2(),
479 local_p.calc_point(parabola->
get_t1()),
480 local_p.calc_point(parabola->
get_t2()))) {
485 if (collide_cat.is_debug()) {
490 PT(CollisionEntry) new_entry =
new CollisionEntry(entry);
492 LPoint3 into_intersection_point = local_p.calc_point(t);
493 set_intersection_point(new_entry, into_intersection_point, 0.0);
499 LVector3 normal = into_intersection_point * _inv_mat;
500 if (normal[1] > _length) {
502 normal[1] -= _length;
503 }
else if (normal[1] > 0.0f) {
507 normal = normalize(normal * _mat);
508 new_entry->set_surface_normal(normal);
518void CollisionCapsule::
520 if (collide_cat.is_debug()) {
522 <<
"Recomputing viz for " << *
this <<
"\n";
528 LVector3 direction = (_b - _a);
529 PN_stdfloat length = direction.length();
531 PT(GeomVertexData) vdata =
new GeomVertexData
534 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
536 PT(GeomTristrips) strip =
new GeomTristrips(Geom::UH_static);
538 static const int num_slices = 8;
539 static const int num_rings = 4;
541 for (ri = 0; ri < num_rings; ri++) {
542 for (si = 0; si <= num_slices; si++) {
543 vertex.add_data3(calc_sphere1_vertex(ri, si, num_rings, num_slices));
544 vertex.add_data3(calc_sphere1_vertex(ri + 1, si, num_rings, num_slices));
546 strip->add_next_vertices((num_slices + 1) * 2);
547 strip->close_primitive();
551 for (si = 0; si <= num_slices; si++) {
552 vertex.add_data3(calc_sphere1_vertex(num_rings, si, num_rings, num_slices));
553 vertex.add_data3(calc_sphere2_vertex(num_rings, si, num_rings, num_slices,
556 strip->add_next_vertices((num_slices + 1) * 2);
557 strip->close_primitive();
560 for (ri = num_rings - 1; ri >= 0; ri--) {
561 for (si = 0; si <= num_slices; si++) {
562 vertex.add_data3(calc_sphere2_vertex(ri + 1, si, num_rings, num_slices, length));
563 vertex.add_data3(calc_sphere2_vertex(ri, si, num_rings, num_slices, length));
565 strip->add_next_vertices((num_slices + 1) * 2);
566 strip->close_primitive();
569 PT(Geom) geom =
new Geom(vdata);
570 geom->add_primitive(strip);
574 look_at(mat, direction, LVector3(0.0f, 0.0f, 1.0f), CS_zup_right);
576 geom->transform_vertices(mat);
578 _viz_geom->add_geom(geom, get_solid_viz_state());
579 _bounds_viz_geom->add_geom(geom, get_solid_bounds_viz_state());
586void CollisionCapsule::
588 LVector3 direction = (_b - _a);
589 _length = direction.length();
591 look_at(_mat, direction, LVector3(0.0f, 0.0f, 1.0f), CS_zup_right);
593 _inv_mat.invert_from(_mat);
596 mark_internal_bounds_stale();
603LVertex CollisionCapsule::
604calc_sphere1_vertex(
int ri,
int si,
int num_rings,
int num_slices) {
605 PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
606 PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
609 PN_stdfloat theta = s * 2.0f * MathNumbers::pi;
610 PN_stdfloat x_rim = ccos(theta);
611 PN_stdfloat z_rim = csin(theta);
614 PN_stdfloat phi = r * 0.5f * MathNumbers::pi;
615 PN_stdfloat to_pole = csin(phi);
617 PN_stdfloat x = _radius * x_rim * to_pole;
618 PN_stdfloat y = -_radius * ccos(phi);
619 PN_stdfloat z = _radius * z_rim * to_pole;
621 return LVertex(x, y, z);
628LVertex CollisionCapsule::
629calc_sphere2_vertex(
int ri,
int si,
int num_rings,
int num_slices,
630 PN_stdfloat length) {
631 PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
632 PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
635 PN_stdfloat theta = s * 2.0f * MathNumbers::pi;
636 PN_stdfloat x_rim = ccos(theta);
637 PN_stdfloat z_rim = csin(theta);
640 PN_stdfloat phi = r * 0.5f * MathNumbers::pi;
641 PN_stdfloat to_pole = csin(phi);
643 PN_stdfloat x = _radius * x_rim * to_pole;
644 PN_stdfloat y = length + _radius * ccos(phi);
645 PN_stdfloat z = _radius * z_rim * to_pole;
647 return LVertex(x, y, z);
654void CollisionCapsule::
655calc_closest_segment_points(
double &t1,
double &t2,
656 const LPoint3 &from1,
const LVector3 &delta1,
657 const LPoint3 &from2,
const LVector3 &delta2) {
664 LVector3 w = from1 - from2;
665 PN_stdfloat a = delta1.dot(delta1);
666 PN_stdfloat b = delta1.dot(delta2);
667 PN_stdfloat c = delta2.dot(delta2);
668 PN_stdfloat d = delta1.dot(w);
669 PN_stdfloat e = delta2.dot(w);
670 PN_stdfloat D = a * c - b * b;
671 PN_stdfloat sN, sD = D;
672 PN_stdfloat tN, tD = D;
675 if (IS_NEARLY_ZERO(D)) {
688 }
else if (sN > sD) {
706 }
else if (tN > tD) {
709 if ((-d + b) < 0.0) {
711 }
else if ((-d + b) > a) {
720 t1 = (IS_NEARLY_ZERO(sN) ? 0.0 : sN / sD);
721 t2 = (IS_NEARLY_ZERO(tN) ? 0.0 : tN / tD);
732bool CollisionCapsule::
733intersects_line(
double &t1,
double &t2,
734 const LPoint3 &from0,
const LVector3 &delta0,
735 PN_stdfloat inflate_radius)
const {
738 LPoint3 from = from0 * _inv_mat;
739 LVector3 delta = delta0 * _inv_mat;
741 PN_stdfloat radius = _radius + inflate_radius;
748 LVector2 from2(from[0], from[2]);
749 LVector2 delta2(delta[0], delta[2]);
751 double A = dot(delta2, delta2);
753 if (IS_NEARLY_ZERO(A)) {
757 if (from2.dot(from2) > radius * radius) {
762 if (IS_NEARLY_ZERO(delta[1])) {
767 if (from[1] < -radius || from[1] > _length + radius) {
771 if (from[1] < 0.0f) {
773 if (from.dot(from) > radius * radius) {
776 }
else if (from[1] > _length) {
779 if (from.dot(from) > radius * radius) {
791 t1 = (-radius - from[1]) / delta[1];
792 t2 = (_length + radius - from[1]) / delta[1];
799 double B = 2.0f * dot(delta2, from2);
800 double fc_d2 = dot(from2, from2);
801 double C = fc_d2 - radius * radius;
803 double radical = B*B - 4.0*A*C;
805 if (IS_NEARLY_ZERO(radical)) {
807 t1 = t2 = -B / (2.0*A);
809 }
else if (radical < 0.0) {
814 double reciprocal_2A = 1.0 / (2.0 * A);
815 double sqrt_radical = sqrtf(radical);
816 t1 = ( -B - sqrt_radical ) * reciprocal_2A;
817 t2 = ( -B + sqrt_radical ) * reciprocal_2A;
823 PN_stdfloat t1_y = from[1] + t1 * delta[1];
824 PN_stdfloat t2_y = from[1] + t2 * delta[1];
826 if (t1_y < -radius && t2_y < -radius) {
829 }
else if (t1_y > _length + radius && t2_y > _length + radius) {
838 if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
845 }
else if (t1_y > _length) {
849 if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
861 if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
868 }
else if (t2_y > _length) {
872 if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
888bool CollisionCapsule::
889sphere_intersects_line(
double &t1,
double &t2, PN_stdfloat center_y,
890 const LPoint3 &from,
const LVector3 &delta,
891 PN_stdfloat radius) {
894 double A = dot(delta, delta);
896 nassertr(A != 0.0,
false);
900 double B = 2.0f* dot(delta, fc);
901 double fc_d2 = dot(fc, fc);
902 double C = fc_d2 - radius * radius;
904 double radical = B*B - 4.0*A*C;
906 if (IS_NEARLY_ZERO(radical)) {
908 t1 = t2 = -B / (2.0 * A);
911 }
else if (radical < 0.0) {
916 double reciprocal_2A = 1.0 / (2.0 * A);
917 double sqrt_radical = sqrtf(radical);
918 t1 = ( -B - sqrt_radical ) * reciprocal_2A;
919 t2 = ( -B + sqrt_radical ) * reciprocal_2A;
932bool CollisionCapsule::
933intersects_parabola(
double &t,
const LParabola ¶bola,
934 double t1,
double t2,
935 const LPoint3 &p1,
const LPoint3 &p2)
const {
943 double tmid = (t1 + t2) * 0.5;
945 if (tmid != t1 && tmid != t2) {
946 LPoint3 pmid = parabola.calc_point(tmid);
947 LPoint3 pmid2 = (p1 + p2) * 0.5f;
949 if ((pmid - pmid2).length_squared() > 0.001f) {
951 if (intersects_parabola(t, parabola, t1, tmid, p1, pmid)) {
954 return intersects_parabola(t, parabola, tmid, t2, pmid, p2);
960 if (!intersects_line(t1a, t2a, p1, p2 - p1, 0.0f)) {
964 if (t2a < 0.0 || t1a > 1.0) {
968 t = std::max(t1a, 0.0);
977void CollisionCapsule::
978calculate_surface_point_and_normal(
const LPoint3 &surface_point,
980 LPoint3 &result_point,
981 LVector3 &result_normal)
const {
983 LPoint3 point = surface_point * _inv_mat;
986 if (point[1] <= 0.0) {
989 if (!normal.normalize()) {
990 normal.set(0.0, -1.0, 0.0);
992 point = normal * _radius;
994 }
else if (point[1] >= _length) {
996 normal.set(point[0], point[1] - _length, point[2]);
997 if (!normal.normalize()) {
998 normal.set(0.0, 1.0, 0.0);
1000 point = normal * _radius;
1001 point[1] += _length;
1005 LVector2d normal2d(point[0], point[2]);
1006 if (!normal2d.normalize()) {
1007 normal2d.set(0.0, 1.0);
1009 normal.set(normal2d[0], 0.0, normal2d[1]);
1010 point.set(normal[0] * _radius, point[1], normal[2] * _radius);
1014 result_point = point * _mat;
1015 result_normal = normal * _mat;
1023void CollisionCapsule::
1025 const LPoint3 &into_intersection_point,
1026 double extra_radius)
const {
1030 calculate_surface_point_and_normal(into_intersection_point,
1062 _a.write_datagram(dg);
1063 _b.write_datagram(dg);
1079 node->fillin(scan, manager);
1088void CollisionCapsule::
1090 CollisionSolid::fillin(scan, manager);
1091 _a.read_datagram(scan);
1092 _b.read_datagram(scan);
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void parse_params(const FactoryParams ¶ms, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
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...
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
This implements a solid consisting of a cylinder with hemispherical endcaps, also known as a capsule ...
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.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
static void register_with_read_factory()
Tells the BamReader how to create objects of type CollisionCapsule.
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...
Defines a single collision event.
get_into
Returns the CollisionSolid pointer for the particular solid was collided into.
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...
void set_surface_normal(const LVector3 &normal)
Stores the surface normal of the "into" object at the point of the intersection.
void set_interior_point(const LPoint3 &point)
Stores the point, within the interior of the "into" object, which represents the depth to which the "...
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
void set_surface_point(const LPoint3 &point)
Stores the point, on the surface of the "into" object, at which a collision is detected.
get_t1
Returns the starting point on the parabola.
get_t2
Returns the ending point on the parabola.
get_parabola
Returns the parabola specified by this solid.
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.
PN_stdfloat get_stdfloat()
Extracts either a 32-bit or a 64-bit floating-point number, according to Datagram::set_stdfloat_doubl...
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
void add_stdfloat(PN_stdfloat value)
Adds either a 32-bit or a 64-bit floating-point number, according to set_stdfloat_double().
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.
bool extend_by(const GeometricBoundingVolume *vol)
Increases the size of the volume to include the given volume.
bool around(const GeometricBoundingVolume **first, const GeometricBoundingVolume **last)
Resets the volume to enclose only the volumes indicated.
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.
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.