Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods to determine orientation of PolyLine #1411

Merged
merged 1 commit into from Oct 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/cinder/PolyLine.h
Expand Up @@ -51,6 +51,11 @@ class PolyLineT {
void setClosed( bool aClosed = true ) { mClosed = aClosed; }
bool isClosed() const { return mClosed; }

//! Returns \c true if PolyLine is clockwise-oriented. If \a isColinear is non-null, it receives \c true if all points are colinear
bool isClockwise( bool *isColinear = nullptr ) const;
//! Returns \c true if PolyLine is counterclockwise-oriented. If \a isColinear is non-null, it receives \c true if all points are colinear
bool isCounterClockwise( bool *isColinear = nullptr ) const;

T getPosition( float t ) const;
T getDerivative( float t ) const;

Expand Down
45 changes: 45 additions & 0 deletions src/cinder/PolyLine.cpp
Expand Up @@ -29,6 +29,51 @@

namespace cinder {


template<typename T>
bool PolyLineT<T>::isClockwise( bool *isColinear ) const
{
if( mPoints.size() < 3 ) {
if( isColinear != nullptr ) *isColinear = true;
return false;
}

size_t last = mPoints.size() - 1;
// If the first and last point are the same (on a closed polygon), ignore one.
if( mPoints.front() == mPoints.back() ) --last;

// Find an extreme point since we know it will be on the hull...
size_t smallest = 0;
for( size_t i = 1; i <= last; ++i ) {
if( mPoints[i].x < mPoints[smallest].x ) {
smallest = i;
} else if( mPoints[i].x == mPoints[smallest].x && mPoints[i].y < mPoints[smallest].y ) {
smallest = i;
}
};
// ...then get the next and previous point
size_t prev = ( smallest == 0 ) ? last : ( smallest - 1 );
size_t next = ( smallest == last ) ? 0 : ( smallest + 1 );
vec2 a = mPoints[next], b = mPoints[smallest], c = mPoints[prev];

// The sign of the determinate indicates the orientation:
// positive is clockwise
// zero is colinear
// negative is counterclockwise
double determinate = ( b.x - a.x ) * ( c.y - a.y ) - ( c.x - a.x ) * ( b.y - a.y );
if( isColinear != nullptr ) *isColinear = determinate == 0.0;
return determinate > 0.0;
}

template<typename T>
bool PolyLineT<T>::isCounterClockwise( bool *isColinear ) const
{
bool colinear;
bool clockwise = this->isClockwise( &colinear );
if( isColinear != nullptr ) *isColinear = colinear;
return colinear ? false : !clockwise;
}

template<typename T>
T PolyLineT<T>::getPosition( float t ) const
{
Expand Down
96 changes: 96 additions & 0 deletions test/unit/src/PolyLineTest.cpp
@@ -0,0 +1,96 @@
#include "cinder/app/App.h"
#include "cinder/PolyLine.h"

#include "catch.hpp"


using namespace ci;
using namespace ci::app;
using namespace std;

TEST_CASE("PolyLine", "Orientation")
{
SECTION("Just two points")
{
vector<vec2> input({
vec2( 294.641, 105.359 ),
vec2( 387.617, 12.3833 ),
});

PolyLine2f p1 = PolyLine2f( input );
bool colinear;
REQUIRE( ! p1.isClockwise() );
REQUIRE( ! p1.isClockwise( &colinear ) );
REQUIRE( colinear );

REQUIRE( ! p1.isCounterClockwise() );
REQUIRE( ! p1.isCounterClockwise( &colinear ) );
REQUIRE( colinear );
}

SECTION("Three in a row")
{
vector<vec2> input({
vec2( 0, 0 ),
vec2( 100, 0 ),
vec2( -29, 0 )
});
PolyLine2f p1 = PolyLine2f( input );
bool colinear;

REQUIRE( ! p1.isClockwise() );
REQUIRE( ! p1.isClockwise( &colinear ) );
REQUIRE( colinear );

REQUIRE( ! p1.isCounterClockwise() );
REQUIRE( ! p1.isCounterClockwise( &colinear ) );
REQUIRE( colinear );
}

SECTION("Open Polygon")
{
vector<vec2> input({
vec2( 294.641, 105.359 ),
vec2( 387.617, 12.3833 ),
vec2( 412.967, 2.96674 ),
vec2( 417.987, -7.98667 ),
vec2( 386.023, -94.0354 )
});
PolyLine2f p1 = PolyLine2f( input );
reverse( input.begin(), input.end() );
PolyLine2f p2 = PolyLine2f( input );
bool colinear;

REQUIRE( p1.isClockwise() );
REQUIRE( p1.isClockwise( &colinear ) );
REQUIRE( ! colinear );

REQUIRE( ! p2.isClockwise() );
REQUIRE( ! p2.isClockwise( &colinear ) );
REQUIRE( ! colinear );
}

SECTION("Closed Polygon")
{
vector<vec2> input({
vec2( 294.641, 105.359 ),
vec2( 387.617, 12.3833 ),
vec2( 412.967, 2.96674 ),
vec2( 417.987, -7.98667 ),
vec2( 386.023, -94.0354 ),
vec2( 294.641, 105.359 )
});
PolyLine2f p1 = PolyLine2f( input );
bool colinear;
reverse( input.begin(), input.end() );
PolyLine2f p2 = PolyLine2f( input );

REQUIRE( p1.isClockwise() );
REQUIRE( p1.isClockwise( &colinear ) );
REQUIRE( ! colinear );

REQUIRE( ! p2.isClockwise() );
REQUIRE( ! p2.isClockwise( &colinear ) );
REQUIRE( ! colinear );
}
}
4 changes: 4 additions & 0 deletions test/unit/xcode/UnitTests.xcodeproj/project.pbxproj
Expand Up @@ -10,6 +10,7 @@
11E4FC491C26788A0082A67E /* BufferUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 11E4FC441C26788A0082A67E /* BufferUnit.cpp */; };
11E4FC4D1C267DB70082A67E /* FftUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 11E4FC451C26788A0082A67E /* FftUnit.cpp */; };
11E4FC4E1C26801E0082A67E /* RingBufferUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 11E4FC471C26788A0082A67E /* RingBufferUnit.cpp */; };
4989E06C1DB6889500503C9A /* PolyLineTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4989E06B1DB6889500503C9A /* PolyLineTest.cpp */; };
9CA851C01C1F74000049358B /* Base64Test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9CA851B61C1F74000049358B /* Base64Test.cpp */; };
9CA851C11C1F74000049358B /* JsonTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9CA851B81C1F74000049358B /* JsonTest.cpp */; };
9CA851C21C1F74000049358B /* ObjLoaderTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9CA851B91C1F74000049358B /* ObjLoaderTest.cpp */; };
Expand Down Expand Up @@ -61,6 +62,7 @@
11E4FC481C26788A0082A67E /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = "<group>"; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
4989E06B1DB6889500503C9A /* PolyLineTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PolyLineTest.cpp; sourceTree = "<group>"; };
5323E6B10EAFCA74003A9687 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = /System/Library/Frameworks/CoreVideo.framework; sourceTree = "<absolute>"; };
6E8118130C2B4ADCA23B5B2B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9CA851B61C1F74000049358B /* Base64Test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Base64Test.cpp; sourceTree = "<group>"; };
Expand Down Expand Up @@ -207,6 +209,7 @@
9CA851B71C1F74000049358B /* catch.hpp */,
9CA851B81C1F74000049358B /* JsonTest.cpp */,
9CA851B91C1F74000049358B /* ObjLoaderTest.cpp */,
4989E06B1DB6889500503C9A /* PolyLineTest.cpp */,
9CA851BA1C1F74000049358B /* RandTest.cpp */,
9CA851BD1C1F74000049358B /* SystemTest.cpp */,
9CA851BE1C1F74000049358B /* TestMain.cpp */,
Expand Down Expand Up @@ -285,6 +288,7 @@
9CA851C01C1F74000049358B /* Base64Test.cpp in Sources */,
9CA851C31C1F74000049358B /* RandTest.cpp in Sources */,
11E4FC4D1C267DB70082A67E /* FftUnit.cpp in Sources */,
4989E06C1DB6889500503C9A /* PolyLineTest.cpp in Sources */,
9CA851C11C1F74000049358B /* JsonTest.cpp in Sources */,
11E4FC4E1C26801E0082A67E /* RingBufferUnit.cpp in Sources */,
9CA851C51C1F74000049358B /* SystemTest.cpp in Sources */,
Expand Down