Çokgen noktalarının bir listesinin saat yönünde olup olmadığını nasıl belirleyebilirim?


260

Bir puan listesi var, saat yönünde olup olmadığını nasıl bulabilirim?

Örneğin:

point[0] = (5,0)
point[1] = (6,4)
point[2] = (4,5)
point[3] = (1,5)
point[4] = (1,0)

saat yönünün tersine (veya bazı insanlar için saat yönünün tersine) olduğunu söyleyebilir.


6
LÜTFEN AKLINIZDA BULUNDURUN: Kabul edilen cevap ve ondan sonraki birçok yanıt çok fazla ekleme ve çarpma gerektirir (bunlar negatif veya pozitif sona eren alan hesaplamalarına dayanır; örneğin "ayakkabı bağı formülü"). Bunlardan birini uygulamadan önce , basit çokgenin wiki'ye dayalı olarak daha basit / daha hızlı olan lhf'nin cevabını düşünün .
ToolmakerSteve

Bunu her zaman iki bitişik vektörün çapraz çarpımı olarak düşünüyorum. Çokgenin çevresinde yürürsem başım uçaktan işaret eder. Koordinat sistemimdeki üçüncü yönü elde etmek için düzlem vektöründen çıkıp yürüme yönü vektörüne geçiyorum. Eğer bu vektör iç mekanın solumda olduğunu gösteriyorsa saat yönünün tersine; iç sağımda ise saat yönünde.
duffymo

Yanıtlar:


416

Hilal gibi dışbükey olmayan bir çokgen durumunda önerilen yöntemlerden bazıları başarısız olacaktır. İşte dışbükey olmayan çokgenlerle çalışacak basit bir tane (sekiz rakamı gibi kendiliğinden kesişen bir çokgenle bile çalışacak ve çoğunlukla saat yönünde olup olmadığını söyleyecektir ).

Kenarların toplamı, (x 2 - x 1 ) (y 2 + y 1 ). Sonuç pozitifse eğri saat yönündedir, negatifse eğri saat yönünün tersidir. (Sonuç, +/- konvansiyonuyla birlikte kapalı alanın iki katıdır.)

point[0] = (5,0)   edge[0]: (6-5)(4+0) =   4
point[1] = (6,4)   edge[1]: (4-6)(5+4) = -18
point[2] = (4,5)   edge[2]: (1-4)(5+5) = -30
point[3] = (1,5)   edge[3]: (1-1)(0+5) =   0
point[4] = (1,0)   edge[4]: (5-1)(0+0) =   0
                                         ---
                                         -44  counter-clockwise

28
Basit bir duruma uygulanan matematiktir. (Grafik yayınlama yeteneğim yok.) Çizgi parçasının altındaki alan ortalama yüksekliğine (y2 + y1) / yatay uzunluğunun (x2-x1) 2 katına eşittir. X'de işaret kuralına dikkat edin. Bunu bazı üçgenlerle deneyin ve yakında nasıl çalıştığını göreceksiniz.
Beta

72
Küçük bir uyarı: bu cevap normal Kartezyen koordinat sistemini varsayar. Bahsetmeye değer nedeni, HTML5 tuval gibi bazı yaygın bağlamların ters Y ekseni kullanmasıdır. Ardından kuralın çevrilmesi gerekir: alan negatifse , eğri saat yönünde olur.
LarsH

8
@ Mr.Qbs: Benim yöntemim işe yarıyor ama hayati bir parçayı atlarsanız işe yaramaz. Bu bir haber değil.
Beta

11
@ Mr.Qbs: Her zaman son noktayı birinciye bağlamak zorundasınız. 0'dan N-1'e kadar numaralandırılmış N noktanız varsa, şunu hesaplamanız gerekir: Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )i = 0'dan N-1'e. Yani, Modulo N ( N ≡ 0) dizinini almalıdır Formül yalnızca kapalı çokgenler için çalışır . Çokgenlerin hayali kenarları yoktur.
Olivier Jacot-Descombes

4
Bu blog.element84.com/polygon-winding.html bu çözümün neden işe yaradığını basitçe İngilizce olarak açıklamaktadır.
David Zorychta

49

Çapraz ürün iki vektörün dik-lik derecesini ölçer. Çokgeninizin her kenarının, üç boyutlu (3 boyutlu) xyz uzayının xy düzleminde bir vektör olduğunu düşünün. Daha sonra birbirini takip eden iki kenarın çapraz çarpımı z yönünde bir vektördür (ikinci segment saat yönünde pozitif z yönü, saat yönünün tersinde eksi z yönü). Bu vektörün büyüklüğü, iki orijinal kenar arasındaki açının sinüsü ile orantılıdır, bu nedenle dikey olduklarında maksimuma ulaşır ve kenarlar birbirine paralel olduğunda (paralel) kaybolur.

Bu nedenle, çokgenin her tepe noktası (noktası) için, bitişik iki kenarın çapraz ürün büyüklüğünü hesaplayın:

Using your data:
point[0] = (5, 0)
point[1] = (6, 4)
point[2] = (4, 5)
point[3] = (1, 5)
point[4] = (1, 0)

