From e158c78ffa04f1e62c68cf7c4395c032e5161121 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Fri, 21 Nov 2008 13:23:55 +0000 Subject: [PATCH] From David Spilling, better support for multitexture in obj loader. --- src/osgPlugins/obj/ReaderWriterOBJ.cpp | 267 +++++++++++++++++-------- src/osgPlugins/obj/obj.cpp | 81 ++++++-- src/osgPlugins/obj/obj.h | 67 +++++-- 3 files changed, 295 insertions(+), 120 deletions(-) diff --git a/src/osgPlugins/obj/ReaderWriterOBJ.cpp b/src/osgPlugins/obj/ReaderWriterOBJ.cpp index b1cacff65..2858ad6c6 100644 --- a/src/osgPlugins/obj/ReaderWriterOBJ.cpp +++ b/src/osgPlugins/obj/ReaderWriterOBJ.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -53,12 +52,22 @@ class ReaderWriterOBJ : public osgDB::ReaderWriter { public: - ReaderWriterOBJ():_fixBlackMaterials(true) + ReaderWriterOBJ() { supportsExtension("obj","Alias Wavefront OBJ format"); supportsOption("noRotation","Do not do the default rotate about X axis"); supportsOption("noTesselateLargePolygons","Do not do the default tesselation of large polygons"); supportsOption("noTriStripPolygons","Do not do the default tri stripping of polygons"); + + supportsOption("DIFFUSE=", "Set texture unit for diffuse texture"); + supportsOption("AMBIENT=", "Set texture unit for ambient texture"); + supportsOption("SPECULAR=", "Set texture unit for specular texture"); + supportsOption("SPECULAR_EXPONENT=", "Set texture unit for specular exponent texture"); + supportsOption("OPACITY=", "Set texture unit for opacity/dissolve texture"); + supportsOption("BUMP=", "Set texture unit for bumpmap texture"); + supportsOption("DISPLACEMENT=", "Set texture unit for displacement texture"); + supportsOption("REFLECTION=", "Set texture unit for reflection texture"); + } virtual const char* className() const { return "Wavefront OBJ Reader"; } @@ -120,19 +129,30 @@ public: protected: + struct ObjOptionsStruct { + bool rotate; + bool noTesselateLargePolygons; + bool noTriStripPolygons; + bool fixBlackMaterials; + // This is the order in which the materials will be assigned to texture maps, unless + // otherwise overriden + typedef std::vector< std::pair > TextureAllocationMap; + TextureAllocationMap textureUnitAllocation; + }; + typedef std::map< std::string, osg::ref_ptr > MaterialToStateSetMap; - void buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToSetSetMap) const; + void buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToSetSetMapObj, ObjOptionsStruct& localOptions) const; osg::Geometry* convertElementListToGeometry(obj::Model& model, obj::Model::ElementList& elementList, bool& rotate) const; - osg::Node* convertModelToSceneGraph(obj::Model& model, bool& rotate, - bool& noTesselateLargePolygons, bool& noTriStripPolygons) const; + osg::Node* convertModelToSceneGraph(obj::Model& model, ObjOptionsStruct& localOptions) const; inline osg::Vec3 transformVertex(const osg::Vec3& vec, const bool rotate) const ; inline osg::Vec3 transformNormal(const osg::Vec3& vec, const bool rotate) const ; - - bool _fixBlackMaterials; + + ObjOptionsStruct parseOptions(const Options* options) const; + }; @@ -164,12 +184,12 @@ inline osg::Vec3 ReaderWriterOBJ::transformNormal(const osg::Vec3& vec, const bo // register with Registry to instantiate the above reader/writer. REGISTER_OSGPLUGIN(obj, ReaderWriterOBJ) -static void load_material_texture( obj::Model &model, - obj::Material &material, - osg::StateSet *stateset, - const std::string & filename, - const unsigned int texture_unit ) +static void load_material_texture( obj::Model &model, + obj::Material::Map &map, + osg::StateSet *stateset, + const unsigned int texture_unit ) { + std::string filename = map.name; if (!filename.empty()) { osg::ref_ptr< osg::Image > image; @@ -188,13 +208,25 @@ static void load_material_texture( obj::Model &model, if ( image.valid() ) { osg::Texture2D* texture = new osg::Texture2D( image.get() ); - osg::Texture::WrapMode textureWrapMode = osg::Texture::REPEAT; + osg::Texture::WrapMode textureWrapMode; + if(map.clamp == true) + { + textureWrapMode = osg::Texture::CLAMP_TO_BORDER; + texture->setBorderColor(osg::Vec4(0.0,0.0,0.0,0.0)); // transparent + //stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + //stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + else + { + textureWrapMode = osg::Texture::REPEAT; + } + texture->setWrap(osg::Texture2D::WRAP_R, textureWrapMode); texture->setWrap(osg::Texture2D::WRAP_S, textureWrapMode); texture->setWrap(osg::Texture2D::WRAP_T, textureWrapMode); stateset->setTextureAttributeAndModes( texture_unit, texture,osg::StateAttribute::ON ); - if ( material.textureReflection ) + if ( map.type == obj::Material::Map::REFLECTION ) { osg::TexGen* texgen = new osg::TexGen; texgen->setMode(osg::TexGen::SPHERE_MAP); @@ -210,19 +242,19 @@ static void load_material_texture( obj::Model &model, } } - if (material.uScale != 1.0f || material.vScale != 1.0f || - material.uOffset != 0.0f || material.vOffset != 0.0f) + if (map.uScale != 1.0f || map.vScale != 1.0f || + map.uOffset != 0.0f || map.vOffset != 0.0f) { osg::Matrix mat; - if (material.uScale != 1.0f || material.vScale != 1.0f) + if (map.uScale != 1.0f || map.vScale != 1.0f) { - osg::notify(osg::DEBUG_INFO) << "Obj TexMat scale=" << material.uScale << "," << material.vScale << std::endl; - mat *= osg::Matrix::scale(material.uScale, material.vScale, 1.0); + osg::notify(osg::DEBUG_INFO) << "Obj TexMat scale=" << map.uScale << "," << map.vScale << std::endl; + mat *= osg::Matrix::scale(map.uScale, map.vScale, 1.0); } - if (material.uOffset != 0.0f || material.vOffset != 0.0f) + if (map.uOffset != 0.0f || map.vOffset != 0.0f) { - osg::notify(osg::DEBUG_INFO) << "Obj TexMat offset=" << material.uOffset << "," << material.uOffset << std::endl; - mat *= osg::Matrix::translate(material.uOffset, material.vOffset, 0.0); + osg::notify(osg::DEBUG_INFO) << "Obj TexMat offset=" << map.uOffset << "," << map.uOffset << std::endl; + mat *= osg::Matrix::translate(map.uOffset, map.vOffset, 0.0); } osg::TexMat* texmat = new osg::TexMat; @@ -232,9 +264,9 @@ static void load_material_texture( obj::Model &model, } -void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToStateSetMap) const +void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToStateSetMap, ObjOptionsStruct& localOptions) const { - if (_fixBlackMaterials) + if (localOptions.fixBlackMaterials) { // hack to fix Maya exported models that contian all black materials. int numBlack = 0; @@ -278,7 +310,7 @@ void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToSt ++itr) { obj::Material& material = itr->second; - + osg::ref_ptr< osg::StateSet > stateset = new osg::StateSet; bool isTransparent = false; @@ -311,15 +343,54 @@ void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToSt } } - // handle textures - enum TextureUnit + // If the user has explicitly set the required texture type to unit map via the options + // string, then we load ONLY those textures that are in the map. + if(localOptions.textureUnitAllocation.size()>0) { - TEXTURE_UNIT_KD = 0, - TEXTURE_UNIT_OPACITY - }; - load_material_texture( model, material, stateset.get(), material.map_Kd, TEXTURE_UNIT_KD ); - load_material_texture( model, material, stateset.get(), material.map_opacity, TEXTURE_UNIT_OPACITY ); - + for(unsigned int i=0;i=0) load_material_texture( model, material.maps[index], stateset.get(), unit ); + } + } + // If the user has set no options, then we load them up in the order contained in the enum. This + // latter method is an attempt not to break user's existing code + else + { + int unit = 0; + for(int i=0;i<(int) obj::Material::Map::UNKNOWN;i++) // for each type + { + obj::Material::Map::TextureMapType type = (obj::Material::Map::TextureMapType) i; + // see if this texture type (e.g. DIFFUSE) is one of those in the material + int index = -1; + for(unsigned int j=0;j=0) + { + load_material_texture( model, material.maps[index], stateset.get(), unit ); + unit++; + } + } + } + if (isTransparent) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); @@ -583,8 +654,7 @@ osg::Geometry* ReaderWriterOBJ::convertElementListToGeometry(obj::Model& model, return geometry; } -osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, - bool& rotate, bool& noTesselateLargePolygons, bool& noTriStripPolygons) const +osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, ObjOptionsStruct& localOptions) const { if (model.elementStateMap.empty()) return 0; @@ -593,7 +663,7 @@ osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, // set up the materials MaterialToStateSetMap materialToSetSetMap; - buildMaterialToStateSetMap(model, materialToSetSetMap); + buildMaterialToStateSetMap(model, materialToSetSetMap, localOptions); // go through the groups of related elements and build geometry from them. for(obj::Model::ElementStateMap::iterator itr=model.elementStateMap.begin(); @@ -604,7 +674,7 @@ osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, const obj::ElementState& es = itr->first; obj::Model::ElementList& el = itr->second; - osg::Geometry* geometry = convertElementListToGeometry(model,el,rotate); + osg::Geometry* geometry = convertElementListToGeometry(model,el,localOptions.rotate); if (geometry) { @@ -613,14 +683,14 @@ osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, geometry->setStateSet(stateset); // tesseleate any large polygons - if (!noTesselateLargePolygons) + if (!localOptions.noTesselateLargePolygons) { osgUtil::Tessellator tessellator; tessellator.retessellatePolygons(*geometry); } // tri strip polygons to improve graphics peformance - if (!noTriStripPolygons) + if (!localOptions.noTriStripPolygons) { osgUtil::TriStripVisitor tsv; tsv.stripify(*geometry); @@ -658,6 +728,72 @@ osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, return group; } +ReaderWriterOBJ::ObjOptionsStruct ReaderWriterOBJ::parseOptions(const osgDB::ReaderWriter::Options* options) const +{ + ObjOptionsStruct localOptions; + localOptions.rotate = true; + localOptions.noTesselateLargePolygons = false; + localOptions.noTriStripPolygons = false; + localOptions.fixBlackMaterials = true; + + if (options!=NULL) + { + std::istringstream iss(options->getOptionString()); + std::string opt; + while (iss >> opt) + { + // split opt into pre= and post= + std::string pre_equals; + std::string post_equals; + + size_t found = opt.find("="); + if(found!=std::string::npos) + { + pre_equals = opt.substr(0,found); + post_equals = opt.substr(found+1); + } + else + { + pre_equals = opt; + } + + if (pre_equals == "noRotation") + { + localOptions.rotate = false; + } + else if (pre_equals == "noTesselateLargePolygons") + { + localOptions.noTesselateLargePolygons = true; + } + else if (pre_equals == "noTriStripPolygons") + { + localOptions.noTriStripPolygons = true; + } + else if (post_equals.length()>0) + { + obj::Material::Map::TextureMapType type = obj::Material::Map::UNKNOWN; + // Now we check to see if we have anything forcing a texture allocation + if (pre_equals == "DIFFUSE") type = obj::Material::Map::DIFFUSE; + else if (pre_equals == "AMBIENT") type = obj::Material::Map::AMBIENT; + else if (pre_equals == "SPECULAR") type = obj::Material::Map::SPECULAR; + else if (pre_equals == "SPECULAR_EXPONENT") type = obj::Material::Map::SPECULAR_EXPONENT; + else if (pre_equals == "OPACITY") type = obj::Material::Map::OPACITY; + else if (pre_equals == "BUMP") type = obj::Material::Map::BUMP; + else if (pre_equals == "DISPLACEMENT") type = obj::Material::Map::DISPLACEMENT; + else if (pre_equals == "REFLECTION") type = obj::Material::Map::REFLECTION; + + if (type!=obj::Material::Map::UNKNOWN) + { + int unit = atoi(post_equals.c_str()); // (probably should use istringstream rather than atoi) + localOptions.textureUnitAllocation.push_back(std::make_pair(unit,(obj::Material::Map::TextureMapType) type)); + osg::notify(osg::NOTICE)<<"Obj Found map in options, ["<getOptionString() == "noRotation") - { - rotate = false; - } - - if (options->getOptionString() == "noTesselateLargePolygons") - { - noTesselateLargePolygons = true; - } - - if (options->getOptionString() == "noTriStripPolygons") - { - noTriStripPolygons = true; - } - } - osg::Node* node = convertModelToSceneGraph(model,rotate, - noTesselateLargePolygons,noTriStripPolygons); + osg::Node* node = convertModelToSceneGraph(model,localOptions); return node; } @@ -720,32 +834,9 @@ osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(std::istream& fin, con obj::Model model; model.readOBJ(fin, options); - // code for checking the nonRotation, noTesselateLargePolygons, - // and noTriStripPolygons - bool rotate = true; - bool noTesselateLargePolygons = false; - bool noTriStripPolygons = false; + ObjOptionsStruct localOptions = parseOptions(options); - if (options!=NULL) - { - if (options->getOptionString() == "noRotation") - { - rotate = false; - } - - if (options->getOptionString() == "noTesselateLargePolygons") - { - noTesselateLargePolygons = true; - } - - if (options->getOptionString() == "noTriStripPolygons") - { - noTriStripPolygons = true; - } - } - - osg::Node* node = convertModelToSceneGraph(model,rotate, - noTesselateLargePolygons,noTriStripPolygons); + osg::Node* node = convertModelToSceneGraph(model, localOptions); return node; } diff --git a/src/osgPlugins/obj/obj.cpp b/src/osgPlugins/obj/obj.cpp index dcbc4d146..33bd5bef6 100644 --- a/src/osgPlugins/obj/obj.cpp +++ b/src/osgPlugins/obj/obj.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -22,7 +23,6 @@ #include #include -#include using namespace obj; @@ -37,8 +37,9 @@ static std::string strip( const std::string& ss ) * parse a subset of texture options, following * http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/ */ -static std::string parseTexture( const std::string& ss, Material& mat) +static Material::Map parseTextureMap( const std::string& ss, Material::Map::TextureMapType type) { + Material::Map map; std::string s(ss); for (;;) { @@ -57,17 +58,17 @@ static std::string parseTexture( const std::string& ss, Material& mat) if (s[1] == 's') { // texture scale - mat.uScale = x; - mat.vScale = y; + map.uScale = x; + map.vScale = y; } else if (s[1] == 'o') { // texture offset - mat.uOffset = x; - mat.vOffset = y; + map.uOffset = x; + map.vOffset = y; } } - else if (s[1] == 'm' && s[2] == 'm') + else if (s.compare(1,2,"mm")==0) { // texture color offset and gain float base, gain; @@ -77,7 +78,7 @@ static std::string parseTexture( const std::string& ss, Material& mat) } // UNUSED } - else if (s[1] == 'b' && s[2] == 'm') + else if (s.compare(1,2,"bm")==0) { // blend multiplier float mult; @@ -87,13 +88,26 @@ static std::string parseTexture( const std::string& ss, Material& mat) } // UNUSED } + else if (s.compare(1,5,"clamp")==0) + { + osg::notify(osg::NOTICE)<<"Got Clamp\n"; + char c[4]; + if (sscanf(s.c_str(), "%*s %3s%n", c, &n) != 1) + { + break; + } + if(strncmp(c,"on",2)==0) map.clamp = true; + else map.clamp = false; // default behavioud + } else break; s = strip(s.substr(n)); } - return s; + map.name = s; + map.type = type; + return map; } bool Model::readline(std::istream& fin, char* line, const int LINE_SIZE) @@ -415,23 +429,58 @@ bool Model::readMTL(std::istream& fin) } else if (strncmp(line,"map_Ka ",7)==0) { - material->map_Ka = parseTexture(strip(line+7), *material); + material->maps.push_back(parseTextureMap(strip(line+7),Material::Map::AMBIENT)); } + // diffuse map else if (strncmp(line,"map_Kd ",7)==0) { - material->map_Kd = parseTexture(strip(line+7), *material); + material->maps.push_back(parseTextureMap(strip(line+7),Material::Map::DIFFUSE)); } + // specular colour/level map else if (strncmp(line,"map_Ks ",7)==0) { - material->map_Ks = parseTexture(strip(line+7), *material); + material->maps.push_back(parseTextureMap(strip(line+7),Material::Map::SPECULAR)); } - else if (strncmp(line,"map_opacity ",12)==0) + // map_opacity doesn't exist in the spec, but was already in the plugin + // so leave it or plugin will break for some users + else if (strncmp(line,"map_opacity ",12)==0) { - material->map_opacity = parseTexture(strip(line+12), *material); + material->maps.push_back(parseTextureMap(strip(line+12),Material::Map::OPACITY)); } - else if (strcmp(line,"refl")==0 || strncmp(line,"refl ",5)==0) + // proper dissolve/opacity map + else if (strncmp(line,"map_d ",6)==0) { - material->textureReflection = true; + material->maps.push_back(parseTextureMap(strip(line+6),Material::Map::OPACITY)); + } + // specular exponent map + else if (strncmp(line,"map_Ns ",7)==0) + { + material->maps.push_back(parseTextureMap(strip(line+7),Material::Map::SPECULAR_EXPONENT)); + } + // modelling tools and convertors variously produce bump, map_bump, and map_Bump so parse them all + else if (strncmp(line,"bump ",5)==0) + { + material->maps.push_back(parseTextureMap(strip(line+5),Material::Map::BUMP)); + } + else if (strncmp(line,"map_bump ",9)==0) + { + material->maps.push_back(parseTextureMap(strip(line+9),Material::Map::BUMP)); + } + else if (strncmp(line,"map_Bump ",9)==0) + { + material->maps.push_back(parseTextureMap(strip(line+9),Material::Map::BUMP)); + } + // displacement map + else if (strncmp(line,"disp ",5)==0) + { + material->maps.push_back(parseTextureMap(strip(line+5),Material::Map::DISPLACEMENT)); + } + // reflection map (the original code had the possibility of a blank "refl" line + // which isn't correct according to the spec, so this bit might break for some + // modelling packages... + else if (strncmp(line,"refl ",5)==0) + { + material->maps.push_back(parseTextureMap(strip(line+5),Material::Map::REFLECTION)); } else { diff --git a/src/osgPlugins/obj/obj.h b/src/osgPlugins/obj/obj.h index 4996ccd9b..308d3feec 100644 --- a/src/osgPlugins/obj/obj.h +++ b/src/osgPlugins/obj/obj.h @@ -44,12 +44,8 @@ public: Tf(0.0f,0.0f,0.0f,1.0f), Ni(0), Ns(0), - textureReflection(false), - alpha(1.0f), - uScale(1.0f), - vScale(1.0f), - uOffset(0.0f), - vOffset(0.0f) {} + // textureReflection(false), + alpha(1.0f) {} std::string name; @@ -58,22 +54,61 @@ public: osg::Vec4 specular; osg::Vec4 emissive; float sharpness; - int illum; + int illum; // read but not implemented (would need specific shaders or state manipulation) osg::Vec4 Tf; int Ni; int Ns; // shininess 0..1000 - std::string map_Ka; - std::string map_Kd; - std::string map_Ks; - std::string map_opacity; - bool textureReflection; + // bool textureReflection; float alpha; - float uScale; - float vScale; - float uOffset; - float vOffset; + + class Map + { + // -o and -s (offset and scale) options supported for the maps + // -clamp is supported + // -blendu, -blendv, -imfchan, not supported + // -mm is parsed but not actually used + // -bm is parsed but not used + public: + enum TextureMapType { + DIFFUSE=0, + OPACITY, + AMBIENT, + SPECULAR, + SPECULAR_EXPONENT, + BUMP, + DISPLACEMENT, + REFLECTION, // read of a reflection map will also apply spherical texgen coordinates + UNKNOWN // UNKNOWN has to be the last + }; + Map(): + type(UNKNOWN), + name(""), + uScale(1.0f), + vScale(1.0f), + uOffset(0.0f), + vOffset(0.0f), + clamp(false) {} + + + TextureMapType type; + std::string name; + + // Texture scale and offset, used for creating the texture matrix. + // Reader only picks u and v from -s u v w, although all u v and w all need to be specified! + // e.g. "map_Kd -s u v w " is OK but "map_Kd -s u v " is not, even though tex is only 2D + float uScale; + float vScale; + float uOffset; + float vOffset; + + // According to the spec, if clamping is off (default), the effect is a texture repeat + // if clamping is on, then the effect is a decal texture; i.e. the border is transparent + bool clamp; + }; + + std::vector maps; protected: };