WS30 - Line feature accuracy and width control

- Fix a bug causing roads to be displaced from their
true position (caused by incorrect assumption in ElevationSlicer).

- Add control of the minimum width of roads that are rendered at
each LoD level.  Control properties under /sim/rendering/static-lod

- Improve marking tiles for re-rendering.
This commit is contained in:
Stuart Buchanan
2021-03-06 17:23:34 +00:00
parent 4c25e7898e
commit 0b986beac6
5 changed files with 1130 additions and 82 deletions

View File

@@ -20,6 +20,7 @@ set(HEADERS
SGVertexArrayBin.hxx
ShaderGeometry.hxx
TreeBin.hxx
VPBElevationSlice.hxx
VPBTechnique.hxx
apt_signs.hxx
obj.hxx
@@ -38,6 +39,7 @@ set(SOURCES
SGVasiDrawable.cxx
ShaderGeometry.cxx
TreeBin.cxx
VPBElevationSlice.cxx
VPBTechnique.cxx
apt_signs.cxx
obj.cxx

View File

@@ -0,0 +1,976 @@
// VPBVPBElevationSlice.cxx -- VirtualPlanetBuilder Elevation Slice
//
// Copyright (C) 2021 Stuart Buchanan
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <osg/CoordinateSystemNode>
#include <osg/io_utils>
#include <osg/Geode>
#include <osg/Geometry>
#include <simgear/debug/logstream.hxx>
#include <simgear/scene/tgdb/VPBElevationSlice.hxx>
#include <osg/Notify>
#include <osgUtil/PlaneIntersector>
#include <osgDB/WriteFile>
using namespace osgSim;
namespace simgear {
namespace VPBElevationSliceUtils
{
struct DistanceHeightXYZ
{
DistanceHeightXYZ():
distance(0.0),
height(0.0) {}
DistanceHeightXYZ(const DistanceHeightXYZ& dh):
distance(dh.distance),
height(dh.height),
position(dh.position) {}
DistanceHeightXYZ(double d, double h, const osg::Vec3d& pos):
distance(d),
height(h),
position(pos) {}
bool operator < (const DistanceHeightXYZ& rhs) const
{
// small distance values first
if (distance < rhs.distance) return true;
if (distance > rhs.distance) return false;
// smallest heights first
return (height < rhs.height);
}
bool operator == (const DistanceHeightXYZ& rhs) const
{
return distance==rhs.distance && height==rhs.height;
}
bool operator != (const DistanceHeightXYZ& rhs) const
{
return distance!=rhs.distance || height!=rhs.height;
}
bool equal_distance(const DistanceHeightXYZ& rhs, double epsilon=1e-6) const
{
return osg::absolute(rhs.distance - distance) <= epsilon;
}
double distance;
double height;
osg::Vec3d position;
};
struct Point : public osg::Referenced, public DistanceHeightXYZ
{
Point() {}
Point(double d, double h, const osg::Vec3d& pos):
DistanceHeightXYZ(d,h,pos)
{
}
Point(const Point& point):
osg::Referenced(),
DistanceHeightXYZ(point) {}
};
struct Segment
{
Segment(Point* p1, Point* p2)
{
if (*p1 < *p2)
{
_p1 = p1;
_p2 = p2;
}
else
{
_p1 = p2;
_p2 = p1;
}
}
bool operator < ( const Segment& rhs) const
{
if (*_p1 < *rhs._p1) return true;
if (*rhs._p1 < *_p1) return false;
return (*_p2 < *rhs._p2);
}
enum Classification
{
UNCLASSIFIED,
IDENTICAL,
SEPARATE,
JOINED,
OVERLAPPING,
ENCLOSING,
ENCLOSED
};
Classification compare(const Segment& rhs) const
{
if (*_p1 == *rhs._p1 && *_p2==*rhs._p2) return IDENTICAL;
const double epsilon = 1e-3; // 1mm
double delta_distance = _p2->distance - rhs._p1->distance;
if (fabs(delta_distance) < epsilon)
{
if (fabs(_p2->height - rhs._p1->height) < epsilon) return JOINED;
}
if (delta_distance==0.0)
{
return SEPARATE;
}
if (rhs._p2->distance < _p1->distance || _p2->distance < rhs._p1->distance) return SEPARATE;
bool rhs_p1_inside = (_p1->distance <= rhs._p1->distance) && (rhs._p1->distance <= _p2->distance);
bool rhs_p2_inside = (_p1->distance <= rhs._p2->distance) && (rhs._p2->distance <= _p2->distance);
if (rhs_p1_inside && rhs_p2_inside) return ENCLOSING;
bool p1_inside = (rhs._p1->distance <= _p1->distance) && (_p1->distance <= rhs._p2->distance);
bool p2_inside = (rhs._p1->distance <= _p2->distance) && (_p2->distance <= rhs._p2->distance);
if (p1_inside && p2_inside) return ENCLOSED;
if (rhs_p1_inside || rhs_p2_inside || p1_inside || p2_inside) return OVERLAPPING;
return UNCLASSIFIED;
}
double height(double d) const
{
double delta = (_p2->distance - _p1->distance);
return _p1->height + ((_p2->height - _p1->height) * (d - _p1->distance) / delta);
}
double deltaHeight(Point& point) const
{
return point.height - height(point.distance);
}
Point* createPoint(double d) const
{
if (d == _p1->distance) return _p1.get();
if (d == _p2->distance) return _p2.get();
double delta = (_p2->distance - _p1->distance);
double r = (d - _p1->distance)/delta;
double one_minus_r = 1.0 - r;
return new Point(d,
_p1->height * one_minus_r + _p2->height * r,
_p1->position * one_minus_r + _p2->position * r);
}
Point* createIntersectionPoint(const Segment& rhs) const
{
double A = _p1->distance;
double B = _p2->distance - _p1->distance;
double C = _p1->height;
double D = _p2->height - _p1->height;
double E = rhs._p1->distance;
double F = rhs._p2->distance - rhs._p1->distance;
double G = rhs._p1->height;
double H = rhs._p2->height - rhs._p1->height;
double div = D*F - B*H;
if (div==0.0)
{
return _p2.get();
}
double r = (G*F - E*H + A*H - C*F) / div;
if (r<0.0)
{
return _p1.get();
}
if (r>1.0)
{
return _p2.get();
}
return new Point(A + B*r, C + D*r, _p1->position + (_p2->position - _p1->position)*r);
}
osg::ref_ptr<Point> _p1;
osg::ref_ptr<Point> _p2;
};
struct LineConstructor
{
typedef std::set<Segment> SegmentSet;
LineConstructor() {}
void add(double d, double h, const osg::Vec3d& pos)
{
osg::ref_ptr<Point> newPoint = new Point(d,h,pos);
if (_previousPoint.valid() && newPoint->distance != _previousPoint->distance)
{
const double maxGradient = 100.0;
double gradient = fabs( (newPoint->height - _previousPoint->height) / (newPoint->distance - _previousPoint->distance) );
if (gradient < maxGradient)
{
_segments.insert( Segment(_previousPoint.get(), newPoint.get()) );
}
}
_previousPoint = newPoint;
}
void endline()
{
_previousPoint = 0;
}
void report()
{
}
void pruneOverlappingSegments()
{
double epsilon = 0.001;
for(SegmentSet::iterator itr = _segments.begin();
itr != _segments.end();
) // itr increment is done manually at end of loop
{
SegmentSet::iterator nextItr = itr;
++nextItr;
Segment::Classification classification = nextItr != _segments.end() ? itr->compare(*nextItr) : Segment::UNCLASSIFIED;
while (classification>=Segment::OVERLAPPING)
{
switch(classification)
{
case(Segment::OVERLAPPING):
{
// cases....
// compute new end points for both segments
// need to work out which points are overlapping - lhs_p2 && rhs_p1 or lhs_p1 and rhs_p2
// also need to check for cross cases.
const Segment& lhs = *itr;
const Segment& rhs = *nextItr;
bool rhs_p1_inside = (lhs._p1->distance <= rhs._p1->distance) && (rhs._p1->distance <= lhs._p2->distance);
bool lhs_p2_inside = (rhs._p1->distance <= lhs._p2->distance) && (lhs._p2->distance <= rhs._p2->distance);
if (rhs_p1_inside && lhs_p2_inside)
{
double distance_between = osg::Vec2d(lhs._p2->distance - rhs._p1->distance,
lhs._p2->height - rhs._p1->height).length2();
if (distance_between < epsilon)
{
Segment newSeg(lhs._p2.get(), rhs._p2.get());
_segments.insert(newSeg);
_segments.erase(nextItr);
nextItr = _segments.find(newSeg);
}
else
{
double dh1 = lhs.deltaHeight(*rhs._p1);
double dh2 = -rhs.deltaHeight(*lhs._p2);
if (dh1 * dh2 < 0.0)
{
Point* cp = lhs.createIntersectionPoint(rhs);
Segment seg1( lhs._p1.get(), lhs.createPoint(rhs._p2->distance) );
Segment seg2( rhs._p1.get(), cp );
Segment seg3( cp, lhs._p2.get() );
Segment seg4( rhs.createPoint(lhs._p2->distance), lhs._p2.get() );
_segments.erase(nextItr);
_segments.erase(itr);
_segments.insert(seg1);
_segments.insert(seg2);
_segments.insert(seg3);
_segments.insert(seg4);
itr = _segments.find(seg1);
nextItr = itr;
++nextItr;
}
else if (dh1 <= 0.0 && dh2 <= 0.0)
{
Segment newSeg(rhs.createPoint(lhs._p2->distance), rhs._p2.get());
_segments.erase(nextItr);
_segments.insert(newSeg);
nextItr = itr;
++nextItr;
}
else if (dh1 >= 0.0 && dh2 >= 0.0)
{
Segment newSeg(lhs._p1.get(), lhs.createPoint(rhs._p1->distance));
_segments.erase(itr);
_segments.insert(newSeg);
itr = _segments.find(newSeg);
nextItr = itr;
++nextItr;
}
else
{
++nextItr;
}
}
}
else
{
++nextItr;
}
break;
}
case(Segment::ENCLOSING):
{
// need to work out if rhs is below lhs or rhs is above lhs or crossing
const Segment& enclosing = *itr;
const Segment& enclosed = *nextItr;
double dh1 = enclosing.deltaHeight(*enclosed._p1);
double dh2 = enclosing.deltaHeight(*enclosed._p2);
if (dh1<=epsilon && dh2<=epsilon)
{
_segments.erase(nextItr);
nextItr = itr;
++nextItr;
}
else if (dh1>=0.0 && dh2>=0.0)
{
double d_left = enclosed._p1->distance - enclosing._p1->distance;
double d_right = enclosing._p2->distance - enclosed._p2->distance;
if (d_left < epsilon && d_right < epsilon)
{
// treat ENCLOSED as ENCLOSING.
nextItr = itr;
++nextItr;
_segments.erase(itr);
itr = nextItr;
++nextItr;
}
else if (d_left < epsilon)
{
Segment newSeg(enclosing.createPoint(enclosed._p2->distance), enclosing._p2.get());
nextItr = itr;
++nextItr;
_segments.erase(itr);
_segments.insert(newSeg);
itr = nextItr;
++nextItr;
}
else if (d_right < epsilon)
{
Segment newSeg(enclosing._p1.get(), enclosing.createPoint(enclosed._p1->distance) );
_segments.insert(newSeg);
_segments.erase(itr);
itr = _segments.find(newSeg);
nextItr = itr;
++nextItr;
}
else
{
Segment newSegLeft(enclosing._p1.get(), enclosing.createPoint(enclosed._p1->distance) );
Segment newSegRight(enclosing.createPoint(enclosed._p2->distance), enclosing._p2.get());
_segments.erase(itr);
_segments.insert(newSegLeft);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
}
else if (dh1 * dh2 < 0.0)
{
double d_left = enclosed._p1->distance - enclosing._p1->distance;
double d_right = enclosing._p2->distance - enclosed._p2->distance;
if (d_left < epsilon && d_right < epsilon)
{
// treat ENCLOSED as ENCLOSING.
if (dh1 < 0.0)
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), cp);
Segment newSegRight(cp, enclosed._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosed._p1.get(), cp);
Segment newSegRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
}
else if (d_left < epsilon)
{
if (dh1 < 0.0)
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), cp);
Segment newSegMid(cp, enclosed._p2.get());
Segment newSegRight(enclosing.createPoint(enclosed._p2->distance), enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegMid);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosed._p1.get(), cp);
Segment newSegRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
}
else if (d_right < epsilon)
{
if (dh1 < 0.0)
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), cp);
Segment newSegRight(cp, enclosed._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), enclosing.createPoint(enclosed._p1->distance));
Segment newSegMid(enclosed._p1.get(), cp);
Segment newSegRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegMid);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
}
else
{
if (dh1 < 0.0)
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), cp);
Segment newSegMid(cp, enclosed._p2.get());
Segment newSegRight(enclosing.createPoint(enclosed._p2->distance), enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegMid);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosing.createIntersectionPoint(enclosed);
Segment newSegLeft(enclosing._p1.get(), enclosing.createPoint(enclosed._p1->distance));
Segment newSegMid(enclosed._p1.get(), cp);
Segment newSegRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(newSegLeft);
_segments.insert(newSegMid);
_segments.insert(newSegRight);
itr = _segments.find(newSegLeft);
nextItr = itr;
++nextItr;
}
}
}
else
{
++nextItr;
}
break;
}
case(Segment::ENCLOSED):
{
// need to work out if lhs is below rhs or lhs is above rhs or crossing
const Segment& enclosing = *nextItr;
const Segment& enclosed = *itr;
double dh1 = enclosing.deltaHeight(*enclosed._p1);
double dh2 = enclosing.deltaHeight(*enclosed._p2);
double d_left = enclosed._p1->distance - enclosing._p1->distance;
double d_right = enclosing._p2->distance - enclosed._p2->distance;
if (d_left<=epsilon)
{
if (dh1<=epsilon && dh2<=epsilon)
{
_segments.erase(itr);
itr = nextItr;
++nextItr;
}
else if (dh1>=0.0 && dh2>=0.0)
{
_segments.insert(Segment(enclosing.createPoint(enclosed._p2->distance), enclosed._p2.get()));
_segments.erase(nextItr);
nextItr = itr;
++nextItr;
}
else if (dh1 * dh2 < 0.0)
{
if (d_right<=epsilon)
{
// enclosed and enclosing effectively overlap
if (dh1 < 0.0)
{
Point* cp = enclosed.createIntersectionPoint(enclosing);
Segment segLeft(enclosed._p1.get(), cp);
Segment segRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(segLeft);
_segments.insert(segRight);
itr = _segments.find(segLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosed.createIntersectionPoint(enclosing);
Segment segLeft(enclosing._p1.get(), cp);
Segment segRight(cp, enclosed._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(segLeft);
_segments.insert(segRight);
itr = _segments.find(segLeft);
nextItr = itr;
++nextItr;
}
}
else
{
// right hand side needs to be created
if (dh1 < 0.0)
{
Point* cp = enclosed.createIntersectionPoint(enclosing);
Segment segLeft(enclosed._p1.get(), cp);
Segment segRight(cp, enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(segLeft);
_segments.insert(segRight);
itr = _segments.find(segLeft);
nextItr = itr;
++nextItr;
}
else
{
Point* cp = enclosed.createIntersectionPoint(enclosing);
Segment segLeft(enclosing._p1.get(), cp);
Segment segMid(cp, enclosed._p2.get());
Segment segRight(enclosing.createPoint(enclosed._p2->distance), enclosing._p2.get());
_segments.erase(itr);
_segments.erase(nextItr);
_segments.insert(segLeft);
_segments.insert(segMid);
_segments.insert(segRight);
itr = _segments.find(segLeft);
nextItr = itr;
++nextItr;
}
}
}
else
{
++nextItr;
}
}
else
{
SG_LOG(SG_TERRAIN, SG_ALERT, "*** ENCLOSED: is not coincendet with left handside of ENCLOSING, case not handled, advancing.");
}
break;
}
default:
SG_LOG(SG_TERRAIN, SG_ALERT, "** Not handled, advancing");
++nextItr;
break;
}
classification = ((itr != _segments.end()) && (nextItr != _segments.end())) ? itr->compare(*nextItr) : Segment::UNCLASSIFIED;
}
// increment iterator it it's not already at end of container.
if (itr!=_segments.end()) ++itr;
}
}
unsigned int numOverlapping(SegmentSet::const_iterator startItr) const
{
if (startItr==_segments.end()) return 0;
SegmentSet::const_iterator nextItr = startItr;
++nextItr;
unsigned int num = 0;
while (nextItr!=_segments.end() && startItr->compare(*nextItr)>=Segment::OVERLAPPING)
{
++num;
++nextItr;
}
return num;
}
unsigned int totalNumOverlapping() const
{
unsigned int total = 0;
for(SegmentSet::const_iterator itr = _segments.begin();
itr != _segments.end();
++itr)
{
total += numOverlapping(itr);
}
return total;
}
void copyPoints(VPBElevationSlice::Vec3dList& intersections, VPBElevationSlice::DistanceHeightList& distanceHeightIntersections)
{
if (_segments.empty()) return;
SegmentSet::iterator prevItr = _segments.begin();
SegmentSet::iterator nextItr = prevItr;
++nextItr;
intersections.push_back( prevItr->_p1->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(prevItr->_p1->distance, prevItr->_p1->height) );
intersections.push_back( prevItr->_p2->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(prevItr->_p2->distance, prevItr->_p2->height) );
for(;
nextItr != _segments.end();
++nextItr,++prevItr)
{
Segment::Classification classification = prevItr->compare(*nextItr);
switch(classification)
{
case(Segment::SEPARATE):
{
intersections.push_back( nextItr->_p1->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p1->distance, nextItr->_p1->height) );
intersections.push_back( nextItr->_p2->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p2->distance, nextItr->_p2->height) );
break;
}
case(Segment::JOINED):
{
#if 1
intersections.push_back( nextItr->_p2->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p2->distance, nextItr->_p2->height) );
#else
intersections.push_back( nextItr->_p1->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p1->distance, nextItr->_p1->height) );
intersections.push_back( nextItr->_p2->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p2->distance, nextItr->_p2->height) );
#endif
break;
}
default:
{
intersections.push_back( nextItr->_p1->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p1->distance, nextItr->_p1->height) );
intersections.push_back( nextItr->_p2->position );
distanceHeightIntersections.push_back( VPBElevationSlice::DistanceHeight(nextItr->_p2->distance, nextItr->_p2->height) );
break;
}
}
}
}
SegmentSet _segments;
osg::ref_ptr<Point> _previousPoint;
osg::Plane _plane;
osg::ref_ptr<osg::EllipsoidModel> _em;
};
}
VPBElevationSlice::VPBElevationSlice()
{
/** Remove the osgSim::DatabaseCacheReadCallback that enables automatic loading
* of external PagedLOD tiles to ensure that the highest level of detail is used in intersections.
* This automatic loading of tiles is done by the intersection traversal that is done within
* the computeIntersections(..) method, so can result in long intersection times when external
* tiles have to be loaded.
* The external loading of tiles can be disabled by removing the read callback, this is done by
* calling the setDatabaseCacheReadCallback(DatabaseCacheReadCallback*) method with a value of 0.*/
_intersectionVisitor.setReadCallback(0);
}
void VPBElevationSlice::computeIntersections(osg::Node* scene, osg::Node::NodeMask traversalMask)
{
osg::Plane plane;
osg::Polytope boundingPolytope;
// set up the main intersection plane
osg::Vec3d planeNormal = (_endPoint - _startPoint) ^ _upVector;
planeNormal.normalize();
plane.set( planeNormal, _startPoint );
// set up the start cut off plane
osg::Vec3d startPlaneNormal = _upVector ^ planeNormal;
startPlaneNormal.normalize();
boundingPolytope.add( osg::Plane(startPlaneNormal, _startPoint) );
// set up the end cut off plane
osg::Vec3d endPlaneNormal = planeNormal ^ _upVector;
endPlaneNormal.normalize();
boundingPolytope.add( osg::Plane(endPlaneNormal, _endPoint) );
osg::ref_ptr<osgUtil::PlaneIntersector> intersector = new osgUtil::PlaneIntersector(plane, boundingPolytope);
intersector->setRecordHeightsAsAttributes(true);
_intersectionVisitor.reset();
_intersectionVisitor.setTraversalMask(traversalMask);
_intersectionVisitor.setIntersector( intersector.get() );
scene->accept(_intersectionVisitor);
osgUtil::PlaneIntersector::Intersections& intersections = intersector->getIntersections();
typedef osgUtil::PlaneIntersector::Intersection::Polyline Polyline;
typedef osgUtil::PlaneIntersector::Intersection::Attributes Attributes;
if (!intersections.empty())
{
osgUtil::PlaneIntersector::Intersections::iterator itr;
for(itr = intersections.begin();
itr != intersections.end();
++itr)
{
osgUtil::PlaneIntersector::Intersection& intersection = *itr;
if (intersection.matrix.valid())
{
// transform points on polyline
for(Polyline::iterator pitr = intersection.polyline.begin();
pitr != intersection.polyline.end();
++pitr)
{
*pitr = (*pitr) * (*intersection.matrix);
}
// matrix no longer needed.
intersection.matrix = 0;
}
}
VPBElevationSliceUtils::LineConstructor constructor;
constructor._plane = plane;
// convert into distance/height
for(itr = intersections.begin();
itr != intersections.end();
++itr)
{
osgUtil::PlaneIntersector::Intersection& intersection = *itr;
for(Polyline::iterator pitr = intersection.polyline.begin();
pitr != intersection.polyline.end();
++pitr)
{
const osg::Vec3d& v = *pitr;
osg::Vec2d delta_xy( v.x() - _startPoint.x(), v.y() - _startPoint.y());
double distance = delta_xy.length();
constructor.add( distance, v.z(), v);
}
constructor.endline();
}
// copy final results
_intersections.clear();
_distanceHeightIntersections.clear();
// constructor.report();
unsigned int numOverlapping = constructor.totalNumOverlapping();
while(numOverlapping>0)
{
unsigned int previousNumOverlapping = numOverlapping;
constructor.pruneOverlappingSegments();
// constructor.report();
numOverlapping = constructor.totalNumOverlapping();
if (previousNumOverlapping == numOverlapping) break;
}
constructor.copyPoints(_intersections, _distanceHeightIntersections);
}
else
{
SG_LOG(SG_TERRAIN, SG_DEBUG, "No intersections found.");
}
}
VPBElevationSlice::Vec3dList VPBElevationSlice::computeVPBElevationSlice(osg::Node* scene, const osg::Vec3d& startPoint, const osg::Vec3d& endPoint, const osg::Vec3d& upVector, osg::Node::NodeMask traversalMask)
{
VPBElevationSlice es;
es.setStartPoint(startPoint);
es.setEndPoint(endPoint);
es.setUpVector(upVector);
es.computeIntersections(scene, traversalMask);
return es.getIntersections();
}
}