Yani arka arkaya kenarları Etiket
edgeAgelen segment point0için point1ve
edgeBaralarında point1etmek point2
...
edgeEarasındadırpoint4 ve point0.

Daha sonra tepe noktasına ( point0) arasındadır
edgeE[kaynaktan point4için point0]
edgeA[kaynaktan point0 `Point1' olarak

Bu iki kenarın kendisi, x ve y koordinatları başlangıç ​​ve bitiş noktalarının koordinatlarının çıkarılmasıyla belirlenebilen vektörlerdir:

edgeE= point0- point4= (1, 0) - (5, 0)= (-4, 0) ve
edgeA= point1- point0= (6, 4) - (1, 0)= (5, 4) ve

Ve bu iki bitişik kenarların çapraz ürün ekseni koordinat üç temsil eden semboller aşağıdaki iki vektörün koordinatları yerleştirilmek sureti ile imal edilir, aşağıdaki matris determinantını kullanılarak hesaplanır ( i, j, ve k). Üçüncü (sıfır) değerli koordinat oradadır, çünkü çapraz ürün kavramı 3 boyutlu bir yapıdır ve bu nedenle çapraz ürünü uygulamak için bu 2-D vektörleri 3-D'ye genişletiriz:

 i    j    k 
-4    0    0
 1    4    0    

Tüm çapraz ürünlerin çarpılan iki vektörün düzlemine dik bir vektör ürettiği göz önüne alındığında, yukarıdaki matrisin determinantı sadece bir k, (veya z ekseni) bileşenine sahiptir. Veya z ekseni bileşeninin
büyüklüğünü hesaplamak için formül = k
a1*b2 - a2*b1 = -4* 4 - 0* 1-16

Bu değerin ( -16) büyüklüğü , 2 orijinal vektör arasındaki açının sinüsünün bir ölçüsüdür ve 2 vektörün büyüklüklerinin çarpımı ile çarpılır.
Aslında değeri için başka bir formül
A X B (Cross Product) = |A| * |B| * sin(AB).

Yani, sadece açının bir ölçüsüne geri dönmek için, bu değeri ( -16) iki vektörün büyüklüğünün çarpımı ile bölmeniz gerekir .

|A| * |B| = 4 * Sqrt(17) =16.4924...

Günahın ölçüsü (AB) = -16 / 16.4924=-.97014...

Bu, köşeden sonraki bir sonraki parçanın sola veya sağa eğilip eğilmediğinin ve ne kadar olduğuna dair bir ölçüdür. Ark sinüsü almaya gerek yoktur. Tek umursadığımız büyüklüğü ve tabii ki işareti (pozitif veya negatif)!

Bunu, kapalı yolun çevresindeki diğer 4 noktanın her biri için yapın ve her hesaplamada bu hesaplamadaki değerleri toplayın.

Nihai toplam pozitifse, saat yönünde, negatif, saat yönünün tersine gittiniz.


3
Aslında, bu çözüm kabul edilen çözümden farklı bir çözümdür. Eşdeğer olup olmadıkları araştırıyorum, ancak şüphelendikleri bir soru ... Kabul edilen cevap, çokgenin üst kenarının altındaki alan ile altındaki alan arasındaki farkı alarak çokgenin alanını hesaplar. çokgenin alt kenarı. Biri negatif (soldan sağa doğru ilerlediğiniz yer) diğeri negatif olacaktır. Saat yönünde hareket ederken, üst kenar soldan sağa doğru hareket eder ve daha büyüktür, bu nedenle toplam pozitiftir.
Charles Bretana

1
Benim çözümüm, her tepe noktasında kenar açılarındaki değişikliklerin sinüslerinin toplamını ölçer. Bu saat yönünde hareket ederken pozitif, saat yönünün tersine hareket ederken negatif olacaktır.
Charles Bretana

2
Bu yaklaşımla, dışbükeyliği varsaymadıkça (bu durumda sadece bir tepe noktasını kontrol etmeniz gerekir)
arkisin

2
Arkini almanız gerekiyor. Bir grup rastgele dışbükey olmayan çokgen üzerinde deneyin ve arcsin'i almazsanız testin bazı çokgenler için başarısız olacağını göreceksiniz.
Luke Hutchison

1
@CharlesBretana - Luke'un testini yapmadığım halde doğru olduğuna inanıyorum. Bu doğası , toplamı bir kombine doğrusal olmayan [arcsin genel arcsin ile olmaksızın] ölçekte. Marsbear'ın önerdiğini, doğru bir şekilde reddettiğinizi düşünün. O sadece "saymak" önerdi ve bir avuç büyük değer çok sayıda küçük değerden daha ağır basabileceğine dikkat çektiniz. Şimdi her bir değerin arcsini düşünün vs değil. Arksin almamanın her bir değere yanlış ağırlık vermesi, bu nedenle aynı kusura sahip olması hala geçerli değil mi (çok daha az olsa da)?
ToolmakerSteve

47

Sanırım bu oldukça eski bir soru, ama yine de başka bir çözüm atacağım, çünkü bu basit ve matematiksel olarak yoğun değil - sadece temel cebir kullanıyor. Çokgenin imzalı alanını hesaplayın. Negatifse, noktalar saat yönünde, pozitifse saat yönünün tersine olurlar. (Bu Beta'nın çözümüne çok benzer.)

İmzalı alanı hesaplayın: A = 1/2 * (x 1 * y 2 - x 2 * y 1 + x 2 * y 3 - x 3 * y 2 + ... + x n * y 1 - x 1 * y n )

Veya sözde kodda:

signedArea = 0
for each point in points:
    x1 = point[0]
    y1 = point[1]
    if point is last point
        x2 = firstPoint[0]
        y2 = firstPoint[1]
    else
        x2 = nextPoint[0]
        y2 = nextPoint[1]
    end if

    signedArea += (x1 * y2 - x2 * y1)
end for
return signedArea / 2

Yalnızca siparişi kontrol ediyorsanız, 2'ye bölme zahmetine girmenize gerek olmadığını unutmayın.

Kaynaklar: http://mathworld.wolfram.com/PolygonArea.html


Yukarıdaki imzalı alan formülünüzdeki bir yazım hatası mıydı? "Xn * y1 - x1 * yn" ile biter; "x_n y_ {n + 1} - y_n x_ {n-1}" olması gerektiğine inandığımda (en azından LaTeX'te). Öte yandan, herhangi bir doğrusal cebir dersi almamın üzerinden on yıl geçti.
Michael Eric Oberlin

Hayır! Kaynağı kontrol ederseniz , formülün aslında son terimdeki (y1 ve x1) ilk noktaya tekrar başvurduğunu görürsünüz. (Maalesef, LaTeX'e çok aşina değilim, ancak aboneleri daha okunabilir hale getirmek için biçimlendirdim.)
Sean the Bean

Bu çözümü kullandım ve kullanımım için mükemmel çalıştı. Dizinizi önceden planlayıp yedekleyebilir ve dizinizde fazladan iki vektör varsa, dizinin kuyruğuna ilk vektör ekleyerek karşılaştırmadan (veya%) kurtulabileceğinizi unutmayın. Bu şekilde, sonuncusu hariç tüm elemanlar üzerinde döngü yaparsınız (uzunluk-1 yerine uzunluk-2).
Eric Fortier

2
@EricFortier - FWIW, büyük bir diziyi yeniden boyutlandırmak yerine, her yinelemenin bir previousPointsonraki yinelemede olduğu gibi noktasını kaydetmesi için etkili bir alternatiftir . Döngü başlamadan önce, previousPointdizinin son noktasına ayarlayın. Takas işlemi ekstra yerel değişken kopyasıdır ancak daha az dizi erişimi vardır. Ve en önemlisi, giriş dizisine dokunmak zorunda değilsiniz.
ToolmakerSteve

2
@MichaelEricOberlin - çizgi parçasını son noktadan ilk noktaya dahil ederek çokgeni kapatmak gerekir . (Hangi nokta kapalı çokgeni başlatırsa
ToolmakerSteve

38

En küçük y (ve bağlar varsa en büyük x) ile tepe noktasını bulun. Köşe Ave listedeki önceki tepe noktası ve listedeki bir Bsonraki tepe noktası olsun C. Şimdi hesaplamak işareti çapraz ürünün ABve AC.


Referanslar:


7
Bu aynı zamanda en.wikipedia.org/wiki/Curve_orientation bölümünde de açıklanmaktadır . Mesele, bulunan noktanın dışbükey gövdede olması gerektiğidir ve tüm çokgenin yönünü belirlemek için dışbükey gövdede (ve hemen komşularında) tek bir noktaya yerel olarak bakmak gerekir.
M Katz

1
Şok ve awed daha fazla oy almamıştır. Basit çokgenler için ( bazı alanlardaki çoğu çokgen ), bu cevap bir O(1)çözüm verir . Diğer tüm cevaplar çokgen noktalarının sayısı O(n)için çözümler üretir n. Daha da derin optimizasyonlar için Wikipedia'nın muhteşem Eğri yönelimi makalesinin Pratik Düşünceler alt bölümüne bakın.
Cecil Curry

8
Açıklama: Bu çözümO(1)yalnızca (A) bu çokgen dışbükey ise (bu durumda herhangi bir keyfi tepe noktası dışbükey gövdede bulunuyorsa ve bu nedenle yeterlidir) veya (B) zaten en küçük Y koordinatına sahip tepe noktasını biliyorsanız. Durum böyle değilse (yani, bu çokgen dışbükey değildir ve bunun hakkında hiçbir şey bilmiyorsanız), birO(n)arama yapılması gerekir. Ancak hiçbir toplama gerekli olmadığından, bu basit çokgenler için diğer çözümlerden önemli ölçüde daha hızlıdır.
Cecil Curry


1
@CecilCurry Sanırım 2. yorumunuz bunun neden daha fazla oy almadığını açıklıyor. Belirli senaryolarda, bu sınırlamalardan bahsetmeden yanlış cevaplar verir.
LarsH

24

İşte bu cevaba dayalı algoritmanın basit bir C # uygulaması .

Diyelim ki bir Vectortürümüz Xve Ytürümüzün özellikleri var double.

public bool IsClockwise(IList<Vector> vertices)
{
    double sum = 0.0;
    for (int i = 0; i < vertices.Count; i++) {
        Vector v1 = vertices[i];
        Vector v2 = vertices[(i + 1) % vertices.Count];
        sum += (v2.X - v1.X) * (v2.Y + v1.Y);
    }
    return sum > 0.0;
}

%( Wikipedia'ya göre ) bir sayının diğerine bölünmesinden sonra geri kalanını bulan modulo işlemini gerçekleştiren modulo ya da geri kalan operatördür .


6

Köşelerden birinden başlayın ve her iki tarafın eğimli açısını hesaplayın.

İlk ve son sıfır olacaktır (bu yüzden bunları atlayın); geri kalanı için, açının sinüsü, normalizasyonların çapraz çarpımı (birim [n]-nokta [0]) ve (nokta [n-1]-nokta [0]) birim uzunluğuna verilecektir.

Değerlerin toplamı pozitifse, poligonunuz saat yönünün tersine çizilir.


Çapraz ürünün temelde açının sinüsünden pozitif bir ölçeklendirme faktörüne nasıl kaynadığını görünce, muhtemelen sadece bir çapraz ürün yapmak daha iyidir. Daha hızlı ve daha az karmaşık olacak.
ReaperUnreal

4

Değeri ne olursa olsun, Google Maps API v3 uygulamaları için sarma sırasını hesaplamak için bu karışımı kullandım.

Kod, çokgen alanların yan etkisinden yararlanır: tepe noktalarının saat yönünde sarma sırası pozitif bir alan verirken, aynı tepe noktalarının saat yönünün tersine sarma sırası negatif bir değerle aynı alanı üretir. Kod ayrıca Google Haritalar geometri kitaplığında bir tür özel API kullanır. Kullanmaktan kendimi rahat hissettim - kendi sorumluluğunuzdadır.

Örnek kullanım:

var myPolygon = new google.maps.Polygon({/*options*/});
var isCW = myPolygon.isPathClockwise();

Birim testleri ile tam örnek @ http://jsfiddle.net/stevejansen/bq2ec/

/** Mixin to extend the behavior of the Google Maps JS API Polygon type
 *  to determine if a polygon path has clockwise of counter-clockwise winding order.
 *  
 *  Tested against v3.14 of the GMaps API.
 *
 *  @author  stevejansen_github@icloud.com
 *
 *  @license http://opensource.org/licenses/MIT
 *
 *  @version 1.0
 *
 *  @mixin
 *  
 *  @param {(number|Array|google.maps.MVCArray)} [path] - an optional polygon path; defaults to the first path of the polygon
 *  @returns {boolean} true if the path is clockwise; false if the path is counter-clockwise
 */
(function() {
  var category = 'google.maps.Polygon.isPathClockwise';
     // check that the GMaps API was already loaded
  if (null == google || null == google.maps || null == google.maps.Polygon) {
    console.error(category, 'Google Maps API not found');
    return;
  }
  if (typeof(google.maps.geometry.spherical.computeArea) !== 'function') {
    console.error(category, 'Google Maps geometry library not found');
    return;
  }

  if (typeof(google.maps.geometry.spherical.computeSignedArea) !== 'function') {
    console.error(category, 'Google Maps geometry library private function computeSignedArea() is missing; this may break this mixin');
  }

  function isPathClockwise(path) {
    var self = this,
        isCounterClockwise;

    if (null === path)
      throw new Error('Path is optional, but cannot be null');

    // default to the first path
    if (arguments.length === 0)
        path = self.getPath();

    // support for passing an index number to a path
    if (typeof(path) === 'number')
        path = self.getPaths().getAt(path);

    if (!path instanceof Array && !path instanceof google.maps.MVCArray)
      throw new Error('Path must be an Array or MVCArray');

    // negative polygon areas have counter-clockwise paths
    isCounterClockwise = (google.maps.geometry.spherical.computeSignedArea(path) < 0);

    return (!isCounterClockwise);
  }

  if (typeof(google.maps.Polygon.prototype.isPathClockwise) !== 'function') {
    google.maps.Polygon.prototype.isPathClockwise = isPathClockwise;
  }
})();

Bunu denediğimde tam tersi bir sonuç elde ederim, saat yönünde çizilen bir çokgen negatif bir alan verirken, saat yönünün tersine çizilen bir pozitif pozitif sonuç verir. Her iki durumda da, bu snippet hala 5 yıl süper yararlı, teşekkür ederim.
Cameron Roberts

@CameronRoberts Norm (özellikle geoJson için IETF'ye bakınız) 'sağ kuralı' izlemektir. Sanırım Google şikayet ediyor. Bu durumda dış halka saat yönünün tersine (pozitif alan verir) ve iç halkalar (delikler) saat yönünde sarılır (negatif alan ana alandan çıkarılmalıdır).
allez l'OM

4

Sean'ın cevabının JavaScript'teki bir uygulaması :

function calcArea(poly) {
    if(!poly || poly.length < 3) return null;
    let end = poly.length - 1;
    let sum = poly[end][0]*poly[0][1] - poly[0][0]*poly[end][1];
    for(let i=0; i<end; ++i) {
        const n=i+1;
        sum += poly[i][0]*poly[n][1] - poly[n][0]*poly[i][1];
    }
    return sum;
}

function isClockwise(poly) {
    return calcArea(poly) > 0;
}

let poly = [[352,168],[305,208],[312,256],[366,287],[434,248],[416,186]];

console.log(isClockwise(poly));

let poly2 = [[618,186],[650,170],[701,179],[716,207],[708,247],[666,259],[637,246],[615,219]];

console.log(isClockwise(poly2));

Bunun doğru olduğundan eminim. Çalışıyor gibi görünüyor :-)

Merak ediyorsanız, bu çokgenler şöyle görünür:


3

Bu OpenLayers 2 için uygulanan işlevdir . Saat yönünde bir çokgene sahip olma koşulu area < 0, bu referansla teyit edilir .

function IsClockwise(feature)
{
    if(feature.geometry == null)
        return -1;

    var vertices = feature.geometry.getVertices();
    var area = 0;

    for (var i = 0; i < (vertices.length); i++) {
        j = (i + 1) % vertices.length;

        area += vertices[i].x * vertices[j].y;
        area -= vertices[j].x * vertices[i].y;
        // console.log(area);
    }

    return (area < 0);
}

Openlayers googlemaps gibi javascript tabanlı bir harita yönetim kütüphanesidir ve openlayers 2'de yazılmıştır ve kullanılmıştır.
MSS

Kodunuzun ne yaptığını ve neden yaptığınızı biraz açıklayabilir misiniz?
nbro

@nbro bu kod lhf yanıtını uygular . Doğrudan parametre olarak köşelere sahip olarak OpenLayer olmayan parçayı saf bir javascript işlevinde tutmak kolaydır . İyi çalışır ve multiPolygon durumuna uyarlanabilir .
allez l'OM

2

Matlab kullanırsanız ispolycw, çokgen köşeleri saat yönünde ise işlev true değerini döndürür.


1

Olarak bu Ara madde açıklandığı Eğri yönde 3 puan verilir, p, qve rdüzlemin (yani, x ve y koordinatları) hakkında, aşağıdaki belirleyici işareti hesaplayabilir

resim açıklamasını buraya girin

Determinant negatifse (yani Orient(p, q, r) < 0), çokgen saat yönünde (CW) yönlendirilir. Belirleyici pozitifse (yani Orient(p, q, r) > 0), çokgen saat yönünün tersine (CCW) yönlendirilir. Belirleyici sıfır (yani Orient(p, q, r) == 0) nokta ise p, qve rvardır doğrudaş .

Yukarıdaki formülde, biz koordinatlarının önünde olanlar öne eklemek p, q ve rkullandığımız için homojen koordinatlar .


@tibetty Çokgen içbükeyse bu yöntemin neden pek işe yaramayacağını açıklayabilir misiniz?
nbro

1
Lütfen yayınınızdaki wiki öğesi referansındaki son tabloya bakın. Yanlış bir örnek vermek benim için kolay ama kanıtlamak zor.
tibetty

1
Lütfen yayınınızdaki wiki öğesi referansındaki son tabloya bakın. Yanlış bir örnek vermek benim için kolay ama kanıtlamak zor.
tibetty

1
@tibetty doğru. Çokgen boyunca üç nokta alamazsınız; o çokgenin dışbükey veya içbükey bir bölgesinde olabilirsiniz. Wiki'yi dikkatlice okurken , çokgeni çevreleyen dışbükey gövde boyunca üç nokta alınmalıdır . "Pratik düşünceler" den: "Uygun bir tepe noktası bulmak için çokgenin dışbükey gövdesini inşa etmeye gerek yoktur. en küçük Y koordinatı ile seçilir.
ToolmakerSteve

1
Bu nedenle, lhf'nin benzer olan ve aynı wiki makalesine atıfta bulunan ancak böyle bir noktayı belirten önceki cevabı . [Görünüşe göre, ortada olmaktan kaçındığı sürece, kişinin en küçük veya en büyük, x veya y olup olmadığı önemli değildir; etkili bir şekilde içbükey bir bölgede garanti etmek için çokgen etrafındaki sınırlama kutusunun bir kenarından çalışıyor.]
ToolmakerSteve

1

LHF'nin cevabını uygulamak için C # kodu :

// https://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon
public static WindingOrder DetermineWindingOrder(IList<Vector2> vertices)
{
    int nVerts = vertices.Count;
    // If vertices duplicates first as last to represent closed polygon,
    // skip last.
    Vector2 lastV = vertices[nVerts - 1];
    if (lastV.Equals(vertices[0]))
        nVerts -= 1;
    int iMinVertex = FindCornerVertex(vertices);
    // Orientation matrix:
    //     [ 1  xa  ya ]
    // O = | 1  xb  yb |
    //     [ 1  xc  yc ]
    Vector2 a = vertices[WrapAt(iMinVertex - 1, nVerts)];
    Vector2 b = vertices[iMinVertex];
    Vector2 c = vertices[WrapAt(iMinVertex + 1, nVerts)];
    // determinant(O) = (xb*yc + xa*yb + ya*xc) - (ya*xb + yb*xc + xa*yc)
    double detOrient = (b.X * c.Y + a.X * b.Y + a.Y * c.X) - (a.Y * b.X + b.Y * c.X + a.X * c.Y);

    // TBD: check for "==0", in which case is not defined?
    // Can that happen?  Do we need to check other vertices / eliminate duplicate vertices?
    WindingOrder result = detOrient > 0
            ? WindingOrder.Clockwise
            : WindingOrder.CounterClockwise;
    return result;
}

public enum WindingOrder
{
    Clockwise,
    CounterClockwise
}

// Find vertex along one edge of bounding box.
// In this case, we find smallest y; in case of tie also smallest x.
private static int FindCornerVertex(IList<Vector2> vertices)
{
    int iMinVertex = -1;
    float minY = float.MaxValue;
    float minXAtMinY = float.MaxValue;
    for (int i = 0; i < vertices.Count; i++)
    {
        Vector2 vert = vertices[i];
        float y = vert.Y;
        if (y > minY)
            continue;
        if (y == minY)
            if (vert.X >= minXAtMinY)
                continue;

        // Minimum so far.
        iMinVertex = i;
        minY = y;
        minXAtMinY = vert.X;
    }

    return iMinVertex;
}

// Return value in (0..n-1).
// Works for i in (-n..+infinity).
// If need to allow more negative values, need more complex formula.
private static int WrapAt(int i, int n)
{
    // "+n": Moves (-n..) up to (0..).
    return (i + n) % n;
}

1
Bu, pozitif pozitif Y koordinatları için gözükmektedir. Standart koordinatlar için CW / CCW'yi çevirin.
Warwick Allison

0

Bazı noktaların saat yönünde verilmesi için tüm kenarların sadece kenarların toplamı değil pozitif olması gerektiğini düşünüyorum. Bir kenar negatifse, saat yönünün tersine en az 3 nokta verilir.


Doğru, ama bir çokgenin sarma sırası kavramını yanlış anlıyorsunuz (saat yönünde veya saat yönünün tersine). Tamamen dışbükey bir çokgende, tüm noktalardaki açı saat yönünde veya tümü [ilk cümlenizde olduğu gibi] saat yönünün tersine olacaktır. İçbükey bölgeleri olan bir çokgendeki "mağaralar" zıt yönde olacaktır, ancak bir bütün olarak çokgenin hala iyi tanımlanmış bir iç kısmı vardır ve buna göre saat yönünde veya saat yönünün tersine kabul edilir. Bkz. En.wikipedia.org/wiki/…
ToolmakerSteve

0

Benim C # / LINQ çözüm aşağıda @charlesbretana çapraz ürün tavsiyesi dayanmaktadır. Sargı için normal bir referans belirtebilirsiniz. Eğri çoğunlukla yukarı vektör tarafından tanımlanan düzlemde olduğu sürece çalışmalıdır.

using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace SolidworksAddinFramework.Geometry
{
    public static class PlanePolygon
    {
        /// <summary>
        /// Assumes that polygon is closed, ie first and last points are the same
        /// </summary>
       public static bool Orientation
           (this IEnumerable<Vector3> polygon, Vector3 up)
        {
            var sum = polygon
                .Buffer(2, 1) // from Interactive Extensions Nuget Pkg
                .Where(b => b.Count == 2)
                .Aggregate
                  ( Vector3.Zero
                  , (p, b) => p + Vector3.Cross(b[0], b[1])
                                  /b[0].Length()/b[1].Length());

            return Vector3.Dot(up, sum) > 0;

        } 

    }
}

birim testi ile

namespace SolidworksAddinFramework.Spec.Geometry
{
    public class PlanePolygonSpec
    {
        [Fact]
        public void OrientationShouldWork()
        {

            var points = Sequences.LinSpace(0, Math.PI*2, 100)
                .Select(t => new Vector3((float) Math.Cos(t), (float) Math.Sin(t), 0))
                .ToList();

            points.Orientation(Vector3.UnitZ).Should().BeTrue();
            points.Reverse();
            points.Orientation(Vector3.UnitZ).Should().BeFalse();



        } 
    }
}

0

Bu benim diğer cevaplardaki açıklamaları kullanarak benim çözümüm:

def segments(poly):
    """A sequence of (x,y) numeric coordinates pairs """
    return zip(poly, poly[1:] + [poly[0]])

def check_clockwise(poly):
    clockwise = False
    if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
        clockwise = not clockwise
    return clockwise

poly = [(2,2),(6,2),(6,6),(2,6)]
check_clockwise(poly)
False

poly = [(2, 6), (6, 6), (6, 2), (2, 2)]
check_clockwise(poly)
True

1
Bu cevabın tam olarak hangi cevaplara dayandığını belirtebilir misiniz?
nbro

0

Çokgen içinde bir noktayı zaten biliyorsanız , çok daha basit bir yöntem :

  1. Orijinal çokgenden, noktalardan ve koordinatlarından bu sırayla herhangi bir çizgi parçası seçin.

  2. Bilinen bir "iç" nokta ekleyin ve bir üçgen oluşturun.

  3. Bu üç nokta ile burada önerildiği gibi CW veya CCW'yi hesaplayın .


Belki çokgen tamamen dışbükeyse bu işe yarar. İçbükey bölgeler varsa kesinlikle güvenilir değildir - mağaranın kenarlarından birinin "yanlış" tarafında bir nokta seçmek kolaydır, daha sonra bu kenara bağlayın. Yanlış cevap alacak.
ToolmakerSteve

Çokgen içbükey olsa bile çalışır. Noktanın içbükey çokgenin içinde olması gerekir. Ancak karmaşık çokgen hakkında emin değilim (test etmedi.)
Venkata Goli

"Çokgen içbükey olsa bile çalışır." - Karşı örnek: poli (0,0), (1,1), (0,2), (2,2), (2,0). Çizgi parçası (1,1), (0, 2). Üçgen oluşturmak için (1,1), (0,2), (1,2) içinde bir iç nokta seçerseniz -> (1,1), (0,2), (0.5,1.5)) elde edersiniz (0,0), (1,1), (1,0)> (1,1), (0,2), (0.5,0.5) içinde bir iç nokta seçtiğinizden daha zıt sargı. Her ikisi de orijinal poligonun iç kısmıdır, ancak zıt sargıları vardır. Bu nedenle, bunlardan biri yanlış cevap veriyor.
ToolmakerSteve

Genel olarak, bir çokgenin içbükey bir bölgesi varsa, içbükey bölgede bir segment seçin. İçbükey olduğu için, çizginin karşı tarafında bulunan iki "iç" nokta bulabilirsiniz. Bu hattın zıt taraflarında oldukları için oluşan üçgenlerin zıt sargıları vardır. İspat sonu.
ToolmakerSteve

0

Birkaç güvenilir olmayan uygulamayı test ettikten sonra, kutudan CW / CCW yönelimi ile ilgili tatmin edici sonuçlar sağlayan algoritma OP tarafından bu iş parçacığında ( shoelace_formula_3) yayınlanan algoritmadır .

Her zaman olduğu gibi, pozitif bir sayı bir CW yönelimini temsil ederken negatif bir sayı CCW'yi temsil eder.


0

Yukarıdaki cevaplara dayanan hızlı 3.0 çözümü:

    for (i, point) in allPoints.enumerated() {
        let nextPoint = i == allPoints.count - 1 ? allPoints[0] : allPoints[i+1]
        signedArea += (point.x * nextPoint.y - nextPoint.x * point.y)
    }

    let clockwise  = signedArea < 0

0

Bunun için başka bir çözüm;

const isClockwise = (vertices=[]) => {
    const len = vertices.length;
    const sum = vertices.map(({x, y}, index) => {
        let nextIndex = index + 1;
        if (nextIndex === len) nextIndex = 0;

        return {
            x1: x,
            x2: vertices[nextIndex].x,
            y1: x,
            y2: vertices[nextIndex].x
        }
    }).map(({ x1, x2, y1, y2}) => ((x2 - x1) * (y1 + y2))).reduce((a, b) => a + b);

    if (sum > -1) return true;
    if (sum < 0) return false;
}

Tüm köşeleri böyle bir dizi olarak alın;

const vertices = [{x: 5, y: 0}, {x: 6, y: 4}, {x: 4, y: 5}, {x: 1, y: 5}, {x: 1, y: 0}];
isClockwise(vertices);

0

R'nin yönü belirlemesi ve saat yönünde geri döndürmesi için çözüm (owin nesneleri için gerekli bulundu):

coords <- cbind(x = c(5,6,4,1,1),y = c(0,4,5,5,0))
a <- numeric()
for (i in 1:dim(coords)[1]){
  #print(i)
  q <- i + 1
  if (i == (dim(coords)[1])) q <- 1
  out <- ((coords[q,1]) - (coords[i,1])) * ((coords[q,2]) + (coords[i,2]))
  a[q] <- out
  rm(q,out)
} #end i loop

rm(i)

a <- sum(a) #-ve is anti-clockwise

b <- cbind(x = rev(coords[,1]), y = rev(coords[,2]))

if (a>0) coords <- b #reverses coords if polygon not traced in anti-clockwise direction

0

Bu cevaplar doğru olmakla birlikte, gereğinden fazla matematiksel olarak daha yoğundur. En kuzey noktasının haritadaki en yüksek nokta olduğu harita koordinatlarını varsayalım. En kuzey noktasını bulun ve 2 puan bağlanırsa, en kuzey o zaman en doğu olandır (bu, lhf'nin cevabında kullandığı noktadır). Puanlarınızda,

nokta [0] = (5,0)

nokta [1] = (6,4)

nokta [2] = (4,5)

nokta [3] = (1,5)

nokta [4] = (1,0)

P2'nin en kuzey olduğunu varsayarsak, o zaman bir önceki veya bir sonraki nokta, saat yönünde, CW veya CCW'yi belirler. En kuzey noktası kuzey cephesinde olduğundan, P1 (önceki) P2'ye doğru doğarsa yön CW'dir. Bu durumda, batıya hareket eder, bu nedenle kabul edilen cevabın söylediği gibi yön CCW'dir. Önceki noktanın yatay hareketi yoksa, aynı sistem sonraki nokta olan P3 için de geçerlidir. P3 P2'nin batısındaysa, hareket CCW'dir. P2 - P3 hareketi doğu ise, bu durumda batı, hareket CW'dir. Verilerinizdeki nte, P2'nin doğu noktasından sonra en kuzey ve prv'nin bir önceki nokta, verilerinizdeki P1 ve nxt'nin bir sonraki nokta, verilerinizdeki P3 ve [0] yatay veya doğu / batısında, doğudan daha az ve [1] dikeydir.

if (nte[0] >= prv[0] && nxt[0] >= nte[0]) return(CW);
if (nte[0] <= prv[0] && nxt[0] <= nte[0]) return(CCW);
// Okay, it's not easy-peasy, so now, do the math
if (nte[0] * nxt[1] - nte[1] * nxt[0] - prv[0] * (nxt[1] - crt[1]) + prv[1] * (nxt[0] - nte[0]) >= 0) return(CCW); // For quadrant 3 return(CW)
return(CW) // For quadrant 3 return (CCW)

IMHO, lhf'nin cevabında gösterilen temel matematiğe bağlı kalmak daha güvenli olurdu - ondan bahsettiğiniz için teşekkür ederim. Çeyreği azaltmadaki zorluk , formülünüzün her durumda doğru olduğunu kanıtlamak için adil bir çalışma olmasıdır. "Daha fazla batı" yı doğru hesapladınız mı? Hem [1] hem de [3] ' ün [2]' nin "batı ve güneyi" olduğu içbükey bir poligonda ? Bu durumda farklı uzunluklarda [1] ve [3] doğru mu çalıştınız? Hiçbir fikrim yok, oysa bu açıyı (veya belirleyicisini) doğrudan hesaplarsam, iyi bilinen formüller kullanıyorum.
ToolmakerSteve

3 nokta dışbükeyse if ifadelerinin her zaman işe yaradığını unutmayın. İf ifadeleri geri dönecek, doğru cevabı alacaksınız. Şekil içbükey ve aşırı ise, if ifadeleri dönmeyecektir. O zaman matematiği yapmak zorundasınız. Çoğu görüntünün bir çeyreği vardır, bu yüzden bu kısım kolaydır. Altyordam çağrılarımın% 99'undan fazlası if ifadeleri tarafından işlenir.
VectorVortec

Bu benim kaygımı gidermiyor. Bu formül nedir? LHF'nin cevabındaki wiki bağlantısında verilen yönelim belirleyicisi midir? Varsa, söyleyin. Standart matematikten kaçınmak için yaptığınız işlemlerin çoğu durumda hızlı kontroller yapmak olduğunu açıklayın. Eğer öyleyse, cevabınız şimdi bana mantıklı geliyor. (Minör nit: Eğer kullanılırsa okumak daha kolay olacağını .xve .ybir yapının yerine, [0]ve [1]ben, senin kod ne dediğini ben ona baktı ilk kez bilmiyordum..)
ToolmakerSteve

Yaklaşımınıza güvenmediğim için lhf'in yaklaşımını uyguladım ; onun formülünden formül. Yavaş parçasıdır bulma uygun köşe - O (N) arama. Bulduktan sonra, determinant O (1) işlemidir, 5 ilavesiyle 6 çarpı kullanılır. Bu son bölüm optimize ettiğiniz şeydir; ancak bunu ek if testleri ekleyerek yaptınız. Standart olmayan bir yaklaşım benimsemeyi haklı gösteremiyorum - her adımın doğru olduğunu doğrulamak gerekir - Ama kadranların ilginç analizi için teşekkür ederiz!
ToolmakerSteve

0

İşte bu cevaba dayanan basit bir Python 3 uygulaması (bu da kabul edilen cevapta önerilen çözüme dayanmaktadır )

def is_clockwise(points):
    # points is your list (or array) of 2d points.
    assert len(points) > 0
    s = 0.0
    for p1, p2 in zip(points, points[1:] + [points[0]]):
        s += (p2[0] - p1[0]) * (p2[1] + p1[1])
    return s > 0.0

-4

bu noktaların kütle merkezini bul.

bu noktadan noktalarınıza doğru çizgiler olduğunu varsayalım.

line0 line1 için iki satır arasındaki açıyı bulma

line1 ve line2 için yapmaktan daha

...

...

eğer bu açı monoton olarak saat yönünün tersine artarsa,

monoton olarak azalansa saat yönündedir

başka (tekdüze değildir)

karar veremezsin, bu yüzden akıllıca değil


"kütle merkezi" ile "sanırım" centroid "demek istediniz?
Vicky Chijwani

Muhtemelen çokgen tamamen dışbükey ise çalışır. Ancak bunun yerine dışbükey olmayan çokgenler için işe yarayacak bir cevap kullanmak daha iyidir.
ToolmakerSteve
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.