osg::Vec3d Geometry::localize() { osg::Vec3d offset; Bounds bounds = getBounds(); if ( bounds.isValid() ) { osg::Vec2d center = bounds.center2d(); offset.set( center.x(), center.y(), 0 ); GeometryIterator i( this ); while( i.hasMore() ) { Geometry* part = i.next(); for( Geometry::iterator j = part->begin(); j != part->end(); ++j ) { *j = *j - offset; } } } return offset; }
bool ExtrudeGeometryFilter::buildStructure(const Geometry* input, double height, double heightOffset, bool flatten, const SkinResource* wallSkin, const SkinResource* roofSkin, Structure& structure, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } // whether this is a closed polygon structure. structure.isPolygon = (input->getComponentType() == Geometry::TYPE_POLYGON); // extrusion working variables double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roofSkin ) { roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) ); if ( roofProjSRS.valid() ) { roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: non-tiled roofs don't really work atm. roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } // prep for wall texture coordinate generation. double texWidthM = wallSkin ? *wallSkin->imageWidth() : 0.0; double texHeightM = wallSkin ? *wallSkin->imageHeight() : 1.0; ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); // skip a part that's too small if (part->size() < 2) continue; // add a new wall. structure.elevations.push_back(Elevation()); Elevation& elevation = structure.elevations.back(); double maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / texHeightM); elevation.texHeightAdjustedM = div > 0.0 ? maxHeight / div : maxHeight; // Step 1 - Create the real corners and transform them into our target SRS. Corners corners; for(Geometry::const_iterator m = part->begin(); m != part->end(); ++m) { Corners::iterator corner = corners.insert(corners.end(), Corner()); // mark as "from source", as opposed to being inserted by the algorithm. corner->isFromSource = true; corner->base = *m; // extrude: if ( height >= 0 ) // extrude up { if ( flatten ) corner->roof.set( corner->base.x(), corner->base.y(), targetLen ); else corner->roof.set( corner->base.x(), corner->base.y(), corner->base.z() + height ); } else // height < 0 .. extrude down { corner->roof = *m; corner->base.z() += height; } // figure out the rooftop texture coords before doing any transformation: if ( roofSkin && srs ) { double xr, yr; if ( srs && srs->isGeographic() && roofProjSRS ) { osg::Vec3d projRoofPt; srs->transform( corner->roof, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (corner->roof.x() - roofBounds.xMin()); yr = (corner->roof.y() - roofBounds.yMin()); } corner->roofTexU = (cosR*xr - sinR*yr) / roofTexSpanX; corner->roofTexV = (sinR*xr + cosR*yr) / roofTexSpanY; } // transform into target SRS. transformAndLocalize( corner->base, srs, corner->base, mapSRS, _world2local, makeECEF ); transformAndLocalize( corner->roof, srs, corner->roof, mapSRS, _world2local, makeECEF ); } // Step 2 - Insert intermediate Corners as needed to satify texturing // requirements (if necessary) and record each corner offset (horizontal distance // from the beginning of the part geometry to the corner.) double cornerOffset = 0.0; double nextTexBoundary = texWidthM; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::iterator this_corner = c; Corners::iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); osg::Vec3d base_vec = next_corner->base - this_corner->base; double span = base_vec.length(); this_corner->offsetX = cornerOffset; if (wallSkin) { base_vec /= span; // normalize osg::Vec3d roof_vec = next_corner->roof - this_corner->roof; roof_vec.normalize(); while(nextTexBoundary < cornerOffset+span) { // insert a new fake corner. Corners::iterator new_corner = corners.insert(next_corner, Corner()); new_corner->isFromSource = false; double advance = nextTexBoundary-cornerOffset; new_corner->base = this_corner->base + base_vec*advance; new_corner->roof = this_corner->roof + roof_vec*advance; new_corner->offsetX = cornerOffset + advance; nextTexBoundary += texWidthM; // advance the main iterator c = new_corner; } } cornerOffset += span; } // Step 3 - Calculate the angle of each corner. osg::Vec3d prev_vec; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); if ( this_corner == corners.begin() ) { Corners::const_iterator prev_corner = corners.end(); --prev_corner; prev_vec = this_corner->roof - prev_corner->roof; prev_vec.normalize(); } osg::Vec3d this_vec = next_corner->roof - this_corner->roof; this_vec.normalize(); if ( c != corners.begin() ) { c->cosAngle = prev_vec * this_vec; } } // Step 4 - Create faces connecting each pair of Posts. Faces& faces = elevation.faces; for(Corners::const_iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); // only close the shape for polygons. if (next_corner != corners.begin() || structure.isPolygon) { faces.push_back(Face()); Face& face = faces.back(); face.left = *this_corner; face.right = *next_corner; // recalculate the final offset on the last face if ( next_corner == corners.begin() ) { osg::Vec3d vec = next_corner->roof - this_corner->roof; face.right.offsetX = face.left.offsetX + vec.length(); } face.widthM = next_corner->offsetX - this_corner->offsetX; } } } return true; }
bool ExtrudeGeometryFilter::extrudeGeometry(const Geometry* input, double height, double heightOffset, bool flatten, osg::Geometry* walls, osg::Geometry* roof, osg::Geometry* base, osg::Geometry* outline, const osg::Vec4& wallColor, const osg::Vec4& wallBaseColor, const osg::Vec4& roofColor, const osg::Vec4& outlineColor, const SkinResource* wallSkin, const SkinResource* roofSkin, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } bool made_geom = false; double tex_width_m = wallSkin ? *wallSkin->imageWidth() : 1.0; double tex_height_m = wallSkin ? *wallSkin->imageHeight() : 1.0; bool tex_repeats_y = wallSkin ? *wallSkin->isTiled() : false; bool useColor = (!wallSkin || wallSkin->texEnvMode() != osg::TexEnv::DECAL) && !_makeStencilVolume; bool isPolygon = input->getComponentType() == Geometry::TYPE_POLYGON; unsigned pointCount = input->getTotalPointCount(); // If we are extruding a polygon, and applying a wall texture, we need an extra // point in the geometry in order to close the polygon and generate a unique // texture coordinate for that final point. bool isSkinnedPolygon = isPolygon && wallSkin != 0L; // Total number of verts. Add 2 to close a polygon (necessary so the first and last // points can have unique texture coordinates) unsigned numWallVerts = 2 * pointCount + (isSkinnedPolygon? (2 * input->getNumGeometries()) : 0); // create all the OSG geometry components osg::Vec3Array* verts = new osg::Vec3Array( numWallVerts ); walls->setVertexArray( verts ); osg::Vec2Array* wallTexcoords = 0L; if ( wallSkin ) { wallTexcoords = new osg::Vec2Array( numWallVerts ); walls->setTexCoordArray( 0, wallTexcoords ); } osg::Vec4Array* colors = 0L; if ( useColor ) { // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw colors = new osg::Vec4Array(); colors->reserve( numWallVerts ); colors->assign( numWallVerts, wallColor ); walls->setColorArray( colors ); walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } // set up rooftop tessellation and texturing, if necessary: osg::Vec3Array* roofVerts = 0L; osg::Vec2Array* roofTexcoords = 0L; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roof ) { roofVerts = new osg::Vec3Array( pointCount ); roof->setVertexArray( roofVerts ); // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw if ( useColor ) { osg::Vec4Array* roofColors = new osg::Vec4Array(); roofColors->reserve( pointCount ); roofColors->assign( pointCount, roofColor ); roof->setColorArray( roofColors ); roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } if ( roofSkin ) { roofTexcoords = new osg::Vec2Array( pointCount ); roof->setTexCoordArray( 0, roofTexcoords ); // Get the orientation of the geometry. This is a hueristic that will help // us align the roof skin texture properly. TODO: make this optional? It makes // sense for buildings and such, but perhaps not for all extruded shapes. roofRotation = getApparentRotation( input ); roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angular(geogCenter.x()), Angular(geogCenter.y()) ); roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: doesn't really work roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } } osg::Vec3Array* baseVerts = NULL; if ( base ) { baseVerts = new osg::Vec3Array( pointCount ); base->setVertexArray( baseVerts ); } osg::Vec3Array* outlineVerts = 0L; osg::Vec3Array* outlineNormals = 0L; if ( outline ) { outlineVerts = new osg::Vec3Array( numWallVerts ); outline->setVertexArray( outlineVerts ); osg::Vec4Array* outlineColors = new osg::Vec4Array(); outlineColors->reserve( numWallVerts ); outlineColors->assign( numWallVerts, outlineColor ); outline->setColorArray( outlineColors ); outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); // cop out, just point all the outline normals up. fix this later. outlineNormals = new osg::Vec3Array(); outlineNormals->reserve( numWallVerts ); outlineNormals->assign( numWallVerts, osg::Vec3(0,0,1) ); outline->setNormalArray( outlineNormals ); } unsigned wallVertPtr = 0; unsigned roofVertPtr = 0; unsigned baseVertPtr = 0; double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; // now generate the extruded geometry. ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); double tex_height_m_adj = tex_height_m; unsigned wallPartPtr = wallVertPtr; unsigned roofPartPtr = roofVertPtr; unsigned basePartPtr = baseVertPtr; double partLen = 0.0; double maxHeight = 0.0; maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / tex_height_m); if (div == 0) div = 1; //Prevent divide by zero tex_height_m_adj = maxHeight / div; //osg::DrawElementsUShort* idx = new osg::DrawElementsUShort( GL_TRIANGLES ); osg::DrawElementsUInt* idx = new osg::DrawElementsUInt( GL_TRIANGLES ); for( Geometry::const_iterator m = part->begin(); m != part->end(); ++m ) { osg::Vec3d basePt = *m; osg::Vec3d roofPt; if ( height >= 0 ) { if ( flatten ) roofPt.set( basePt.x(), basePt.y(), targetLen ); else roofPt.set( basePt.x(), basePt.y(), basePt.z() + height ); } else // height < 0 { roofPt = *m; basePt.z() += height; } // add to the approprate vertex lists: int p = wallVertPtr; // figure out the rooftop texture coordinates before doing any // transformations: if ( roofSkin && roofProjSRS && srs ) { double xr, yr; if ( srs && srs->isGeographic() ) { osg::Vec3d projRoofPt; srs->transform( roofPt, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (roofPt.x() - roofBounds.xMin()); yr = (roofPt.y() - roofBounds.yMin()); } float u = (cosR*xr - sinR*yr) / roofTexSpanX; float v = (sinR*xr + cosR*yr) / roofTexSpanY; (*roofTexcoords)[roofVertPtr].set( u, v ); } transformAndLocalize( basePt, srs, basePt, mapSRS, _world2local, makeECEF ); transformAndLocalize( roofPt, srs, roofPt, mapSRS, _world2local, makeECEF ); if ( base ) { (*baseVerts)[baseVertPtr] = basePt; } if ( roof ) { (*roofVerts)[roofVertPtr] = roofPt; } baseVertPtr++; roofVertPtr++; (*verts)[p] = roofPt; (*verts)[p+1] = basePt; if ( useColor ) { (*colors)[p+1] = wallBaseColor; } if ( outline ) { (*outlineVerts)[p] = roofPt; (*outlineVerts)[p+1] = basePt; } partLen += wallVertPtr > wallPartPtr ? ((*verts)[p] - (*verts)[p-2]).length() : 0.0; double h = tex_repeats_y ? -((*verts)[p] - (*verts)[p+1]).length() : -tex_height_m_adj; if ( wallSkin ) { (*wallTexcoords)[p].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+1].set( partLen/tex_width_m, h/tex_height_m_adj ); } // form the 2 triangles if ( (m+1) == part->end() ) { if ( isPolygon ) { // end of the wall; loop around to close it off. if ( isSkinnedPolygon ) { // if we requested an extra geometry point, that means we are generating // a polygon-closing line so we can have a unique texcoord for it. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); (*verts)[p+2] = (*verts)[wallPartPtr]; (*verts)[p+3] = (*verts)[wallPartPtr+1]; if ( wallSkin ) { partLen += ((*verts)[p+2] - (*verts)[p]).length(); double h = tex_repeats_y ? -((*verts)[p+2] - (*verts)[p+3]).length() : -tex_height_m_adj; (*wallTexcoords)[p+2].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+3].set( partLen/tex_width_m, h/tex_height_m_adj ); } wallVertPtr += 2; } else { // either not a poly, or no wall skin, so we can share the polygon-closing // loop point. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr+1); idx->push_back(wallPartPtr); } } else { //nop - no elements required at the end of a line } } else { idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); } wallVertPtr += 2; made_geom = true; } walls->addPrimitiveSet( idx ); if ( roof ) { roof->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, roofPartPtr, roofVertPtr - roofPartPtr ) ); } if ( base ) { // reverse the base verts: int len = baseVertPtr - basePartPtr; for( int i=basePartPtr; i<len/2; i++ ) std::swap( (*baseVerts)[i], (*baseVerts)[basePartPtr+(len-1)-i] ); base->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, basePartPtr, baseVertPtr - basePartPtr ) ); } if ( outline ) { unsigned len = baseVertPtr - basePartPtr; GLenum roofLineMode = isPolygon ? GL_LINE_LOOP : GL_LINE_STRIP; osg::DrawElementsUInt* roofLine = new osg::DrawElementsUInt( roofLineMode ); roofLine->reserveElements( len ); for( unsigned i=0; i<len; ++i ) roofLine->addElement( basePartPtr + i*2 ); outline->addPrimitiveSet( roofLine ); // if the outline is tessellated, we only want outlines on the original // points (not the inserted points) unsigned step = std::max( 1u, _outlineSymbol->tessellation().isSet() ? *_outlineSymbol->tessellation() : 1u ); osg::DrawElementsUInt* wallLines = new osg::DrawElementsUInt( GL_LINES ); wallLines->reserve( len*2 ); for( unsigned i=0; i<len; i+=step ) { wallLines->push_back( basePartPtr + i*2 ); wallLines->push_back( basePartPtr + i*2 + 1 ); } outline->addPrimitiveSet( wallLines ); applyLineSymbology( outline->getOrCreateStateSet(), _outlineSymbol.get() ); } } return made_geom; }