diff --git a/osgplugin/ReaderWriterCityGML.cpp b/osgplugin/ReaderWriterCityGML.cpp index 8529227a..83834a79 100644 --- a/osgplugin/ReaderWriterCityGML.cpp +++ b/osgplugin/ReaderWriterCityGML.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,7 @@ class CityGMLSettings CityGMLSettings( void ) : _printNames(false) , _useMaxLODOnly(false) + , _singleObject(false) , _storeGeomIDs(false) , _theme("") {} @@ -108,7 +110,8 @@ class CityGMLSettings else if ( currentOption == "maxlod" ) iss >> _params.maxLOD; else if ( currentOption == "optimize" ) _params.optimize = true; else if ( currentOption == "pruneemptyobjects" ) _params.pruneEmptyObjects = true; - else if ( currentOption == "usemaxlodonly" ) _useMaxLODOnly = true; + else if (currentOption == "usemaxlodonly") _useMaxLODOnly = true; + else if (currentOption == "singleobject") _singleObject = true; else if ( currentOption == "usetheme" ) iss >> _theme; else if ( currentOption == "storegeomids" ) _storeGeomIDs = true; } @@ -118,11 +121,36 @@ class CityGMLSettings citygml::ParserParams _params; bool _printNames; bool _useMaxLODOnly; + bool _singleObject; bool _storeGeomIDs; std::map< std::string, osg::Texture2D* > _textureMap; std::string _theme; }; +class materialArrays +{ + +public: + materialArrays(int sizehint = 3) + { + texCoords = new osg::Vec2Array; + vertices = new osg::Vec3Array; + texCoords = new osg::Vec2Array; + indices.reserve(sizehint); + vertices->reserve(sizehint); + texCoords->reserve(sizehint); + texture = nullptr; + } + ~materialArrays() + { + } + osg::Texture2D* texture; + std::string textureName; + osg::ref_ptr vertices; + osg::ref_ptr texCoords; + std::vector indices; +}; + class ReaderWriterCityGML : public osgDB::ReaderWriter { public: @@ -149,16 +177,37 @@ class ReaderWriterCityGML : public osgDB::ReaderWriter virtual ReadResult readNode( const std::string&, const osgDB::ReaderWriter::Options* ) const override; virtual ReadResult readNode( std::istream&, const osgDB::ReaderWriter::Options* ) const override; + virtual ReadResult readObject(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const + { + ReadResult result = readNode(fileName, options); + osg::Node* node = result.getNode(); + if (node) return node; + else return result; + } -private: + virtual ReadResult readObject(std::istream& fin, const Options* options) const + { + ReadResult result = readNode(fin, options); + osg::Node* node = result.getNode(); + if (node) return node; + else return result; + } +private: std::shared_ptr m_logger; static unsigned int getHighestLodForObject(const citygml::CityObject& object); ReadResult readCity(std::shared_ptr, CityGMLSettings& ) const; bool createCityObject(const citygml::CityObject&, CityGMLSettings&, osg::Group*, const osg::Vec3d& offset = osg::Vec3d(0.0, 0.0, 0.0), unsigned int minimumLODToConsider = 0) const; + bool createSingleCityObject(const citygml::CityObject&, CityGMLSettings&, const osg::Vec3d& offset = osg::Vec3d(0.0, 0.0, 0.0), unsigned int minimumLODToConsider = 0) const; + + void createSingleOsgGeometryFromCityGMLGeometry(const citygml::CityObject& object,const citygml::Geometry& geometry, CityGMLSettings& settings, const osg::Vec3d& offset) const; + }; +std::map ma; + + // Register with Registry to instantiate the above reader/writer. REGISTER_OSGPLUGIN( citygml, ReaderWriterCityGML ) @@ -190,6 +239,16 @@ osgDB::ReaderWriter::ReadResult ReaderWriterCityGML::readNode( const std::string std::streambuf* cerrsb = std::cerr.rdbuf( osg::notify(osg::NOTICE).rdbuf() ); osg::notify(osg::NOTICE) << "Parsing CityGML file " << fileName << "..." << std::endl; +#ifdef WIN32 + if (fileName[0] == '/') + { + //xerces on windows wants \ as a start to figure out that it is an absolute path + fileName[0] = '\\'; + if(fileName[1]=='/') + fileName[1] = '\\'; + } +#endif + std::unique_ptr tesselator = std::unique_ptr(new Tesselator(nullptr)); std::shared_ptr city = citygml::load( fileName, settings._params, std::move(tesselator), m_logger ); @@ -200,6 +259,7 @@ osgDB::ReaderWriter::ReadResult ReaderWriterCityGML::readNode( const std::string // Let osg calculate the normals osgUtil::SmoothingVisitor sv; + sv.setCreaseAngle(osg::PI_2); rr.getNode()->accept(sv); } @@ -260,8 +320,78 @@ osgDB::ReaderWriter::ReadResult ReaderWriterCityGML::readCity(std::shared_ptrgetEnvelope().getLowerBound(); offset = osg::Vec3d(lb.x, lb.y, lb.z); } + if(settings._singleObject) + { + osg::Geode* geode = new osg::Geode(); + + // Vertices + materialArrays *arrW = new materialArrays(); + ma["wall"] = arrW; + materialArrays* arrR = new materialArrays(); + ma["roof"] = arrR; + + for (unsigned int i = 0; i < roots.size(); ++i) createSingleCityObject(*roots[i], settings, offset); + for (const auto& it : ma) + { + materialArrays* arrays = it.second; + if(arrays->vertices->size()>0) + { + osg::Geometry* geom = new osg::Geometry; + geom->setVertexArray(arrays->vertices); + osg::DrawElementsUInt* indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, arrays->indices.begin(), arrays->indices.end()); + geom->addPrimitiveSet(indices); + + + // Appearance + + osg::ref_ptr stateset = geom->getOrCreateStateSet(); + + + osg::Material* material = new osg::Material; + material->setColorMode(osg::Material::OFF); + if (it.first == "wall") + { + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.9f, 0.9f, 0.9f, 1.0f)); + } + else if (it.first == "roof") + { + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.2f, 0.2f, 0.2f, 1.0f)); + } + else // textured + { + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); + if (arrays->texture) + { + if (arrays->texCoords->size() > 0) + { + geom->setTexCoordArray(0, arrays->texCoords); + + stateset->setTextureAttributeAndModes(0, arrays->texture, osg::StateAttribute::ON); + } + } + } + material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); + material->setShininess(osg::Material::FRONT_AND_BACK, 128.f * 0.5f); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(0.1f, 0.1f, 0.1f, 1.0f)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); osg::CullFace* cullFace = new osg::CullFace(); + cullFace->setMode(osg::CullFace::BACK); + stateset->setAttributeAndModes(cullFace, osg::StateAttribute::ON); + geode->addDrawable(geom); + } + delete arrays; + } + ma.clear(); - for ( unsigned int i = 0; i < roots.size(); ++i ) createCityObject( *roots[i], settings, root, offset ); + + + root->addChild(geode); + } + else + { + for (unsigned int i = 0; i < roots.size(); ++i) createCityObject(*roots[i], settings, root, offset); + } osg::notify(osg::NOTICE) << "Done." << std::endl; @@ -271,6 +401,57 @@ osgDB::ReaderWriter::ReadResult ReaderWriterCityGML::readCity(std::shared_ptr stateset, osg::Geometry* geom,CityGMLSettings& settings, std::shared_ptr citygmlTex) { + + if (!citygmlTex) + { + return; + } + osg::Texture2D* texture = nullptr; + + if (settings._textureMap.find(citygmlTex->getUrl()) == settings._textureMap.end()) { + std::string fullPath = osgDB::findDataFile(citygmlTex->getUrl()); + + if (fullPath.empty()) { + osg::notify(osg::NOTICE) << " Texture file " << citygmlTex->getUrl() << " not found..." << std::endl; + return; + } + + // Load a new texture + osg::notify(osg::NOTICE) << " Loading texture " << fullPath << "..." << std::endl; + + osg::Image* image = osgDB::readImageFile(citygmlTex->getUrl()); + + if (!image) { + osg::notify(osg::NOTICE) << " Warning: Failed to read Texture " << fullPath << std::endl; + return; + } + + texture = new osg::Texture2D; + texture->setImage(image); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::REPEAT); + + settings._textureMap[citygmlTex->getUrl()] = texture; + } + else { + texture = settings._textureMap[citygmlTex->getUrl()]; + } + + if (!texture) + { + return; + } + + + stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); + osg::CullFace* cullFace = new osg::CullFace(); + cullFace->setMode(osg::CullFace::BACK); + stateset->setAttributeAndModes(cullFace, osg::StateAttribute::ON); +} void setTexture(osg::ref_ptr stateset, osg::Geometry* geom, const citygml::Polygon& polygon, CityGMLSettings& settings) { const auto citygmlTex = polygon.getTextureFor(settings._theme); @@ -332,6 +513,9 @@ void setTexture(osg::ref_ptr stateset, osg::Geometry* geom, const geom->setTexCoordArray( 0, tex ); stateset->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON ); + osg::CullFace* cullFace = new osg::CullFace(); + cullFace->setMode(osg::CullFace::BACK); + stateset->setAttributeAndModes(cullFace, osg::StateAttribute::ON); } void setMaterial(osg::ref_ptr stateset, const citygml::Polygon& polygon, CityGMLSettings& settings) { @@ -357,53 +541,137 @@ void setMaterial(osg::ref_ptr stateset, const citygml::Polygon& p material->setTransparency( osg::Material::FRONT_AND_BACK, citygmlMaterial->getTransparency() ); stateset->setAttributeAndModes( material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON ); stateset->setMode( GL_LIGHTING, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON ); + + osg::CullFace *cullFace = new osg::CullFace(); + cullFace->setMode(osg::CullFace::BACK); + stateset->setAttributeAndModes(cullFace, osg::StateAttribute::ON); } void createOsgGeometryFromCityGMLGeometry(const citygml::Geometry& geometry, CityGMLSettings& settings, osg::Geode* geometryContainer, const osg::Vec3d& offset ) { - for ( unsigned int j = 0; j < geometry.getPolygonsCount(); j++ ) - { - const citygml::Polygon& p = *geometry.getPolygon(j); - - if ( p.getIndices().size() == 0 ) continue; - - // Geometry management + std::string texName = "axaxaxax"; + if (geometry.getPolygonsCount() > 0) + { osg::Geometry* geom = new osg::Geometry; - geom->setName( p.getId() ); geom->setUserValue("cot_type", geometry.getTypeAsString()); - - // Vertices osg::Vec3Array* vertices = new osg::Vec3Array; - const std::vector& vert = p.getVertices(); - vertices->reserve( vert.size() ); - for ( unsigned int k = 0; k < vert.size(); k++ ) + osg::ref_ptr tex = new osg::Vec2Array; + tex->reserve(3); + vertices->reserve(3); + std::vector indicesVec; + indicesVec.reserve(2); + + for (unsigned int j = 0; j < geometry.getPolygonsCount(); j++) { - TVec3d v = vert[k]; - osg::Vec3d pt = osg::Vec3d( v.x, v.y, v.z ) - offset; - vertices->push_back( pt ); - } + const citygml::Polygon& p = *geometry.getPolygon(j); + const auto citygmlTex = p.getTextureFor(settings._theme); - geom->setVertexArray( vertices ); + if (p.getIndices().size() == 0) continue; - // Indices - osg::DrawElementsUInt* indices = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, p.getIndices().begin(), p.getIndices().end()); - geom->addPrimitiveSet( indices ); + // Geometry management + + if (texName != "axaxaxax" && (citygmlTex != nullptr && (citygmlTex->getUrl() != texName)) || (citygmlTex == nullptr && (texName != ""))) + { + + geom->setVertexArray(vertices); + if (tex->size() > 0) + geom->setTexCoordArray(0, tex); - // Appearance + // Indices + osg::DrawElementsUInt* indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, indicesVec.begin(), indicesVec.end()); + geom->addPrimitiveSet(indices); + indicesVec.clear(); - osg::ref_ptr stateset = geom->getOrCreateStateSet(); + // Appearance - setMaterial(stateset, p, settings); - setTexture(stateset, geom, p, settings); + osg::ref_ptr stateset = geom->getOrCreateStateSet(); + + setMaterial(stateset, p, settings); + setTextureOnly(stateset, geom, settings,citygmlTex); #if OSG_VERSION_GREATER_OR_EQUAL(3,3,2) - if (settings._storeGeomIDs) { - geom->addDescription(p.getId()); + if (settings._storeGeomIDs) { + geom->addDescription(p.getId()); + } +#endif + + + geometryContainer->addDrawable(geom); + // create new Geometry + geom = new osg::Geometry; + geom->setUserValue("cot_type", geometry.getTypeAsString()); + vertices = new osg::Vec3Array; + tex = new osg::Vec2Array; + vertices->reserve(3); + tex->reserve(3); + + } + + if (citygmlTex == nullptr) + texName = ""; + else + texName = citygmlTex->getUrl(); + geom->setName(p.getId()); + + + GLuint startIndex = vertices->size(); + for (const auto& i : p.getIndices()) + { + indicesVec.push_back(i+ startIndex); + } + + // Vertices + const std::vector& vert = p.getVertices(); + for (unsigned int k = 0; k < vert.size(); k++) + { + TVec3d v = vert[k]; + osg::Vec3d pt = osg::Vec3d(v.x, v.y, v.z) - offset; + vertices->push_back(pt); + } + //texcoords + + + + const std::vector& texCoords = p.getTexCoordsForTheme(settings._theme, true); + + if (!texCoords.empty()) { + for (unsigned int k = 0; k < texCoords.size(); k++) + tex->push_back(osg::Vec2(texCoords[k].x, texCoords[k].y)); } + + + + + + } + if (vertices->size()) + { + const citygml::Polygon& p = *geometry.getPolygon(geometry.getPolygonsCount() - 1); + geom->setVertexArray(vertices); + if (tex->size() > 0) + geom->setTexCoordArray(0, tex); + + // Indices + osg::DrawElementsUInt* indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, indicesVec.begin(), indicesVec.end()); + geom->addPrimitiveSet(indices); + + // Appearance + + osg::ref_ptr stateset = geom->getOrCreateStateSet(); + + setMaterial(stateset, p, settings); + const auto citygmlTex = p.getTextureFor(settings._theme); + setTextureOnly(stateset, geom, settings,citygmlTex); + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,2) + if (settings._storeGeomIDs) { + geom->addDescription(p.getId()); + } #endif - geometryContainer->addDrawable( geom ); + geometryContainer->addDrawable(geom); + } } // Parse child geoemtries @@ -412,6 +680,116 @@ void createOsgGeometryFromCityGMLGeometry(const citygml::Geometry& geometry, Cit } } +void ReaderWriterCityGML::createSingleOsgGeometryFromCityGMLGeometry(const citygml::CityObject& object, const citygml::Geometry& geometry, CityGMLSettings& settings, const osg::Vec3d& offset) const +{ + for (unsigned int j = 0; j < geometry.getPolygonsCount(); j++) + { + const citygml::Polygon& p = *geometry.getPolygon(j); + const auto citygmlTex = p.getTextureFor(settings._theme); + + if (p.getIndices().size() == 0) continue; + + const std::vector& vert = p.getVertices(); + materialArrays* arrays = nullptr; + + if (object.getType() == citygml::CityObject::CityObjectsType::COT_RoofSurface) + { + } + if (citygmlTex) + { + auto it = ma.find(citygmlTex->getUrl()); + if (it != ma.end()) + arrays = it->second; + else + { + arrays = new materialArrays(); + arrays->textureName = citygmlTex->getUrl(); + ma[arrays->textureName] = arrays; + + if (settings._textureMap.find(citygmlTex->getUrl()) == settings._textureMap.end()) { + std::string fullPath = osgDB::findDataFile(citygmlTex->getUrl()); + + if (fullPath.empty()) { + osg::notify(osg::NOTICE) << " Texture file " << citygmlTex->getUrl() << " not found..." << std::endl; + return; + } + + // Load a new texture + osg::notify(osg::NOTICE) << " Loading texture " << fullPath << "..." << std::endl; + + osg::Image* image = osgDB::readImageFile(citygmlTex->getUrl()); + + if (!image) { + osg::notify(osg::NOTICE) << " Warning: Failed to read Texture " << fullPath << std::endl; + return; + } + + arrays->texture = new osg::Texture2D; + arrays->texture->setImage(image); + arrays->texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + arrays->texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST); + arrays->texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + arrays->texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + arrays->texture->setWrap(osg::Texture::WRAP_R, osg::Texture::REPEAT); + + settings._textureMap[citygmlTex->getUrl()] = arrays->texture; + } + else { + arrays->texture = settings._textureMap[citygmlTex->getUrl()]; + } + } + } + else + { + if (object.getType() == citygml::CityObject::CityObjectsType::COT_RoofSurface) + { + auto it = ma.find("roof"); + if (it != ma.end()) + arrays = it->second; + } + else if (object.getType() == citygml::CityObject::CityObjectsType::COT_WallSurface) + { + auto it = ma.find("wall"); + if (it != ma.end()) + arrays = it->second; + } + } + if(arrays) + { + GLuint startIndex = arrays->vertices->size(); + for (const auto& i : p.getIndices()) + { + arrays->indices.push_back(i + startIndex); + } + + for (unsigned int k = 0; k < vert.size(); k++) + { + TVec3d v = vert[k]; + osg::Vec3d pt = osg::Vec3d(v.x, v.y, v.z) - offset; + arrays->vertices->push_back(pt); + } + if (citygmlTex) + { + const std::vector& texCoords = p.getTexCoordsForTheme(settings._theme, true); + + if (!texCoords.empty()) { + for (unsigned int k = 0; k < texCoords.size(); k++) + arrays->texCoords->push_back(osg::Vec2(texCoords[k].x, texCoords[k].y)); + } + } + } + + + // Indices + //osg::DrawElementsUInt* indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, p.getIndices().begin(), p.getIndices().end()); + } + + // Parse child geoemtries + for (unsigned int i = 0; i < geometry.getGeometriesCount(); i++) { + createSingleOsgGeometryFromCityGMLGeometry(object, geometry.getGeometry(i), settings, offset); + } +} + bool ReaderWriterCityGML::createCityObject(const citygml::CityObject& object, CityGMLSettings& settings, osg::Group* parent, const osg::Vec3d& offset , unsigned int minimumLODToConsider) const { // Skip objects without geometry @@ -431,6 +809,7 @@ bool ReaderWriterCityGML::createCityObject(const citygml::CityObject& object, Ci unsigned int highestLOD = ReaderWriterCityGML::getHighestLodForObject(object); + bool gotGeometry = false; for ( unsigned int i = 0; i < object.getGeometriesCount(); i++ ) { const citygml::Geometry& geometry = object.getGeometry( i ); @@ -440,7 +819,7 @@ bool ReaderWriterCityGML::createCityObject(const citygml::CityObject& object, Ci if (settings._useMaxLODOnly && (currentLOD < highestLOD || currentLOD < minimumLODToConsider )){ continue; } - + gotGeometry = true; createOsgGeometryFromCityGMLGeometry(geometry, settings, geode, offset); } @@ -479,9 +858,43 @@ bool ReaderWriterCityGML::createCityObject(const citygml::CityObject& object, Ci geodeSS->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } + if(!gotGeometry) + { + for (unsigned int i = 0; i < object.getChildCityObjectsCount(); ++i) + createCityObject(object.getChildCityObject(i), settings, grp, offset, highestLOD); + } + + return true; +} + +bool ReaderWriterCityGML::createSingleCityObject(const citygml::CityObject& object, CityGMLSettings& settings, const osg::Vec3d& offset, unsigned int minimumLODToConsider) const +{ + osg::ref_ptr roof_color = new osg::Vec4Array; + roof_color->push_back(osg::Vec4(0.9f, 0.1f, 0.1f, 1.0f)); + + unsigned int highestLOD = ReaderWriterCityGML::getHighestLodForObject(object); + + for (unsigned int i = 0; i < object.getGeometriesCount(); i++) + { + const citygml::Geometry& geometry = object.getGeometry(i); + + const unsigned int currentLOD = geometry.getLOD(); + + if (settings._useMaxLODOnly && (currentLOD < highestLOD || currentLOD < minimumLODToConsider)) { + continue; + } + + createSingleOsgGeometryFromCityGMLGeometry(object,geometry, settings, offset); + } + + + // Manage transparency for windows + if (object.getType() == citygml::CityObject::CityObjectsType::COT_Window) + { + } - for ( unsigned int i = 0; i < object.getChildCityObjectsCount(); ++i ) - createCityObject( object.getChildCityObject(i), settings, grp, offset, highestLOD); + for (unsigned int i = 0; i < object.getChildCityObjectsCount(); ++i) + createSingleCityObject(object.getChildCityObject(i), settings, offset, highestLOD); return true; } diff --git a/sources/src/citygml/cityobject.cpp b/sources/src/citygml/cityobject.cpp index fa0d5d78..f1ffb7c2 100644 --- a/sources/src/citygml/cityobject.cpp +++ b/sources/src/citygml/cityobject.cpp @@ -65,7 +65,7 @@ namespace citygml { void CityObject::addImplictGeometry(ImplicitGeometry* implictGeom) { - m_implicitGeometries.push_back(std::unique_ptr(implictGeom)); + m_implicitGeometries.push_back(std::unique_ptr(implictGeom)); } unsigned int CityObject::getChildCityObjectsCount() const @@ -85,7 +85,11 @@ namespace citygml { void CityObject::addChildCityObject(CityObject* cityObj) { - m_children.push_back(std::unique_ptr(cityObj)); + // don't add empty nodes as children + if(cityObj!=NULL) + { + m_children.push_back(std::unique_ptr(cityObj)); + } } const Address* CityObject::address() const diff --git a/sources/src/parser/cityobjectelementparser.cpp b/sources/src/parser/cityobjectelementparser.cpp index b2dd2150..1518c43f 100644 --- a/sources/src/parser/cityobjectelementparser.cpp +++ b/sources/src/parser/cityobjectelementparser.cpp @@ -41,6 +41,7 @@ namespace citygml { , m_lastAttributeType(AttributeType::String) { m_callback = callback; + m_model = nullptr; } std::string CityObjectElementParser::elementParserName() const diff --git a/sources/src/parser/parserxercesc.cpp b/sources/src/parser/parserxercesc.cpp index 55e15029..be7e6677 100644 --- a/sources/src/parser/parserxercesc.cpp +++ b/sources/src/parser/parserxercesc.cpp @@ -304,7 +304,7 @@ namespace citygml } #endif - delete parser; + delete parser; return handler.getModel(); }