1 /* -------------------------------------------------------------------------- *
2  *                       OpenSim:  ModelVisualizer.cpp                        *
3  * -------------------------------------------------------------------------- *
4  * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
5  * See http://opensim.stanford.edu and the NOTICE file for more information.  *
6  * OpenSim is developed at Stanford University and supported by the US        *
7  * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
8  * through the Warrior Web program.                                           *
9  *                                                                            *
10  * Copyright (c) 2005-2017 Stanford University and the Authors                *
11  * Author(s): Michael A. Sherman                                              *
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 "ModelVisualizer.h"
25 #include "Model.h"
26 #include <OpenSim/version.h>
27 #include <OpenSim/Common/ModelDisplayHints.h>
28 #include <simbody/internal/Visualizer_InputListener.h>
29 #include <simbody/internal/Visualizer_Reporter.h>
30 
31 #include <string>
32 using std::string;
33 #include <iostream>
34 using std::cout; using std::cerr; using std::clog; using std::endl;
35 
36 using namespace OpenSim;
37 using namespace SimTK;
38 
39 //==============================================================================
40 //                       OPENSIM INPUT LISTENER
41 //==============================================================================
42 
43 // These constants are used to identify the OpenSim display menu in the
44 // Visualizer window and particular selections from it.
45 static const int ShowMenuId = 1;
46 static const int ToggleWrapGeometry = 0;
47 static const int ToggleContactGeometry = 1;
48 static const int ToggleMusclePaths = 2;
49 static const int TogglePathPoints = 3;
50 static const int ToggleMarkers = 4;
51 static const int ToggleFrames = 5;
52 static const int ToggleDefaultGeometry = 6;
53 
54 /* This class gets first crack at user input coming in through the Visualizer
55 window. We use it to intercept anything we want to handle as part of the
56 standard OpenSim-provided interface, such as turning on and off display of wrap
57 objects. Anything we don't handle here will just get passed on to the
58 Visualizer's InputSilo where it will remain until the user's program goes
59 looking for it. */
60 class OpenSimInputListener : public Visualizer::InputListener {
61 public:
OpenSimInputListener(Model & model)62     OpenSimInputListener(Model& model) : _model(model) {}
63 
64     /* This is the implementation of the InputListener interface. We're going
65     to override only the menu-pick method and ignore anything we don't
66     recognize. Caution: this is being called from the Visualizer's input thread,
67     *not* the main execution thread. Synchronization is required to do anything
68     complicated; here we're just setting/clearing visualization flags so no
69     synchronization is required. Note that the Visualizer's InputSilo class
70     handles synchronization automatically but this one does not. */
menuSelected(int menu,int item)71     bool menuSelected(int menu, int item) override {
72         if (menu != ShowMenuId) return false; // some other menu
73         ModelDisplayHints& hints = _model.updDisplayHints();
74         switch(item) {
75         case ToggleWrapGeometry:
76             hints.set_show_wrap_geometry(!hints.get_show_wrap_geometry());
77             return true; // absorb this input
78         case ToggleContactGeometry:
79             hints.set_show_contact_geometry(!hints.get_show_contact_geometry());
80             return true;
81         case ToggleMusclePaths:
82             hints.set_show_path_geometry(!hints.get_show_path_geometry());
83             return true;
84         case TogglePathPoints:
85             hints.set_show_path_points(!hints.get_show_path_points());
86             return true;
87         case ToggleMarkers:
88             hints.set_show_markers(!hints.get_show_markers());
89             return true;
90         case ToggleDefaultGeometry: {
91             SimbodyMatterSubsystem& matter =
92                 _model.updMatterSubsystem();
93             matter.setShowDefaultGeometry(!matter.getShowDefaultGeometry());
94             return true;
95             }
96         };
97         return false; // let someone else deal with this input
98     }
99 private:
100     Model&  _model;
101 };
102 
103 
104 
105 
106 
107 // Draw a path point with a small body-axis-aligned cross centered on
108 // the point.
drawPathPoint(const MobilizedBodyIndex & body,const Vec3 & pt_B,const Vec3 & color,Array_<SimTK::DecorativeGeometry> & geometry)109 void DefaultGeometry::drawPathPoint(const MobilizedBodyIndex&             body,
110                           const Vec3&                           pt_B,
111                           const Vec3&                           color,
112                           Array_<SimTK::DecorativeGeometry>&    geometry)
113 {
114     geometry.push_back(DecorativeSphere(0.005)
115                 .setTransform(pt_B)
116                 .setBodyId(body)
117                 .setColor(color)
118                 .setOpacity(0.8));
119 }
120 
generateDecorations(const State & state,Array_<SimTK::DecorativeGeometry> & geometry)121 void DefaultGeometry::generateDecorations
122    (const State&                         state,
123     Array_<SimTK::DecorativeGeometry>&   geometry)
124 {
125     // Ask all the ModelComponents to generate dynamic geometry.
126     _model.generateDecorations(false, _model.getDisplayHints(),
127                                state, geometry);
128 }
129 
130 //==============================================================================
131 //                            MODEL VISUALIZER
132 //==============================================================================
133 
show(const SimTK::State & state) const134 void ModelVisualizer::show(const SimTK::State& state) const {
135     // Make sure we're realized at least through Velocity stage.
136     _model.getMultibodySystem().realize(state, SimTK::Stage::Velocity);
137     getSimbodyVisualizer().report(state);
138 }
139 
140 // See if we can find the given file. The rules are
141 //  - if it is an absolute pathname, we only get one shot, else:
142 //  - define "modelDir" to be the absolute pathname of the
143 //      directory from which we read in the .osim model, if we did,
144 //      otherwise modelDir="." (current directory).
145 //  - look for the geometry file in modelDir
146 //  - look for the geometry file in modelDir/Geometry
147 //  - search the user added paths in dirToSearch in reverse chronological order
148 //    i.e. latest path added is searched first.
149 //  - look for the geometry file in installDir/Geometry
150 bool ModelVisualizer::
findGeometryFile(const Model & aModel,const std::string & geoFile,bool & geoFileIsAbsolute,SimTK::Array_<std::string> & attempts)151 findGeometryFile(const Model& aModel,
152                  const std::string&          geoFile,
153                  bool&                       geoFileIsAbsolute,
154                  SimTK::Array_<std::string>& attempts)
155 {
156     attempts.clear();
157     std::string geoDirectory, geoFileName, geoExtension;
158     SimTK::Pathname::deconstructPathname(geoFile,
159         geoFileIsAbsolute, geoDirectory, geoFileName, geoExtension);
160 
161     bool foundIt = false;
162     if (geoFileIsAbsolute) {
163         attempts.push_back(geoFile);
164         foundIt = Pathname::fileExists(attempts.back());
165     } else {
166         const string geoDir = "Geometry" + Pathname::getPathSeparator();
167         string modelDir;
168         if (aModel.getInputFileName() == "Unassigned")
169             modelDir = Pathname::getCurrentWorkingDirectory();
170         else {
171             bool isAbsolutePath; string directory, fileName, extension;
172             SimTK::Pathname::deconstructPathname(
173                 aModel.getInputFileName(),
174                 isAbsolutePath, directory, fileName, extension);
175             modelDir = isAbsolutePath
176                 ? directory
177                 : Pathname::getCurrentWorkingDirectory() + directory;
178         }
179 
180         attempts.push_back(modelDir + geoFile);
181         foundIt = Pathname::fileExists(attempts.back());
182 
183         if (!foundIt) {
184             attempts.push_back(modelDir + geoDir + geoFile);
185             foundIt = Pathname::fileExists(attempts.back());
186         }
187 
188         if (!foundIt) {
189             for(auto dir = dirsToSearch.crbegin();
190                 dir != dirsToSearch.crend();
191                 ++dir) {
192                 attempts.push_back(*dir + geoFile);
193                 if(Pathname::fileExists(attempts.back())) {
194                     foundIt = true;
195                     break;
196                 }
197             }
198         }
199 
200         if (!foundIt) {
201             const string installDir =
202                 Pathname::getInstallDir("OPENSIM_HOME", "OpenSim");
203             attempts.push_back(installDir + geoDir + geoFile);
204             foundIt = Pathname::fileExists(attempts.back());
205         }
206     }
207 
208     return foundIt;
209 }
210 
211 // Initialize the static variable.
212 SimTK::Array_<std::string> ModelVisualizer::dirsToSearch{};
213 
addDirToGeometrySearchPaths(const std::string & dir)214 void ModelVisualizer::addDirToGeometrySearchPaths(const std::string& dir) {
215     // Make sure to add trailing path-separator if one is not present.
216     if(dir.back() == Pathname::getPathSeparator().back())
217         dirsToSearch.push_back(dir);
218     else
219         dirsToSearch.push_back(dir + Pathname::getPathSeparator());
220 }
221 
222 // Call this on a newly-constructed ModelVisualizer (typically from the Model's
223 // initSystem() method) to set up the various auxiliary classes used for
224 // visualization and user interaction. This involves modifications to the
225 // System that must be done prior to realizeTopology(), and may modify the
226 // Model also.
createVisualizer()227 void ModelVisualizer::createVisualizer() {
228     _model.updMatterSubsystem().setShowDefaultGeometry(false);
229 
230     // Allocate a Simbody Visualizer. The search will go as
231     // follows: first look in the same directory as the currently-
232     // executing executable; then look at all the paths in the environment
233     // variable PATH, then look in various default Simbody places.
234     Array_<String> searchPath;
235     if (SimTK::Pathname::environmentVariableExists("PATH")) {
236         const auto& path = SimTK::Pathname::getEnvironmentVariable("PATH");
237         std::string buffer{};
238         for(const auto ch : path) {
239             if(ch == ':' || ch == ';') {
240                 searchPath.push_back(buffer);
241                 buffer.clear();
242             } else
243                 buffer.push_back(ch);
244         }
245     }
246     _viz = new SimTK::Visualizer(_model.getMultibodySystem(),
247                                  searchPath);
248 
249     // Make the Simbody Visualizer (that is, the display window) kill itself
250     // when the API-side connection is lost (because the Visualizer object gets
251     // destructed). Otherwise it will hang around afterwards.
252     _viz->setShutdownWhenDestructed(true);
253 
254     _viz->setCameraClippingPlanes(.01,100.);
255 
256     // Give it an OpenSim-friendly window heading.
257     bool isAbsolutePath; string directory, fileName, extension;
258     SimTK::Pathname::deconstructPathname(
259         SimTK::Pathname::getThisExecutablePath(),
260         isAbsolutePath, directory, fileName, extension);
261     _viz->setWindowTitle("OpenSim " + OpenSim::GetVersion()
262                             + ": " + fileName + " (" + _model.getName() + ")");
263 
264     // Create a menu for choosing what to display.
265     SimTK::Array_< std::pair<SimTK::String, int> > selections;
266     selections.push_back(std::make_pair("Wrap geometry",
267                                         ToggleWrapGeometry));
268     selections.push_back(std::make_pair("Contact geometry",
269                                         ToggleContactGeometry));
270     selections.push_back(std::make_pair("Muscle paths",ToggleMusclePaths));
271     selections.push_back(std::make_pair("Path points",TogglePathPoints));
272     selections.push_back(std::make_pair("Markers",ToggleMarkers));
273     selections.push_back(std::make_pair("Frames",ToggleFrames));
274     selections.push_back(std::make_pair("Default geometry",
275                                         ToggleDefaultGeometry));
276     _viz->addMenu("Show", ShowMenuId, selections);
277 
278     // Add a DecorationGenerator to dispatch runtime generateDecorations()
279     // calls.
280     _decoGen = new DefaultGeometry(_model);
281     _viz->addDecorationGenerator(_decoGen);
282 
283     // Add an input listener to handle display menu picks.
284     _viz->addInputListener(new OpenSimInputListener(_model));
285 
286     // Allocate an InputSilo to pick up anything the above listener doesn't.
287     _silo = new SimTK::Visualizer::InputSilo();
288     _viz->addInputListener(_silo);
289 
290     // This is used for regular output of frames during forward dynamics.
291     // TODO: allow user control of timing.
292     _model.updMultibodySystem().addEventReporter
293         (new SimTK::Visualizer::Reporter(*_viz, 1./30));
294 }
295 
296 // We also rummage through the model to find fixed geometry that should be part
297 // of every frame. The supplied State must be realized through Instance stage.
collectFixedGeometry(const State & state) const298 void ModelVisualizer::collectFixedGeometry(const State& state) const {
299     // Collect any fixed geometry from the ModelComponents.
300     Array_<DecorativeGeometry> fixedGeometry;
301     _model.generateDecorations
302        (true, _model.getDisplayHints(), state, fixedGeometry);
303 
304     for (unsigned i=0; i < fixedGeometry.size(); ++i) {
305         const DecorativeGeometry& dgeo = fixedGeometry[i];
306         //cout << dgeo.getBodyId() << dgeo.getTransform() << endl;
307         _viz->addDecoration(MobilizedBodyIndex(dgeo.getBodyId()),
308                             Transform(), dgeo);
309     }
310 }
311