cgal/Straight_skeleton_2/test/Straight_skeleton_2/test_sls.cpp

1411 lines
39 KiB
C++

// Copyright (c) 2005, 2006 Fernando Luis Cacciola Carballal. All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Fernando Cacciola <fernando_cacciola@ciudad.com.ar>
//
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <boost/tokenizer.hpp>
#include <CGAL/assertions_behaviour.h>
bool sTestInner = true ;
bool sTestOuter = true ;
bool sTestOffsets = true ;
bool sVerbose = false ;
bool sNoOp = false ;
bool sClassifyCases = false ;
bool sDumpEPS = false ;
bool sDumpDXF = false ;
bool sLogFailures = false ;
bool sAbortOnError = false ;
bool sAcceptNonSimpleInput = false ;
bool sReportNonSimpleOffset = false ;
bool sValidateGeometry = false ;
bool sDumpOffsetPolygons = false ;
bool sAlwaysTestFullSkeleton = false ;
int sMaxShift = 1 ;
int sMaxVertexCount = 0 ;
double sOffset = 0.0 ;
bool sOffsetAtNodes = false ;
size_t sOffsetCount = 0 ;
std::vector<int> sOffsetAtEntry ;
double sMaxTime = 0.0 ;
double sDX = 0.0 ;
double sDY = 0.0 ;
double sScale = 1.0 ;
double sTimeout = 0.0 ;
//#define CGAL_STRAIGHT_SKELETON_ENABLE_TRACE 4
//#define CGAL_STRAIGHT_SKELETON_TRAITS_ENABLE_TRACE
//#define CGAL_STRAIGHT_SKELETON_VALIDITY_ENABLE_TRACE
//#define CGAL_POLYGON_OFFSET_ENABLE_TRACE 3
//#define CGAL_STRAIGHT_SKELETON_PROFILING_ENABLED
bool lAppToLog = false ;
void Straight_skeleton_external_trace ( std::string m )
{
std::ofstream out("sls_log.txt", ( lAppToLog ? std::ios::app | std::ios::ate : std::ios::trunc | std::ios::ate ) );
out << std::setprecision(17) << m << std::endl << std::flush ;
lAppToLog = true ;
}
void Straight_skeleton_traits_external_trace ( std::string m )
{
std::ofstream out("sls_log.txt", ( lAppToLog ? std::ios::app | std::ios::ate : std::ios::trunc | std::ios::ate ) ) ;
out << std::setprecision(17) << m << std::endl << std::flush ;
lAppToLog = true ;
}
void error_handler ( char const* what, char const* expr, char const* file, int line, char const* msg )
{
std::cerr << "CGAL error: " << what << " violation!" << std::endl
<< "Expr: " << expr << std::endl
<< "File: " << file << std::endl
<< "Line: " << line << std::endl;
if ( msg != nullptr)
std::cerr << "Explanation:" << msg << std::endl;
if ( sAbortOnError )
std::exit(1);
throw std::runtime_error("CGAL Error");
}
typedef std::vector<std::string> FP_filter_failure_list ;
typedef std::map<std::string,FP_filter_failure_list> FP_filter_failure_map ;
typedef std::map<std::string,int> FP_filter_success_map ;
FP_filter_failure_map sPredFailureMap ;
FP_filter_failure_map sConsFailureMap ;
FP_filter_success_map sPredSuccessMap ;
FP_filter_success_map sConsSuccessMap ;
int sTotalPredFailures = 0 ;
int sTotalConsFailures = 0 ;
int sTotalPredSuccess = 0 ;
int sTotalConsSuccess = 0 ;
void register_predicate_failure ( std::string pred, std::string failure ) { sPredFailureMap[pred].push_back(failure); ++ sTotalPredFailures ; }
void register_construction_failure ( std::string cons, std::string failure ) { sConsFailureMap[cons].push_back(failure); ++ sTotalConsFailures ; }
void register_predicate_success ( std::string pred) { ++ sPredSuccessMap[pred] ; ++ sTotalPredSuccess ; }
void register_construction_success ( std::string cons) { ++ sConsSuccessMap[cons] ; ++ sTotalConsSuccess ; }
#include <CGAL/test_sls_types.h>
#include <CGAL/Straight_skeleton_2/IO/Dxf_stream.h>
typedef CGAL::Dxf_stream<IK> DxfStream ;
using namespace std ;
using namespace CGAL ;
using namespace CGAL::IO ;
inline string to_string( double n ) { ostringstream ss ; ss << n ; return ss.str(); }
inline bool is_even ( int n ) { return n % 2 == 0 ; }
struct Zone
{
Zone()
:
FullSkeletonTime(0.0)
,PartialSkeletonTime(0.0)
,ContouringTime(0.0)
{}
IRegionPtr Input ;
ISlsPtr FullSkeleton ;
ISlsPtr PartialSkeleton ;
double FullSkeletonTime ;
double PartialSkeletonTime ;
ORegion Contours ;
double ContouringTime ;
} ;
struct TestCase
{
TestCase ( string aFilename )
:
Filename(aFilename)
{
}
string Filename ;
Zone Inner ;
Zone Outer ;
} ;
const int cTimedOut = -6 ;
const int cLoadException = -5 ;
const int cFileNotFound = -4 ;
const int cLargeInputIgnored = -3 ;
const int cNonSimpleInput = -2 ;
const int cDegenerateInput = -1 ;
const int cOK = 0 ;
const int cFailed = 1 ;
const int cUnknown = 2 ;
const char* cStatusStr[] = { "Timed-out "
, "Can't load "
, "Not-found "
, "Too-large "
, "Non-simple "
, "Degenerate "
, "OK "
, "Failed "
} ;
const char* StatusToStr( int aStatus ) { return cStatusStr[ ( 6 + aStatus) % 8 ] ; }
struct Timed_out {} ;
CGAL::Real_timer sTimeoutWatchdog ;
bool sHadTimedOut ;
void start_timeout_watchdog()
{
sTimeoutWatchdog.reset();
sTimeoutWatchdog.start();
sHadTimedOut = false ;
}
void end_timeout_watchdog()
{
sTimeoutWatchdog.stop();
}
void check_timeout()
{
if ( sTimeout > 0.0 && sTimeoutWatchdog.time() > sTimeout )
{
sHadTimedOut = true ;
throw Timed_out() ;
}
}
IRegionPtr load_region( string file, int aShift, int& rStatus )
{
IRegionPtr rRegion ;
rStatus = cOK ;
try
{
ifstream in(file.c_str());
if ( in )
{
CGAL::IO::set_ascii_mode(in);
rRegion = IRegionPtr( new IRegion() ) ;
int ccb_count = 0 ;
in >> ccb_count ;
for ( int i = 0 ; i < ccb_count && in ; ++ i )
{
IPolygonPtr lPoly( new IPolygon() );
int v_count = 0 ;
in >> v_count ;
if ( sMaxVertexCount == 0 || ( v_count <= sMaxVertexCount ) )
{
for ( int j = 0 ; j < v_count && in ; ++ j )
{
double x = 0.0, y = 0.0 ;
in >> x >> y ;
if ( in )
{
x += sDX ;
y += sDY ;
x *= sScale ;
y *= sScale ;
lPoly->push_back( IPoint(x,y) ) ;
}
}
if ( lPoly->size() >= 3 )
{
bool lIsSimple = is_simple_2(lPoly->begin(),lPoly->end(),IK());
if ( sAcceptNonSimpleInput || ( !sAcceptNonSimpleInput && lIsSimple ) )
{
Orientation expected = ( i == 0 ? COUNTERCLOCKWISE : CLOCKWISE ) ;
double area = to_double(polygon_area_2(lPoly->begin(),lPoly->end(),IK()));
Orientation orientation = area > 0 ? CGAL::COUNTERCLOCKWISE : area < 0 ? CGAL::CLOCKWISE : CGAL::COLLINEAR ;
if ( aShift > 0 )
std::rotate(lPoly->begin(),lPoly->begin()+aShift,lPoly->end());
if ( orientation == expected )
rRegion->push_back(lPoly);
else rRegion->push_back( IPolygonPtr( new IPolygon(lPoly->rbegin(),lPoly->rend()) ) ) ;
}
else
{
rStatus = cNonSimpleInput ;
}
}
else
{
rStatus = cDegenerateInput ;
rRegion = IRegionPtr();
break;
}
}
else
{
rStatus = cLargeInputIgnored ;
}
}
}
else
{
rStatus = cFileNotFound ;
}
}
catch ( exception const& x )
{
rStatus = cLoadException ;
cerr << "std::exception caught loading file: " << x.what() << endl ;
}
catch ( ... )
{
rStatus = cLoadException ;
cerr << "Unhandled exception loading file." << endl ;
}
if ( rRegion && rRegion->size() == 0 )
{
rStatus = cDegenerateInput ;
rRegion = IRegionPtr();
}
return rRegion ;
}
void update_bbox ( IRegionPtr const& aRegion, std::optional<Bbox_2>& rBBox )
{
if ( aRegion )
{
for ( IRegion::const_iterator bit = aRegion->begin() ; bit != aRegion->end() ; ++ bit )
{
for( IPolygon::const_iterator vit = (*bit)->begin(); vit != (*bit)->end(); ++vit)
{
Bbox_2 lVBBox = vit->bbox() ;
if( !rBBox )
rBBox = lVBBox ;
else rBBox = lVBBox + *rBBox ;
}
}
}
}
template<class Region>
void dump_region_to_eps( Region const& aRegion, const char* aType, double aScale, ostream& rOut )
{
typedef typename Region::value_type PolygonPtr ;
typedef typename PolygonPtr::element_type Polygon ;
typedef typename Region::const_iterator boundary_const_iterator ;
typedef typename Polygon::const_iterator vertex_const_iterator ;
for ( boundary_const_iterator bit = aRegion.begin() ; bit != aRegion.end() ; ++ bit )
{
vertex_const_iterator beg = (*bit)->begin();
vertex_const_iterator end = (*bit)->end ();
vertex_const_iterator last = end - 1 ;
for( vertex_const_iterator curr = beg ; curr != end ; ++ curr )
{
vertex_const_iterator next = curr == last ? beg : curr + 1 ;
rOut << aType << endl
<< aScale * curr->x()
<< " "
<< aScale * curr->y()
<< " "
<< aScale * next->x()
<< " "
<< aScale * next->y()
<< " E\n";
}
}
}
void dump_skeleton_to_eps( ISls const& aSkeleton, double aScale, ostream& rOut )
{
for(Halfedge_const_iterator hit = aSkeleton.halfedges_begin(); hit != aSkeleton.halfedges_end(); ++hit)
{
Halfedge_const_handle h = hit ;
if( h->is_bisector() && is_even(h->id()) && !h->has_infinite_time() && !h->opposite()->has_infinite_time() )
{
rOut << "skel" << endl
<< aScale * h->vertex()->point().x()
<< " "
<< aScale * h->vertex()->point().y()
<< " "
<< aScale * h->opposite()->vertex()->point().x()
<< " "
<< aScale * h->opposite()->vertex()->point().y()
<< " E\n";
}
}
}
string change_extension ( string aFilename, string aNewExt )
{
string::size_type pos = aFilename.find_last_of('.');
if ( pos != string::npos )
return aFilename.replace(pos,aFilename.length()-pos,aNewExt);
else return aFilename + aNewExt ;
}
void dump_to_eps ( TestCase const& aCase )
{
std::optional<Bbox_2> lBBox ;
update_bbox(aCase.Inner.Input, lBBox ) ;
update_bbox(aCase.Outer.Input, lBBox ) ;
if ( lBBox )
{
double lScale = 1000 / (lBBox->xmax() - lBBox->xmin()) ;
if ( lScale < 1 )
lScale = 1 ;
string lEpsName = change_extension(aCase.Filename,".eps") ;
std::ofstream lOut( lEpsName.c_str() );
if ( lOut )
{
if ( sVerbose )
cout << " Dumping results to " << lEpsName << endl ;
lOut << "%!PS-Adobe-2.0 EPSF-2.0\n%%BoundingBox:"
<< static_cast<int>(std::floor(lScale* lBBox->xmin()-1))
<< " "
<< static_cast<int>(std::floor(lScale* lBBox->ymin()-1))
<< " "
<< static_cast<int>(std::ceil(lScale*lBBox->xmax()+1))
<< " "
<< static_cast<int>(std::ceil(lScale*lBBox->ymax()+1))
<< std::endl;
lOut << "%%EndComments\n"
"gsave\n"
"1.0 setlinewidth\n"
"/border { 0 1 0 setrgbcolor } bind def\n"
"/border_w { 0.1 setlinewidth } bind def\n"
"/cont { 0 0 0 setrgbcolor } bind def\n"
"/cont_w { 0.1 setlinewidth } bind def\n"
"/skel { 1 0 0 setrgbcolor } bind def\n"
"/skel_w { 1.0 setlinewidth } bind def\n"
"% stroke - x1 y1 x2 y2 E\n"
"/E {newpath moveto lineto stroke} bind def\n"
<< std::endl;
if ( aCase.Inner.Input )
dump_region_to_eps(*aCase.Inner.Input,"border",lScale,lOut);
if ( aCase.Outer.Input )
dump_region_to_eps(*aCase.Inner.Input,"border",lScale,lOut);
if ( aCase.Inner.PartialSkeleton )
dump_skeleton_to_eps(*aCase.Inner.PartialSkeleton,lScale,lOut);
if ( aCase.Outer.PartialSkeleton )
dump_skeleton_to_eps(*aCase.Outer.PartialSkeleton,lScale,lOut);
dump_region_to_eps(aCase.Inner.Contours,"cont",lScale,lOut);
dump_region_to_eps(aCase.Outer.Contours,"cont",lScale,lOut);
lOut << "grestore\nshowpage" << std::endl;
}
}
}
template<class Polygon>
void dump_polygon_to_dxf( Polygon const& aPolygon, IO::Color aColor, string aLayer, DxfStream& rDXF )
{
rDXF << aColor << Dxf_layer(aLayer) ;
rDXF.write_closed_segment_chain_2(aPolygon.begin(),aPolygon.end()) ;
}
template<class Region>
void dump_region_to_dxf( Region const& aRegion, IO::Color aColor, string aBaseLayer, DxfStream& rDXF )
{
int lN = 0 ;
for ( typename Region::const_iterator bit = aRegion.begin() ; bit != aRegion.end() ; ++ bit )
{
ostringstream ss ; ss << aBaseLayer << "_" << lN ;
string lLayer = ss.str();
dump_polygon_to_dxf(**bit,aColor,lLayer,rDXF);
++ lN ;
}
}
void dump_skeleton_to_dxf( ISls const& aSkeleton
, IO::Color aContourBisectorColor
, IO::Color aSkeletonBisectorColor
, IO::Color aPeakBisectorColor
, IO::Color /*aInfiniteBisectorColor*/
, string aLayer
, DxfStream& rDXF
)
{
rDXF << Dxf_layer(aLayer) ;
for(Halfedge_const_iterator hit = aSkeleton.halfedges_begin(); hit != aSkeleton.halfedges_end(); ++hit)
{
Halfedge_const_handle h = hit ;
if ( handle_assigned(h->opposite())
&& handle_assigned(h->vertex())
&& handle_assigned(h->opposite()->vertex())
)
{
if( h->is_bisector() && is_even(h->id()) && !h->has_infinite_time() && !h->opposite()->has_infinite_time() )
{
if ( h->is_inner_bisector() )
{
if ( h->slope() == ZERO )
{
rDXF << aPeakBisectorColor ;
}
else
{
rDXF << aSkeletonBisectorColor ;
}
}
else
{
rDXF << aContourBisectorColor ;
}
rDXF << ISegment( h->vertex()->point(), h->opposite()->vertex()->point() ) ;
}
}
}
}
void dump_to_dxf ( TestCase const& aCase )
{
string lDxfName = change_extension(aCase.Filename,".dxf") ;
std::ofstream lDxfFile( lDxfName.c_str() );
if ( lDxfFile )
{
if ( sVerbose )
cout << " Dumping results to " << lDxfName << endl ;
DxfStream lDxf(lDxfFile);
if ( aCase.Inner.Input )
{
if ( sVerbose )
cout << " Dumping input region. " << endl ;
dump_region_to_dxf(*aCase.Inner.Input,blue(),"Input",lDxf);
}
if ( aCase.Inner.PartialSkeleton )
{
if ( sVerbose )
cout << " Dumping inner skeleton." << endl ;
dump_skeleton_to_dxf(*aCase.Inner.PartialSkeleton,yellow(),green(),purple(),gray(),"InnerSkeleton",lDxf);
}
if ( aCase.Outer.PartialSkeleton )
{
if ( sVerbose )
cout << " Dumping outer skeleton." << endl ;
dump_skeleton_to_dxf(*aCase.Outer.PartialSkeleton,yellow(),green(),purple(),gray(),"OuterSkeleton",lDxf);
}
dump_region_to_dxf(aCase.Inner.Contours,gray(),"InnerOffset",lDxf);
dump_region_to_dxf(aCase.Outer.Contours,gray(),"OuterOffset",lDxf);
}
}
IPolygonPtr create_outer_frame ( IPolygon const& aOuter )
{
IPolygonPtr rFrame ;
try
{
Bbox_2 lBbox = bbox_2(aOuter.begin(),aOuter.end());
double w = lBbox.xmax() - lBbox.xmin();
double h = lBbox.ymax() - lBbox.ymin();
double s = std::sqrt(w*w+h*h);
IFT lOffset = s * 0.3 ;
std::optional<IFT> lOptMargin = compute_outer_frame_margin(aOuter.begin(),aOuter.end(),lOffset) ;
if ( lOptMargin )
{
double lMargin = to_double(*lOptMargin);
double flx = lBbox.xmin() - lMargin ;
double fhx = lBbox.xmax() + lMargin ;
double fly = lBbox.ymin() - lMargin ;
double fhy = lBbox.ymax() + lMargin ;
rFrame = IPolygonPtr( new IPolygon() ) ;
rFrame->push_back( IPoint(IFT(flx),IFT(fly)) );
rFrame->push_back( IPoint(IFT(fhx),IFT(fly)) );
rFrame->push_back( IPoint(IFT(fhx),IFT(fhy)) );
rFrame->push_back( IPoint(IFT(flx),IFT(fhy)) );
}
else
{
if ( sVerbose )
cout << " Unable to calculate outer margin for offset " << lOffset << endl ;
}
}
catch ( exception const& x )
{
if ( sVerbose )
cout << " Failed calculating outer frame margin: std::exception caught: " << x.what() << endl ;
}
catch ( ... )
{
if ( sVerbose )
cout << " Failed calculating outer frame margin: Unhandled exception." << endl ;
}
return rFrame ;
}
template<class Region, class Point>
bool is_point_inside_region( Region const& aRegion, Point const& aP )
{
bool rR = true ;
typedef typename Region::value_type PolygonPtr ;
typedef typename PolygonPtr::element_type Polygon ;
for ( typename Region::const_iterator bit = aRegion.begin() ; bit != aRegion.end() && rR ; ++ bit )
{
Polygon const& lPoly = **bit ;
if ( oriented_side_2(lPoly.begin(),lPoly.end(),aP) == ON_NEGATIVE_SIDE )
rR = false ;
}
return rR ;
}
bool is_skeleton_valid( IRegion const& aRegion, ISls const& aSkeleton, bool is_partial )
{
bool rValid = aSkeleton.is_valid(is_partial) ;
if ( !rValid )
{
if ( sVerbose )
cout << " Failed: Skeleton invalid.\n" ;
}
else
{
if( sValidateGeometry )
{
for(Vertex_const_iterator vit = aSkeleton.vertices_begin(); vit != aSkeleton.vertices_end() && rValid ; ++vit)
{
Vertex_const_handle v = vit ;
if ( v->is_skeleton() && ! v->has_infinite_time() )
{
if ( !is_point_inside_region(aRegion,v->point()) )
{
rValid = false ;
if ( sVerbose )
cout << " Failed: skeleton node misplaced.\n" ;
}
}
}
}
}
return rValid ;
}
int create_skeleton ( Zone& rZone, std::optional<IFT> const& aMaxTime = std::optional<IFT>() )
{
int rStatus = cUnknown ;
Real_timer t ;
t.start();
ISlsPtr lSls ;
ISlsBuilderVisitor lWatchdog(check_timeout);
try
{
if ( sVerbose )
cout << " Building " << ( aMaxTime ? "partial" : "full" ) << " straight skeleton." << endl ;
ISlsBuilderTraits lTraits ;
ISlsBuilder builder(aMaxTime, lTraits, lWatchdog) ;
for( IRegion::const_iterator bit = rZone.Input->begin(), ebit = rZone.Input->end() ; bit != ebit ; ++ bit )
builder.enter_contour((*bit)->begin(),(*bit)->end());
lSls = builder.construct_skeleton(false) ;
}
catch ( exception const& x )
{
rStatus = cFailed ;
if ( sVerbose )
cout << " Failed: std::exception caught: " << x.what() << endl ;
}
catch ( ... )
{
rStatus = cFailed ;
if ( sVerbose )
cout << " Failed: Unhandled exception." << endl ;
}
t.stop();
if ( sHadTimedOut )
rStatus = cTimedOut ;
if ( rStatus == cUnknown )
rStatus = lSls && is_skeleton_valid(*rZone.Input,*lSls, (bool) aMaxTime) ? cOK : cFailed ;
double lEllapsedTime = t.time();
if ( aMaxTime )
{
rZone.PartialSkeleton = lSls ;
rZone.PartialSkeletonTime = lEllapsedTime ;
}
else
{
rZone.FullSkeleton = lSls ;
rZone.FullSkeletonTime = lEllapsedTime ;
}
return rStatus ;
}
int test_zone ( Zone& rZone )
{
ISlsPtr lFullSls, lPar ;
bool lUseFullSkeleton = sMaxTime == 0 || sAlwaysTestFullSkeleton ;
int rStatus = lUseFullSkeleton ? create_skeleton(rZone) : cOK ;
if ( sMaxTime > 0 )
{
std::optional<IFT> lMaxTime = static_cast<IFT>(sMaxTime) ;
rStatus = create_skeleton(rZone,lMaxTime) ;
}
else
{
rZone.PartialSkeleton = rZone.FullSkeleton ;
rZone.PartialSkeletonTime = rZone.FullSkeletonTime ;
}
if ( rZone.PartialSkeleton )
{
if ( rStatus == cOK && sTestOffsets )
{
set<double> lTimes ;
double lMaxTime = 999999999 ;
for ( Vertex_const_iterator vit = rZone.PartialSkeleton->vertices_begin()
,evit = rZone.PartialSkeleton->vertices_end ()
; vit != evit
; ++ vit
)
{
Vertex_const_handle v = vit ;
if ( v->is_skeleton() )
{
double lTime = to_double(v->time());
lTimes.insert(lTime);
if ( lTime > lMaxTime )
lMaxTime = lTime ;
}
}
if ( sVerbose )
cout << " Max offset time: " << lMaxTime << endl ;
vector<double> lOffsets ;
if ( sOffsetAtNodes )
{
if ( sOffsetAtEntry.size() > 0 )
{
std::ptrdiff_t lSize = std::distance(lTimes.begin(),lTimes.end());
for ( std::vector<int>::const_iterator oi = sOffsetAtEntry.begin() ; oi != sOffsetAtEntry.end() ; ++ oi )
{
int lEntry = *oi ;
if ( lEntry < lSize )
{
set<double>::const_iterator it = lTimes.begin();
std::advance(it,lEntry);
lOffsets.push_back(*it);
}
}
}
else
{
copy(lTimes.begin(),lTimes.end(), back_inserter(lOffsets) ) ;
}
}
else if ( sOffset > 0.0 )
{
double lOffset = sOffset ;
for ( size_t i = 0 ; i < sOffsetCount && lOffset < lMaxTime ; ++ i )
{
lOffsets.push_back(lOffset);
lOffset += sOffset ;
}
}
if ( lOffsets.size() > 0 )
{
double lAccTime = 0.0 ;
int lNumContours = 0 ;
SlsConverter CvtSls ;
OSlsPtr lOSkeleton = CvtSls(*rZone.PartialSkeleton) ;
assert( lOSkeleton->is_valid(true) ) ;
IOffsetBuilderVisitor lWatchdog(check_timeout);
OffsetBuilderTraits lTraits ;
OffsetBuilder lOffsetBuilder(*lOSkeleton,lTraits,lWatchdog);
for ( vector<double>::const_iterator oit = lOffsets.begin() ; oit != lOffsets.end() ; ++ oit )
{
double lOffset = *oit ;
if ( lOffset > 1e-10 )
{
if ( sVerbose )
cout << " Building offset contours at " << lOffset << endl ;
ORegion lContours ;
Real_timer t2 ;
t2.start();
try
{
lOffsetBuilder.construct_offset_contours(OFT(lOffset), std::back_inserter(lContours) );
}
catch ( exception const& x )
{
rStatus = cFailed ;
if ( sVerbose )
cout << " Failed: std::exception caught: " << x.what() << endl ;
}
catch ( ... )
{
rStatus = cFailed ;
if ( sVerbose )
cout << " Failed: Unhandled exception." << endl ;
}
t2.stop();
if ( sVerbose )
{
cout << " " << lContours.size() << " contours built." << endl ;
for ( ORegion::const_iterator bit = lContours.begin() ; bit != lContours.end() ; ++ bit )
{
OPolygonPtr lBdry = *bit ;
cout << " " << lBdry->size() << " vertices." << endl ;
if ( sDumpOffsetPolygons )
{
cout << " " ;
for ( OPolygon::const_iterator vit = lBdry->begin() ; vit != lBdry->end() ; ++ vit )
cout << "(" << vit->x() << "," << vit->y() << ") " ;
cout << endl ;
}
}
}
if ( sDumpOffsetPolygons)
{
for ( ORegion::const_iterator bit = lContours.begin() ; bit != lContours.end() ; ++ bit )
{
OPolygonPtr lBdry = *bit ;
cout << " " ;
for ( OPolygon::const_iterator vit = lBdry->begin() ; vit != lBdry->end() ; ++ vit )
cout << "(" << vit->x() << "," << vit->y() << ") " ;
cout << endl ;
}
}
if ( sReportNonSimpleOffset )
{
for ( ORegion::const_iterator bit = lContours.begin() ; bit != lContours.end() ; ++ bit )
{
if ( !is_simple_2((*bit)->begin(),(*bit)->end(),OK()) )
{
cout << " Warning: Non-simple offset polygon # "
<< ( bit - lContours.begin())
<< " generated at offset: "
<< lOffset
<< " (#" << ( oit - lOffsets.begin() ) << ")"
<< endl ;
}
}
}
copy(lContours.begin(),lContours.end(),std::back_inserter(rZone.Contours));
lAccTime += t2.time();
++ lNumContours ;
}
}
if ( sHadTimedOut )
rStatus = cTimedOut ;
if ( lNumContours > 0 )
rZone.ContouringTime = lAccTime / double(lNumContours) ;
}
}
}
else
{
if ( sVerbose )
cout << " Failed." << endl ;
}
return rStatus ;
}
int test( TestCase& rCase, int aShift )
{
int rR = 0 ;
start_timeout_watchdog();
if ( sVerbose )
cout << "Testing " << rCase.Filename << "(" << aShift << ")" << endl ;
int lStatus = cLoadException ;
IRegionPtr lInnerRegion = load_region(rCase.Filename,aShift,lStatus);
if ( lStatus == cOK )
{
if ( lInnerRegion )
{
rCase.Inner.Input = lInnerRegion ;
if ( !sNoOp )
{
if ( sTestInner )
{
if ( sVerbose )
cout << " Testing inner zone" << endl ;
lStatus = test_zone(rCase.Inner) ;
}
if ( sTestOuter )
{
if ( sVerbose )
cout << " Testing outer zone" << endl ;
IPolygonPtr lOuterPoly = lInnerRegion->front();
IPolygonPtr lFrame = create_outer_frame(*lOuterPoly);
if ( lFrame )
{
rCase.Outer.Input = IRegionPtr ( new IRegion ) ;
rCase.Outer.Input->push_back(lFrame);
rCase.Outer.Input->push_back( IPolygonPtr( new IPolygon(lOuterPoly->rbegin(),lOuterPoly->rend()) ) ) ;
int lOuterStatus = test_zone( rCase.Outer ) ;
if ( lStatus == cOK )
lStatus = lOuterStatus ;
}
}
}
rR = lStatus == cOK ? 1 : lStatus == cFailed ? -1 : 0 ;
if ( sVerbose )
{
cout << " FullInnerSlsTime=" << rCase.Inner.FullSkeletonTime
<< " PartialInnerSlsTime=" << rCase.Inner.PartialSkeletonTime
<< " InnerOffTime=" << rCase.Inner.ContouringTime
<< " FullOuterSlsTime=" << rCase.Outer.FullSkeletonTime
<< " PartialOuterSlsTime=" << rCase.Outer.PartialSkeletonTime
<< " OuterOffTime=" << rCase.Outer.ContouringTime
<< endl ;
}
if ( sClassifyCases )
{
if ( rR > 0 )
{
ofstream ok_cases("./test_sls_ok_cases.txt", ios::app | ios::ate );
ok_cases << rCase.Filename << endl ;
}
else if ( rR < 0 )
{
ofstream failed_cases("./test_sls_failed_cases.txt", ios::app | ios::ate );
failed_cases << rCase.Filename << endl ;
}
else
{
ofstream failed_cases("./test_sls_invalid_cases.txt", ios::app | ios::ate );
failed_cases << rCase.Filename << endl ;
}
}
if ( sDumpEPS )
dump_to_eps(rCase);
if ( sDumpDXF)
dump_to_dxf(rCase);
}
}
cerr << StatusToStr(lStatus) << rCase.Filename ;
if ( aShift > 0 )
cerr << "(" << aShift << ")" ;
if ( rR > 0 )
{
cerr << " ( "
<< ( rCase.Inner.FullSkeletonTime
+ rCase.Inner.PartialSkeletonTime
+ rCase.Inner.ContouringTime
+ rCase.Outer.FullSkeletonTime
+ rCase.Outer.PartialSkeletonTime
+ rCase.Outer.ContouringTime
)
<< " secs) " ;
}
cerr << endl ;
end_timeout_watchdog();
return rR ;
}
int test( TestCase& rCase )
{
int rR = 0 ;
for ( int s = 0 ; s < sMaxShift ; ++ s )
rR += test(rCase,s) ;
return rR ;
}
int main( int argc, char const* argv[] )
{
cout << "Straight skeleton test program" << endl ;
cout << setprecision(17);
cerr << setprecision(17);
CGAL::set_error_handler (error_handler);
CGAL::set_warning_handler(error_handler);
int lExitCode = 0 ;
bool lPrintUsage = true ;
string lFolder ;
vector<string> args ;
string rspline ;
for ( int aidx = 1 ; aidx < argc ; ++ aidx )
{
string arg(argv[aidx]);
if ( arg[0] == '@' )
{
ifstream response(&arg[1]);
while ( getline(response,rspline) )
if ( !rspline.empty() )
args.push_back(rspline);
}
else
{
args.push_back(arg);
}
}
bool lDoNotUsePartialSkeleton = false ;
for( vector<string>::const_iterator ait = args.begin() ; ait != args.end() ; ++ ait )
{
string arg = *ait ;
switch ( arg[0] )
{
case '-' :
{
if ( arg == "--full_skeleton" )
{
sAlwaysTestFullSkeleton = true ;
cout << "ALways testing full skeleton even if partial skeleton is used when offsetting." << endl ;
break ;
}
switch(arg[1])
{
case 'e' : sDumpEPS = true ; break ;
case 'd' : sDumpDXF = true ; break ;
case 'v' : sVerbose = true ; break ;
case 'l' : sDumpOffsetPolygons = true ; break ;
case 'a' : sAbortOnError = true ; break ;
case 'p' : sAcceptNonSimpleInput = true ; break ;
case 'n' : sNoOp = true ; break ;
case 'g' : sValidateGeometry = true ; break ;
case 'L' : sClassifyCases = true ; break ;
case 'r' :
if ( arg.length() > 2 )
sMaxShift = atoi(&arg[2]) ;
break ;
case 'm' :
if ( arg.length() > 2 )
sMaxVertexCount = atoi(&arg[2]) ;
break ;
case 'x' :
if ( arg.length() > 2 )
sDX = atof(&arg[2]) ;
break ;
case 'y' :
if ( arg.length() > 2 )
sDY = atof(&arg[2]) ;
break ;
case 's' :
if ( arg.length() > 2 )
sScale = atof(&arg[2]) ;
break ;
case 't' :
if ( arg.length() > 2 )
sTimeout = atof(&arg[2]) ;
break ;
case 'f' :
if ( arg.length() > 2 )
lFolder = string(&arg[2]) ;
break ;
case 'o' :
if ( arg.length() > 2 )
{
string sopt(&arg[2]);
if ( sopt == "!" )
{
sTestOffsets = false ;
cout << "Offsetting disabled." << endl ;
}
else
{
if ( sopt[0] == '#' )
{
sOffsetAtNodes = true ;
if ( sopt.length() > 1 )
{
cout << "Offsseting at nodes set. Using entries " ;
boost::tokenizer<> tok(sopt);
for( boost::tokenizer<>::iterator beg=tok.begin(); beg!=tok.end();++beg)
{
int lEntry = atoi(beg->c_str());
sOffsetAtEntry.push_back(lEntry) ;
cout << lEntry << " " ;
}
cout << endl ;
}
else
{
sOffsetAtEntry.clear() ;
cout << "Offsseting at nodes set until full depth" << endl ;
}
}
else
{
sOffsetAtNodes = false ;
string::size_type pos = sopt.find_first_of('x');
string lOffVal ;
string lOffCount ;
if ( pos != string::npos )
{
string soptl = sopt.substr(0,pos) ;
string soptr = sopt.substr(pos+1) ;
sOffset = atof(soptl.c_str()) ;
if ( soptr != "*" )
{
sOffsetCount = strtoul(soptr.c_str(),nullptr,10) ;
cout << "Repeared Offset set at " << sOffset << " " << sOffsetCount << " times." << endl ;
}
else
{
sOffsetCount = (std::numeric_limits<size_t>::max)() ;
cout << "Repeared Offset set at " << sOffset << " until full depth" << endl ;
}
}
else
{
sOffset = atof(sopt.c_str()) ;
sOffsetCount = 1 ;
cout << "Single Offset set at " << sOffset << endl ;
}
sMaxTime = sOffset * sOffsetCount ;
}
}
}
break ;
default: cerr << "Invalid option: " << arg << endl ; break ;
}
}
break ;
case '~' :
{
if ( arg.length() > 1 )
{
switch(arg[1])
{
case 'i' :
cout << "Not testing inside the polygon" << endl ;
sTestInner = false ;
break ;
case 'o' :
cout << "Not testing outside the polygon" << endl ;
sTestOuter = false ;
break ;
case 'p' :
lDoNotUsePartialSkeleton = true ;
break ;
}
}
}
break ;
}
}
if ( lDoNotUsePartialSkeleton )
sMaxTime = 0 ;
if ( sMaxTime > 0 )
cout << "Creating partial skeleton at max depth: " << sMaxTime << endl ;
if ( sVerbose )
cout << "Verbose mode is ON" << endl ;
if ( sDumpDXF )
cout << "Dump to DXF is ON" << endl ;
if ( sDumpEPS )
cout << "Dump to EPS is ON" << endl ;
if ( sAbortOnError )
cout << "Abort on first error is ON" << endl ;
if ( sMaxShift > 1 )
cout << "Each polygon vertex sequence will be rotated up to " << sMaxShift << " times" << endl ;
if ( sMaxVertexCount > 0 )
cout << "Polygons with more than " << sMaxVertexCount << " will be ignored" << endl ;
if ( sDX != 0.0 )
cout << "DeltaX=" << sDX << endl ;
if ( sDY != 0.0 )
cout << "DeltaY=" << sDY << endl ;
if ( sScale != 1.0 )
cout << "Scale=" << sScale << endl ;
if ( lFolder != "" )
cout << "Input folder is " << lFolder << endl ;
vector<TestCase> lCases ;
for( vector<string>::const_iterator ait = args.begin() ; ait != args.end() ; ++ ait )
{
string arg = *ait ;
if ( arg.length() > 1 )
if ( arg.find(".poly") != string::npos )
lCases.push_back( TestCase( lFolder + arg ) );
}
if ( lCases.size() > 0 )
{
int lOK_cases = 0 ;
int lFailed_cases = 0 ;
lPrintUsage = false ;
for ( vector<TestCase>::iterator it = lCases.begin() ; it != lCases.end(); ++ it )
{
int lR = test(*it);
if ( lR > 0 )
{
++ lOK_cases ;
}
else if ( lR < 0 )
{
++ lFailed_cases ;
lExitCode = 1 ;
if ( sAbortOnError )
break ;
}
}
cout << "OK cases: " << lOK_cases << endl ;
cout << "Failed cases: " << lFailed_cases << endl ;
cout << endl ;
int lTotalPred = sTotalPredFailures + sTotalPredSuccess ;
int lTotalCons = sTotalConsFailures + sTotalConsSuccess ;
if ( lTotalPred > 0 && lTotalCons > 0 )
{
cout << "Floating-point filter statistics:" << endl << endl ;
cout << " " << sTotalPredFailures << " predicate failures (" << ( sTotalPredFailures / lTotalPred * 100.0 ) << "%)" << endl ;
cout << " " << sTotalPredSuccess << " predicate successes " << endl ;
cout << " " << sTotalConsFailures << " construction failures (" << ( sTotalConsFailures / lTotalCons * 100.0 ) << "%)" << endl ;
cout << " " << sTotalConsSuccess << " construction successes " << endl ;
cout << " " << (sTotalPredFailures + sTotalConsFailures ) << " total failures ("
<< ( (sTotalPredFailures + sTotalConsFailures ) / (lTotalPred + lTotalCons) * 100.0 ) << "%)" << endl ;
cout << " Details..." << endl ;
for ( FP_filter_failure_map::iterator it = sPredFailureMap.begin(); it != sPredFailureMap.end() ; ++ it )
cout << " " << it->first << " : " << it->second.size() << " failures." << endl ;
for ( FP_filter_success_map::iterator it = sPredSuccessMap.begin(); it != sPredSuccessMap.end() ; ++ it )
cout << " " << it->first << " : " << it->second << " successes." << endl ;
for ( FP_filter_failure_map::iterator it = sConsFailureMap.begin(); it != sConsFailureMap.end() ; ++ it )
cout << " " << it->first << " : " << it->second.size() << " failures." << endl ;
for ( FP_filter_success_map::iterator it = sConsSuccessMap.begin(); it != sConsSuccessMap.end() ; ++ it )
cout << " " << it->first << " : " << it->second << " successes." << endl ;
}
}
if ( lPrintUsage )
{
cout << "USAGE: test_sls <options> file0 file1 ... fileN @response_file" << endl
<< endl
<< " <options>: " << endl
<< endl
<< " -o<offset-choice>" << endl
<< " ! Disabled" << endl
<< " # At every node." << endl
<< " #N At the offset number N in a set of all node offsets." << endl
<< " D Just one offset at distance 'D' via partial skeleton." << endl
<< " DxS 'S' evenly space offsets separated distance 'D' via partial skeleton." << endl
<< " Dx* Evenly spaced offsets separated distance 'D' via partial skeleton." << endl
<< endl
<< " -tSecs Aborts a test case if it hasn't finished within 'Secs' seconds." << endl
<< " -a Abort on first error." << endl
<< " -p Permissive mode. Accept non-simple input polygons." << endl
<< " -e Dumps result into an .eps file." << endl
<< " -d Dumps result into an .dxf file." << endl
<< " -v Verbose log." << endl
<< " -l Dump offset polygons." << endl
<< " -n No-op mode. Doesn't create skeletons." << endl
<< " -g Validate results geometrically and not just topologically." << endl
<< " -fPATH Append PATH to each filename" << endl
<< " -rMAX Rotate input vertex sequence by one vertex up to MAX times" << endl
<< " -mMAX Ignore polygons with a vertex count greater than MAX" << endl
<< " -L list of OK and failed cases on (separated) text files" << endl
<< endl
<< " --full_skeleton Create, verify and discard full skeleton even if partial skeleton will be used (for offsetting)." << endl
<< endl
<< " ~i Do not test the polygon inside." << endl
<< " ~o Do not test the polygon outside." << endl
<< " ~p Do not use partial skeletons for offsetting, use full skeleton instead." << endl
<< endl
<< " -xDX Translates the polygons by DX (horiontally)." << endl
<< " -yDY Translates the polygons by DY (vertically)." << endl
<< " -sSCALE Scales the polygon by SCALE." << endl
<< endl ;
}
return lExitCode ;
}