View File

@@ -0,0 +1,88 @@
// VPBVPBElevationSlice.hxx -- VirtualPlanetBuilder Elevation Slice
//
// Copyright (C) 2021 Stuart Buchanan
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef VPBELEVATIONSLICE
#define VPBELEVATIONSLICE 1
#include <osgUtil/IntersectionVisitor>
// include so we can get access to the DatabaseCacheReadCallback
#include <osgSim/LineOfSight>
namespace simgear {
class VPBElevationSlice
{
public :
VPBElevationSlice();
/** Set the start point of the slice.*/
void setStartPoint(const osg::Vec3d& startPoint) { _startPoint = startPoint; }
/** Get the start point of the slice.*/
const osg::Vec3d& getStartPoint() const { return _startPoint; }
/** Set the end point of the slice.*/
void setEndPoint(const osg::Vec3d& endPoint) { _endPoint = endPoint; }
/** Get the end point of the slice.*/
const osg::Vec3d& getEndPoint() const { return _endPoint; }
/** Set the up Vector of the slice.*/
void setUpVector(const osg::Vec3d& upVector) { _upVector = upVector; }
/** Get the start point of the slice.*/
const osg::Vec3d& getUpVector() const { return _upVector; }
typedef std::vector<osg::Vec3d> Vec3dList;
/** Get the intersections in the form of a vector of Vec3d. */
const Vec3dList& getIntersections() const { return _intersections; }
typedef std::pair<double,double> DistanceHeight;
typedef std::vector<DistanceHeight> DistanceHeightList;
/** Get the intersections in the form a vector of pair<double,double> representing distance along the slice and height. */
const DistanceHeightList& getDistanceHeightIntersections() const { return _distanceHeightIntersections; }
/** Compute the intersections with the specified scene graph, the results are stored in vectors of Vec3d.
* Note, if the topmost node is a CoordinateSystemNode then the input points are assumed to be geocentric,
* with the up vector defined by the EllipsoidModel attached to the CoordinateSystemNode.
* If the topmost node is not a CoordinateSystemNode then a local coordinates frame is assumed, with a local up vector. */
void computeIntersections(osg::Node* scene, osg::Node::NodeMask traversalMask=0xffffffff);
/** Compute the vertical distance between the specified scene graph and a single HAT point.*/
static Vec3dList computeVPBElevationSlice(osg::Node* scene, const osg::Vec3d& startPoint, const osg::Vec3d& endPoint, const osg::Vec3d& upVector, osg::Node::NodeMask traversalMask=0xffffffff);
protected :
osg::Vec3d _startPoint;
osg::Vec3d _endPoint;
osg::Vec3d _upVector;
Vec3dList _intersections;
DistanceHeightList _distanceHeightIntersections;
osgUtil::IntersectionVisitor _intersectionVisitor;
};
}
#endif

