1 /* -------------------------------------------------------------------------- *
2 * Simbody(tm) *
3 * -------------------------------------------------------------------------- *
4 * This is part of the SimTK biosimulation toolkit originating from *
5 * Simbios, the NIH National Center for Physics-Based Simulation of *
6 * Biological Structures at Stanford, funded under the NIH Roadmap for *
7 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. *
8 * *
9 * Portions copyright (c) 2010-14 Stanford University and the Authors. *
10 * Authors: Peter Eastman, Michael Sherman *
11 * Contributors: *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 #include "simbody/internal/common.h"
25 #include "simbody/internal/MobilizedBody.h"
26 #include "simbody/internal/MultibodySystem.h"
27 #include "simbody/internal/SimbodyMatterSubsystem.h"
28 #include "simbody/internal/Visualizer.h"
29 #include "simbody/internal/Visualizer_InputListener.h"
30
31 #include "VisualizerGeometry.h"
32 #include "VisualizerProtocol.h"
33
34 #include <cstdlib>
35 #include <cstdio>
36 #include <string>
37 #include <ctime>
38 #include <iostream>
39 #include <limits>
40 #include <condition_variable>
41
42 using namespace SimTK;
43 using namespace std;
44
45 static void drawingThreadMain(Visualizer::Impl& vizImpl);
46
47 static const long long UsToNs = 1000LL; // ns = us * UsToNs
48 static const long long MsToNs = 1000LL * UsToNs; // ns = ms * MsToNs
49
50 static const Real DefaultFrameRateFPS = 30;
51 static const Real DefaultDesiredBufferLengthInSec = Real(0.15); // 150ms
52
53 // These are not currently overrideable.
54 static const long long DefaultAllowableFrameJitterInNs = 5 * MsToNs; //5ms
55 static const Real DefaultSlopAsFractionOfFrameInterval = Real(0.05); //5%
56
57 namespace { // local classes
58 //==============================================================================
59 // VISUALIZER IMPL
60 //==============================================================================
61 /* This is the private implementation object contained in a Visualizer handle.
62 See the "implementation notes" section of the Visualizer class documentation
63 for some information about how this works. */
64
65 // If we are buffering frames, this is the object that represents a frame
66 // in the queue. It consists of a copy of a reported State, and a desired
67 // draw time for the frame, in adjusted real time (AdjRT).
68 struct Frame {
Frame__anonab20b5810111::Frame69 Frame() : desiredDrawTimeAdjRT(-1LL) {}
Frame__anonab20b5810111::Frame70 Frame(const State& state, const long long& desiredDrawTimeAdjRT)
71 : state(state), desiredDrawTimeAdjRT(desiredDrawTimeAdjRT) {}
72 // default copy constructor, copy assignment, destructor
73
clear__anonab20b5810111::Frame74 void clear() {desiredDrawTimeAdjRT = -1LL;}
isValid__anonab20b5810111::Frame75 bool isValid() const {return desiredDrawTimeAdjRT >= 0LL;}
76
77 State state;
78 long long desiredDrawTimeAdjRT; // in adjusted real time
79 };
80
81 // This holds the specs for rubber band lines that are added directly
82 // to the Visualizer.
83 class RubberBandLine {
84 public:
RubberBandLine(MobilizedBodyIndex b1,const Vec3 & station1,MobilizedBodyIndex b2,const Vec3 & station2,const DecorativeLine & line)85 RubberBandLine(MobilizedBodyIndex b1, const Vec3& station1,
86 MobilizedBodyIndex b2, const Vec3& station2,
87 const DecorativeLine& line)
88 : b1(b1), station1(station1), b2(b2), station2(station2), line(line) {}
89
90 MobilizedBodyIndex b1, b2;
91 Vec3 station1, station2;
92 DecorativeLine line;
93 };
94 } // end local namespace
95
96 // Implementation of the Visualizer.
97 class Visualizer::Impl {
98 public:
99 // Create a Visualizer and put it in PassThrough mode.
Impl(Visualizer * owner,const MultibodySystem & system,const Array_<String> & searchPath)100 Impl(Visualizer* owner, const MultibodySystem& system,
101 const Array_<String>& searchPath)
102 : m_system(system), m_protocol(*owner, searchPath),
103 m_shutdownWhenDestructed(false), m_upDirection(YAxis), m_groundHeight(0),
104 m_mode(PassThrough), m_frameRateFPS(DefaultFrameRateFPS),
105 m_simTimeUnitsPerSec(1),
106 m_desiredBufferLengthInSec(DefaultDesiredBufferLengthInSec),
107 m_timeBetweenFramesInNs(secToNs(1/DefaultFrameRateFPS)),
108 m_allowableFrameJitterInNs(DefaultAllowableFrameJitterInNs),
109 m_allowableFrameTimeSlopInNs(
110 secToNs(DefaultSlopAsFractionOfFrameInterval/DefaultFrameRateFPS)),
111 m_adjustedRealTimeBase(realTimeInNs()),
112 m_prevFrameSimTime(-1), m_nextFrameDueAdjRT(-1),
113 m_oldest(0),m_nframe(0),
114 m_drawThreadIsRunning(false), m_drawThreadShouldSuicide(false),
115 m_refCount(0)
116 {
117 setMode(PassThrough);
118 clearStats();
119
120 m_protocol.setMaxFrameRate(m_frameRateFPS);
121 m_protocol.setBackgroundColor(White);
122 m_protocol.setBackgroundType(system.getUseUniformBackground()
123 ? SolidColor : GroundAndSky);
124 m_protocol.setSystemUpDirection(system.getUpDirection());
125 }
126
~Impl()127 ~Impl() {
128 if (m_mode==RealTime && m_pool.size()) {
129 killDrawThreadIfNecessary();
130 }
131 for (unsigned i = 0; i < m_controllers.size(); i++)
132 delete m_controllers[i];
133 for (unsigned i = 0; i < m_listeners.size(); i++)
134 delete m_listeners[i];
135 for (unsigned i = 0; i < m_generators.size(); i++)
136 delete m_generators[i];
137
138 if (m_shutdownWhenDestructed) {
139 try {
140 // This throws an exception if the pipe is broken (e.g., if the
141 // simbody-visualizer has already been shut down).
142 m_protocol.shutdownGUI();
143 } catch (...) {}
144 }
145 }
146
setShutdownWhenDestructed(bool shouldShutdown)147 void setShutdownWhenDestructed(bool shouldShutdown)
148 { m_shutdownWhenDestructed = shouldShutdown; }
149
getShutdownWhenDestructed() const150 bool getShutdownWhenDestructed() const
151 { return m_shutdownWhenDestructed; }
152
153 // Call from simulation thread.
startDrawThread()154 void startDrawThread() {
155 SimTK_ASSERT_ALWAYS(!m_drawThreadIsRunning,
156 "Tried to start the draw thread when it was already running.");
157 m_drawThreadShouldSuicide = false;
158 m_drawThread = std::thread(drawingThreadMain, std::ref(*this));
159 m_drawThreadIsRunning = true;
160 }
161
162 // Call from simulation thread.
killDrawThread()163 void killDrawThread() {
164 SimTK_ASSERT_ALWAYS(m_drawThreadIsRunning,
165 "Tried to kill the draw thread when it wasn't running.");
166 m_drawThreadShouldSuicide = true;
167 // The draw thread might be waiting on an empty queue, in which
168 // case we have to wake it up (see getOldestFrameInQueue()).
169 // We'll do it twice 100ms apart to avoid a timing issue where
170 // we signal just before the thread waits.
171 m_queueNotEmpty.notify_one(); // wake it if necessary
172 sleepInSec(0.1);
173 m_queueNotEmpty.notify_one();
174 m_drawThread.join(); // wait for death
175 m_drawThreadIsRunning = m_drawThreadShouldSuicide = false;
176 }
177
startDrawThreadIfNecessary()178 void startDrawThreadIfNecessary()
179 { if (!m_drawThreadIsRunning) startDrawThread(); }
180
killDrawThreadIfNecessary()181 void killDrawThreadIfNecessary()
182 { if (m_drawThreadIsRunning) killDrawThread(); }
183
184 // Whenever a "set" method is called that may change one of the
185 // interrelated time quantities, set all of them. We expect
186 // the mode to have been set already.
resetTimeRelatedQuantities(Real framesPerSec,Real timeScale,Real desiredBufLengthSec)187 void resetTimeRelatedQuantities(Real framesPerSec,
188 Real timeScale, Real desiredBufLengthSec)
189 {
190 if (framesPerSec <= 0) framesPerSec = DefaultFrameRateFPS;
191 if (timeScale <= 0) timeScale = 1;
192 if (desiredBufLengthSec < 0)
193 desiredBufLengthSec = DefaultDesiredBufferLengthInSec;
194
195 // Frame rate.
196 m_frameRateFPS = framesPerSec;
197 m_timeBetweenFramesInNs = secToNs(1/m_frameRateFPS);
198 m_allowableFrameTimeSlopInNs =
199 secToNs(DefaultSlopAsFractionOfFrameInterval/m_frameRateFPS);
200 m_allowableFrameJitterInNs = DefaultAllowableFrameJitterInNs;
201
202 // Time scale.
203 m_simTimeUnitsPerSec = timeScale;
204
205 // Realtime buffer.
206 m_desiredBufferLengthInSec = desiredBufLengthSec;
207
208 int numFrames =
209 (int)(m_desiredBufferLengthInSec/nsToSec(m_timeBetweenFramesInNs)
210 + 0.5);
211 if (numFrames==0 && m_desiredBufferLengthInSec > 0)
212 numFrames = 1;
213
214 // If we're in RealTime mode and we have changed the number of
215 // frames in the buffer, reallocate the pool and kill or start
216 // the draw thread if necessary.
217 if (m_mode == RealTime && numFrames != m_pool.size()) {
218 if (m_pool.size()) {
219 // draw thread isn't needed if we get rid of the buffer
220 if (numFrames == 0)
221 killDrawThreadIfNecessary();
222 initializePool(numFrames);
223 } else {
224 // draw thread is needed if we don't have one
225 initializePool(numFrames);
226 startDrawThreadIfNecessary();
227 }
228 }
229
230 clearStats();
231
232 // Note that the next frame we see is the first one and we'll need
233 // to initialize adjusted real time then.
234 m_nextFrameDueAdjRT = -1LL; // i.e., now
235 }
236
setMode(Visualizer::Mode newMode)237 void setMode(Visualizer::Mode newMode) {
238 // If we're not changing modes we just clear the stats and invalidate
239 // the next expected frame time so that we'll take the first one that
240 // shows up.
241 if (newMode==m_mode) {
242 resetTimeRelatedQuantities(m_frameRateFPS,
243 m_simTimeUnitsPerSec,
244 m_desiredBufferLengthInSec);
245 return;
246 }
247
248 // Mode is changing. If it was buffered RealTime before we have
249 // to clean up first.
250 if (m_mode == RealTime && m_pool.size()) {
251 killDrawThreadIfNecessary();
252 initializePool(0); // clear the buffer
253 }
254
255 m_mode = newMode; // change mode
256 resetTimeRelatedQuantities(m_frameRateFPS,
257 m_simTimeUnitsPerSec,
258 m_desiredBufferLengthInSec);
259 }
260
setDesiredFrameRate(Real framesPerSec)261 void setDesiredFrameRate(Real framesPerSec) {
262 resetTimeRelatedQuantities(framesPerSec,
263 m_simTimeUnitsPerSec,
264 m_desiredBufferLengthInSec);
265 // Make sure the GUI doesn't try to outrace us when it generates
266 // its own frames.
267 m_protocol.setMaxFrameRate(framesPerSec);
268 }
269
setDesiredBufferLengthInSec(Real bufferLengthInSec)270 void setDesiredBufferLengthInSec(Real bufferLengthInSec) {
271 resetTimeRelatedQuantities(m_frameRateFPS,
272 m_simTimeUnitsPerSec,
273 bufferLengthInSec);
274 }
275
setRealTimeScale(Real simTimePerRealSec)276 void setRealTimeScale(Real simTimePerRealSec) {
277 resetTimeRelatedQuantities(m_frameRateFPS,
278 simTimePerRealSec,
279 m_desiredBufferLengthInSec);
280 }
281
getDesiredBufferLengthInSec() const282 Real getDesiredBufferLengthInSec() const
283 { return m_desiredBufferLengthInSec; }
284
getActualBufferLengthInFrames() const285 int getActualBufferLengthInFrames() const {return m_pool.size();}
getActualBufferLengthInSec() const286 Real getActualBufferLengthInSec() const
287 { return (Real)nsToSec(getActualBufferLengthInFrames()
288 *m_timeBetweenFramesInNs); }
289
290 // Generate this frame and send it immediately to the renderer without
291 // thinking too hard about it.
292 void drawFrameNow(const State& state);
293
294 // In RealTime mode we have a frame to draw and a desired draw time in
295 // AdjRT. Draw it when the time comes, and adjust AdjRT if necessary.
296 void drawRealtimeFrameWhenReady
297 (const State& state, const long long& desiredDrawTimeAdjRT);
298
299 // Queuing is used only in RealTime mode.
300
301 // Called from the simulation thread when it wants to report a frame
302 // and we are in RealTime mode.
303 void reportRealtime(const State& state);
304
305 // Set the maximum number of frames in the buffer.
initializePool(int sz)306 void initializePool(int sz) {
307 std::lock_guard<std::mutex> lock(m_queueMutex);
308 m_pool.resize(sz);m_oldest=m_nframe=0;
309 }
310
getNFramesInQueue() const311 int getNFramesInQueue() const {return m_nframe;}
312
313 // Queing is enabled if the pool was allocated.
queuingIsEnabled() const314 bool queuingIsEnabled() const {return m_pool.size() != 0;}
queueIsFull() const315 bool queueIsFull() const {return m_nframe==m_pool.size();}
queueIsEmpty() const316 bool queueIsEmpty() const {return m_nframe==0;}
317
318 // Called from simulation thread. Blocks until there is room in
319 // the queue, then inserts this state unconditionally, with the indicated
320 // desired rendering time in adjusted real time. We then update the
321 // "time of next queue slot" to be one ideal frame interval later than
322 // the desired draw time.
addFrameToQueueWithWait(const State & state,const long long & desiredDrawTimeAdjRT)323 void addFrameToQueueWithWait(const State& state,
324 const long long& desiredDrawTimeAdjRT)
325 {
326 std::unique_lock<std::mutex> lock(m_queueMutex);
327 ++numReportedFramesThatWereQueued;
328 if (queueIsFull()) {
329 ++numQueuedFramesThatHadToWait;
330 // atomic: unlock, long wait, relock
331 // Only wake up if queue is not full (ignore spurious wakeups).
332 m_queueNotFull.wait(lock, [&] {return !queueIsFull();});
333 }
334
335 // There is room in the queue now. We're holding the lock.
336 Frame& frame = m_pool[(m_oldest+m_nframe)%m_pool.size()];
337 frame.state = state;
338 frame.desiredDrawTimeAdjRT = desiredDrawTimeAdjRT;
339
340 // Record the frame time.
341 m_prevFrameSimTime = state.getTime();
342
343 // Set the expected next frame time (in AdjRT).
344 m_nextFrameDueAdjRT = desiredDrawTimeAdjRT + m_timeBetweenFramesInNs;
345
346 if (++m_nframe == 1)
347 // wake up rendering thread on first frame
348 m_queueNotEmpty.notify_one();
349
350 lock.unlock();
351 }
352
353 // Call from simulation thread to allow the drawing thread to flush
354 // any frames currently in the queue.
waitUntilQueueIsEmpty()355 void waitUntilQueueIsEmpty() {
356 if ( !queuingIsEnabled() || m_nframe==0
357 || !m_drawThreadIsRunning || m_drawThreadShouldSuicide)
358 return;
359 std::unique_lock<std::mutex> lock(m_queueMutex);
360 m_queueIsEmpty.wait(lock, [&] {return m_nframe == 0;});
361 lock.unlock();
362 }
363
364 // The drawing thread uses this to find the oldest frame in the buffer.
365 // It may then at its leisure use the contained State to generate a screen
366 // image. There is no danger of the simulation thread modifying this
367 // frame; once it has been put in it stays there until the drawing thread
368 // takes it out. When done it should return the frame to the pool.
369 // Returns true if it gets a frame (which will always happen in normal
370 // operation since it waits until one is available), false if the draw
371 // thread should quit.
getOldestFrameInQueue(const Frame ** fp)372 bool getOldestFrameInQueue(const Frame** fp) {
373 std::unique_lock<std::mutex> lock(m_queueMutex);
374 if (m_nframe == 0 && !m_drawThreadShouldSuicide) {
375 ++numTimesDrawThreadBlockedOnEmptyQueue;
376 // atomic: unlock, long wait, relock; ignore spurious wakeups.
377 m_queueNotEmpty.wait(lock,
378 [&] {return m_nframe || m_drawThreadShouldSuicide;});
379 } else {
380 sumOfQueueLengths += double(m_nframe);
381 sumSquaredOfQueueLengths += double(square(m_nframe));
382 }
383 // There is at least one frame available now, unless we're supposed
384 // to quit. We are holding the lock.
385 lock.unlock();
386 if (m_drawThreadShouldSuicide) {*fp=0; return false;}
387 else {*fp=&m_pool[m_oldest]; return true;} // sim thread won't change oldest
388 }
389
390 // Drawing thread uses this to note that it is done with the oldest
391 // frame which may now be reused by the simulation thread. The queueNotFull
392 // condition is posted if there is a reasonable amount of room in the pool
393 // now.
noteThatOldestFrameIsNowAvailable()394 void noteThatOldestFrameIsNowAvailable() {
395 std::unique_lock<std::mutex> lock(m_queueMutex);
396 m_oldest = (m_oldest+1)%m_pool.size(); // move to next-oldest
397 --m_nframe; // there is now one fewer frame in use
398 if (m_nframe == 0)
399 m_queueIsEmpty.notify_one(); // in case we're flushing
400 // Start the simulation again when the pool is about half empty.
401 if (m_nframe <= m_pool.size()/2+1)
402 m_queueNotFull.notify_one();
403 lock.unlock();
404 }
405
406 // Given a time t in simulation time units, return the equivalent time r in
407 // seconds of real time. That is the amount of real time that should have
408 // elapsed since t=0 if this simulation were running at exactly the desired
409 // real time rate.
convertSimTimeToNs(const double & t)410 long long convertSimTimeToNs(const double& t)
411 { return secToNs(t / m_simTimeUnitsPerSec); }
412
413 // same as ns; that's what AdjRT tries to be
convertSimTimeToAdjRT(const double & t)414 long long convertSimTimeToAdjRT(const double& t)
415 { return convertSimTimeToNs(t); }
416
convertAdjRTtoSimTime(const long long & a)417 double convertAdjRTtoSimTime(const long long& a)
418 { return nsToSec(a) * m_simTimeUnitsPerSec; }
419
convertRTtoAdjRT(const long long & r)420 long long convertRTtoAdjRT(const long long& r)
421 { return r - m_adjustedRealTimeBase; }
422
convertAdjRTtoRT(const long long & a)423 long long convertAdjRTtoRT(const long long& a)
424 { return a + m_adjustedRealTimeBase; }
425
426
427 // Adjust the real time base by a given signed offset in nanoseconds. We're
428 // seeing incorrect adjusted realtime a* = r - r0*, but we know the actual
429 // value is a. Pass in the error e=(a*-a), then we want to calculate a
430 // new base adjustment r0 such that a = r - r0. So:
431 // a = r - r0* - e => r0=r0*+e.
readjustAdjustedRealTimeBy(const long long & e)432 void readjustAdjustedRealTimeBy(const long long& e) {
433 m_adjustedRealTimeBase += e;
434 ++numAdjustmentsToRealTimeBase;
435 }
436
getAdjustedRealTime()437 long long getAdjustedRealTime()
438 { return realTimeInNs() - m_adjustedRealTimeBase; }
439
440 // Increment the reference count and return its new value.
incrRefCount() const441 int incrRefCount() const {return ++m_refCount;}
442
443 // Decrement the reference count and return its new value.
decrRefCount() const444 int decrRefCount() const {return --m_refCount;}
445
446 // Get the current value of the reference counter.
getRefCount() const447 int getRefCount() const {return m_refCount;}
448
449 const MultibodySystem& m_system;
450 VisualizerProtocol m_protocol;
451 bool m_shutdownWhenDestructed;
452
453 Array_<DecorativeGeometry> m_addedGeometry;
454 Array_<RubberBandLine> m_lines;
455 Array_<DecorationGenerator*> m_generators;
456 Array_<Visualizer::InputListener*> m_listeners;
457 Array_<Visualizer::FrameController*> m_controllers;
458
459 CoordinateDirection m_upDirection;
460 Real m_groundHeight;
461
462 // User control of Visualizer behavior.
463 Visualizer::Mode m_mode;
464 Real m_frameRateFPS; // in frames/sec if > 0, else use default
465 Real m_simTimeUnitsPerSec; // ratio of sim time units to real seconds
466 Real m_desiredBufferLengthInSec; // RT only: how much delay (<0 => default)
467
468 // How many nanoseconds between frames?
469 long long m_timeBetweenFramesInNs;
470 // How much accuracy should we require from sleep()?
471 long long m_allowableFrameJitterInNs;
472 // How much slop is allowed in matching the time of a simulation frame
473 // to the real time at which its frame is drawn?
474 long long m_allowableFrameTimeSlopInNs;
475
476 // The offset r0 to subtract from the interval timer reading to produce
477 // the adjusted real time a that we expect to match the current simulation
478 // time t in ns. That is a = realTimeInNs()-r0. This base is adjusted by
479 // the drawing thread when we see what time we actually were able to
480 // deliver a frame.
481 long long m_adjustedRealTimeBase; // r0
482
483 // This is when we would like the simulation to send us another frame.
484 // It is optimistically set to one frame interval later than the desired
485 // draw time of the most recent frame to be put in the queue. This is
486 // also used in non-RealTime mode where AdjRT==RT.
487 long long m_nextFrameDueAdjRT;
488
489 // In RealTime mode we remember the simulated time in the previous
490 // supplied frame so that we can tell if we see an earlier frame,
491 // meaning (most likely) that we are starting a new simulation or
492 // seeing a playback of an old one.
493 double m_prevFrameSimTime;
494
495 // The frame buffer:
496 Array_<Frame,int> m_pool; // fixed size, old to new order but circular
497 int m_oldest, m_nframe; // oldest is index into pool, nframe is #valid entries
498 std::mutex m_queueMutex;
499 std::condition_variable m_queueNotFull; // these must use m_queueMutex
500 std::condition_variable m_queueNotEmpty;
501 std::condition_variable m_queueIsEmpty;
502
503 std::thread m_drawThread; // the rendering thread
504 bool m_drawThreadIsRunning;
505 bool m_drawThreadShouldSuicide;
506
507 mutable int m_refCount; // how many Visualizer handles reference
508 // this Impl object?
509
510 // Statistics
511 int numFramesReportedBySimulation;
512 int numReportedFramesThatWereIgnored;
513 int numReportedFramesThatHadToWait;
514 int numReportedFramesThatSkippedAhead;
515 int numReportedFramesThatArrivedTooLate;
516 int numReportedFramesThatWereQueued;
517 int numQueuedFramesThatHadToWait;
518
519 int numFramesSentToRenderer;
520 int numFramesDelayedByRenderer;
521 int numTimesDrawThreadBlockedOnEmptyQueue;
522 int numAdjustmentsToRealTimeBase;
523
524 double sumOfAllJitter; // updated at time sent to renderer (ms)
525 double sumSquaredOfAllJitter; // ms^2
526
527 // These are updated by the drawing thread each time it looks at the
528 // queue to pull off a frame.
529 double sumOfQueueLengths; // for computing the average length
530 double sumSquaredOfQueueLengths; // for std deviation
531
532
clearStats()533 void clearStats() {
534 numFramesReportedBySimulation=0;
535 numReportedFramesThatWereIgnored=0;
536 numReportedFramesThatHadToWait=0;
537 numReportedFramesThatSkippedAhead=0;
538 numReportedFramesThatArrivedTooLate=0;
539 numReportedFramesThatWereQueued=0;
540 numQueuedFramesThatHadToWait=0;
541
542 numFramesSentToRenderer=0;
543 numFramesDelayedByRenderer=0;
544 numTimesDrawThreadBlockedOnEmptyQueue=0;
545 numAdjustmentsToRealTimeBase=0;
546
547 sumOfAllJitter = 0;
548 sumSquaredOfAllJitter = 0;
549
550 sumOfQueueLengths = 0;
551 sumSquaredOfQueueLengths = 0;
552 }
553
dumpStats(std::ostream & o) const554 void dumpStats(std::ostream& o) const {
555 o << "Visualizer stats:\n";
556 o << " Mode: ";
557 switch(m_mode) {
558 case PassThrough: o << "PassThrough\n"; break;
559 case Sampling: o << "Sampling\n"; break;
560 case RealTime:
561 o << "RealTime, TimeScale=" << m_simTimeUnitsPerSec
562 << " sim time units/real second\n";
563 o << " Desired/actual buffer length(s): "
564 << getDesiredBufferLengthInSec() << "/"
565 << getActualBufferLengthInSec() << " ("
566 << getActualBufferLengthInFrames() << " frames)\n";
567 break;
568 };
569 o << " Desired frame rate: " << m_frameRateFPS << endl;
570 o << " reported frames: " << numFramesReportedBySimulation << endl;
571 o << " | ignored: " << numReportedFramesThatWereIgnored << endl;
572 o << " | had to wait: " << numReportedFramesThatHadToWait << endl;
573 o << " | skipped ahead: " << numReportedFramesThatSkippedAhead << endl;
574 o << " | came too late: " << numReportedFramesThatArrivedTooLate << endl;
575 o << " | were buffered: " << numReportedFramesThatWereQueued << endl;
576 o << " | | full buffer: " << numQueuedFramesThatHadToWait << endl;
577 o << " frames sent to renderer: " << numFramesSentToRenderer << endl;
578 o << " | delayed by renderer : " << numFramesDelayedByRenderer << endl;
579 if (numReportedFramesThatWereQueued && numFramesSentToRenderer) {
580 const double avg = sumOfQueueLengths/numFramesSentToRenderer;
581 o << " | average buffer length (frames): " << avg << endl;
582 o << " | std dev buffer length (frames): "
583 << std::sqrt(std::max(0.,
584 sumSquaredOfQueueLengths/numFramesSentToRenderer
585 - square(avg))) << endl;
586 }
587 o << " draw blocked for empty buffer: "
588 << numTimesDrawThreadBlockedOnEmptyQueue << endl;
589 o << " adjustments to real time base: "
590 << numAdjustmentsToRealTimeBase << endl;
591 if (numFramesSentToRenderer > 0) {
592 const double avg = sumOfAllJitter/numFramesSentToRenderer;
593 o << " average jitter (ms): " << avg << endl;
594 o << " jitter std dev (ms): "
595 << std::sqrt(sumSquaredOfAllJitter/numFramesSentToRenderer
596 - square(avg)) << endl;
597 }
598 }
599
600 };
601
602 // Generate geometry for the given state and send it to the visualizer using
603 // the VisualizerProtocol object. In buffered mode this is called from the
604 // rendering thread; otherwise, this is just the main simulation thread.
drawFrameNow(const State & state)605 void Visualizer::Impl::drawFrameNow(const State& state) {
606 m_system.realize(state, Stage::Position);
607
608 // Collect up the geometry that constitutes this scene.
609 Array_<DecorativeGeometry> geometry;
610 for (Stage stage = Stage::Topology; stage <= state.getSystemStage(); ++stage)
611 m_system.calcDecorativeGeometryAndAppend(state, stage, geometry);
612 for (unsigned i = 0; i < m_generators.size(); i++)
613 m_generators[i]->generateDecorations(state, geometry);
614
615 // Execute frame controls (e.g. camera positioning).
616 for (unsigned i = 0; i < m_controllers.size(); ++i)
617 m_controllers[i]->generateControls(Visualizer(this), state, geometry);
618
619 // Calculate the spatial pose of all the geometry and send it to the
620 // renderer.
621 m_protocol.beginScene(state.getTime());
622 VisualizerGeometry geometryCreator
623 (m_protocol, m_system.getMatterSubsystem(), state);
624 for (unsigned i = 0; i < geometry.size(); ++i)
625 geometry[i].implementGeometry(geometryCreator);
626 for (unsigned i = 0; i < m_addedGeometry.size(); ++i)
627 m_addedGeometry[i].implementGeometry(geometryCreator);
628 const SimbodyMatterSubsystem& matter = m_system.getMatterSubsystem();
629 for (unsigned i = 0; i < m_lines.size(); ++i) {
630 const RubberBandLine& line = m_lines[i];
631 const MobilizedBody& B1 = matter.getMobilizedBody(line.b1);
632 const MobilizedBody& B2 = matter.getMobilizedBody(line.b2);
633 const Transform& X_GB1 = B1.getBodyTransform(state);
634 const Transform& X_GB2 = B2.getBodyTransform(state);
635 const Vec3 end1 = X_GB1*line.station1;
636 const Vec3 end2 = X_GB2*line.station2;
637 const Real thickness = line.line.getLineThickness() == -1
638 ? 1 : line.line.getLineThickness();
639 m_protocol.drawLine(end1, end2,
640 VisualizerGeometry::getColor(line.line), thickness);
641 }
642 m_protocol.finishScene();
643
644 ++numFramesSentToRenderer;
645 }
646
647 // This is called from the drawing thread if we're buffering, otherwise
648 // directly from the simulation thread.
drawRealtimeFrameWhenReady(const State & state,const long long & desiredDrawTimeAdjRT)649 void Visualizer::Impl::drawRealtimeFrameWhenReady
650 (const State& state, const long long& desiredDrawTimeAdjRT)
651 {
652 const long long earliestDrawTimeAdjRT =
653 desiredDrawTimeAdjRT - m_allowableFrameJitterInNs;
654 const long long latestDrawTimeAdjRT =
655 desiredDrawTimeAdjRT + m_allowableFrameJitterInNs;
656
657 // Wait for the next frame time, allowing for a little jitter
658 // since we can't expect sleep to wake us up at the exact time.
659 long long now = getAdjustedRealTime();
660 if (now < earliestDrawTimeAdjRT) {
661 ++numFramesDelayedByRenderer;
662 do {sleepInNs(desiredDrawTimeAdjRT-now);}
663 while ((now=getAdjustedRealTime()) < earliestDrawTimeAdjRT);
664
665 // Keep stats on the jitter situation.
666 const long long jitterInNs = now - desiredDrawTimeAdjRT;
667 const double jitterInMs = jitterInNs*1e-6;
668 sumOfAllJitter += jitterInMs;
669 sumSquaredOfAllJitter += square(jitterInMs);
670 }
671
672 // timingError is signed with + meaning we sent the frame late.
673 const long long timingError = now - desiredDrawTimeAdjRT;
674
675 // If we sent this frame more than one frame time late we're going to
676 // admit we're not making real time and adjust the
677 // AdjRT base to match.
678 if (timingError > m_timeBetweenFramesInNs)
679 readjustAdjustedRealTimeBy(now - desiredDrawTimeAdjRT);
680
681 // It is time to render the frame.
682 drawFrameNow(state);
683 }
684
685 // Attempt to report a frame while we're in realtime mode.
reportRealtime(const State & state)686 void Visualizer::Impl::reportRealtime(const State& state) {
687 // If we see a simulation time that is earlier than the last one,
688 // we are probably starting a new simulation or playback. Flush the
689 // old one. Note that we're using actual simulation time; we don't
690 // want to get tricked by adjustments to the real time base.
691 if (state.getTime() < m_prevFrameSimTime) {
692 waitUntilQueueIsEmpty();
693 m_nextFrameDueAdjRT = -1; // restart time base
694 }
695
696 // scale, convert to ns (doesn't depend on real time base)
697 const long long t = convertSimTimeToAdjRT(state.getTime());
698
699 // If this is the first frame, or first since last setMode(), then
700 // we synchronize Adjusted Real Time to match. Readjustments will occur
701 // if the simulation doesn't keep up with real time.
702 if (m_nextFrameDueAdjRT < 0 || t == 0) {
703 m_adjustedRealTimeBase = realTimeInNs() - t;
704 // now getAdjustedRealTime()==t
705 m_nextFrameDueAdjRT = t; // i.e., now
706 }
707
708 // "timeSlop" is the amount we'll allow a frame's simulation time to
709 // deviate from the real time at which we draw it. That is, if we're
710 // expecting a frame at time t_f and the simulator instead delivers a
711 // frame at t_s, we'll consider that a match if |t_s-t_f|<=slop.
712 // The reason for this is that we prefer to issue frames at regular
713 // intervals, so if the frame time and sim time match closely enough
714 // we won't reschedule the frames. Otherwise, a sim frame whose time
715 // is too early (t_s<t_f-slop) gets thrown away (or used in desperation
716 // if we're not keeping up with real time), and a sim frame
717 // whose time is too late (t_s>t_f+slop) causes us to delay drawing
718 // that frame until real time catches up with what's in it. Typically
719 // timeSlop is set to a small fraction of the frame time, like 5%.
720 const long long timeSlop = m_allowableFrameTimeSlopInNs;
721 const long long next = m_nextFrameDueAdjRT;
722 const long long earliest = next - timeSlop;
723 const long long latest = next + timeSlop;
724
725 if (t < earliest) {
726 ++numReportedFramesThatWereIgnored; // we don't need this one
727 return;
728 }
729
730 long long desiredDrawTimeAdjRT = next;
731 if (t > latest) {
732 ++numReportedFramesThatSkippedAhead;
733 desiredDrawTimeAdjRT = t;
734 }
735
736 // If buffering is enabled, push this onto the queue. Note that we
737 // might have to wait until the queue has some room.
738 if (queuingIsEnabled()) {
739 // This also sets expectations for the next frame.
740 addFrameToQueueWithWait(state, desiredDrawTimeAdjRT);
741 return;
742 }
743
744 // There is no buffer. We'll just render this frame as soon as its
745 // drawing time arrives. No need to copy the state here. Note that
746 // the simulation thread is doing the drawing as well as the simulating.
747 // This method will also readjust adjusted real time if the frame came
748 // too late.
749 drawRealtimeFrameWhenReady(state, desiredDrawTimeAdjRT);
750
751 // Now set expectations for the next frame.
752 m_nextFrameDueAdjRT = t + m_timeBetweenFramesInNs;
753 }
754
755
756
757 //==============================================================================
758 // VISUALIZER
759 //==============================================================================
Visualizer(Visualizer::Impl * srcImpl)760 Visualizer::Visualizer(Visualizer::Impl* srcImpl) : impl(srcImpl) {
761 if (impl) impl->incrRefCount();
762 }
763
Visualizer(const MultibodySystem & system)764 Visualizer::Visualizer(const MultibodySystem& system) : impl(0) {
765 impl = new Impl(this, system, Array_<String>());
766 impl->incrRefCount();
767 }
768
Visualizer(const MultibodySystem & system,const Array_<String> & searchPath)769 Visualizer::Visualizer(const MultibodySystem& system,
770 const Array_<String>& searchPath) : impl(0) {
771 impl = new Impl(this, system, searchPath);
772 impl->incrRefCount();
773 }
774
Visualizer(const Visualizer & source)775 Visualizer::Visualizer(const Visualizer& source) : impl(0) {
776 if (source.impl) {
777 impl = source.impl;
778 impl->incrRefCount();
779 }
780 }
781
operator =(const Visualizer & source)782 Visualizer& Visualizer::operator=(const Visualizer& source) {
783 if (impl != source.impl) {
784 if (impl&& impl->decrRefCount()==0) delete impl;
785 impl = source.impl;
786 impl->incrRefCount();
787 }
788 return *this;
789 }
790
~Visualizer()791 Visualizer::~Visualizer() {
792 if (impl && impl->decrRefCount()==0)
793 delete impl;
794 }
795
shutdown()796 void Visualizer::shutdown()
797 { updImpl().m_protocol.shutdownGUI(); }
798
setShutdownWhenDestructed(bool shouldShutdown)799 Visualizer& Visualizer::setShutdownWhenDestructed(bool shouldShutdown)
800 { updImpl().setShutdownWhenDestructed(shouldShutdown); return *this; }
801
getShutdownWhenDestructed() const802 bool Visualizer::getShutdownWhenDestructed() const
803 { return getImpl().getShutdownWhenDestructed(); }
804
getRefCount() const805 int Visualizer::getRefCount() const
806 { return impl ? impl->getRefCount() : 0; }
807
808 // Frame drawing methods
809
drawFrameNow(const State & state) const810 void Visualizer::drawFrameNow(const State& state) const
811 { const_cast<Visualizer*>(this)->updImpl().drawFrameNow(state); }
812
flushFrames() const813 void Visualizer::flushFrames() const
814 { const_cast<Visualizer*>(this)->updImpl().waitUntilQueueIsEmpty(); }
815
816 // The simulation thread normally delivers frames here. Handling is dispatched
817 // according the current visualization mode.
report(const State & state) const818 void Visualizer::report(const State& state) const {
819 Visualizer::Impl& rep = const_cast<Visualizer*>(this)->updImpl();
820
821 ++rep.numFramesReportedBySimulation;
822 if (rep.m_mode == RealTime) {
823 rep.reportRealtime(state);
824 return;
825 }
826
827 // We're in Sampling or PassThrough mode. AdjRT and RT are the same in
828 // these modes; they are just real time as determined by realTimeInNs(),
829 // the current value of the interval counter. We don't care at all what
830 // time the simulation thinks it is.
831
832 // If this is the first frame, or first since last setMode(), then
833 // we set our expected next frame arrival time to now.
834 if (rep.m_nextFrameDueAdjRT < 0LL)
835 rep.m_nextFrameDueAdjRT = realTimeInNs(); // now
836
837 // If someone asked for an infinite frame rate just send this along now.
838 if (rep.m_timeBetweenFramesInNs == 0LL) {
839 drawFrameNow(state);
840 return;
841 }
842
843 const long long earliestDrawTime = rep.m_nextFrameDueAdjRT
844 - rep.m_allowableFrameJitterInNs;
845 long long now = realTimeInNs();
846 if (now < earliestDrawTime) {
847 // Too early to draw this frame. In Sampling mode that means we
848 // just ignore it.
849 if (rep.m_mode == Sampling) {
850 ++rep.numReportedFramesThatWereIgnored;
851 return;
852 }
853
854 // We're in PassThrough mode.
855 ++rep.numReportedFramesThatHadToWait;
856 do {sleepInNs(rep.m_nextFrameDueAdjRT - now);}
857 while ((now=realTimeInNs()) < earliestDrawTime);
858
859 // We're not going to wake up exactly when we wanted to; keep stats.
860 const double jitterInMs = (now - rep.m_nextFrameDueAdjRT)*1e-6;
861 rep.sumOfAllJitter += jitterInMs;
862 rep.sumSquaredOfAllJitter += square(jitterInMs);
863 }
864
865 // Frame time reached in Sampling or PassThrough modes. Draw the frame.
866 drawFrameNow(state);
867
868 // This frame might have been on time or late; we'll schedule the next
869 // time for one ideal frame interval later to keep the maximum rate down
870 // to the specified rate. Otherwise a late frame could be followed by lots
871 // of fast frames playing catch-up.
872 if (now-rep.m_nextFrameDueAdjRT <= rep.m_timeBetweenFramesInNs)
873 rep.m_nextFrameDueAdjRT += rep.m_timeBetweenFramesInNs;
874 else { // a late frame; delay the next one
875 rep.m_nextFrameDueAdjRT = now + rep.m_timeBetweenFramesInNs;
876 ++rep.numAdjustmentsToRealTimeBase;
877 }
878 }
879
880 // Visualizer display options
881
setBackgroundType(BackgroundType type)882 Visualizer& Visualizer::setBackgroundType(BackgroundType type)
883 { updImpl().m_protocol.setBackgroundType(type); return *this; }
884
setBackgroundColor(const Vec3 & color) const885 const Visualizer& Visualizer::setBackgroundColor(const Vec3& color) const
886 { getImpl().m_protocol.setBackgroundColor(color); return *this; }
887
setShowShadows(bool showShadows) const888 const Visualizer& Visualizer::setShowShadows(bool showShadows) const
889 { getImpl().m_protocol.setShowShadows(showShadows); return *this; }
890
setShowFrameRate(bool showFrameRate) const891 const Visualizer& Visualizer::setShowFrameRate(bool showFrameRate) const
892 { getImpl().m_protocol.setShowFrameRate(showFrameRate); return *this; }
893
setShowSimTime(bool showSimTime) const894 const Visualizer& Visualizer::setShowSimTime(bool showSimTime) const
895 { getImpl().m_protocol.setShowSimTime(showSimTime); return *this; }
896
setShowFrameNumber(bool showFrameNumber) const897 const Visualizer& Visualizer::setShowFrameNumber(bool showFrameNumber) const
898 { getImpl().m_protocol.setShowFrameNumber(showFrameNumber); return *this; }
899
setWindowTitle(const String & title) const900 const Visualizer& Visualizer::setWindowTitle(const String& title) const
901 { getImpl().m_protocol.setWindowTitle(title); return *this; }
902
903 // Visualizer options
904
setSystemUpDirection(const CoordinateDirection & upDir)905 Visualizer& Visualizer::setSystemUpDirection(const CoordinateDirection& upDir)
906 { updImpl().m_upDirection = upDir;
907 updImpl().m_protocol.setSystemUpDirection(upDir); return *this; }
getSystemUpDirection() const908 CoordinateDirection Visualizer::getSystemUpDirection() const
909 { return getImpl().m_upDirection; }
910
911
setGroundHeight(Real height)912 Visualizer& Visualizer::setGroundHeight(Real height) {
913 updImpl().m_groundHeight = height;
914 updImpl().m_protocol.setGroundHeight(height); return *this;
915 }
getGroundHeight() const916 Real Visualizer::getGroundHeight() const
917 { return getImpl().m_groundHeight; }
918
setMode(Visualizer::Mode mode)919 Visualizer& Visualizer::setMode(Visualizer::Mode mode)
920 { updImpl().setMode(mode); return *this; }
getMode() const921 Visualizer::Mode Visualizer::getMode() const {return getImpl().m_mode;}
922
setDesiredFrameRate(Real fps)923 Visualizer& Visualizer::setDesiredFrameRate(Real fps)
924 { updImpl().setDesiredFrameRate(std::max(fps, Real(0))); return *this; }
getDesiredFrameRate() const925 Real Visualizer::getDesiredFrameRate() const
926 { return getImpl().m_frameRateFPS; }
927
setRealTimeScale(Real simTimePerRealSec)928 Visualizer& Visualizer::setRealTimeScale(Real simTimePerRealSec)
929 { updImpl().setRealTimeScale(simTimePerRealSec); return *this; }
getRealTimeScale() const930 Real Visualizer::getRealTimeScale() const
931 { return getImpl().m_simTimeUnitsPerSec; }
932
setDesiredBufferLengthInSec(Real bufferLengthInSec)933 Visualizer& Visualizer::setDesiredBufferLengthInSec(Real bufferLengthInSec)
934 { updImpl().setDesiredBufferLengthInSec(bufferLengthInSec); return *this; }
getDesiredBufferLengthInSec() const935 Real Visualizer::getDesiredBufferLengthInSec() const
936 { return getImpl().getDesiredBufferLengthInSec(); }
getActualBufferLengthInFrames() const937 int Visualizer::getActualBufferLengthInFrames() const
938 { return getImpl().getActualBufferLengthInFrames(); }
getActualBufferLengthInSec() const939 Real Visualizer::getActualBufferLengthInSec() const
940 { return getImpl().getActualBufferLengthInSec(); }
941
942
addInputListener(Visualizer::InputListener * listener)943 int Visualizer::addInputListener(Visualizer::InputListener* listener) {
944 Impl& impl = updImpl();
945 const int nxt = (int)impl.m_listeners.size();
946 impl.m_listeners.push_back(listener);
947 return nxt;
948 }
getNumInputListeners() const949 int Visualizer::getNumInputListeners() const
950 { return (int)getImpl().m_listeners.size(); }
getInputListener(int i) const951 const Visualizer::InputListener& Visualizer::getInputListener(int i) const
952 { return *getImpl().m_listeners[i]; }
updInputListener(int i)953 Visualizer::InputListener& Visualizer::updInputListener(int i)
954 { return *updImpl().m_listeners[i]; }
955
addFrameController(Visualizer::FrameController * fc)956 int Visualizer::addFrameController(Visualizer::FrameController* fc) {
957 Impl& impl = updImpl();
958 const int nxt = (int)impl.m_controllers.size();
959 impl.m_controllers.push_back(fc);
960 return nxt;
961 }
getNumFrameControllers() const962 int Visualizer::getNumFrameControllers() const
963 { return (int)getImpl().m_controllers.size(); }
getFrameController(int i) const964 const Visualizer::FrameController& Visualizer::getFrameController(int i) const
965 { return *getImpl().m_controllers[i]; }
updFrameController(int i)966 Visualizer::FrameController& Visualizer::updFrameController(int i)
967 { return *updImpl().m_controllers[i]; }
968
969
970 // Scene-building methods
971
972 Visualizer& Visualizer::
addMenu(const String & title,int menuId,const Array_<pair<String,int>> & items)973 addMenu(const String& title, int menuId,
974 const Array_<pair<String, int> >& items)
975 {
976 SimTK_ERRCHK2_ALWAYS(menuId >= 0, "Visualizer::addMenu()",
977 "Assigned menu ids must be nonnegative, but an attempt was made to create"
978 " a menu %s with id %d.", title.c_str(), menuId);
979
980 updImpl().m_protocol.addMenu(title, menuId, items);
981 return *this;
982 }
983
984 Visualizer& Visualizer::
addSlider(const String & title,int sliderId,Real minVal,Real maxVal,Real value)985 addSlider(const String& title, int sliderId,
986 Real minVal, Real maxVal, Real value)
987 {
988 SimTK_ERRCHK2_ALWAYS(sliderId >= 0, "Visualizer::addSlider()",
989 "Assigned slider ids must be nonnegative, but an attempt was made to create"
990 " a slider %s with id %d.", title.c_str(), sliderId);
991 SimTK_ERRCHK4_ALWAYS(minVal <= value && value <= maxVal, "Visualizer::addSlider()",
992 "Initial slider value %g for slider %s was outside the specified range [%g,%g].",
993 value, title.c_str(), minVal, maxVal);
994
995 updImpl().m_protocol.addSlider(title, sliderId, minVal, maxVal, value);
996 return *this;
997 }
998
999 int Visualizer::
addDecoration(MobilizedBodyIndex mobodIx,const Transform & X_BD,const DecorativeGeometry & geom)1000 addDecoration(MobilizedBodyIndex mobodIx, const Transform& X_BD,
1001 const DecorativeGeometry& geom)
1002 {
1003 Array_<DecorativeGeometry>& addedGeometry = updImpl().m_addedGeometry;
1004 const int nxt = (int)addedGeometry.size();
1005 addedGeometry.push_back(geom);
1006 DecorativeGeometry& geomCopy = addedGeometry.back();
1007 geomCopy.setBodyId((int)mobodIx);
1008 geomCopy.setTransform(X_BD * geomCopy.getTransform());
1009 return nxt;
1010 }
getNumDecorations() const1011 int Visualizer::getNumDecorations() const
1012 { return (int)getImpl().m_addedGeometry.size(); }
getDecoration(int i) const1013 const DecorativeGeometry& Visualizer::getDecoration(int i) const
1014 { return getImpl().m_addedGeometry[i]; }
updDecoration(int i) const1015 DecorativeGeometry& Visualizer::updDecoration(int i) const
1016 { return const_cast<Visualizer*>(this)->updImpl().m_addedGeometry[i]; }
1017
1018 int Visualizer::
addRubberBandLine(MobilizedBodyIndex b1,const Vec3 & station1,MobilizedBodyIndex b2,const Vec3 & station2,const DecorativeLine & line)1019 addRubberBandLine(MobilizedBodyIndex b1, const Vec3& station1,
1020 MobilizedBodyIndex b2, const Vec3& station2,
1021 const DecorativeLine& line)
1022 {
1023 Impl& impl = updImpl();
1024 const int nxt = (int)impl.m_lines.size();
1025 impl.m_lines.push_back(RubberBandLine(b1,station1, b2,station2, line));
1026 return nxt;
1027 }
getNumRubberBandLines() const1028 int Visualizer::getNumRubberBandLines() const
1029 { return (int)getImpl().m_lines.size(); }
getRubberBandLine(int i) const1030 const DecorativeLine& Visualizer::getRubberBandLine(int i) const
1031 { return getImpl().m_lines[i].line; }
updRubberBandLine(int i) const1032 DecorativeLine& Visualizer::updRubberBandLine(int i) const
1033 { return const_cast<Visualizer*>(this)->updImpl().m_lines[i].line; }
1034
1035 int Visualizer::
addDecorationGenerator(DecorationGenerator * generator)1036 addDecorationGenerator(DecorationGenerator* generator)
1037 {
1038 Impl& impl = updImpl();
1039 const int nxt = (int)impl.m_generators.size();
1040 impl.m_generators.push_back(generator);
1041 return nxt;
1042 }
1043 int Visualizer::
getNumDecorationGenerators() const1044 getNumDecorationGenerators() const
1045 { return (int)getImpl().m_generators.size(); }
1046 const DecorationGenerator& Visualizer::
getDecorationGenerator(int i) const1047 getDecorationGenerator(int i) const
1048 { return *getImpl().m_generators[i]; }
1049 DecorationGenerator& Visualizer::
updDecorationGenerator(int i)1050 updDecorationGenerator(int i)
1051 { return *updImpl().m_generators[i]; }
1052
1053 // Frame control methods
1054 const Visualizer& Visualizer::
setCameraTransform(const Transform & transform) const1055 setCameraTransform(const Transform& transform) const
1056 { getImpl().m_protocol.setCameraTransform(transform); return *this; }
1057
zoomCameraToShowAllGeometry() const1058 const Visualizer& Visualizer::zoomCameraToShowAllGeometry() const
1059 { getImpl().m_protocol.zoomCamera(); return *this; }
1060
1061 const Visualizer& Visualizer::
pointCameraAt(const Vec3 & point,const Vec3 & upDirection) const1062 pointCameraAt(const Vec3& point, const Vec3& upDirection) const
1063 { getImpl().m_protocol.lookAt(point, upDirection); return *this; }
1064
setCameraFieldOfView(Real fov) const1065 const Visualizer& Visualizer::setCameraFieldOfView(Real fov) const
1066 { getImpl().m_protocol.setFieldOfView(fov); return *this; }
1067
1068 const Visualizer& Visualizer::
setCameraClippingPlanes(Real nearPlane,Real farPlane) const1069 setCameraClippingPlanes(Real nearPlane, Real farPlane) const
1070 { getImpl().m_protocol.setClippingPlanes(nearPlane, farPlane);
1071 return *this; }
1072
1073
setSliderValue(int slider,Real newValue) const1074 const Visualizer& Visualizer::setSliderValue(int slider, Real newValue) const
1075 { getImpl().m_protocol.setSliderValue(slider, newValue); return *this; }
1076
1077 const Visualizer& Visualizer::
setSliderRange(int slider,Real newMin,Real newMax) const1078 setSliderRange(int slider, Real newMin, Real newMax) const
1079 { getImpl().m_protocol.setSliderRange(slider, newMin, newMax); return *this; }
1080
1081 // Debugging and statistics
dumpStats(std::ostream & o) const1082 void Visualizer::dumpStats(std::ostream& o) const {getImpl().dumpStats(o);}
clearStats()1083 void Visualizer::clearStats() {updImpl().clearStats();}
1084
1085 // Internal use only
getInputListeners() const1086 const Array_<Visualizer::InputListener*>& Visualizer::getInputListeners() const
1087 { return getImpl().m_listeners; }
getFrameControllers() const1088 const Array_<Visualizer::FrameController*>& Visualizer::getFrameControllers() const
1089 { return getImpl().m_controllers; }
getSystem() const1090 const MultibodySystem& Visualizer::getSystem() const {return getImpl().m_system;}
1091
1092
1093 //==============================================================================
1094 // BODY FOLLOWER
1095 //==============================================================================
BodyFollower(const MobilizedBody & mobodB,const Vec3 & stationPinB,const Vec3 & offset,const UnitVec3 & upDirection)1096 Visualizer::BodyFollower::BodyFollower(
1097 const MobilizedBody& mobodB,
1098 const Vec3& stationPinB,
1099 const Vec3& offset,
1100 const UnitVec3& upDirection)
1101 : m_mobodB(mobodB), m_stationPinB(stationPinB), m_offset(offset),
1102 m_upDirection(upDirection) {}
1103
generateControls(const Visualizer & viz,const State & state,Array_<DecorativeGeometry> & geometry)1104 void Visualizer::BodyFollower::generateControls(
1105 const Visualizer& viz,
1106 const State& state,
1107 Array_< DecorativeGeometry >& geometry)
1108 {
1109 // Offset.
1110 Vec3 offset(m_offset);
1111 if (m_offset.isNaN()) {
1112 // Default: offset is based on system up direction and ground height.
1113 offset = Vec3(1, 1, 1);
1114 offset[viz.getSystemUpDirection().getAxis()] += viz.getGroundHeight();
1115 }
1116
1117 // Up direction. Default: use System up direction.
1118 const UnitVec3& upDirection = m_upDirection.isNaN() ?
1119 UnitVec3(viz.getSystemUpDirection()) : m_upDirection;
1120
1121 const Vec3 P = m_mobodB.findStationLocationInGround(state, m_stationPinB);
1122 // Position of camera (C) from ground origin (G), expressed in ground.
1123 const Vec3 p_GC = P + offset;
1124 // Rotation of camera frame (C) in ground frame (G).
1125 // To get the camera to point at P, we require the camera's z direction
1126 // (which points "back") to be parallel to the offset. We also want the
1127 // camera's y direction (which points to the top of the screen) to be as
1128 // closely aligned with the provided up direction as is possible.
1129 const Rotation R_GC(UnitVec3(offset), ZAxis, upDirection, YAxis);
1130 viz.setCameraTransform(Transform(R_GC, p_GC));
1131 }
1132
1133
1134 //==============================================================================
1135 // THE DRAWING THREAD
1136 //==============================================================================
1137 /* When we're in RealTime mode, we run a separate thread that actually sends
1138 frames to the renderer. It pulls frames off the back (oldest) end of the
1139 frame buffer queue, while the simulation thread is pushing frames onto the
1140 front (newest) end of the queue. The rendering thread is created whenever
1141 the Visualizer enters RealTime mode, and canceled whenever it leaves
1142 RealTime mode or is destructed.
1143
1144 This is the main function for the buffered drawing thread. Its job is to
1145 pull the oldest frames off the queue and send them to the renderer at
1146 the right real times. We use adjusted real time (AdjRT) which should match
1147 the simulation time kept with the frame as its desired draw time. If the
1148 frame is ahead of AdjRT, we'll sleep to let real time catch up. If the
1149 frame is substantially behind, we'll render it now and then adjust the AdjRT
1150 base to acknowledge that we have irretrievably slipped from real time and need
1151 to adjust our expectations for the future. */
drawingThreadMain(Visualizer::Impl & vizImpl)1152 static void drawingThreadMain(Visualizer::Impl& vizImpl) {
1153
1154 do {
1155 // Grab the oldest frame in the queue.
1156 // This will wait if necessary until a frame is available.
1157 const Frame* framep;
1158 if (vizImpl.getOldestFrameInQueue(&framep)) {
1159 // Draw this frame as soon as its draw time arrives, and readjust
1160 // adjusted real time if necessary.
1161 vizImpl.drawRealtimeFrameWhenReady
1162 (framep->state, framep->desiredDrawTimeAdjRT);
1163
1164 // Return the now-rendered frame to circulation in the pool. This may
1165 // wake up the simulation thread if it was waiting for space.
1166 vizImpl.noteThatOldestFrameIsNowAvailable();
1167 }
1168 } while (!vizImpl.m_drawThreadShouldSuicide);
1169
1170 // Attempt to wake up the simulation thread if it is waiting for
1171 // the draw thread since there won't be any more notices!
1172 vizImpl.m_queueNotFull.notify_one(); // wake up if waiting for queue space
1173 vizImpl.m_queueIsEmpty.notify_one(); // wake up if flushing
1174 }
1175