Panda3D
Loading...
Searching...
No Matches
cullResult.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 cullResult.cxx
10 * @author drose
11 * @date 2002-02-28
12 */
13
14#include "cullResult.h"
15#include "cullBinManager.h"
16#include "cullBinAttrib.h"
17#include "textureAttrib.h"
18#include "lightAttrib.h"
19#include "colorAttrib.h"
20#include "alphaTestAttrib.h"
21#include "depthWriteAttrib.h"
22#include "colorScaleAttrib.h"
23#include "fogAttrib.h"
24#include "transparencyAttrib.h"
25#include "renderState.h"
26#include "rescaleNormalAttrib.h"
27#include "clockObject.h"
28#include "config_pgraph.h"
29#include "depthOffsetAttrib.h"
30#include "colorBlendAttrib.h"
31#include "shaderAttrib.h"
32
33TypeHandle CullResult::_type_handle;
34
35/*
36 * This value is used instead of 1.0 to represent the alpha level of a pixel
37 * that is to be considered "opaque" for the purposes of M_dual. Ideally, 1.0
38 * is the only correct value for this. Realistically, we have to fudge it
39 * lower for two reasons: (1) The modelers tend to paint textures with very
40 * slight transparency levels in places that are not intended to be
41 * transparent, without realizing it. These very faint transparency regions
42 * are normally (almost) invisible, but when rendered with M_dual they may be
43 * revealed as regions of poor alpha sorting. (2) There seems to be some
44 * problem in DX where, in certain circumstances apparently related to
45 * automatic texture management, it spontaneously drops out the bottom two
46 * bits of an eight-bit alpha channel, causing a value of 255 to become a
47 * value of 252 instead. We use 256 as the denominator here (instead of, say,
48 * 255) because a fractional power of two will have a terminating
49 * representation in base 2, and thus will be more likely to have a precise
50 * value in whatever internal representation the graphics API will use.
51 */
52static const PN_stdfloat dual_opaque_level = 252.0 / 256.0;
53static const double bin_color_flash_rate = 1.0; // 1 state change per second
54
55/**
56 *
57 */
58CullResult::
59CullResult(GraphicsStateGuardianBase *gsg,
60 const PStatCollector &draw_region_pcollector) :
61 _gsg(gsg),
62 _draw_region_pcollector(draw_region_pcollector)
63{
64#ifdef DO_MEMORY_USAGE
65 MemoryUsage::update_type(this, get_class_type());
66#endif
67
68#ifndef NDEBUG
69 _show_transparency = show_transparency.get_value();
70#endif
71}
72
73/**
74 * Returns a newly-allocated CullResult object that contains a copy of just
75 * the subset of the data from this CullResult object that is worth keeping
76 * around for next frame.
77 */
78PT(CullResult) CullResult::
79make_next() const {
80 PT(CullResult) new_result = new CullResult(_gsg, _draw_region_pcollector);
81 new_result->_bins.reserve(_bins.size());
82
84
85 for (size_t i = 0; i < _bins.size(); ++i) {
86 CullBin *old_bin = _bins[i];
87 if (old_bin == nullptr ||
88 old_bin->get_bin_type() != bin_manager->get_bin_type(i)) {
89 new_result->_bins.push_back(nullptr);
90 } else {
91 new_result->_bins.push_back(old_bin->make_next());
92 }
93 }
94
95 return new_result;
96}
97
98/**
99 * Adds the indicated CullableObject to the appropriate bin. The bin becomes
100 * the owner of the object pointer, and will eventually delete it.
101 */
102void CullResult::
103add_object(CullableObject *object, const CullTraverser *traverser) {
104 static const LColor flash_alpha_color(0.92, 0.96, 0.10, 1.0f);
105 static const LColor flash_binary_color(0.21f, 0.67f, 0.24, 1.0f);
106 static const LColor flash_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
107 static const LColor flash_dual_color(0.92, 0.01f, 0.01f, 1.0f);
108
109 nassertv(object->_draw_callback != nullptr || object->_geom != nullptr);
110
111 bool force = !traverser->get_effective_incomplete_render();
112 Thread *current_thread = traverser->get_current_thread();
114
115 // This is probably a good time to check for an auto rescale setting.
116 const RescaleNormalAttrib *rescale;
117 object->_state->get_attrib_def(rescale);
118 if (rescale->get_mode() == RescaleNormalAttrib::M_auto) {
119 RescaleNormalAttrib::Mode mode;
120
121 if (object->_internal_transform->has_identity_scale()) {
122 mode = RescaleNormalAttrib::M_none;
123 } else if (object->_internal_transform->has_uniform_scale()) {
124 mode = RescaleNormalAttrib::M_rescale;
125 } else {
126 mode = RescaleNormalAttrib::M_normalize;
127 }
128
129 object->_state = object->_state->compose(get_rescale_normal_state(mode));
130 }
131
132 // Check for a special wireframe setting.
133 const RenderModeAttrib *rmode;
134 if (object->_state->get_attrib(rmode)) {
135 if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) {
136 CullableObject *wireframe_part = new CullableObject(*object);
137 const ShaderAttrib *shader = nullptr;
138 object->_state->get_attrib(shader);
139 wireframe_part->_state = get_wireframe_overlay_state(rmode, shader);
140
141 if (wireframe_part->munge_geom
142 (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread),
143 traverser, force)) {
144 int wireframe_bin_index = bin_manager->find_bin("fixed");
145 CullBin *bin = get_bin(wireframe_bin_index);
146 nassertv(bin != nullptr);
147 check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index);
148 bin->add_object(wireframe_part, current_thread);
149 } else {
150 delete wireframe_part;
151 }
152
153 object->_state = object->_state->compose(get_wireframe_filled_state());
154 }
155 }
156
157 // Check to see if there's a special transparency setting.
158 const TransparencyAttrib *trans;
159 if (object->_state->get_attrib(trans)) {
160 switch (trans->get_mode()) {
161 case TransparencyAttrib::M_alpha:
162 case TransparencyAttrib::M_premultiplied_alpha:
163 // M_alpha implies an alpha-write test, so we don't waste time writing
164 // 0-valued pixels.
165 object->_state = object->_state->compose(get_alpha_state());
166 check_flash_transparency(object->_state, flash_alpha_color);
167 break;
168
169 case TransparencyAttrib::M_binary:
170 // M_binary is implemented by explicitly setting the alpha test.
171 object->_state = object->_state->compose(get_binary_state());
172 check_flash_transparency(object->_state, flash_binary_color);
173 break;
174
175 case TransparencyAttrib::M_multisample:
176 case TransparencyAttrib::M_multisample_mask:
177 // The multisample modes are implemented using M_binary if the GSG in
178 // use doesn't support multisample.
179 if (!_gsg->get_supports_multisample()) {
180 object->_state = object->_state->compose(get_binary_state());
181 }
182 check_flash_transparency(object->_state, flash_multisample_color);
183 break;
184
185 case TransparencyAttrib::M_dual:
186#ifndef NDEBUG
187 check_flash_transparency(object->_state, flash_dual_color);
188#endif
189 if (!m_dual) {
190 // If m_dual is configured off, it becomes M_alpha.
191 break;
192 }
193
194 // M_dual is implemented by drawing the opaque parts first, without
195 // transparency, then drawing the transparent parts later. This means
196 // we must copy the object and add it to both bins. We can only do this
197 // if we do not have an explicit bin already applied; otherwise, M_dual
198 // falls back to M_alpha.
199 {
200 const CullBinAttrib *bin_attrib;
201 if (!object->_state->get_attrib(bin_attrib) ||
202 bin_attrib->get_bin_name().empty()) {
203 // We make a copy of the object to draw the transparent part; this
204 // gets placed in the transparent bin.
205#ifndef NDEBUG
206 if (m_dual_transparent)
207#endif
208 {
209 CullableObject *transparent_part = new CullableObject(*object);
210 CPT(RenderState) transparent_state = get_dual_transparent_state();
211 transparent_part->_state = object->_state->compose(transparent_state);
212 if (transparent_part->munge_geom
213 (_gsg, _gsg->get_geom_munger(transparent_part->_state, current_thread),
214 traverser, force)) {
215 int transparent_bin_index = transparent_part->_state->get_bin_index();
216 CullBin *bin = get_bin(transparent_bin_index);
217 nassertv(bin != nullptr);
218 check_flash_bin(transparent_part->_state, bin_manager, transparent_bin_index);
219 bin->add_object(transparent_part, current_thread);
220 } else {
221 delete transparent_part;
222 }
223 }
224
225 // Now we can draw the opaque part. This will end up in the opaque
226 // bin.
227 object->_state = object->_state->compose(get_dual_opaque_state());
228#ifndef NDEBUG
229 if (!m_dual_opaque) {
230 delete object;
231 return;
232 }
233#endif
234 }
235 // The object is assigned to a specific bin; M_dual becomes M_alpha.
236 }
237 break;
238
239 default:
240 // Other kinds of transparency need no special handling.
241 break;
242 }
243 }
244
245 int bin_index = object->_state->get_bin_index();
246 CullBin *bin = get_bin(bin_index);
247 nassertv(bin != nullptr);
248 check_flash_bin(object->_state, bin_manager, bin_index);
249
250 // Munge vertices as needed for the GSG's requirements, and the object's
251 // current state.
252 if (object->munge_geom(_gsg, _gsg->get_geom_munger(object->_state, current_thread), traverser, force)) {
253 // The object may or may not now be fully resident, but this may not
254 // matter, since the GSG may have the necessary buffers already loaded.
255 // We'll let the GSG ultimately decide whether to render it.
256 bin->add_object(object, current_thread);
257 } else {
258 delete object;
259 }
260}
261
262/**
263 * Called after all the geoms have been added, this indicates that the cull
264 * process is finished for this frame and gives the bins a chance to do any
265 * post-processing (like sorting) before moving on to draw.
266 */
268finish_cull(SceneSetup *scene_setup, Thread *current_thread) {
270
271 for (size_t i = 0; i < _bins.size(); ++i) {
272 if (!bin_manager->get_bin_active(i)) {
273 // If the bin isn't active, don't sort it, and don't draw it. In fact,
274 // clear it.
275 _bins[i] = nullptr;
276
277 } else {
278 CullBin *bin = _bins[i];
279 if (bin != nullptr) {
280 bin->finish_cull(scene_setup, current_thread);
281 }
282 }
283 }
284}
285
286/**
287 * Asks all the bins to draw themselves in the correct order.
288 */
290draw(Thread *current_thread) {
291 bool force = !_gsg->get_effective_incomplete_render();
292
293 // Ask the bin manager for the correct order to draw all the bins.
295 int num_bins = bin_manager->get_num_bins();
296 for (int i = 0; i < num_bins; i++) {
297 int bin_index = bin_manager->get_bin(i);
298 nassertv(bin_index >= 0);
299
300 if (bin_index < (int)_bins.size() && _bins[bin_index] != nullptr) {
301
302 _gsg->push_group_marker(_bins[bin_index]->get_name());
303 _bins[bin_index]->draw(force, current_thread);
304 _gsg->pop_group_marker();
305 }
306 }
307}
308
309/**
310 * Returns a special scene graph constructed to represent the results of the
311 * cull. This will be a hierarchy of nodes, one node for each bin, each of
312 * which will in term be a parent of a number of GeomNodes, representing the
313 * geometry drawn in each bin.
314 *
315 * This is useful mainly for high-level debugging and abstraction tools; it
316 * should not be mistaken for the low-level cull result itself. For the low-
317 * level cull result, use draw() to efficiently draw the culled scene.
318 */
319PT(PandaNode) CullResult::
320make_result_graph() {
321 PT(PandaNode) root_node = new PandaNode("cull_result");
322
323 // Ask the bin manager for the correct order to draw all the bins.
325 int num_bins = bin_manager->get_num_bins();
326 for (int i = 0; i < num_bins; i++) {
327 int bin_index = bin_manager->get_bin(i);
328 nassertr(bin_index >= 0, nullptr);
329
330 if (bin_index < (int)_bins.size() && _bins[bin_index] != nullptr) {
331 root_node->add_child(_bins[bin_index]->make_result_graph());
332 }
333 }
334
335 return root_node;
336}
337
338/**
339 * Intended to be called by CullBinManager::remove_bin(), this informs all the
340 * CullResults in the world to remove the indicated bin_index from their cache
341 * if it has been cached.
342 */
344bin_removed(int bin_index) {
345 // Do something here.
346 nassertv(false);
347}
348
349/**
350 * Allocates a new CullBin for the given bin_index and stores it for next
351 * time.
352 */
353CullBin *CullResult::
354make_new_bin(int bin_index) {
356 PT(CullBin) bin = bin_manager->make_new_bin(bin_index, _gsg,
357 _draw_region_pcollector);
358 CullBin *bin_ptr = bin.p();
359
360 if (bin_ptr != nullptr) {
361 // Now store it in the vector.
362 while (bin_index >= (int)_bins.size()) {
363 _bins.push_back(nullptr);
364 }
365 nassertr(bin_index >= 0 && bin_index < (int)_bins.size(), nullptr);
366
367 // Prevent unnecessary refunref by swapping the PointerTos.
368 std::swap(_bins[bin_index], bin);
369 }
370
371 return bin_ptr;
372}
373
374/**
375 * Returns a RenderState containing the given rescale normal attribute.
376 */
377const RenderState *CullResult::
378get_rescale_normal_state(RescaleNormalAttrib::Mode mode) {
379 static CPT(RenderState) states[RescaleNormalAttrib::M_auto + 1];
380 if (states[mode].is_null()) {
381 states[mode] = RenderState::make(RescaleNormalAttrib::make(mode),
382 RenderState::get_max_priority());
383 }
384 return states[mode].p();
385}
386
387/**
388 * Returns a RenderState that changes the alpha test to > 0, for implementing
389 * M_alpha.
390 */
391const RenderState *CullResult::
392get_alpha_state() {
393 static CPT(RenderState) state = nullptr;
394 if (state == nullptr) {
395 // We don't monkey with the priority, since we want to allow the user to
396 // override this if he desires.
397 state = RenderState::make(AlphaTestAttrib::make(AlphaTestAttrib::M_greater, 0.0f));
398 }
399 return state.p();
400}
401
402/**
403 * Returns a RenderState that applies the effects of M_binary.
404 */
405const RenderState *CullResult::
406get_binary_state() {
407 static CPT(RenderState) state = nullptr;
408 if (state == nullptr) {
409 state = RenderState::make(AlphaTestAttrib::make(AlphaTestAttrib::M_greater_equal, 0.5f),
410 TransparencyAttrib::make(TransparencyAttrib::M_none),
411 RenderState::get_max_priority());
412 }
413 return state.p();
414}
415
416#ifndef NDEBUG
417/**
418 * Update the object's state to flash the geometry with a solid color.
419 */
420void CullResult::
421apply_flash_color(CPT(RenderState) &state, const LColor &flash_color) {
422 int cycle = (int)(ClockObject::get_global_clock()->get_frame_time() * bin_color_flash_rate);
423 if ((cycle & 1) == 0) {
424 state = state->remove_attrib(TextureAttrib::get_class_slot());
425 state = state->remove_attrib(LightAttrib::get_class_slot());
426 state = state->remove_attrib(ColorScaleAttrib::get_class_slot());
427 state = state->remove_attrib(FogAttrib::get_class_slot());
428 state = state->add_attrib(ColorAttrib::make_flat(flash_color),
429 RenderState::get_max_priority());
430 }
431}
432#endif // NDEBUG
433
434/**
435 * Returns a RenderState that renders only the transparent parts of an object,
436 * in support of M_dual.
437 */
438const RenderState *CullResult::
439get_dual_transparent_state() {
440 static CPT(RenderState) state = nullptr;
441 if (state == nullptr) {
442 // The alpha test for > 0 prevents us from drawing empty pixels, and hence
443 // filling up the depth buffer with large empty spaces that may obscure
444 // other things. However, this does mean we draw pixels twice where the
445 // alpha == 1.0 (since they were already drawn in the opaque pass). This
446 // is not normally a problem.
447 state = RenderState::make(AlphaTestAttrib::make(AlphaTestAttrib::M_greater, 0.0f),
448 TransparencyAttrib::make(TransparencyAttrib::M_alpha),
449 DepthWriteAttrib::make(DepthWriteAttrib::M_off),
450 RenderState::get_max_priority());
451 }
452
453#ifndef NDEBUG
454 if (m_dual_flash) {
455 int cycle = (int)(ClockObject::get_global_clock()->get_frame_time() * bin_color_flash_rate);
456 if ((cycle & 1) == 0) {
457 static CPT(RenderState) flash_state = nullptr;
458 if (flash_state == nullptr) {
459 flash_state = state->add_attrib(ColorAttrib::make_flat(LColor(0.8f, 0.2, 0.2, 1.0f)),
460 RenderState::get_max_priority());
461
462 flash_state = flash_state->add_attrib(ColorScaleAttrib::make(LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)),
463 RenderState::get_max_priority());
464
465 flash_state = flash_state->add_attrib(AlphaTestAttrib::make(AlphaTestAttrib::M_less, 1.0f),
466 RenderState::get_max_priority());
467 }
468 return flash_state.p();
469 }
470 }
471#endif // NDEBUG
472
473 return state.p();
474}
475
476/**
477 * Returns a RenderState that renders only the opaque parts of an object, in
478 * support of M_dual.
479 */
480const RenderState *CullResult::
481get_dual_opaque_state() {
482 static CPT(RenderState) state = nullptr;
483 if (state == nullptr) {
484 state = RenderState::make(AlphaTestAttrib::make(AlphaTestAttrib::M_greater_equal, dual_opaque_level),
485 TransparencyAttrib::make(TransparencyAttrib::M_none),
486 RenderState::get_max_priority());
487 }
488
489#ifndef NDEBUG
490 if (m_dual_flash) {
491 int cycle = (int)(ClockObject::get_global_clock()->get_frame_time() * bin_color_flash_rate);
492 if ((cycle & 1) == 0) {
493 static CPT(RenderState) flash_state = nullptr;
494 if (flash_state == nullptr) {
495 flash_state = state->add_attrib(ColorAttrib::make_flat(LColor(0.2, 0.2, 0.8f, 1.0f)),
496 RenderState::get_max_priority());
497 flash_state = flash_state->add_attrib(ColorScaleAttrib::make(LVecBase4(1.0f, 1.0f, 1.0f, 1.0f)),
498 RenderState::get_max_priority());
499
500 }
501 return flash_state.p();
502 }
503 }
504#endif // NDEBUG
505
506 return state.p();
507}
508
509/**
510 * Returns a RenderState that is composed with the filled part of an
511 * M_filled_wireframe model.
512 */
513const RenderState *CullResult::
514get_wireframe_filled_state() {
515 static CPT(RenderState) state = RenderState::make(
516 RenderModeAttrib::make(RenderModeAttrib::M_filled),
517 RenderState::get_max_priority());
518 return state.p();
519}
520
521/**
522 * Returns a RenderState that renders only the wireframe part of an
523 * M_filled_wireframe model.
524 */
525CPT(RenderState) CullResult::
526get_wireframe_overlay_state(const RenderModeAttrib *rmode) {
527 return get_wireframe_overlay_state(rmode, nullptr);
528}
529
530/**
531 * Returns a RenderState that renders only the wireframe part of an
532 * M_filled_wireframe model.
533 * If a shader attrib is provided, a constant color is used in ColorBlendAttrib
534 * to emulate the flat color.
535 */
536CPT(RenderState) CullResult::
537get_wireframe_overlay_state(const RenderModeAttrib *rmode, const ShaderAttrib *shader) {
538 CPT(RenderState) state = RenderState::make(
539 DepthOffsetAttrib::make(1, 0, 0.99999f),
540 RenderModeAttrib::make(RenderModeAttrib::M_wireframe,
541 rmode->get_thickness(),
542 rmode->get_perspective()));
543 if (filled_wireframe_apply_shader) {
544 state = state->add_attrib(ColorBlendAttrib::make(ColorBlendAttrib::M_add,
545 ColorBlendAttrib::O_zero,
546 ColorBlendAttrib::O_constant_color,
547 ColorBlendAttrib::M_add,
548 ColorBlendAttrib::O_one,
549 ColorBlendAttrib::O_one_minus_incoming_alpha,
550 rmode->get_wireframe_color()));
551 state = state->add_attrib(shader);
552 } else {
553 state = state->add_attrib(ColorBlendAttrib::make(ColorBlendAttrib::M_add,
554 ColorBlendAttrib::O_incoming_alpha,
555 ColorBlendAttrib::O_one_minus_incoming_alpha));
556 state = state->add_attrib(ColorAttrib::make_flat(rmode->get_wireframe_color()));
557 }
558 return state;
559}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition clockObject.h:91
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
get_value
Returns the variable's value.
Assigns geometry to a particular bin by name.
get_bin_name
Returns the name of the bin this attribute specifies.
This is a global object that maintains the collection of named CullBins in the world.
BinType get_bin_type(int bin_index) const
Returns the type of the bin with the indicated bin_index (where bin_index was retrieved by get_bin() ...
static CullBinManager * get_global_ptr()
Returns the pointer to the global CullBinManager object.
bool get_bin_active(int bin_index) const
Returns the active flag of the bin with the indicated bin_index (where bin_index was retrieved by get...
get_bin
Returns the bin_index of the nth bin in the set, where n is a number between 0 and get_num_bins().
get_num_bins
Returns the number of bins in the world.
int find_bin(const std::string &name) const
Returns the bin_index associated with the bin of the given name, or -1 if no bin has that name.
A collection of Geoms and their associated state, for a particular scene.
Definition cullBin.h:40
This stores the result of a BinCullHandler traversal: an ordered collection of CullBins,...
Definition cullResult.h:44
void draw(Thread *current_thread)
Asks all the bins to draw themselves in the correct order.
static void bin_removed(int bin_index)
Intended to be called by CullBinManager::remove_bin(), this informs all the CullResults in the world ...
CullBin * get_bin(int bin_index)
Returns the CullBin associated with the indicated bin_index, or NULL if the bin_index is invalid.
Definition cullResult.I:27
void finish_cull(SceneSetup *scene_setup, Thread *current_thread)
Called after all the geoms have been added, this indicates that the cull process is finished for this...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Thread * get_current_thread() const
Returns the currently-executing thread object, as passed to the CullTraverser constructor.
bool get_effective_incomplete_render() const
Returns true if the cull traversal is effectively in incomplete_render state, considering both the GS...
The smallest atom of cull.
bool munge_geom(GraphicsStateGuardianBase *gsg, GeomMunger *munger, const CullTraverser *traverser, bool force)
Uses the indicated GeomMunger to transform the geom and/or its vertices.
This is a base class for the GraphicsStateGuardian class, which is itself a base class for the variou...
static void update_type(ReferenceCount *ptr, TypeHandle type)
Associates the indicated type with the given pointer.
Definition memoryUsage.I:55
A lightweight class that represents a single element that may be timed and/or counted via stats.
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
Specifies how polygons are to be drawn.
get_perspective
Returns the perspective flag.
get_thickness
Returns the line width or point thickness.
get_wireframe_color
Returns the color that is used in M_filled_wireframe mode to distinguish the wireframe from the rest ...
get_mode
Returns the render mode.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition renderState.h:47
Specifies how polygons are to be drawn.
get_mode
Returns the render mode.
This object holds the camera position, etc., and other general setup information for rendering a part...
Definition sceneSetup.h:32
A thread; that is, a lightweight process.
Definition thread.h:46
This controls the enabling of transparency.
get_mode
Returns the transparency mode.
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.
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.