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