- Come ordinare i vertici di un poligono in senso orario-
|
|||
COSA SERVE PER QUESTO TUTORIAL | |||
Download | Chiedi sul FORUM | Glossario | cognizioni basiche di C# e sul framework XNA | ||
Ordinamento dei vertici con un po' di geometria | |||
ORDINARE PUNTI DI UN POLIGONO Perché è utile ordinare i punti di un poligono e come si può farlo.
Nel
precedente tutorial avevamo visto come era possibile muoversi
all'interno di un poligono convesso e verificare se un punto selezionato
dall'utente si trovasse all'interno o all'esterno. Tuttavia, per
questioni di semplicità, avevamo dovuto imporre che i punti del poligono fossero
forniti dall'utente in senso orario; questa è una grande limitazione che
ci proponiamo di eliminare in questo tutorial. L'ordinamento in
particolare ci servirà nel prossimo articolo per poter lavorare con
scioltezza con i poligoni risultanti dalle intersezioni di due oggetti
di cui si intende rilevare la collisione. protected override void Update(GameTime gameTime) { // [...] // Se nel precedente Update il tasto sinistro del mouse era premuto e ora non lo è più if (lastMouseState.LeftButton == ButtonState.Pressed && ms.LeftButton == ButtonState.Released) { // Punto in cui si è fatto click Vector2 ptCurrent = new Vector2(ms.X, ms.Y); // Se non è già presente tra i vertici if (!vertexes.Contains(ptCurrent)) { // Aggiungiamo il punto cliccato alla lista dei vertici vertexes.Add(ptCurrent); // Creiamo un ClockwiseComparer tramite il quale ri-ordiniamo // la lista ClockwiseComparer cmpClockwise = new ClockwiseComparer(vertexes); vertexes.Sort(cmpClockwise); } } // [...] // Memorizziamo lo stato del mouse corrente lastMouseState = ms; base.Update(gameTime); } Non dobbiamo far altro che inizializzare la nostra classe ClockwiseComparer passandogli la lista di punti da ordinare e quindi invocare il metodo Sort sulla lista di vertici perché vengano ordinati secondo i criteri stabiliti da ClockwiseComparer.
LA CLASSE DI ORDINAMENTO IN SENSO ORARIO Vediamo come si presenta la classe che ci permette di ordinare i vertici del poligono: // Classe che implementa IComparer, di cui ci serviremo per // ordinare i vertici in senso orario public class ClockwiseComparer : IComparer<Vector2> { // Variabile che conterrà il centro del poligono private Vector2 center; public Vector2 VertexCenter { get { return center; } } // Primo punto del poligono che useremo come riferimento // rispetto al quale calcolare tutti gli angoli per poi // poter ordinare. private Vector2 firstPoint; // Lunghezza del primo segmento, ovvero distanza del // centro dal primo punto private float firstSegmentLength; public ClockwiseComparer(IList<Vector2> ptVertexes) { // Calcoliamo il centro come media di tutti i punti foreach (Vector2 pt in ptVertexes) { center.X += pt.X / ptVertexes.Count; center.Y += pt.Y / ptVertexes.Count; } // Memorizziamo il primo punto, che useremo come riferimento firstPoint = ptVertexes[0]; // Calcoliamo la distanza del primo punto dal centro, ci servirà // per poter applicare il teorema del coseno (o teorema di Carnot) this.firstSegmentLength = Distance(center, firstPoint); } // Funzione statica che calcola la distanza tra due vettori utilizzando // il teorema di Pitagora static private float Distance(Vector2 pt1, Vector2 pt2) { return (float)Math.Sqrt(Math.Pow((pt1.X - pt2.X), 2) + Math.Pow((pt1.Y - pt2.Y), 2)); } // Implementiamo la funzione di confronto che servirà per ordinare la lista // di vertici public int Compare(Vector2 x, Vector2 y) { // Un vertice viene prima di un altro in senso orario // se l'angolo che forma con il segmento di riferimento // è minore di quello dell'altro punto return GetAngle(x).CompareTo(GetAngle(y)); } public float GetAngle(Vector2 pt) { // [...] } }
Per prima cosa notiamo che implementa IComparer<Vector2>, il che
significa che deve contenere un metodo Compare che restituisca un valore
positivo, negativo o nullo a seconda che il primo punto sia maggiore,
minore o uguale del secondo, seguendo un certo criterio. In questo caso
restituirà +1 se il primo punto viene dopo in senso orario o
-1 se viene
prima. Il metodo List.Sort potrà grazie a questo metodo ordinare e gli
elementi della lista e restituirceli in ordine crescente. public float GetAngle(Vector2 pt) { // Teorema di Carnot: // a^2 = b^2 + c^2 - 2 * b * c * cos(alpha) // b^2 + c^2 - a^2 // cos(alpha) = --------------- // 2 * b * c // Prepariamo le variabili che servono per il teorema float a, b, c; // Distanza del primo punto del poligono dal punto corrente a = Distance(firstPoint, pt); // Distanza del primo punto del poligono dal centro b = this.firstSegmentLength; // Distanza del punto corrente dal centro c = Distance(pt, center); // Applichiamo il teorema del coseno float cos = (float)(Math.Pow(b, 2) + Math.Pow(c, 2) - Math.Pow(a, 2)) / (2 * b * c); // cos è compreso tra -1 e +1, aggiungiamo 1 in modo che sia // compreso tra 0 e +2 cos++; // Decidiamo il segno in base alla posizione rispetto al segmento // di riferimento, a seconda che siamo prima o dopo in senso orario. // Per fare questo vediamo se l'angolo compreso tra il punto corrente, // il primo punto del poligono e il centro è positivo o negativo, tramite // il prodotto vettore (cross-product) if (SortedConvexPolygonGame.CrossProduct(pt - firstPoint, center - firstPoint) > 0) cos *= -1; return cos; } Per prima cosa chiariamo che GetAngle non restituisce il valore di un angolo in gradi, radianti o altro, ma semplicemente un valore compreso tra -2 e +2 proporzionale all'angolo, calcolato come segue:
Il risultato, come detto, è un valore compreso tra -2 e +2 proporzionale all'angolo formato, ma non l'angolo esatto, poiché abbiamo evitato di utilizzare funzioni trigonometriche inverse inutilmente.
|
|||
<< INDIETRO | by VeNoM00 |