$search
00001 /* 00002 * Copyright (c) 2010, Willow Garage, Inc. 00003 * All rights reserved. 00004 * 00005 * Redistribution and use in source and binary forms, with or without 00006 * modification, are permitted provided that the following conditions are met: 00007 * 00008 * * Redistributions of source code must retain the above copyright 00009 * notice, this list of conditions and the following disclaimer. 00010 * * Redistributions in binary form must reproduce the above copyright 00011 * notice, this list of conditions and the following disclaimer in the 00012 * documentation and/or other materials provided with the distribution. 00013 * * Neither the name of the Willow Garage, Inc. nor the names of its 00014 * contributors may be used to endorse or promote products derived from 00015 * this software without specific prior written permission. 00016 * 00017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00018 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00021 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00022 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00023 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00024 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00025 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00026 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00027 * POSSIBILITY OF SUCH DAMAGE. 00028 */ 00029 00030 #include "mesh_loader.h" 00031 #include <resource_retriever/retriever.h> 00032 00033 #include <boost/filesystem.hpp> 00034 00035 #include <ogre_tools/stl_loader.h> 00036 00037 #include <OGRE/OgreMeshManager.h> 00038 #include <OGRE/OgreTextureManager.h> 00039 #include <OGRE/OgreMaterialManager.h> 00040 #include <OGRE/OgreTexture.h> 00041 #include <OGRE/OgrePass.h> 00042 #include <OGRE/OgreTechnique.h> 00043 #include <OGRE/OgreMaterial.h> 00044 #include <OGRE/OgreTextureUnitState.h> 00045 #include <OGRE/OgreMeshSerializer.h> 00046 #include <OGRE/OgreSubMesh.h> 00047 #include <OGRE/OgreHardwareBufferManager.h> 00048 00049 #include <ros/assert.h> 00050 00051 #include <assimp/assimp.hpp> 00052 #include <assimp/aiScene.h> 00053 #include <assimp/aiPostProcess.h> 00054 #include <assimp/IOStream.h> 00055 #include <assimp/IOSystem.h> 00056 00057 00058 namespace fs = boost::filesystem; 00059 00060 namespace rviz 00061 { 00062 00063 class ResourceIOStream : public Assimp::IOStream 00064 { 00065 public: 00066 ResourceIOStream(const resource_retriever::MemoryResource& res) 00067 : res_(res) 00068 , pos_(res.data.get()) 00069 {} 00070 00071 ~ResourceIOStream() 00072 {} 00073 00074 size_t Read(void* buffer, size_t size, size_t count) 00075 { 00076 size_t to_read = size * count; 00077 if (pos_ + to_read > res_.data.get() + res_.size) 00078 { 00079 to_read = res_.size - (pos_ - res_.data.get()); 00080 } 00081 00082 memcpy(buffer, pos_, to_read); 00083 pos_ += to_read; 00084 00085 return to_read; 00086 } 00087 00088 size_t Write( const void* buffer, size_t size, size_t count) { ROS_BREAK(); return 0; } 00089 00090 aiReturn Seek( size_t offset, aiOrigin origin) 00091 { 00092 uint8_t* new_pos = 0; 00093 switch (origin) 00094 { 00095 case aiOrigin_SET: 00096 new_pos = res_.data.get() + offset; 00097 break; 00098 case aiOrigin_CUR: 00099 new_pos = pos_ + offset; // TODO is this right? can offset really not be negative 00100 break; 00101 case aiOrigin_END: 00102 new_pos = res_.data.get() + res_.size - offset; // TODO is this right? 00103 break; 00104 default: 00105 ROS_BREAK(); 00106 } 00107 00108 if (new_pos < res_.data.get() || new_pos > res_.data.get() + res_.size) 00109 { 00110 return aiReturn_FAILURE; 00111 } 00112 00113 pos_ = new_pos; 00114 return aiReturn_SUCCESS; 00115 } 00116 00117 size_t Tell() const 00118 { 00119 return pos_ - res_.data.get(); 00120 } 00121 00122 size_t FileSize() const 00123 { 00124 return res_.size; 00125 } 00126 00127 void Flush() {} 00128 00129 private: 00130 resource_retriever::MemoryResource res_; 00131 uint8_t* pos_; 00132 }; 00133 00134 class ResourceIOSystem : public Assimp::IOSystem 00135 { 00136 public: 00137 ResourceIOSystem() 00138 { 00139 } 00140 00141 ~ResourceIOSystem() 00142 { 00143 } 00144 00145 // Check whether a specific file exists 00146 bool Exists(const char* file) const 00147 { 00148 // Ugly -- two retrievals where there should be one (Exists + Open) 00149 // resource_retriever needs a way of checking for existence 00150 // TODO: cache this 00151 resource_retriever::MemoryResource res; 00152 try 00153 { 00154 res = retriever_.get(file); 00155 } 00156 catch (resource_retriever::Exception& e) 00157 { 00158 return false; 00159 } 00160 00161 return true; 00162 } 00163 00164 // Get the path delimiter character we'd like to see 00165 char getOsSeparator() const 00166 { 00167 return '/'; 00168 } 00169 00170 // ... and finally a method to open a custom stream 00171 Assimp::IOStream* Open(const char* file, const char* mode) 00172 { 00173 ROS_ASSERT(mode == std::string("r") || mode == std::string("rb")); 00174 00175 // Ugly -- two retrievals where there should be one (Exists + Open) 00176 // resource_retriever needs a way of checking for existence 00177 resource_retriever::MemoryResource res; 00178 try 00179 { 00180 res = retriever_.get(file); 00181 } 00182 catch (resource_retriever::Exception& e) 00183 { 00184 return 0; 00185 } 00186 00187 return new ResourceIOStream(res); 00188 } 00189 00190 void Close(Assimp::IOStream* stream) { delete stream; } 00191 00192 private: 00193 mutable resource_retriever::Retriever retriever_; 00194 }; 00195 00196 // Mostly stolen from gazebo 00197 void buildMesh(const aiScene* scene, const aiNode* node, const Ogre::MeshPtr& mesh, Ogre::AxisAlignedBox& aabb, float& radius) 00198 { 00199 if (!node) 00200 { 00201 return; 00202 } 00203 00204 aiMatrix4x4 transform = node->mTransformation; 00205 aiNode *pnode = node->mParent; 00206 while (pnode) 00207 { 00208 // Don't convert to y-up orientation, which is what the root node in 00209 // Assimp does 00210 if (pnode->mParent != NULL) 00211 transform = pnode->mTransformation * transform; 00212 pnode = pnode->mParent; 00213 } 00214 00215 aiMatrix3x3 rotation(transform); 00216 aiMatrix3x3 inverse_transpose_rotation(rotation); 00217 inverse_transpose_rotation.Inverse(); 00218 inverse_transpose_rotation.Transpose(); 00219 00220 for (uint32_t i = 0; i < node->mNumMeshes; i++) 00221 { 00222 aiMesh* input_mesh = scene->mMeshes[node->mMeshes[i]]; 00223 00224 Ogre::SubMesh* submesh = mesh->createSubMesh(); 00225 submesh->useSharedVertices = false; 00226 submesh->vertexData = new Ogre::VertexData(); 00227 Ogre::VertexData* vertex_data = submesh->vertexData; 00228 Ogre::VertexDeclaration* vertex_decl = vertex_data->vertexDeclaration; 00229 00230 size_t offset = 0; 00231 // positions 00232 vertex_decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION); 00233 offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); 00234 00235 // normals 00236 if (input_mesh->HasNormals()) 00237 { 00238 vertex_decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); 00239 offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); 00240 } 00241 00242 // texture coordinates (only support 1 for now) 00243 if (input_mesh->HasTextureCoords(0)) 00244 { 00245 vertex_decl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0); 00246 offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); 00247 } 00248 00249 // todo vertex colors 00250 00251 // allocate the vertex buffer 00252 vertex_data->vertexCount = input_mesh->mNumVertices; 00253 Ogre::HardwareVertexBufferSharedPtr vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(vertex_decl->getVertexSize(0), 00254 vertex_data->vertexCount, 00255 Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, 00256 false); 00257 00258 vertex_data->vertexBufferBinding->setBinding(0, vbuf); 00259 float* vertices = static_cast<float*>(vbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); 00260 00261 // Add the vertices 00262 for (uint32_t j = 0; j < input_mesh->mNumVertices; j++) 00263 { 00264 aiVector3D p = input_mesh->mVertices[j]; 00265 p *= transform; 00266 *vertices++ = p.x; 00267 *vertices++ = p.y; 00268 *vertices++ = p.z; 00269 00270 Ogre::Vector3 v(p.x, p.y, p.z); 00271 aabb.merge(v); 00272 float dist = v.length(); 00273 if (dist > radius) 00274 { 00275 radius = dist; 00276 } 00277 00278 if (input_mesh->HasNormals()) 00279 { 00280 aiVector3D n = inverse_transpose_rotation * input_mesh->mNormals[j]; 00281 *vertices++ = n.x; 00282 *vertices++ = n.y; 00283 *vertices++ = n.z; 00284 } 00285 00286 if (input_mesh->HasTextureCoords(0)) 00287 { 00288 *vertices++ = input_mesh->mTextureCoords[0][j].x; 00289 *vertices++ = input_mesh->mTextureCoords[0][j].y; 00290 } 00291 } 00292 00293 // calculate index count 00294 submesh->indexData->indexCount = 0; 00295 for (uint32_t j = 0; j < input_mesh->mNumFaces; j++) 00296 { 00297 aiFace& face = input_mesh->mFaces[j]; 00298 submesh->indexData->indexCount += face.mNumIndices; 00299 } 00300 00301 // If we have less than 65536 (2^16) vertices, we can use a 16-bit index buffer. 00302 if( vertex_data->vertexCount < (1<<16) ) 00303 { 00304 printf( "mesh_loader.cpp: using 16-bit index buffer.\n"); 00305 // allocate index buffer 00306 submesh->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer( 00307 Ogre::HardwareIndexBuffer::IT_16BIT, 00308 submesh->indexData->indexCount, 00309 Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, 00310 false); 00311 00312 Ogre::HardwareIndexBufferSharedPtr ibuf = submesh->indexData->indexBuffer; 00313 uint16_t* indices = static_cast<uint16_t*>(ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); 00314 00315 // add the indices 00316 for (uint32_t j = 0; j < input_mesh->mNumFaces; j++) 00317 { 00318 aiFace& face = input_mesh->mFaces[j]; 00319 for (uint32_t k = 0; k < face.mNumIndices; ++k) 00320 { 00321 *indices++ = face.mIndices[k]; 00322 } 00323 } 00324 00325 ibuf->unlock(); 00326 } 00327 else 00328 { 00329 // Else we have more than 65536 (2^16) vertices, so we must 00330 // use a 32-bit index buffer (or subdivide the mesh, which 00331 // I'm too impatient to do right now) 00332 00333 // allocate index buffer 00334 submesh->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer( 00335 Ogre::HardwareIndexBuffer::IT_32BIT, 00336 submesh->indexData->indexCount, 00337 Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, 00338 false); 00339 00340 Ogre::HardwareIndexBufferSharedPtr ibuf = submesh->indexData->indexBuffer; 00341 uint32_t* indices = static_cast<uint32_t*>(ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); 00342 00343 // add the indices 00344 for (uint32_t j = 0; j < input_mesh->mNumFaces; j++) 00345 { 00346 aiFace& face = input_mesh->mFaces[j]; 00347 for (uint32_t k = 0; k < face.mNumIndices; ++k) 00348 { 00349 *indices++ = face.mIndices[k]; 00350 } 00351 } 00352 00353 ibuf->unlock(); 00354 } 00355 vbuf->unlock(); 00356 } 00357 00358 for (uint32_t i=0; i < node->mNumChildren; ++i) 00359 { 00360 buildMesh(scene, node->mChildren[i], mesh, aabb, radius); 00361 } 00362 } 00363 00364 void loadTexture(const std::string& resource_path) 00365 { 00366 if (!Ogre::TextureManager::getSingleton().resourceExists(resource_path)) 00367 { 00368 resource_retriever::Retriever retriever; 00369 resource_retriever::MemoryResource res; 00370 try 00371 { 00372 res = retriever.get(resource_path); 00373 } 00374 catch (resource_retriever::Exception& e) 00375 { 00376 ROS_ERROR("%s", e.what()); 00377 } 00378 00379 if (res.size != 0) 00380 { 00381 Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(res.data.get(), res.size)); 00382 Ogre::Image image; 00383 std::string extension = fs::extension(fs::path(resource_path)); 00384 00385 if (extension[0] == '.') 00386 { 00387 extension = extension.substr(1, extension.size() - 1); 00388 } 00389 00390 try 00391 { 00392 image.load(stream, extension); 00393 Ogre::TextureManager::getSingleton().loadImage(resource_path, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, image); 00394 } 00395 catch (Ogre::Exception& e) 00396 { 00397 ROS_ERROR("Could not load texture [%s]: %s", resource_path.c_str(), e.what()); 00398 } 00399 } 00400 } 00401 } 00402 00403 // Mostly cribbed from gazebo 00404 void loadMaterialsForMesh(const std::string& resource_path, const aiScene* scene, const Ogre::MeshPtr& mesh) 00405 { 00406 std::vector<Ogre::MaterialPtr> material_lookup; 00407 00408 for (uint32_t i = 0; i < scene->mNumMaterials; i++) 00409 { 00410 std::stringstream ss; 00411 ss << resource_path << "Material" << i; 00412 Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(ss.str(), ROS_PACKAGE_NAME, true); 00413 material_lookup.push_back(mat); 00414 00415 Ogre::Technique* tech = mat->getTechnique(0); 00416 Ogre::Pass* pass = tech->getPass(0); 00417 00418 aiMaterial *amat = scene->mMaterials[i]; 00419 00420 Ogre::ColourValue diffuse(1.0, 1.0, 1.0, 1.0); 00421 Ogre::ColourValue specular(1.0, 1.0, 1.0, 1.0); 00422 Ogre::ColourValue ambient(0.5, 0.5, 0.5, 1.0); 00423 00424 for (uint32_t j=0; j < amat->mNumProperties; j++) 00425 { 00426 aiMaterialProperty *prop = amat->mProperties[j]; 00427 std::string propKey = prop->mKey.data; 00428 00429 if (propKey == "$tex.file") 00430 { 00431 aiString texName; 00432 aiTextureMapping mapping; 00433 uint32_t uvIndex; 00434 amat->GetTexture(aiTextureType_DIFFUSE,0, &texName, &mapping, &uvIndex); 00435 00436 // Assume textures are in paths relative to the mesh 00437 std::string texture_path = fs::path(resource_path).parent_path().string() + "/" + texName.data; 00438 loadTexture(texture_path); 00439 Ogre::TextureUnitState* tu = pass->createTextureUnitState(); 00440 tu->setTextureName(texture_path); 00441 } 00442 else if (propKey == "$clr.diffuse") 00443 { 00444 aiColor3D clr; 00445 amat->Get(AI_MATKEY_COLOR_DIFFUSE, clr); 00446 diffuse = Ogre::ColourValue(clr.r, clr.g, clr.b); 00447 } 00448 else if (propKey == "$clr.ambient") 00449 { 00450 aiColor3D clr; 00451 amat->Get(AI_MATKEY_COLOR_AMBIENT, clr); 00452 00453 // Most of are DAE files don't have ambient color defined 00454 if (clr.r > 0 && clr.g > 0 && clr.b > 0) 00455 { 00456 ambient = Ogre::ColourValue(clr.r, clr.g, clr.b); 00457 } 00458 } 00459 else if (propKey == "$clr.specular") 00460 { 00461 aiColor3D clr; 00462 amat->Get(AI_MATKEY_COLOR_SPECULAR, clr); 00463 specular = Ogre::ColourValue(clr.r, clr.g, clr.b); 00464 } 00465 else if (propKey == "$clr.emissive") 00466 { 00467 aiColor3D clr; 00468 amat->Get(AI_MATKEY_COLOR_EMISSIVE, clr); 00469 mat->setSelfIllumination(clr.r, clr.g, clr.b); 00470 } 00471 else if (propKey == "$clr.opacity") 00472 { 00473 float o; 00474 amat->Get(AI_MATKEY_OPACITY, o); 00475 diffuse.a = o; 00476 } 00477 else if (propKey == "$mat.shininess") 00478 { 00479 float s; 00480 amat->Get(AI_MATKEY_SHININESS, s); 00481 mat->setShininess(s); 00482 } 00483 else if (propKey == "$mat.shadingm") 00484 { 00485 int model; 00486 amat->Get(AI_MATKEY_SHADING_MODEL, model); 00487 switch(model) 00488 { 00489 case aiShadingMode_Flat: 00490 mat->setShadingMode(Ogre::SO_FLAT); 00491 break; 00492 case aiShadingMode_Phong: 00493 mat->setShadingMode(Ogre::SO_PHONG); 00494 break; 00495 case aiShadingMode_Gouraud: 00496 default: 00497 mat->setShadingMode(Ogre::SO_GOURAUD); 00498 break; 00499 } 00500 } 00501 } 00502 00503 int mode = aiBlendMode_Default; 00504 amat->Get(AI_MATKEY_BLEND_FUNC, mode); 00505 switch(mode) 00506 { 00507 case aiBlendMode_Additive: 00508 mat->setSceneBlending(Ogre::SBT_ADD); 00509 break; 00510 case aiBlendMode_Default: 00511 default: 00512 { 00513 if (diffuse.a < 0.99) 00514 { 00515 pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); 00516 } 00517 else 00518 { 00519 pass->setSceneBlending(Ogre::SBT_REPLACE); 00520 } 00521 } 00522 break; 00523 } 00524 00525 mat->setAmbient(ambient); 00526 mat->setDiffuse(diffuse); 00527 specular.a = diffuse.a; 00528 mat->setSpecular(specular); 00529 } 00530 00531 for (uint32_t i = 0; i < mesh->getNumSubMeshes(); ++i) 00532 { 00533 mesh->getSubMesh(i)->setMaterialName(material_lookup[scene->mMeshes[i]->mMaterialIndex]->getName()); 00534 } 00535 } 00536 00537 Ogre::MeshPtr meshFromAssimpScene(const std::string& name, const aiScene* scene) 00538 { 00539 if (!scene->HasMeshes()) 00540 { 00541 ROS_ERROR("No meshes found in file [%s]", name.c_str()); 00542 return Ogre::MeshPtr(); 00543 } 00544 00545 Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual(name, ROS_PACKAGE_NAME); 00546 00547 Ogre::AxisAlignedBox aabb(Ogre::AxisAlignedBox::EXTENT_NULL); 00548 float radius = 0.0f; 00549 buildMesh(scene, scene->mRootNode, mesh, aabb, radius); 00550 00551 mesh->_setBounds(aabb); 00552 mesh->_setBoundingSphereRadius(radius); 00553 mesh->buildEdgeList(); 00554 00555 loadMaterialsForMesh(name, scene, mesh); 00556 00557 mesh->load(); 00558 00559 return mesh; 00560 } 00561 00562 Ogre::MeshPtr loadMeshFromResource(const std::string& resource_path) 00563 { 00564 if (Ogre::MeshManager::getSingleton().resourceExists(resource_path)) 00565 { 00566 return Ogre::MeshManager::getSingleton().getByName(resource_path); 00567 } 00568 else 00569 { 00570 fs::path model_path(resource_path); 00571 #if BOOST_FILESYSTEM_VERSION == 3 00572 std::string ext = model_path.extension().string(); 00573 #else 00574 std::string ext = model_path.extension(); 00575 #endif 00576 if (ext == ".mesh" || ext == ".MESH") 00577 { 00578 resource_retriever::Retriever retriever; 00579 resource_retriever::MemoryResource res; 00580 try 00581 { 00582 res = retriever.get(resource_path); 00583 } 00584 catch (resource_retriever::Exception& e) 00585 { 00586 ROS_ERROR("%s", e.what()); 00587 return Ogre::MeshPtr(); 00588 } 00589 00590 if (res.size == 0) 00591 { 00592 return Ogre::MeshPtr(); 00593 } 00594 00595 Ogre::MeshSerializer ser; 00596 Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(res.data.get(), res.size)); 00597 Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual(resource_path, "rviz"); 00598 ser.importMesh(stream, mesh.get()); 00599 00600 return mesh; 00601 } 00602 else if (ext == ".stl" || ext == ".STL" || ext == ".stlb" || ext == ".STLB") 00603 { 00604 resource_retriever::Retriever retriever; 00605 resource_retriever::MemoryResource res; 00606 try 00607 { 00608 res = retriever.get(resource_path); 00609 } 00610 catch (resource_retriever::Exception& e) 00611 { 00612 ROS_ERROR("%s", e.what()); 00613 return Ogre::MeshPtr(); 00614 } 00615 00616 if (res.size == 0) 00617 { 00618 return Ogre::MeshPtr(); 00619 } 00620 00621 ogre_tools::STLLoader loader; 00622 if (!loader.load(res.data.get())) 00623 { 00624 ROS_ERROR("Failed to load file [%s]", resource_path.c_str()); 00625 return Ogre::MeshPtr(); 00626 } 00627 00628 return loader.toMesh(resource_path); 00629 } 00630 else 00631 { 00632 Assimp::Importer importer; 00633 importer.SetIOHandler(new ResourceIOSystem()); 00634 const aiScene* scene = importer.ReadFile(resource_path, aiProcess_SortByPType|aiProcess_GenNormals|aiProcess_Triangulate|aiProcess_GenUVCoords|aiProcess_FlipUVs); 00635 if (!scene) 00636 { 00637 ROS_ERROR("Could not load resource [%s]: %s", resource_path.c_str(), importer.GetErrorString()); 00638 return Ogre::MeshPtr(); 00639 } 00640 00641 return meshFromAssimpScene(resource_path, scene); 00642 } 00643 } 00644 00645 return Ogre::MeshPtr(); 00646 } 00647 00648 }