mesh_loader.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010, Willow Garage, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Willow Garage, Inc. nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "mesh_loader.h"
32 
33 #include <boost/filesystem.hpp>
34 #include <boost/algorithm/string.hpp>
35 
36 #include <OgreMeshManager.h>
37 #include <OgreTextureManager.h>
38 #include <OgreMaterialManager.h>
39 #include <OgreTexture.h>
40 #include <OgrePass.h>
41 #include <OgreTechnique.h>
42 #include <OgreMaterial.h>
43 #include <OgreTextureUnitState.h>
44 #include <OgreMeshSerializer.h>
45 #include <OgreSubMesh.h>
46 #include <OgreHardwareBufferManager.h>
47 #include <OgreSharedPtr.h>
48 #include <OgreTechnique.h>
49 
50 #include <tinyxml2.h>
51 
52 #include <ros/assert.h>
53 
54 #if defined(ASSIMP_UNIFIED_HEADER_NAMES)
55 #include <assimp/Importer.hpp>
56 #include <assimp/scene.h>
57 #include <assimp/postprocess.h>
58 #include <assimp/IOStream.hpp>
59 #include <assimp/IOSystem.hpp>
60 #else
61 #include <assimp/assimp.hpp>
62 #include <assimp/aiScene.h>
63 #include <assimp/aiPostProcess.h>
64 #include <assimp/IOStream.h>
65 #include <assimp/IOSystem.h>
66 #endif
67 
68 namespace fs = boost::filesystem;
69 
70 namespace rviz
71 {
72 class ResourceIOStream : public Assimp::IOStream
73 {
74 public:
76  {
77  }
78 
79  ~ResourceIOStream() override
80  {
81  }
82 
83  size_t Read(void* buffer, size_t size, size_t count) override
84  {
85  size_t to_read = size * count;
86  if (pos_ + to_read > res_.data.get() + res_.size)
87  {
88  to_read = res_.size - (pos_ - res_.data.get());
89  }
90 
91  memcpy(buffer, pos_, to_read);
92  pos_ += to_read;
93 
94  return to_read;
95  }
96 
97  size_t Write(const void* /*buffer*/, size_t /*size*/, size_t /*count*/) override
98  {
99  ROS_BREAK();
100  return 0;
101  }
102 
103  aiReturn Seek(size_t offset, aiOrigin origin) override
104  {
105  uint8_t* new_pos = nullptr;
106  switch (origin)
107  {
108  case aiOrigin_SET:
109  new_pos = res_.data.get() + offset;
110  break;
111  case aiOrigin_CUR:
112  new_pos = pos_ + offset; // TODO is this right? can offset really not be negative
113  break;
114  case aiOrigin_END:
115  new_pos = res_.data.get() + res_.size - offset; // TODO is this right?
116  break;
117  default:
118  ROS_BREAK();
119  }
120 
121  if (new_pos < res_.data.get() || new_pos > res_.data.get() + res_.size)
122  {
123  return aiReturn_FAILURE;
124  }
125 
126  pos_ = new_pos;
127  return aiReturn_SUCCESS;
128  }
129 
130  size_t Tell() const override
131  {
132  return pos_ - res_.data.get();
133  }
134 
135  size_t FileSize() const override
136  {
137  return res_.size;
138  }
139 
140  void Flush() override
141  {
142  }
143 
144 private:
146  uint8_t* pos_;
147 };
148 
149 class ResourceIOSystem : public Assimp::IOSystem
150 {
151 public:
153  {
154  }
155 
156  ~ResourceIOSystem() override
157  {
158  }
159 
160  // Check whether a specific file exists
161  bool Exists(const char* file) const override
162  {
163  // Ugly -- two retrievals where there should be one (Exists + Open)
164  // resource_retriever needs a way of checking for existence
165  // TODO: cache this
167  try
168  {
169  res = retriever_.get(file);
170  }
172  {
173  return false;
174  }
175 
176  return true;
177  }
178 
179  // Get the path delimiter character we'd like to see
180  char getOsSeparator() const override
181  {
182  return '/';
183  }
184 
185  // ... and finally a method to open a custom stream
186  Assimp::IOStream* Open(const char* file, const char* mode = "rb") override
187  {
188  ROS_ASSERT(mode == std::string("r") || mode == std::string("rb"));
189  (void)mode;
190 
191  // Ugly -- two retrievals where there should be one (Exists + Open)
192  // resource_retriever needs a way of checking for existence
194  try
195  {
196  res = retriever_.get(file);
197  }
199  {
200  return nullptr;
201  }
202 
203  return new ResourceIOStream(res);
204  }
205 
206  void Close(Assimp::IOStream* stream) override;
207 
208 private:
210 };
211 
212 void ResourceIOSystem::Close(Assimp::IOStream* stream)
213 {
214  delete stream;
215 }
216 
217 // Mostly stolen from gazebo
223 void buildMesh(const aiScene* scene,
224  const aiNode* node,
225  const Ogre::MeshPtr& mesh,
226  Ogre::AxisAlignedBox& aabb,
227  float& radius,
228  std::vector<Ogre::MaterialPtr>& material_table,
229  aiMatrix4x4 transform = aiMatrix4x4())
230 {
231  if (!node)
232  return;
233 
234  if (node->mParent == nullptr)
235  {
236  // Use root node's transform
237  transform = node->mTransformation;
238  // but don't rotate to y-up orientation, which is *sometimes* done in assimp's root node
239  aiVector3D scaling, axis, pos;
240  float angle;
241  transform.Decompose(scaling, axis, angle, pos);
242  // drop rotation, but keep scaling and position
243  transform = aiMatrix4x4(scaling, aiQuaternion(), pos);
244  }
245  else
246  transform *= node->mTransformation;
247 
248  // rotation (+scaling) part of transform applied to normals
249  aiMatrix3x3 rotation(transform);
250 
251  for (uint32_t i = 0; i < node->mNumMeshes; i++)
252  {
253  aiMesh* input_mesh = scene->mMeshes[node->mMeshes[i]];
254 
255  Ogre::SubMesh* submesh = mesh->createSubMesh();
256  submesh->useSharedVertices = false;
257  submesh->vertexData = new Ogre::VertexData();
258  Ogre::VertexData* vertex_data = submesh->vertexData;
259  Ogre::VertexDeclaration* vertex_decl = vertex_data->vertexDeclaration;
260 
261  size_t offset = 0;
262  // positions
263  vertex_decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
264  offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
265 
266  // normals
267  if (input_mesh->HasNormals())
268  {
269  vertex_decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
270  offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
271  }
272 
273  // texture coordinates (only support 1 for now)
274  if (input_mesh->HasTextureCoords(0))
275  {
276  vertex_decl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0);
277  offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);
278  }
279 
280  // todo vertex colors
281 
282  // allocate the vertex buffer
283  vertex_data->vertexCount = input_mesh->mNumVertices;
284  Ogre::HardwareVertexBufferSharedPtr vbuf =
285  Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
286  vertex_decl->getVertexSize(0), vertex_data->vertexCount,
287  Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
288 
289  vertex_data->vertexBufferBinding->setBinding(0, vbuf);
290  float* vertices = static_cast<float*>(vbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
291 
292  // Add the vertices
293  for (uint32_t j = 0; j < input_mesh->mNumVertices; j++)
294  {
295  aiVector3D p = input_mesh->mVertices[j];
296  p *= transform;
297  *vertices++ = p.x;
298  *vertices++ = p.y;
299  *vertices++ = p.z;
300 
301  Ogre::Vector3 v(p.x, p.y, p.z);
302  aabb.merge(v);
303  float dist = v.length();
304  if (dist > radius)
305  {
306  radius = dist;
307  }
308 
309  if (input_mesh->HasNormals())
310  {
311  aiVector3D n = input_mesh->mNormals[j];
312  n *= rotation;
313  n.Normalize();
314  *vertices++ = n.x;
315  *vertices++ = n.y;
316  *vertices++ = n.z;
317  }
318 
319  if (input_mesh->HasTextureCoords(0))
320  {
321  *vertices++ = input_mesh->mTextureCoords[0][j].x;
322  *vertices++ = input_mesh->mTextureCoords[0][j].y;
323  }
324  }
325 
326  // calculate index count
327  submesh->indexData->indexCount = 0;
328  for (uint32_t j = 0; j < input_mesh->mNumFaces; j++)
329  {
330  aiFace& face = input_mesh->mFaces[j];
331  submesh->indexData->indexCount += face.mNumIndices;
332  }
333 
334  // If we have less than 65536 (2^16) vertices, we can use a 16-bit index buffer.
335  if (vertex_data->vertexCount < (1 << 16))
336  {
337  // allocate index buffer
338  submesh->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
339  Ogre::HardwareIndexBuffer::IT_16BIT, submesh->indexData->indexCount,
340  Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
341 
342  Ogre::HardwareIndexBufferSharedPtr ibuf = submesh->indexData->indexBuffer;
343  uint16_t* indices = static_cast<uint16_t*>(ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
344 
345  // add the indices
346  for (uint32_t j = 0; j < input_mesh->mNumFaces; j++)
347  {
348  aiFace& face = input_mesh->mFaces[j];
349  for (uint32_t k = 0; k < face.mNumIndices; ++k)
350  {
351  *indices++ = face.mIndices[k];
352  }
353  }
354 
355  ibuf->unlock();
356  }
357  else
358  {
359  // Else we have more than 65536 (2^16) vertices, so we must
360  // use a 32-bit index buffer (or subdivide the mesh, which
361  // I'm too impatient to do right now)
362 
363  // allocate index buffer
364  submesh->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
365  Ogre::HardwareIndexBuffer::IT_32BIT, submesh->indexData->indexCount,
366  Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
367 
368  Ogre::HardwareIndexBufferSharedPtr ibuf = submesh->indexData->indexBuffer;
369  uint32_t* indices = static_cast<uint32_t*>(ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
370 
371  // add the indices
372  for (uint32_t j = 0; j < input_mesh->mNumFaces; j++)
373  {
374  aiFace& face = input_mesh->mFaces[j];
375  for (uint32_t k = 0; k < face.mNumIndices; ++k)
376  {
377  *indices++ = face.mIndices[k];
378  }
379  }
380 
381  ibuf->unlock();
382  }
383  vbuf->unlock();
384 
385  submesh->setMaterialName(material_table[input_mesh->mMaterialIndex]->getName());
386  }
387 
388  for (uint32_t i = 0; i < node->mNumChildren; ++i)
389  {
390  buildMesh(scene, node->mChildren[i], mesh, aabb, radius, material_table, transform);
391  }
392 }
393 
394 void loadTexture(const std::string& resource_path)
395 {
396  if (!Ogre::TextureManager::getSingleton().resourceExists(resource_path))
397  {
400  try
401  {
402  res = retriever.get(resource_path);
403  }
405  {
406  ROS_ERROR("%s", e.what());
407  }
408 
409  if (res.size != 0)
410  {
411  Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(res.data.get(), res.size));
412  Ogre::Image image;
413  std::string extension = fs::extension(fs::path(resource_path));
414 
415  if (extension[0] == '.')
416  {
417  extension = extension.substr(1, extension.size() - 1);
418  }
419 
420  try
421  {
422  image.load(stream, extension);
423  Ogre::TextureManager::getSingleton().loadImage(
424  resource_path, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, image);
425  }
426  catch (Ogre::Exception& e)
427  {
428  ROS_ERROR("Could not load texture [%s]: %s", resource_path.c_str(), e.what());
429  }
430  }
431  }
432 }
433 
434 // Mostly cribbed from gazebo
443 void loadMaterials(const std::string& resource_path,
444  const aiScene* scene,
445  std::vector<Ogre::MaterialPtr>& material_table_out)
446 {
447 #if BOOST_FILESYSTEM_VERSION == 3
448  std::string ext = fs::path(resource_path).extension().string();
449 #else
450  std::string ext = fs::path(resource_path).extension();
451 #endif
452  boost::algorithm::to_lower(ext);
453  if (ext == ".stl" ||
454  ext == ".stlb") // STL meshes don't support proper materials: use Ogre's default material
455  {
456  material_table_out.push_back(Ogre::MaterialManager::getSingleton().getByName("BaseWhiteNoLighting"));
457  return;
458  }
459 
460  for (uint32_t i = 0; i < scene->mNumMaterials; i++)
461  {
462  std::stringstream ss;
463  ss << resource_path << "Material" << i;
464  Ogre::MaterialPtr mat =
465  Ogre::MaterialManager::getSingleton().create(ss.str(), ROS_PACKAGE_NAME, true);
466  material_table_out.push_back(mat);
467 
468  Ogre::Technique* tech = mat->getTechnique(0);
469  Ogre::Pass* pass = tech->getPass(0);
470 
471  aiMaterial* amat = scene->mMaterials[i];
472 
473  Ogre::ColourValue diffuse(1.0, 1.0, 1.0, 1.0);
474  Ogre::ColourValue specular(1.0, 1.0, 1.0, 1.0);
475  Ogre::ColourValue ambient(0, 0, 0, 1.0);
476 
477  for (uint32_t j = 0; j < amat->mNumProperties; j++)
478  {
479  aiMaterialProperty* prop = amat->mProperties[j];
480  std::string propKey = prop->mKey.data;
481 
482  if (propKey == "$tex.file")
483  {
484  aiString texName;
485  aiTextureMapping mapping;
486  uint32_t uvIndex;
487  amat->GetTexture(aiTextureType_DIFFUSE, 0, &texName, &mapping, &uvIndex);
488 
489  // Assume textures are in paths relative to the mesh
490  std::string texture_path = fs::path(resource_path).parent_path().string() + "/" + texName.data;
491  loadTexture(texture_path);
492  Ogre::TextureUnitState* tu = pass->createTextureUnitState();
493  tu->setTextureName(texture_path);
494  }
495  else if (propKey == "$clr.diffuse")
496  {
497  aiColor3D clr;
498  amat->Get(AI_MATKEY_COLOR_DIFFUSE, clr);
499  diffuse = Ogre::ColourValue(clr.r, clr.g, clr.b);
500  }
501  else if (propKey == "$clr.ambient")
502  {
503  aiColor3D clr;
504  amat->Get(AI_MATKEY_COLOR_AMBIENT, clr);
505  ambient = Ogre::ColourValue(clr.r, clr.g, clr.b);
506  }
507  else if (propKey == "$clr.specular")
508  {
509  aiColor3D clr;
510  amat->Get(AI_MATKEY_COLOR_SPECULAR, clr);
511  specular = Ogre::ColourValue(clr.r, clr.g, clr.b);
512  }
513  else if (propKey == "$clr.emissive")
514  {
515  aiColor3D clr;
516  amat->Get(AI_MATKEY_COLOR_EMISSIVE, clr);
517  mat->setSelfIllumination(clr.r, clr.g, clr.b);
518  }
519  else if (propKey == "$clr.opacity")
520  {
521  float o;
522  amat->Get(AI_MATKEY_OPACITY, o);
523  diffuse.a = o;
524  }
525  else if (propKey == "$mat.shininess")
526  {
527  float s;
528  amat->Get(AI_MATKEY_SHININESS, s);
529  mat->setShininess(s);
530  }
531  else if (propKey == "$mat.shadingm")
532  {
533  int model;
534  amat->Get(AI_MATKEY_SHADING_MODEL, model);
535  switch (model)
536  {
537  case aiShadingMode_Flat:
538  mat->setShadingMode(Ogre::SO_FLAT);
539  break;
540  case aiShadingMode_Phong:
541  mat->setShadingMode(Ogre::SO_PHONG);
542  break;
543  case aiShadingMode_Gouraud:
544  default:
545  mat->setShadingMode(Ogre::SO_GOURAUD);
546  break;
547  }
548  }
549  }
550 
551  int mode = aiBlendMode_Default;
552  amat->Get(AI_MATKEY_BLEND_FUNC, mode);
553  switch (mode)
554  {
555  case aiBlendMode_Additive:
556  mat->setSceneBlending(Ogre::SBT_ADD);
557  break;
558  case aiBlendMode_Default:
559  default:
560  {
561  if (diffuse.a < 0.99)
562  {
563  pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
564  }
565  else
566  {
567  pass->setSceneBlending(Ogre::SBT_REPLACE);
568  }
569  }
570  break;
571  }
572 
573  mat->setAmbient(ambient * 0.5);
574  mat->setDiffuse(diffuse);
575  specular.a = diffuse.a;
576  mat->setSpecular(specular);
577  }
578 }
579 
580 Ogre::MeshPtr meshFromAssimpScene(const std::string& name, const aiScene* scene)
581 {
582  if (!scene->HasMeshes())
583  {
584  ROS_ERROR("No meshes found in file [%s]", name.c_str());
585  return Ogre::MeshPtr();
586  }
587 
588  std::vector<Ogre::MaterialPtr> material_table;
589  loadMaterials(name, scene, material_table);
590 
591  Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual(name, ROS_PACKAGE_NAME);
592 
593  Ogre::AxisAlignedBox aabb(Ogre::AxisAlignedBox::EXTENT_NULL);
594  float radius = 0.0f;
595  buildMesh(scene, scene->mRootNode, mesh, aabb, radius, material_table);
596 
597  mesh->_setBounds(aabb);
598  mesh->_setBoundingSphereRadius(radius);
599  mesh->buildEdgeList();
600 
601  mesh->load();
602 
603  return mesh;
604 }
605 
606 Ogre::MeshPtr loadMeshFromResource(const std::string& resource_path)
607 {
608  if (Ogre::MeshManager::getSingleton().resourceExists(resource_path))
609  {
610  return Ogre::MeshManager::getSingleton().getByName(resource_path);
611  }
612  else
613  {
614  fs::path model_path(resource_path);
615 #if BOOST_FILESYSTEM_VERSION == 3
616  std::string ext = model_path.extension().string();
617 #else
618  std::string ext = model_path.extension();
619 #endif
620  boost::algorithm::to_lower(ext);
621  if (ext == ".mesh")
622  {
625  try
626  {
627  res = retriever.get(resource_path);
628  }
630  {
631  ROS_ERROR("%s", e.what());
632  return Ogre::MeshPtr();
633  }
634 
635  if (res.size == 0)
636  {
637  return Ogre::MeshPtr();
638  }
639 
640  Ogre::MeshSerializer ser;
641  Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(res.data.get(), res.size));
642  Ogre::MeshPtr mesh =
643  Ogre::MeshManager::getSingleton().createManual(resource_path, ROS_PACKAGE_NAME);
644  ser.importMesh(stream, mesh.get());
645 
646  return mesh;
647  }
648  else
649  {
650  Assimp::Importer importer;
651  importer.SetIOHandler(new ResourceIOSystem());
652  const aiScene* scene =
653  importer.ReadFile(resource_path, aiProcess_SortByPType | aiProcess_FindInvalidData |
654  aiProcess_GenNormals | aiProcess_Triangulate |
655  aiProcess_GenUVCoords | aiProcess_FlipUVs);
656  if (!scene)
657  {
658  ROS_ERROR("Could not load resource [%s]: %s", resource_path.c_str(), importer.GetErrorString());
659  return Ogre::MeshPtr();
660  }
661 
662  return meshFromAssimpScene(resource_path, scene);
663  }
664  }
665 
666  return Ogre::MeshPtr();
667 }
668 
669 } // namespace rviz
size_t Write(const void *, size_t, size_t) override
Definition: mesh_loader.cpp:97
char getOsSeparator() const override
XmlRpcServer s
ResourceIOStream(const resource_retriever::MemoryResource &res)
Definition: mesh_loader.cpp:75
Assimp::IOStream * Open(const char *file, const char *mode="rb") override
Ogre::MeshPtr loadMeshFromResource(const std::string &resource_path)
void Close(Assimp::IOStream *stream) override
def get(url)
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
void loadTexture(const std::string &resource_path)
size_t FileSize() const override
void loadMaterials(const std::string &resource_path, const aiScene *scene, std::vector< Ogre::MaterialPtr > &material_table_out)
Load all materials needed by the given scene.
boost::shared_array< uint8_t > data
resource_retriever::Retriever retriever_
~ResourceIOStream() override
Definition: mesh_loader.cpp:79
aiReturn Seek(size_t offset, aiOrigin origin) override
size_t Tell() const override
~ResourceIOSystem() override
MemoryResource get(const std::string &url)
void buildMesh(const aiScene *scene, const aiNode *node, const Ogre::MeshPtr &mesh, Ogre::AxisAlignedBox &aabb, float &radius, std::vector< Ogre::MaterialPtr > &material_table, aiMatrix4x4 transform=aiMatrix4x4())
Recursive mesh-building function.
size_t Read(void *buffer, size_t size, size_t count) override
Definition: mesh_loader.cpp:83
#define ROS_ASSERT(cond)
bool Exists(const char *file) const override
#define ROS_BREAK()
resource_retriever::MemoryResource res_
#define ROS_ERROR(...)
void Flush() override
Ogre::MeshPtr meshFromAssimpScene(const std::string &name, const aiScene *scene)


rviz
Author(s): Dave Hershberger, David Gossow, Josh Faust
autogenerated on Sat May 27 2023 02:06:24