View File

@@ -18,7 +18,6 @@
#include <cmath>
#include <osgSim/ElevationSlice>
#include <osgTerrain/TerrainTile>
#include <osgTerrain/Terrain>
@@ -44,6 +43,7 @@
#include <simgear/scene/material/Effect.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/model/model.hxx>
#include <simgear/scene/tgdb/VPBElevationSlice.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/util/SGSceneFeatures.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
@@ -134,8 +134,6 @@ void VPBTechnique::setFilterMatrixAs(FilterType filterType)
void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
{
OSG_INFO<<"Doing VPBTechnique::init()"<<std::endl;
if (!_terrainTile) return;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_writeBufferMutex);
@@ -144,26 +142,15 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
if (dirtyMask==0) return;
osgTerrain::TileID tileID = tile->getTileID();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Init of tile " << tileID.x << "," << tileID.y << " level " << tileID.level << " " << dirtyMask);
osg::ref_ptr<BufferData> buffer = new BufferData;
Locator* masterLocator = computeMasterLocator();
osg::Vec3d centerModel = computeCenterModel(*buffer, masterLocator);
// We use TerrainTile::IMAGERY_DIRTY to (re-)generate features from a .STG file, such as line features
// We use TerrainTile::ELEVATION_DIRTY to regenerate the geometry
if (dirtyMask & TerrainTile::ELEVATION_DIRTY) {
generateGeometry(*buffer, masterLocator, centerModel);
applyColorLayers(*buffer, masterLocator);
applyTrees(*buffer, masterLocator);
}
if (dirtyMask & TerrainTile::IMAGERY_DIRTY) {
applyLineFeatures(*buffer, masterLocator);
}
/*
if ((dirtyMask & TerrainTile::IMAGERY_DIRTY)==0)
{
generateGeometry(*buffer, masterLocator, centerModel);
@@ -189,7 +176,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
applyTrees(*buffer, masterLocator);
applyLineFeatures(*buffer, masterLocator);
}
*/
if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true);
if (!_currentBufferData || !assumeMultiThreaded)
@@ -837,8 +824,6 @@ void VPBTechnique::generateGeometry(BufferData& buffer, Locator* masterLocator,
if (mat) {
effectProp = new SGPropertyNode();
makeChild(effectProp, "inherits-from")->setStringValue(mat->get_effect_name());
//effectPropeffect->getChild("texture", 0, true)->getChild("image", 0, true)->setStringValue("Terrain/forest1a.png");
//SG_LOG(SG_TERRAIN, SG_ALERT, "Created VPB effect property " << mat->get_effect_name());
} else {
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get effect for VPB - no matching material in library");
effectProp = new SGPropertyNode;
@@ -1763,39 +1748,39 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator)
void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator)
{
int line_features_lod_range = 6;
unsigned int line_features_lod_range = 6;
float minWidth = 9999.9;
SGPropertyNode* propertyNode = _options->getPropertyNode().get();
const unsigned int tileLevel = _terrainTile->getTileID().level;
const SGPropertyNode* propertyNode = _options->getPropertyNode().get();
if (propertyNode) {
line_features_lod_range = propertyNode->getIntValue("/sim/rendering/static-lod/line-features-lod-range", line_features_lod_range);
const SGPropertyNode* static_lod = propertyNode->getNode("/sim/rendering/static-lod");
line_features_lod_range = static_lod->getIntValue("line-features-lod-level", line_features_lod_range);
if (static_lod->getChildren("lod-level").size() > tileLevel) {
minWidth = static_lod->getChildren("lod-level")[tileLevel]->getFloatValue("line-features-min-width", minWidth);
}
}
// Do not generate vegetation for tiles too far away
if (_terrainTile->getTileID().level < line_features_lod_range) {
if (tileLevel < line_features_lod_range) {
// Do not generate vegetation for tiles too far away
return;
}
SGMaterialLibPtr matlib = _options->getMaterialLib();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Generating roads of width > " << minWidth << " for tile LoD level " << tileLevel);
const SGMaterialLibPtr matlib = _options->getMaterialLib();
SGMaterial* mat = 0;
osg::Vec3d world;
SGGeod loc;
const osg::Vec3d world = buffer._transform->getMatrix().getTrans();
if (! matlib) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate roads");
return;
}
// Determine the center of the tile, sadly non-trivial.
osg::Vec3d tileloc = computeCenter(buffer, masterLocator);
masterLocator->convertLocalToModel(tileloc, world);
const SGVec3d world2 = SGVec3d(world.x(), world.y(), world.z());
loc = SGGeod::fromCart(world2);
const osg::Matrixd R_vert = makeZUpFrameRelative(loc);
// Get all appropriate roads. We assume that the VPB terrain tile is smaller than a Bucket size.
SGBucket bucket = SGBucket(loc);
const SGGeod loc = SGGeod::fromCart(toSG(world));
const SGBucket bucket = SGBucket(loc);
auto roads = std::find_if(_lineFeatureLists.begin(), _lineFeatureLists.end(), [bucket](BucketLineFeatureBinList b){return (b.first == bucket);});
if (roads == _lineFeatureLists.end()) return;
@@ -1803,7 +1788,7 @@ void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator)
SGMaterialCache* matcache = _options->getMaterialLib()->generateMatCache(loc, _options);
for (; roads != _lineFeatureLists.end(); ++roads) {
LineFeatureBinList roadBins = roads->second;
const LineFeatureBinList roadBins = roads->second;
for (auto rb = roadBins.begin(); rb != roadBins.end(); ++rb)
{
@@ -1826,7 +1811,7 @@ void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator)
auto lineFeatures = rb->getLineFeatures();
for (auto r = lineFeatures.begin(); r != lineFeatures.end(); ++r) {
generateLineFeature(buffer, masterLocator, *r, world, R_vert, v, t, n, xsize, ysize);
if (r->_width > minWidth) generateLineFeature(buffer, masterLocator, *r, world, v, t, n, xsize, ysize);
}
if (v->size() == 0) continue;
@@ -1854,7 +1839,7 @@ void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator)
}
}
void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature road, osg::Vec3d modelCenter, const osg::Matrixd R_vert, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize)
void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature road, osg::Vec3d modelCenter, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize)
{
if (road._nodes.size() < 2) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - LineFeatureBin::LineFeature with fewer than two nodes");
@@ -1867,7 +1852,11 @@ void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocato
auto road_iter = road._nodes.begin();
ma = *road_iter - modelCenter;
osg::Vec3d intersect = getMeshIntersection(buffer, masterLocator, ma);
// We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0)
osg::Vec3d up = modelCenter;
up.normalize();
osg::Vec3d intersect = getMeshIntersection(buffer, masterLocator, ma, up);
// If we've got an intersection, add it to the list
if (intersect != ma) roadPoints.push_back(intersect);
@@ -1877,28 +1866,22 @@ void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocato
for (; road_iter != road._nodes.end(); road_iter++) {
mb = *road_iter - modelCenter;
intersect = getMeshIntersection(buffer, masterLocator, mb);
intersect = getMeshIntersection(buffer, masterLocator, mb, up);
if (intersect != mb) roadPoints.push_back(intersect);
osgSim::ElevationSlice es;
es.setStartPoint(ma);
es.setEndPoint(mb);
es.computeIntersections(buffer._geometry);
auto esl = VPBElevationSlice::computeVPBElevationSlice(buffer._geometry, ma, mb, up);
auto esl = es.getIntersections();
for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr)
{
roadPoints.push_back(*eslitr);
for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr) {
roadPoints.push_back(*eslitr);
}
// Now traverse the next segment
ma = mb;
ma = intersect;
}
// We now have a series of points following the topography of the elevation mesh.
float xtex, ytex;
osg::Vec3d local, origin, top, start, end;
osg::Vec3d up = osg::Matrixd::inverse(R_vert) * osg::Vec3d(0,0,1);
auto iter = roadPoints.begin();
start = *iter + up;
@@ -1951,17 +1934,10 @@ void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocato
}
// Find the intersection of a given SGGeod with the terrain mesh
osg::Vec3d VPBTechnique::getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt)
osg::Vec3d VPBTechnique::getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up)
{
osg::Vec3d local, origin, top;
// Get a vertical line segment.
masterLocator->convertModelToLocal(pt, local);
masterLocator->convertLocalToModel(osg::Vec3d(local.x(), local.y(), -1000.0), origin);
masterLocator->convertLocalToModel(osg::Vec3d(local.x(), local.y(), 100000.0), top);
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector;
intersector = new osgUtil::LineSegmentIntersector(origin, top);
intersector = new osgUtil::LineSegmentIntersector(pt - up*100.0, pt + up*8000.0);
osgUtil::IntersectionVisitor visitor(intersector.get());
buffer._geometry->accept(visitor);
@@ -1969,7 +1945,7 @@ osg::Vec3d VPBTechnique::getMeshIntersection(BufferData& buffer, Locator* master
// We have an intersection with the terrain model, so return it
return intersector->getFirstIntersection().getWorldIntersectPoint();
} else {
// No intersection. Likely this point is outside our geometry. So just return the cartesian element.
// No intersection. Likely this point is outside our geometry. So just return the original element.
return pt;
}
}
@@ -2039,15 +2015,17 @@ void VPBTechnique::releaseGLObjects(osg::State* state) const
}
// Simple vistor to check for any underlying terrain meshes that intersect with a given constraint therefore may need to be modified
// (e.g elevation lowered to ensure the terrain doesn't poke through an airport mesh, orn roads generated)
// (e.g elevation lowered to ensure the terrain doesn't poke through an airport mesh, or line features generated)
class TerrainVisitor : public osg::NodeVisitor {
public:
osg::ref_ptr<osg::Node> _constraint; // Object to flatten the terrain under.
osg::ref_ptr<osg::Node> _constraint; // Object describing the volume to be modified.
int _dirtyMask; // Dirty mask to apply.
TerrainVisitor( osg::ref_ptr<osg::Node> node, int mask) :
int _minLevel; // Minimum LoD level to modify.
TerrainVisitor( osg::ref_ptr<osg::Node> node, int mask, int minLevel) :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
_constraint(node),
_dirtyMask(mask)
_dirtyMask(mask),
_minLevel(minLevel)
{ }
virtual ~TerrainVisitor()
{ }
@@ -2055,13 +2033,17 @@ class TerrainVisitor : public osg::NodeVisitor {
void apply(osg::Node& node)
{
osgTerrain::TerrainTile* tile = dynamic_cast<osgTerrain::TerrainTile*>(&node);
if (tile)
{
if (tile) {
// Determine if the constraint should affect this tile.
const int level = tile->getTileID().level;
const osg::BoundingSphere tileBB = tile->getBound();
if (tileBB.intersects(_constraint->getBound())) {
if ((level >= _minLevel) && tileBB.intersects(_constraint->getBound())) {
// Dirty any existing terrain tiles containing this constraint, which will force regeneration
osgTerrain::TileID tileID = tile->getTileID();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Setting dirty mask for tile " << tileID.x << "," << tileID.y << " level " << tileID.level << " " << _dirtyMask);
tile->setDirtyMask(_dirtyMask);
} else if (tileBB.intersects(_constraint->getBound())) {
traverse(node);
}
} else {
traverse(node);
@@ -2077,7 +2059,7 @@ void VPBTechnique::addElevationConstraint(osg::ref_ptr<osg::Node> constraint, os
const std::lock_guard<std::mutex> lock(VPBTechnique::_constraint_mutex); // Lock the _constraintGroup for this scope
_constraintGroup->addChild(constraint.get());
TerrainVisitor ftv(constraint, TerrainTile::ALL_DIRTY);
TerrainVisitor ftv(constraint, TerrainTile::ALL_DIRTY, 0);
terrain->accept(ftv);
}
@@ -2120,24 +2102,25 @@ void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadLi
// We need to trigger a re-build of the appropriate Terrain tile, so create a pretend node and run the TerrainVisitor to
// "dirty" the TerrainTile that it intersects with.
osg::ref_ptr<osg::Node> n = new osg::Node();
SGVec3d coord1, coord2;
//SGVec3d coord1, coord2;
//SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() -0.5*bucket.get_width(), bucket.get_center_lat() -0.5*bucket.get_height(), 0.0), coord1);
//SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() +0.5*bucket.get_width(), bucket.get_center_lat() +0.5*bucket.get_height(), 0.0), coord2);
//osg::BoundingBox bbox = osg::BoundingBox(toOsg(coord1), toOsg(coord2));
//n->setInitialBound(bbox);
SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() -0.5*bucket.get_width(), bucket.get_center_lat() -0.5*bucket.get_height(), 0.0), coord1);
SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() +0.5*bucket.get_width(), bucket.get_center_lat() +0.5*bucket.get_height(), 0.0), coord2);
//SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon(), bucket.get_center_lat(), 0.0), coord);
//n->setInitialBound(osg::BoundingSphere(toOsg(coord), max(bucket.get_width_m(), bucket.get_height_m())));
osg::BoundingBox bbox = osg::BoundingBox(toOsg(coord1), toOsg(coord2));
n->setInitialBound(bbox);
SGVec3d coord;
SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon(), bucket.get_center_lat(), 0.0), coord);
n->setInitialBound(osg::BoundingSphere(toOsg(coord), max(bucket.get_width_m(), bucket.get_height_m())));
SG_LOG(SG_TERRAIN, SG_DEBUG, "Adding line features to " << bucket.gen_index_str());
TerrainVisitor ftv(n, TerrainTile::IMAGERY_DIRTY);
TerrainVisitor ftv(n, TerrainTile::ALL_DIRTY, 0);
terrainNode->accept(ftv);
}
void VPBTechnique::unloadLineFeatures(SGBucket bucket)
{
SG_LOG(SG_TERRAIN, SG_ALERT, "Erasing all roads with entry " << bucket);
SG_LOG(SG_TERRAIN, SG_DEBUG, "Erasing all roads with entry " << bucket);
const std::lock_guard<std::mutex> lock(VPBTechnique::_lineFeatureLists_mutex); // Lock the _lineFeatureLists for this scope
// C++ 20...
//std::erase_if(_lineFeatureLists, [bucket](BucketLineFeatureBinList p) { return p.first == bucket; } );

View File

@@ -132,13 +132,12 @@ class VPBTechnique : public TerrainTechnique
virtual void generateLineFeature(BufferData& buffer, Locator*
masterLocator, LineFeatureBin::LineFeature road,
osg::Vec3d modelCenter,
const osg::Matrixd R_vert,
osg::Vec3Array* v,
osg::Vec2Array* t,
osg::Vec3Array* n,
unsigned int xsize,
unsigned int ysize);
virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt);
virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up);
OpenThreads::Mutex _writeBufferMutex;
osg::ref_ptr<BufferData> _currentBufferData;