Panda3D
Loading...
Searching...
No Matches
clockObject.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 clockObject.cxx
10 * @author drose
11 * @date 2000-02-17
12 */
13
14#include "clockObject.h"
15#include "config_putil.h"
16#include "configVariableEnum.h"
17#include "string_utils.h"
18#include "thread.h"
19
20using std::istream;
21using std::ostream;
22using std::string;
23
24void (*ClockObject::_start_clock_wait)() = ClockObject::dummy_clock_wait;
25void (*ClockObject::_start_clock_busy_wait)() = ClockObject::dummy_clock_wait;
26void (*ClockObject::_stop_clock_wait)() = ClockObject::dummy_clock_wait;
27
28AtomicAdjust::Pointer ClockObject::_global_clock = nullptr;
29TypeHandle ClockObject::_type_handle;
30
31/**
32 *
33 */
34ClockObject::
35ClockObject(Mode mode) : _ticks(get_class_type()), _mode(mode) {
36 _true_clock = TrueClock::get_global_ptr();
37
38 _start_short_time = _true_clock->get_short_time();
39 _start_long_time = _true_clock->get_long_time();
40 _actual_frame_time = 0.0;
41
43 ("max-dt", -1.0,
44 PRC_DESC("Sets a limit on the value returned by ClockObject::get_dt(). If "
45 "this value is less than zero, no limit is imposed; "
46 "otherwise, this is the maximum value that will ever "
47 "be returned by get_dt(), regardless of how much time "
48 "has actually elapsed between frames. See ClockObject::set_dt()."));
49 ConfigVariableDouble clock_frame_rate
50 ("clock-frame-rate", 1.0,
51 PRC_DESC("In non-real-time clock mode, sets the number of frames per "
52 "second that we should appear to be running. In forced "
53 "mode or limited mode, sets our target frame rate. In "
54 "normal mode, this has no effect. See ClockObject::set_frame_rate()."));
55 ConfigVariableDouble clock_degrade_factor
56 ("clock-degrade-factor", 1.0,
57 PRC_DESC("In degrade clock mode, returns the ratio by which the "
58 "performance is degraded. A value of 2.0 causes the "
59 "clock to be slowed down by a factor of two (reducing "
60 "performance to 1/2 what would be otherwise). See ClockObject::set_degrade_factor()."));
61 ConfigVariableDouble average_frame_rate_interval
62 ("average-frame-rate-interval", 1.0,
63 PRC_DESC("See ClockObject::set_average_frame_rate_interval()."));
64
65 _max_dt = max_dt;
66 _user_frame_rate = clock_frame_rate;
67 _degrade_factor = clock_degrade_factor;
68 _average_frame_rate_interval = average_frame_rate_interval;
69
70 _error_count = _true_clock->get_error_count();
71}
72
73/**
74 *
75 */
76ClockObject::
77ClockObject(const ClockObject &copy) :
78 _true_clock(copy._true_clock),
79 _mode(copy._mode),
80 _start_short_time(copy._start_short_time),
81 _start_long_time(copy._start_long_time),
82 _actual_frame_time(copy._actual_frame_time),
83 _max_dt(copy._max_dt),
84 _user_frame_rate(copy._user_frame_rate),
85 _degrade_factor(copy._degrade_factor),
86 _error_count(copy._error_count),
87 _average_frame_rate_interval(copy._average_frame_rate_interval),
88 _ticks(copy._ticks),
89 _cycler(copy._cycler)
90{
91}
92
93/**
94 * Changes the mode of the clock. Normally, the clock is in mode M_normal.
95 * In this mode, each call to tick() will set the value returned by
96 * get_frame_time() to the current real time; thus, the clock simply reports
97 * time advancing.
98 *
99 * Other possible modes:
100 *
101 * M_non_real_time - the clock ignores real time completely; at each call to
102 * tick(), it pretends that exactly dt seconds have elapsed since the last
103 * call to tick(). You may set the value of dt with set_dt() or
104 * set_frame_rate().
105 *
106 * M_limited - the clock will run as fast as it can, as in M_normal, but will
107 * not run faster than the rate specified by set_frame_rate(). If the
108 * application would run faster than this rate, the clock will slow down the
109 * application.
110 *
111 * M_integer - the clock will run as fast as it can, but the rate will be
112 * constrained to be an integer multiple or divisor of the rate specified by
113 * set_frame_rate(). The clock will slow down the application a bit to
114 * guarantee this.
115 *
116 * M_integer_limited - a combination of M_limited and M_integer; the clock
117 * will not run faster than set_frame_rate(), and if it runs slower, it will
118 * run at a integer divisor of that rate.
119 *
120 * M_forced - the clock forces the application to run at the rate specified by
121 * set_frame_rate(). If the application would run faster than this rate, the
122 * clock will slow down the application; if the application would run slower
123 * than this rate, the clock slows down time so that the application believes
124 * it is running at the given rate.
125 *
126 * M_degrade - the clock runs at real time, but the application is slowed down
127 * by a set factor of its frame rate, specified by set_degrade_factor().
128 *
129 * M_slave - the clock does not advance, but relies on the user to call
130 * set_frame_time() and/or set_frame_count() each frame.
131 */
132void ClockObject::
133set_mode(ClockObject::Mode mode) {
134 Thread *current_thread = Thread::get_current_thread();
135 nassertv(current_thread->get_pipeline_stage() == 0);
136 CDWriter cdata(_cycler, current_thread);
137
138 _mode = mode;
139
140 // In case we have set the clock to one of the modes that uses
141 // _reported_frame_time_epoch, recompute the epoch.
142 switch (_mode) {
143 case M_non_real_time:
144 case M_forced:
145 cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
146 cdata->_frame_count / _user_frame_rate;
147 cdata->_dt = 1.0 / _user_frame_rate;
148
149 default:
150 break;
151 }
152}
153
154/**
155 * Resets the clock to the indicated time. This changes only the real time of
156 * the clock as reported by get_real_time(), but does not immediately change
157 * the time reported by get_frame_time()--that will change after the next call
158 * to tick(). Also see reset(), set_frame_time(), and set_frame_count().
159 */
160void ClockObject::
161set_real_time(double time) {
162#ifdef NOTIFY_DEBUG
163 // This is only a debug message, since it happens during normal development,
164 // particularly at startup, or whenever you break into the task loop.
165 if (util_cat.is_debug() && this == _global_clock) {
166 util_cat.debug()
167 << "Adjusting global clock's real time by " << time - get_real_time()
168 << " seconds.\n";
169 }
170#endif // NOTIFY_DEBUG
171 _start_short_time = _true_clock->get_short_time() - time;
172 _start_long_time = _true_clock->get_long_time() - time;
173}
174
175/**
176 * Changes the time as reported for the current frame to the indicated time.
177 * Normally, the way to adjust the frame time is via tick(); this function is
178 * provided only for occasional special adjustments.
179 */
180void ClockObject::
181set_frame_time(double time, Thread *current_thread) {
182 nassertv(current_thread->get_pipeline_stage() == 0);
183#ifdef NOTIFY_DEBUG
184 if (this == _global_clock && _mode != M_slave) {
185 util_cat.warning()
186 << "Adjusting global clock's frame time by " << time - get_frame_time()
187 << " seconds.\n";
188 }
189#endif // NOTIFY_DEBUG
190 CDWriter cdata(_cycler, current_thread);
191 _actual_frame_time = time;
192 cdata->_reported_frame_time = time;
193
194 // Recompute the epoch in case we are in a mode that relies on this.
195 cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
196 cdata->_frame_count / _user_frame_rate;
197}
198
199/**
200 * Resets the number of frames counted to the indicated number. Also see
201 * reset(), set_real_time(), and set_frame_time().
202 */
203void ClockObject::
204set_frame_count(int frame_count, Thread *current_thread) {
205 nassertv(current_thread->get_pipeline_stage() == 0);
206#ifdef NOTIFY_DEBUG
207 if (this == _global_clock && _mode != M_slave) {
208 util_cat.warning()
209 << "Adjusting global clock's frame count by "
210 << frame_count - get_frame_count() << " frames.\n";
211 }
212#endif // NOTIFY_DEBUG
213 CDWriter cdata(_cycler, current_thread);
214 cdata->_frame_count = frame_count;
215
216 // Recompute the epoch in case we are in a mode that relies on this.
217 cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
218 cdata->_frame_count / _user_frame_rate;
219}
220
221/**
222 * In non-real-time mode, sets the number of seconds that should appear to
223 * elapse between frames. In forced mode or limited mode, sets our target dt.
224 * In normal mode, this has no effect.
225 *
226 * Also see set_frame_rate(), which is a different way to specify the same
227 * quantity.
228 */
229void ClockObject::
230set_dt(double dt) {
231 if (_mode == M_slave) {
232 // In M_slave mode, we can set any dt we like.
233 CDWriter cdata(_cycler, Thread::get_current_thread());
234 cdata->_dt = dt;
235 if (dt != 0.0) {
236 set_frame_rate(1.0 / dt);
237 }
238
239 } else {
240 // In any other mode, we can only set non-zero dt.
241 nassertv(dt != 0.0);
242 set_frame_rate(1.0 / dt);
243 }
244}
245
246/**
247 * In non-real-time mode, sets the number of frames per second that we should
248 * appear to be running. In forced mode or limited mode, sets our target
249 * frame rate. In normal mode, this has no effect.
250 *
251 * Also see set_dt(), which is a different way to specify the same quantity.
252 */
254set_frame_rate(double frame_rate) {
255 nassertv(frame_rate != 0.0);
256
257 Thread *current_thread = Thread::get_current_thread();
258 nassertv(current_thread->get_pipeline_stage() == 0);
259
260 CDWriter cdata(_cycler, current_thread);
261 _user_frame_rate = frame_rate;
262
263 switch (_mode) {
264 case M_non_real_time:
265 case M_forced:
266 cdata->_reported_frame_time_epoch = cdata->_reported_frame_time -
267 cdata->_frame_count / _user_frame_rate;
268 cdata->_dt = 1.0 / _user_frame_rate;
269
270 default:
271 break;
272 }
273}
274
275/**
276 * Returns the average frame rate in number of frames per second over the last
277 * get_average_frame_rate_interval() seconds. This measures the virtual frame
278 * rate if the clock is in M_non_real_time mode.
279 */
280double ClockObject::
281get_average_frame_rate(Thread *current_thread) const {
282 CDStageReader cdata(_cycler, 0, current_thread);
283 if (_ticks.size() <= 1) {
284 return 0.0;
285 } else {
286 return _ticks.size() / (cdata->_reported_frame_time - _ticks.front());
287 }
288}
289
290/**
291 * Returns the maximum frame duration over the last
292 * get_average_frame_rate_interval() seconds.
293 */
294double ClockObject::
295get_max_frame_duration(Thread *current_thread) const {
296 CDStageReader cdata(_cycler, 0, current_thread);
297 double max_duration = 0.0;
298 double cur_duration = 0.0;
299 size_t i;
300 for (i = 0; i < _ticks.size() - 1; i++) {
301 cur_duration = _ticks[i + 1] - _ticks[i];
302 if (cur_duration > max_duration) {
303 max_duration = cur_duration;
304 }
305 }
306 return max_duration;
307}
308
309/**
310 * Returns the standard deviation of the frame times of the frames rendered
311 * over the past get_average_frame_rate_interval() seconds. This number gives
312 * an estimate of the chugginess of the frame rate; if it is large, there is a
313 * large variation in the frame rate; if is small, all of the frames are
314 * consistent in length.
315 *
316 * A large value might also represent just a recent change in frame rate, for
317 * instance, because the camera has just rotated from looking at a simple
318 * scene to looking at a more complex scene.
319 */
321calc_frame_rate_deviation(Thread *current_thread) const {
322 CDStageReader cdata(_cycler, 0, current_thread);
323 if (_ticks.size() <= 1) {
324 return 0.0;
325 } else {
326 double mean = (_ticks.back() - _ticks.front()) / (_ticks.size() - 1);
327 size_t i;
328 double sum_squares = 0.0;
329 for (i = 0; i < _ticks.size() - 1; ++i) {
330 double delta = _ticks[i + 1] - _ticks[i];
331 double diff = (delta - mean);
332 sum_squares += (diff * diff);
333 }
334 double deviation_2 = sum_squares / (_ticks.size() - 1);
335 return sqrt(deviation_2);
336 }
337}
338
339/**
340 * Instructs the clock that a new frame has just begun. In normal, real-time
341 * mode, get_frame_time() will henceforth report the time as of this instant
342 * as the current start-of-frame time. In non-real-time mode,
343 * get_frame_time() will be incremented by the value of dt.
344 */
346tick(Thread *current_thread) {
347 nassertv(current_thread->get_pipeline_stage() == 0);
348 CDWriter cdata(_cycler, current_thread);
349 double old_reported_time = cdata->_reported_frame_time;
350
351 if (_mode != M_slave) {
352 double old_time = _actual_frame_time;
353 _actual_frame_time = get_real_time();
354
355 // In case someone munged the clock last frame and sent us backward in
356 // time, clamp the previous time to the current time to make sure we don't
357 // report anything strange (or wait interminably).
358 old_time = std::min(old_time, _actual_frame_time);
359
360 ++cdata->_frame_count;
361
362 switch (_mode) {
363 case M_normal:
364 // Time runs as it will; we simply report time elapsing.
365 cdata->_dt = _actual_frame_time - old_time;
366 cdata->_reported_frame_time = _actual_frame_time;
367 break;
368
369 case M_non_real_time:
370 // Ignore real time. We always report the same interval having elapsed
371 // each frame.
372 cdata->_reported_frame_time = cdata->_reported_frame_time_epoch +
373 cdata->_frame_count / _user_frame_rate;
374 break;
375
376 case M_limited:
377 // If we are running faster than the desired interval, slow down.
378 {
379 double wait_until_time = old_time + 1.0 / _user_frame_rate;
380 wait_until(wait_until_time);
381 cdata->_dt = _actual_frame_time - old_time;
382 cdata->_reported_frame_time = std::max(_actual_frame_time, wait_until_time);
383 }
384 break;
385
386 case M_integer:
387 {
388 double dt = _actual_frame_time - old_time;
389 double target_dt = 1.0 / _user_frame_rate;
390 if (dt < target_dt) {
391 // We're running faster than the desired interval, so slow down to
392 // the next integer multiple of the frame rate.
393 target_dt = target_dt / floor(target_dt / dt);
394 } else {
395 // We're running slower than the desired interval, so slow down to
396 // the next integer divisor of the frame rate.
397 target_dt = target_dt * ceil(dt / target_dt);
398 }
399 double wait_until_time = old_time + target_dt;
400 wait_until(wait_until_time);
401 cdata->_dt = target_dt;
402 cdata->_reported_frame_time = wait_until_time;
403 }
404 break;
405
406 case M_integer_limited:
407 {
408 double dt = _actual_frame_time - old_time;
409 double target_dt = 1.0 / _user_frame_rate;
410 if (dt < target_dt) {
411 // We're running faster than the desired interval, so slow down to
412 // the target frame rate.
413
414 } else {
415 // We're running slower than the desired interval, so slow down to
416 // the next integer divisor of the frame rate.
417 target_dt = target_dt * ceil(dt / target_dt);
418 }
419 double wait_until_time = old_time + target_dt;
420 wait_until(wait_until_time);
421 cdata->_dt = target_dt;
422 cdata->_reported_frame_time = wait_until_time;
423 }
424 break;
425
426 case M_forced:
427 // If we are running faster than the desired interval, slow down. If we
428 // are running slower than the desired interval, ignore that and pretend
429 // we're running at the specified rate.
430 wait_until(old_time + 1.0 / _user_frame_rate);
431 cdata->_reported_frame_time = cdata->_reported_frame_time_epoch +
432 cdata->_frame_count / _user_frame_rate;
433 break;
434
435 case M_degrade:
436 // Each frame, wait a certain fraction of the previous frame's time to
437 // degrade performance uniformly.
438 cdata->_dt = (_actual_frame_time - old_time) * _degrade_factor;
439
440 if (_degrade_factor < 1.0) {
441 // If the degrade_factor is less than one, we want to simulate a
442 // higher frame rate by incrementing the clock more slowly.
443 cdata->_reported_frame_time += cdata->_dt;
444
445 } else {
446 // Otherwise, we simulate a lower frame rate by waiting until the
447 // appropriate time has elapsed.
448 wait_until(old_time + cdata->_dt);
449 cdata->_reported_frame_time = _actual_frame_time;
450 }
451
452 break;
453
454 case M_slave:
455 // Handled above.
456 break;
457 }
458 }
459
460 if (_average_frame_rate_interval > 0.0) {
461 _ticks.push_back(old_reported_time);
462 while (_ticks.size() > 2 &&
463 cdata->_reported_frame_time - _ticks.front() > _average_frame_rate_interval) {
464 _ticks.pop_front();
465 }
466 }
467}
468
469/**
470 * Resets the frame time to the current real time. This is similar to tick(),
471 * except that it does not advance the frame counter and does not affect dt.
472 * This is intended to be used in the middle of a particularly long frame to
473 * compensate for the time that has already elapsed.
474 *
475 * In non-real-time mode, this function has no effect (because in this mode
476 * all frames take the same length of time).
477 */
479sync_frame_time(Thread *current_thread) {
480 if (_mode == M_normal) {
481 CDWriter cdata(_cycler, current_thread);
482 cdata->_reported_frame_time = get_real_time();
483 }
484}
485
486/**
487 * Waits at the end of a frame until the indicated time has arrived. This is
488 * used to implement M_forced and M_degrade.
489 */
490void ClockObject::
491wait_until(double want_time) {
492 if (want_time <= _actual_frame_time) {
493 return;
494 }
495
496#ifdef DO_PSTATS
497 (*_start_clock_wait)();
498#endif
499
500 double wait_interval = (want_time - _actual_frame_time) - sleep_precision;
501
502 if (wait_interval > 0.0) {
503 Thread::sleep(wait_interval);
504 }
505
506#ifdef DO_PSTATS
507 (*_start_clock_busy_wait)();
508#endif
509
510 // Now busy-wait until the actual time elapses.
511 while (_actual_frame_time < want_time) {
512 _actual_frame_time = get_real_time();
513 }
514
515#ifdef DO_PSTATS
516 (*_stop_clock_wait)();
517#endif
518}
519
520/**
521 * Called once per application to create the global clock object.
522 */
523void ClockObject::
524make_global_clock() {
525 nassertv(_global_clock == nullptr);
526
528 ("clock-mode", ClockObject::M_normal,
529 PRC_DESC("Specifies the mode of the global clock. The default mode, normal, "
530 "is a real-time clock; other modes allow non-real-time special "
531 "effects like simulated reduced frame rate. See "
532 "ClockObject::set_mode()."));
533
534 ClockObject *clock = new ClockObject(clock_mode);
535 clock->local_object();
536
537 if (AtomicAdjust::compare_and_exchange_ptr(_global_clock, nullptr, clock) != nullptr) {
538 // Another thread beat us to it.
539 delete clock;
540 }
541}
542
543/**
544 * This no-op function is assigned as the initial pointer for
545 * _start_clock_wait and _stop_clock_wait, until the PStatClient comes along
546 * and replaces it.
547 */
548void ClockObject::
549dummy_clock_wait() {
550}
551
552/**
553 *
554 */
555ClockObject::CData::
556CData() {
557 _frame_count = 0;
558 _reported_frame_time = 0.0;
559 _reported_frame_time_epoch = 0.0;
560 _dt = 0.0;
561}
562
563/**
564 *
565 */
566CycleData *ClockObject::CData::
567make_copy() const {
568 return new CData(*this);
569}
570
571/**
572 *
573 */
574ostream &
575operator << (ostream &out, ClockObject::Mode mode) {
576 switch (mode) {
577 case ClockObject::M_normal:
578 return out << "normal";
579
580 case ClockObject::M_non_real_time:
581 return out << "non-real-time";
582
583 case ClockObject::M_limited:
584 return out << "limited";
585
586 case ClockObject::M_integer:
587 return out << "integer";
588
589 case ClockObject::M_integer_limited:
590 return out << "integer_limited";
591
592 case ClockObject::M_forced:
593 return out << "forced";
594
595 case ClockObject::M_degrade:
596 return out << "degrade";
597
598 case ClockObject::M_slave:
599 return out << "slave";
600 };
601
602 return out << "**invalid ClockObject::Mode(" << (int)mode << ")**";
603}
604
605/**
606 *
607 */
608istream &
609operator >> (istream &in, ClockObject::Mode &mode) {
610 string word;
611 in >> word;
612
613 if (cmp_nocase_uh(word, "normal") == 0) {
614 mode = ClockObject::M_normal;
615 } else if (cmp_nocase_uh(word, "non-real-time") == 0) {
616 mode = ClockObject::M_non_real_time;
617 } else if (cmp_nocase_uh(word, "limited") == 0) {
618 mode = ClockObject::M_limited;
619 } else if (cmp_nocase_uh(word, "integer") == 0) {
620 mode = ClockObject::M_integer;
621 } else if (cmp_nocase_uh(word, "integer_limited") == 0) {
622 mode = ClockObject::M_integer_limited;
623 } else if (cmp_nocase_uh(word, "forced") == 0) {
624 mode = ClockObject::M_forced;
625 } else if (cmp_nocase_uh(word, "degrade") == 0) {
626 mode = ClockObject::M_degrade;
627 } else if (cmp_nocase_uh(word, "slave") == 0) {
628 mode = ClockObject::M_slave;
629 } else {
630 util_cat->error()
631 << "Invalid ClockObject::Mode: " << word << "\n";
632 mode = ClockObject::M_normal;
633 }
634
635 return in;
636}
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
A ClockObject keeps track of elapsed real time and discrete time.
Definition clockObject.h:58
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
void set_frame_rate(double frame_rate)
In non-real-time mode, sets the number of frames per second that we should appear to be running.
void tick(Thread *current_thread=Thread::get_current_thread())
Instructs the clock that a new frame has just begun.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition clockObject.h:94
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition clockObject.h:92
set_frame_time
Changes the time as reported for the current frame to the indicated time.
Definition clockObject.h:91
set_frame_count
Resets the number of frames counted to the indicated number.
Definition clockObject.h:94
set_real_time
Resets the clock to the indicated time.
Definition clockObject.h:92
void sync_frame_time(Thread *current_thread=Thread::get_current_thread())
Resets the frame time to the current real time.
set_mode
Changes the mode of the clock.
Definition clockObject.h:77
double calc_frame_rate_deviation(Thread *current_thread=Thread::get_current_thread()) const
Returns the standard deviation of the frame times of the frames rendered over the past get_average_fr...
get_average_frame_rate
Returns the average frame rate in number of frames per second over the last get_average_frame_rate_in...
set_dt
In non-real-time mode, sets the number of seconds that should appear to elapse between frames.
Definition clockObject.h:99
get_max_frame_duration
Returns the maximum frame duration over the last get_average_frame_rate_interval() seconds.
This is a convenience class to specialize ConfigVariable as a floating- point type.
This class specializes ConfigVariable as an enumerated type.
This class is similar to CycleDataReader, except it allows reading from a particular stage of the pip...
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
A single page of data maintained by a PipelineCycler.
Definition cycleData.h:50
void local_object()
This function should be called, once, immediately after creating a new instance of some ReferenceCoun...
A thread; that is, a lightweight process.
Definition thread.h:46
static void sleep(double seconds)
Suspends the current thread for at least the indicated amount of time.
Definition thread.I:192
get_pipeline_stage
Returns the Pipeline stage number associated with this thread.
Definition thread.h:105
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition thread.h:109
static TrueClock * get_global_ptr()
Returns a pointer to the one TrueClock object in the world.
Definition trueClock.I:68
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.