Il Bar dell'Ingegneria

Algoritmi: Offset di un poligono

« Older   Newer »
 
  Share  
.
  1.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    CITAZIONE (francesco.coppola @ 13/9/2012, 18:32) 
    Prima porzione di codice per il calcolo dell'azimuth di un segmento. (Mattone che ovviamente serve per il resto).
    In input 4 parametri: x1,y1 coordinate del primo punto; x2,y2 coordinate del secondo. Orientamento del segmento: da 1 a 2.

    CODICE
    float azimuth_segmento (float x1,float y1,float x2,float y2)
    {
    float azmth;
    float deltax,deltay;

    deltax=x2-x1;
    deltay=y2-y1;

    if (deltay!=0.0) azmth=atan(fabs(deltax)/fabs(deltay));
    else
          {
           if (deltax>0.0) azmth=1.570796; else azmth=4.712389;
          }

    if (deltay<0.0 && deltax>=0.0) azmth=3.141593-azmth; /* Segmento nel II quadrante */
    if (deltay<0.0 && deltax<=0.0) azmth=3.141593+azmth; /* segmento nel III quadrante */
    if (deltay>0.0 && deltax<0.0)  azmth=6.283185-azmth; /* Segmento nel IV quadrante */

    return(azmth);
    }


    Come vedete sono stato molto 'pedissequo'.
    D'altra parte sono andato a riprendere un vecchio libro di topografia che parlava ancora di tavole logaritmiche per ricavare il valore dell'arcotangente del rapporto dei valori assoluti di deltax e deltay (il fabs() dentro cui sono contenuti nella funzione atan()), e dal segno dei quali poi ricavare il quadrante in cui si trova effettivamente il segmento orientato, con il relativo aggiustamento angolare.

    La stessa funzione in VBA
    CODICE
    Function azimuth_segmento(x1 As Double, y1 As Double, x2 As Double, y2 As Double) As Double

    Dim azmth As Double
    Dim deltax As Double, deltay As Double
    Dim pi As Double
    pi = 4 * Atn(1)

    deltax = x2 - x1
    deltay = y2 - y1

    If deltay <> 0 Then
      azmth = Atn(Abs(deltax) / Abs(deltay))
    Else
         
       If (deltax > 0) Then
           azmth = pi / 2
       Else
           azmth = 3 * pi / 2
       End If
    End If

    If (deltay < 0 And deltax >= 0) Then azmth = pi - azmth ' /* Segmento nel II quadrante */
    If (deltay < 0 And deltax <= 0) Then azmth = pi + azmth ' /* segmento nel III quadrante */
    If (deltay > 0 And deltax < 0) Then azmth = 2 * pi - azmth ' /* Segmento nel IV quadrante */

    azimuth_segmento = azmth
    End Function


    CITAZIONE (francesco.coppola @ 14/9/2012, 11:58) 
    CODICE
    struct vertici_poligono
    {
    float x[100];
    float y[100];
    int numv;
    };

    la stessa struttura di tipo in VBA

    CODICE
    Public Type vertici_poligono
       x(0 To 100) As Double
       y(0 To 100) As Double
       numv As Integer
    End Type
     
    Top
    .
  2.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    CITAZIONE (francesco.coppola @ 14/9/2012, 11:58) 
    CODICE
    void offset_poligono(float dist,int flag)
    {
    int register k;
    int km1,kp1;
    float azm1,azm2,azmb,alfa;
    float dist1;

    for (k=1;k<=polig1.numv;k++)
         {
           if (k==1) km1=polig1.numv; else km1=k-1;
           if (k==polig1.numv) kp1=1; else kp1=k+1;
           
           azm1=azimuth_segmento(polig1.x[k],polig1.y[k],polig1.x[km1],polig1.y[km1]);
           azm2=azimuth_segmento(polig1.x[k],polig1.y[k],polig1.x[kp1],polig1.y[kp1]);
           azmb=(azm1+azm2)/2;

           alfa=fabs(azmb-azm2);
           dist1=flag*dist/sin(alfa);
           
           polig2.x[k]=polig1.x[k]+dist1*sin(azmb);
           polig2.y[k]=polig1.x[k]+dist1*cos(azmb);
         }
    polig2.numv=polig1.numv;
    }

    ecco la stessa funzione in VBA


    CODICE
    Function offset_poligono(PolyOrigine As vertici_poligono, ByRef PolyOffsettato As vertici_poligono, _
                           dist As Double, flag As Integer) As Boolean
                           
    Dim k As Integer
    Dim km1 As Integer, kp1 As Integer
    Dim azm1 As Double, azm2 As Double, azmb As Double, alfa As Double
    Dim dist1 As Double

    For k = 1 To PolyOrigine.numv
       If k = 1 Then km1 = PolyOrigine.numv Else km1 = k - 1
       If k = PolyOrigine.numv Then kp1 = 1 Else kp1 = k + 1
       azm1 = azimuth_segmento(PolyOrigine.x(k), PolyOrigine.y(k), PolyOrigine.x(km1), PolyOrigine.y(km1))
       azm2 = azimuth_segmento(PolyOrigine.x(k), PolyOrigine.y(k), PolyOrigine.x(kp1), PolyOrigine.y(kp1))
       azmb = (azm1 + azm2) / 2
       
       alfa = Abs(azmb - azm2)
       dist1 = flag * dist / Sin(alfa)
       
       PolyOffsettato.x(k) = PolyOrigine.x(k) + dist1 * Sin(azmb)
       PolyOffsettato.y(k) = PolyOrigine.y(k) + dist1 * Cos(azmb)
    Next
    PolyOffsettato.numv = PolyOrigine.numv
    offset_poligono = True
    End Function

     
    Top
    .
  3.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Per provare la funzione di Francesco riscritta in VBA (sperando di non aver commesso errori nell'interpretare un codice scritto in un linguaggio che poco conosco), ho predisposto un foglio di calcolo.

    Nel foglio ho inserito dei range che ho chiamto come segue:
    POPoly il gruppo di celle contenenti i vertici del poligono
    POPolyOffsettato il gruppo di celle destinato a ricevere i vertici del poligono ottenuto dall'offset
    POoff la cella dove si inserisce il valore dell'offest
    POFLAg la cella dove si inserisce il flag come definito da Francceso.

    Ho inserito quindi un pulsante e legato al codice che segue

    CODICE
    Sub AvviaOffsetPoly()

    Dim i As Long
    Dim distanza As Double
    Dim Of_Set As Integer

    Dim nPointPoly As Long
    nPointPoly = Range("POpoly").Rows.Count

    Dim PoliGono1 As vertici_poligono
    Dim PoliGono2 As vertici_poligono

    For i = 1 To nPointPoly
       PoliGono1.x(i) = Range("POPoly").Cells(i, 1).Value
       PoliGono1.y(i) = Range("POPoly").Cells(i, 2).Value
    Next
       PoliGono1.numv = nPointPoly

    distanza = Range("POoff").Value
    Of_Set = Range("POflag").Value

    If offset_poligono(PoliGono1, PoliGono2, distanza, Of_Set) Then
       For i = 1 To nPointPoly
           Range("POPolyOffsettato").Cells(i, 1).Value = PoliGono2.x(i)
           Range("POPolyOffsettato").Cells(i, 2).Value = PoliGono2.y(i)
       Next
       Range("POPolyOffsettato").Cells(nPointPoly + 1, 1).Value = PoliGono2.x(1)
       Range("POPolyOffsettato").Cells(nPointPoly + 1, 2).Value = PoliGono2.y(1)
    End If


    End Sub


    Il codice esegue le seguenti operazioni.

    - legge il range dei vertici del poligono e li mette nella variabile PoliGono1 che ho definito essere di tipo vertici_poligono
    - legge il valore dell'offset e lo mette nella variabile distanza
    - legge il valore del flag e lo mette nella variabile Of_Set
    - definisce una variabile (vuota) PoliGono2 anchessa di tipo vertici_poligono che sara riempita dalla funzione

    - infine chiama la funzione offset_poligono(PoliGono1, PoliGono2, distanza, Of_Set) che restituisce sempre valore vero

    alla funzione vengono passate le variabili precedentemente definite, e restituisce il valore verodopo aver riempito la variabile Poligono2 con i vertici del poligono offsettato

    Il codice si completa con la trascrizione dei vertici del poligono offsetttao nel range di cella denominato Range("POPolyOffsettato")

    Qualcosa mi è andato storto ed ottengo una cosa del genere:

    Con offset positivo:
    offset001

    Con offset negativo:
    offset002

    questo è il solito file di test al quale aggiungo le cose nuove

    https://www.box.com/s/ldzels9ie0v92lld5yuv
     
    Top
    .
  4. francesco.coppola
        +1   -1
     
    .

    User deleted


    Si afazio, come ti dico in chat, qualcosa nel codice comunque non va.

    Intanto ho aggiunto qualche uguale di troppo nelle condizioni di controllo dei segni di deltax nel calcolo dell'azimuth dei segmenti.

    La prima condizione:

    CODICE
    If (deltay < 0 And deltax >= 0) Then azmth = pi - azmth ' /* Segmento nel II quadrante */


    Togli l'uguale e lascia il maggiore, e già buona parte delle 'stranezze' spariscono, ovvero:

    CODICE
    If (deltay < 0 And deltax > 0) Then azmth = pi - azmth ' /* Segmento nel II quadrante */


    Per un rettangolo i punti generati vanno tutti bene, tranne per lo spigolo in basso a destra. Rendendo negativo il segno di dist1, per quel punto in particolare, la cosa si risolve.
     
    Top
    .
  5. francesco.coppola
        +1   -1
     
    .

    User deleted


    Atteso che quanto ti avevo suggerito prima serve a risolvere eventuali piccoli disguidi con lati verticali, e quindi è da fare, ho trovato - pare - il bandolo della matassa.

    Bisogna semplicemente aggiungere una sola riga nella funzione prima riportata.
    In tale riga bisogna sommare un intero angolo giro all'azimuth del primo segmento, se questo giace nel I o II quadrante e contemporaneamente il secondo segmento giace nel III o IV quadrante.

    CODICE
    void offset_poligono(float dist,int flag)
    {
    int register k;
    int km1,kp1;
    float azm1,azm2,azmb,alfa;
    float dist1;

    for (k=1;k<=polig1.numv;k++)
        {
          if (k==1) km1=polig1.numv; else km1=k-1;
          if (k==polig1.numv) kp1=1; else kp1=k+1;
         
          azm1=azimuth_segmento(polig1.x[k],polig1.y[k],polig1.x[km1],polig1.y[km1]);
          azm2=azimuth_segmento(polig1.x[k],polig1.y[k],polig1.x[kp1],polig1.y[kp1]);
         
          if (azm1<=3.141593 && azm2>=3.141593) azm1+=6.283185; /* Riga aggiuntiva 'risolutrice' */

          azmb=(azm1+azm2)/2;

          alfa=fabs(azmb-azm2);
          dist1=flag*dist/sin(alfa);
         
          polig2.x[k]=polig1.x[k]+dist1*sin(azmb);
          polig2.y[k]=polig1.x[k]+dist1*cos(azmb);
        }
    polig2.numv=polig1.numv;
    }


    Provo adesso ad inserire tale riga nel listato VBA. Afazio che mi legge provvederà eventualmente a correggere.

    CODICE
    Function offset_poligono(PolyOrigine As vertici_poligono, ByRef PolyOffsettato As vertici_poligono, _
                          dist As Double, flag As Integer) As Boolean
                         
    Dim k As Integer
    Dim km1 As Integer, kp1 As Integer
    Dim azm1 As Double, azm2 As Double, azmb As Double, alfa As Double
    Dim dist1 As Double

    For k = 1 To PolyOrigine.numv
      If k = 1 Then km1 = PolyOrigine.numv Else km1 = k - 1
      If k = PolyOrigine.numv Then kp1 = 1 Else kp1 = k + 1
      azm1 = azimuth_segmento(PolyOrigine.x(k), PolyOrigine.y(k), PolyOrigine.x(km1), PolyOrigine.y(km1))
      azm2 = azimuth_segmento(PolyOrigine.x(k), PolyOrigine.y(k), PolyOrigine.x(kp1), PolyOrigine.y(kp1))

      If (azm1<=3.141593 And azm2>=3.141593) Then azm1=azm1+6.283185;

      azmb = (azm1 + azm2) / 2
     
      alfa = Abs(azmb - azm2)
      dist1 = flag * dist / Sin(alfa)
     
      PolyOffsettato.x(k) = PolyOrigine.x(k) + dist1 * Sin(azmb)
      PolyOffsettato.y(k) = PolyOrigine.y(k) + dist1 * Cos(azmb)
    Next
    PolyOffsettato.numv = PolyOrigine.numv
    offset_poligono = True
    End Function


    Io ho fatto qualche prova, stavolta con esito assolutamente corretto e soddisfacente. Ma non si sa mai.
     
    Top
    .
  6.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Ho apportato le correzioni precise precise indicate ma ancora qualcosa qui non và.

    Prima di procedere dobbiamo sincronizzare le arcotangenti

    mi occorre sapere cosa restituisce la funzione atan(y/x) di C e confrontarla con cosa restituisce la funzione Atn(dy/dx) di VBA
    al variare dei segni di dx e di dy.

    Per questo posto una tabella in cui per fissati valori assoluti dx=2 e dy=1 facendone variare i segni elle quattro combinazioni possibili, riporto i seguenti valori:

    valore restituito dalla funzione Atn(dy/dx) di VBA e lo stesso espresso in gradi sessadecimali
    valore restituito dalla funzione Arctan(numero) di foglio (con numero=dy/dx) e lo stesso espresso in gradi sessadecimali
    valore restituito dalla funzione Arctan2(dx,dy) di foglio e lo stesso espresso in gradi sessadecimali
    valore restituito dalla mia funzione rivisitazione di Atn e lo stesso espresso in gradi sessadecimali

    arcotangente

    la mia funzione sarebbe questa:

    CODICE
    '----------------------------------------------------------------------
    ' la funzione arcotangente restituisce il valore dell'angolo
    ' dati di sue valori deltax e deltay ciascuno col proprio segno
    '----------------------------------------------------------------------
    Function arcotangenteXY(dx As Double, dy As Double) As Double
    Dim pi As Double
    pi = 4 * Atn(1)
    If dx = 0 Then
       arcotangenteXY = Sgn(dy) * pi / 2
       If arcotangenteXY < 0 Then arcotangenteXY = 2 * pi + arcotangenteXY
    Else
       arcotangenteXY = Atn(dy / dx)
       If (dx) < 0 Then arcotangenteXY = arcotangenteXY + pi
       If arcotangenteXY < 0 Then arcotangenteXY = 2 * pi + arcotangenteXY
    End If
    End Function


    Intanto tutto il lavorio sugli azimangoli fatto in occasione di questo 3d mi è servito ad apportare qualche correzione alla mia funzione ed estenderla con diversi valori di input. E cosi, oltre alla originaria funzione
    Function arcotangente(X_i As Double, Y_i As Double, X_f As Double, Y_f As Double) As Double
    che calcolava l'angolo date le coordinate dei due estremi di un vettore
    ho anche scritto le seguenti
    Function arcotangenteVettore(Segmento As TipoSegmento) As Double
    che calcola l'angolo di un vettore dato da un segmento
    Function arcotangenteXY(dx As Double, dy As Double) As Double
    che calcola l'angolo dati dx e dy

    ponendolo sempre tra 0 e 360 gradi
     
    Top
    .
  7. francesco.coppola
        +1   -1
     
    .

    User deleted


    Afazio, effettivamente pensavo di averci messo la pezza 'definitiva'.
    Una precisazione è che io sto operando con poligoni sicuramente orari. Tu forse no, e questo comporta quindi qualche differenza nella 'pezza'.
    Ma una semplice L manda in crisi l'algoritmo. Il vertice interno della zampina (quello che in comune con l'anima interna) viene 'sparato' fuori dal poligono. (Ma solamente se la L presenta il lato verticale o appartenente al I quadrante), insomma guarda un risultato:

    jpg

    Perchè in quel caso l'orientamento della bisettrice, corretto in origine, viene modificato dalla riga 'pezza'.
    Oppure questo con esito positivo perchè il lato 'verticale' giace nel IV quadrante):

    jpg

    Riguardo i valori di atan sono esattamente quelli che riporti tu. Ma aggiungo che apposta nella funzione avevo inserito i valori assoluti di deltax e deltay. In modo da ottenere sempre un risultato positivo di atan, da correggere poi per ottenere tutti i possibili angoli, ma sempre positivi e contenuti nell'angolo 2*pi.

    Insomma la mia funzione azimuth_segmento restituisce esattamente i valori in tabella specifici della "funzione afazio"
     
    Top
    .
  8. francesco.coppola
        +1   -1
     
    .

    User deleted


    Afazio, fatta qualche uteriore prova.

    Adesso non voglio cantare vittoria come ieri sera, ma mi pare proprio che dovremmo esserci.

    Bisogna modificare semplicemente la riga 'pezza'. Così:

    CODICE
    if (azm1<azm2) azm1+=6.283185;


    Ovvero non è semplicemente un problema di angolo tra quadranti I-II e III-IV, ma semplicemente che azm1 deve sempre essere maggiore di azm2.

    Attento però!

    Questa condizione implica, e a questo punto ne sono sicuro, che il poligono sia certamente orario.
    Se il poligono fosse antiorario è probabile che la condizione debba essere al contrario, ossia che azm2 sempre maggiore di azm1 e se questo non accadesse sommare l'angolo giro a azm2 piuttosto che ad azm1.

    Ho fatto tutte le prove, sezioni ad L, ad L specchiate, sezioni a 'casaccio', ecc. pare tutto funzioni. Ma aspetto una tua conferma.

    Ah. Ho pure provato con 3 vertici allineati. Perfetto!
     
    Top
    .
  9.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Già

    Ho deciso di adattare il tuo codice (tanto hai spiegato cosi bene la filosofia che mi è stato facile) ricorrendo pero' alla mia funzione che valuta gli angoli rispetto ad x e non ad y.
    Adesso le cose funzionano ancora non perfettamente
    quando il poligono è orario ad un valore negativo di flag corrisponde un offset esterno
    quando il poligono è antiorario ad un valore negativo di flag corrisponde un offset interno

    loraria

    lantioraria

    a questo punto, allora le cose si aggiusterebbero se all'interno della funzione moltiplicassimo il flag dato in input per il segno dell'area del poligono (che si calcola in un battibaleno con apposita funzione)

    Che ne dici?

    Post Scriptum: io ancora non ho introdotto icontrolli di congruità del poligono dato in input.
     
    Top
    .
  10.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Penso di avere risolto la faccenda. A breve aggiorno il file di test e descrivo le nuove funzioni scritte.
    Alcune di queste riguardano anche altri topic aperti sulle questioni degli algoritmi.

    ecco la versione aggiornata

    https://www.box.com/s/ldzels9ie0v92lld5yuv
     
    Top
    .
  11.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Intanto per poter condurre diversi test sia su poligoni definiti in senso orario che per poligoni definiti in senso antiorario, per evitare la noia di introdurre manualmente nuove coordinate, ho avuto necessita di crearmi una sub che inverte il senso di un poligono gia definito

    La sub è semplice:
    CITAZIONE
    '----------------------------------------------------
    ' la funzione InvertiPoligonClock esegue l'inversione del senso di definizione
    ' del poligono passato come argomento attraverso la lista dei suoi vertici
    ' cioè, se il poligono è definito in senso orario viene trasformato
    ' nello stesso poligono ma definito in senso antiorario con uguale punto iniziale
    ' e viceversa
    '----------------------------------------------------------
    Sub InvertiPoligonClock(ByRef poligon() As TipoPunto)
    Dim i As Long
    Dim j As Long

    ReDim PolyTemp(LBound(poligon) To UBound(poligon)) As TipoPunto
    PolyTemp = poligon
    j = LBound(poligon)
    For i = UBound(poligon) To LBound(poligon) + 1 Step -1
    j = j + 1
    poligon(j) = PolyTemp(i)
    Next
    End Sub
    '---------------------------------------------------------------

    La sub accetta come argomento un poligono definito attraverso i suoi vertici strutturati come da definizione del tipo TipoPunto
    e restituisce lo stesso poligono con uguale punto iniziale ma definito in senso opposto
     
    Top
    .
  12.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    ecco per esempio un pescepalla orario (lo si vede dall'ordine dei suoi primi due vertici) col suo offset esterno

    fishorario

    trasformato un un pescepalla antiorario mediante la pressione del tasto "Inverti Clok poligono" a cui è stato associato il codice che richiama la sub sopra scritta.

    fishanti

    Nota: i diversi valori dell'area dei due pesci palla sono dovuti al fatto che tra le due operazioni ho cambiato qualche coordinata

    Per discernere da codice se un poligono è stato definito in senso orario, ho scritto la funzione che segue:


    CODICE
    '----------------------------------------------------
    ' la funzione IsClockwise restituisce il valore Vero se il
    ' poligono passato come argomento è definito in senso orario
    '---------------------------------------------------------
    Function IsClockwise(poligon() As TipoPunto) As Boolean
     IsClockwise = Sgn(AreaPoly(poligon)) = -1
    End Function


    questa funzione richiama la funzione che calcola l'area del poligono e ne estraee solo il segno, ponendosi a Vero se il segno è negativo e a falso altrimenti.
    Questo significa (e se ne dovrà tenere conto) che se il poligono ha area nulla la funzione mi restituisce Falso.

    La funzione che mi calcola l'area di un poligono invece è la seguente:

    CODICE
    '----------------------------------------------------
    ' la funzione AreaPoly restituisce il valore dell'area con segno
    ' del poligono passato come argomento attraverso la lista dei
    ' suoi vertici
    '----------------------------------------------------------
    Function AreaPoly(poligon() As TipoPunto) As Double
    Dim i As Long
    Dim k As Long
    Dim area As Double
    area = 0
    For i = LBound(poligon) To UBound(poligon)
       k = i + 1
       If i = UBound(poligon) Then k = LBound(poligon)
       area = area + 0.5 * (poligon(i).x * poligon(k).y - poligon(k).x * poligon(i).y)
    Next
    AreaPoly = area
    End Function
     
    Top
    .
  13. francesco.coppola
        +1   -1
     
    .

    User deleted


    Ok, afazio. Avevo intuito bene.
    L'algoritmo funziona con poligoni orari, ma funzionerebbe lo stesso, con un piccolo adattamento nel caso di poligoni anti orari.
    A dirti la verità ho appena sostituito la 50-ina di righe della mia precedente funzione (piena di if, di mirabolanti cambi di segno, ecc., in una parola: incomprensibile) con questa dozzina di semplici righe. L'ho sempre utilizzata per ottenere sezioni cave, posizionare armature-reggistaffe a copriferro, armature di 'pelle'.

    Ho sempre a che fare con dei poligono orari, poichè anche se inserisco i vertici a casaccio, proprio dal controllo dell'area, inverto internamente al programma la numerazione in modo che il poligono sia sempre orario.
    Insomma ho la funzione che avresti pubblicato in altro post.

    Altra cosa che provvedo a fare è evitare di avere due vertici sovrapposti. In questo caso non so cosa restituirebbe la funzione di calcolo dell'azimuth (deltax=0 e deltay=0) ammesso abbia senso ancora parlare di azimuth per un segmento che....non esiste.
    Visto che la funzione ... funziona, sarebbe il caso di passare dalla pentola anche ai coperchi. Ovvero di cose che possono non andare per il verso giusto ce ne sono ancora parecchie. Ma le voglio affrontare dopo. Per adesso...

    Piccola dissertazione su questa attività programmatoria
    Chi ci legge potrebbe pensare (forse con ragione): "ma questi sono squilibrati forte!"
    Io di professione faccio l'ingegnere e non il programmatore. Penso proprio che quest'ultima professione non riuscirei a farla. Di fatto riesco a trasformare in codice, una formula o una procedura delle 'nostre'. Tutto qui. Un hobby che mi da soddisfazione.

    Come dice afazio, ogni volta che si affronta un problema, anche quello a prima vista banale, si 'apre' un ventaglio di cose che non erano affatto considerate, e che bisogna però per forza affrontare se si vuole portare a casa qualcosa di funzionante.
    Sembra frustrante, ed in effetti lo è. Programmare è però un utile esercizio. Io la definisco una immersione in un "bagno di umiltà".

    Il vaglio cui sottoponi il tuo listatino è dei più severi che si possa provare sulla propria pelle. Convinti di aver fatto bene, di aver considerato tutto, il "deve per forza funzionare" si scontra con l'inevitabile realtà di un sistema che se le cose non sono esattissimamente come devono essere ti boccia inesorabilmente e senza pietà.
    Per fortuna si può ritentare l'esame ogni volta che si vuole.
    Durante la digitazione di un qualsiasi pezzetto di codice il vostro cervello andrà al 110% per l'attenzione che metterete nella stessa digitazione, nel tentativo di prevedere l'esito delle istruzioni che state introducendo.

    Un effetto collaterale di questa attenzione concentrata io la ritrovo quando scrivo in italiano. Faccio molto meno errori di digitazione di un tempo. Inoltre, ma questa mi sembra una 'degenerazione' da bimbo autistico, sono in grado di intercettare con estrema facilità un errore di ortografia/battitura in qualsiasi cosa leggo, alla velocità della luce. Insomma avrei un futuro garantito come correttore di bozze.

    Come dicevo ad inizio di questo lungo topic, il problema dell'offset lo avevo affrontato più di 10 anni fa. Rabberciato ma funzionante, non mi aveva mai del tutto soddisfatto. Insieme a tanti altri problemi invece provati e riprovati senza esito positivo. Alcuni li ho successivamente risolti o migliorati di botto, inaspettatamente, semplicemente rivedendoli e/o cambiando prospettiva (come in questo specifico caso).
    Ti arrivano delle folgorazioni quando meno te lo aspetti. Sotto la doccia, appena ti risvegli al mattino...

    E si, forse un pò di squilibrio....
     
    Top
    .
  14.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    La funzione dedicata alla esecuzione dell'offset del poligono è la seguente:

    CODICE
    Function offset_poligono_afazio(PolyOrigine() As TipoPunto, ByRef PolyOffsettato() As TipoPunto, _
                           dist As Double, flag As Double) As Boolean
                           
    Dim k As Integer
    Dim k1 As Integer, k2 As Integer
    Dim azm1 As Double, azm2 As Double, azmb As Double, alfa As Double
    Dim dist1 As Double
    Dim pi As Double

    pi = 4 * Atn(1)

    If IsClockwise(PolyOrigine) Then flag = -flag

    For k = LBound(PolyOrigine) To UBound(PolyOrigine)
       k1 = k - 1
       k2 = k + 1
       
       If k = LBound(PolyOrigine) Then
           k1 = UBound(PolyOrigine)
           k2 = k + 1
       End If
       If k = UBound(PolyOrigine) Then
           k1 = k - 1
           k2 = LBound(PolyOrigine)
       End If
       
       azm1 = arcotangente(PolyOrigine(k).x, PolyOrigine(k).y, PolyOrigine(k1).x, PolyOrigine(k1).y)
       azm2 = arcotangente(PolyOrigine(k2).x, PolyOrigine(k2).y, PolyOrigine(k).x, PolyOrigine(k).y)
       
       azmb = (azm1 + azm2 - pi) / 2
       
       alfa = (azmb - azm2)
       dist1 = flag * dist / Sin(alfa)
       
       PolyOffsettato(k).x = PolyOrigine(k).x + dist1 * Cos(azmb)
       PolyOffsettato(k).y = PolyOrigine(k).y + dist1 * Sin(azmb)
    Next
    offset_poligono_afazio = True
    End Function


    che commento di seguito:

    la struttura della funzione:

    Function offset_poligono_afazio(PolyOrigine() As TipoPunto, ByRef PolyOffsettato() As TipoPunto, _
    dist As Double, flag As Double) As Boolean

    PolyOrigine() As TipoPunto è il poligono originario definito attraverso la semplice lista dei suoi vertici. Non c'e' limite, se non il massimo valore di un long, al numero di vertici (mentre usando la struttura adottata da Francesco, saremmo condizionati dal valore che imponiamo per i vettori all'atto della definizione della struttura stessa). E' naturale che la variabile deve essere riempita prima della chiamata della funzione e di questo se ne fa carico il programma chiamante

    ByRef PolyOffsettato() As TipoPunto questa variabile rappresenta anch'essa un poligono, viene passata alla funzione per riferimento ed inizialmente è praticamente vuota. Penserà la funzione a riempirla con la lista dei vertici del poligono offsettato. Uscendo dalla funzione, se tutto è andato a buon fine, il programma chiamante si troverà il piatto pieno e ne farà quel che vorrà.

    dist As Double e la distanza che dovrà esserci tra i lati dei poligono originario e del suo offsettato

    flag As Double è il parametro il cui segno ci indica se vogliamo un offset interno o esterno al poligono ed il cui valore serve come moltiplicatore della variabile dist. Se non vogliamo che dist sia amplificata o ridotta dal parametrro flag, daremo a quest'ultimo il valore 1 o -1.
    Se il parametro flag è positivo allora l'offset sarà esterno mentre se è negativo allora l'offset sarà verso l'interno


    Valore restituito dalla funzione
    Function offset_poligono_afazio(....) As Boolean
    La funzione restituisce un valore boolean, cioè vero o falso. Nel caso in esame avendo ipotizzato che comunque l'operazione dell'offset va a buon fine, allo stato attuale la funzione restituisce sempre un valore True (Vero). Ma è destinato a poter variare a falso nel seguito dello sviluppo della funzione quando saranno aggiunti i controlli di congruenza del poligono originario. Pensate per esempio che il programma chimante passi un poligono vuoto, o un poligono formato da soli due punti o ancora uno formato da soli tre punti allineati. Il controllo di queste condizioni potrà rendere falso il risultato restituito dalla funzione.

    Definizione della variabili locali di appoggio:
    Segue la parte di codice con la dichiarazione della variabili locali, cioè quelle che nascono e muoiono con la funzione
    Dim k As Integer
    Dim k1 As Integer, k2 As Integer
    Dim azm1 As Double, azm2 As Double, azmb As Double, alfa As Double
    Dim dist1 As Double
    Dim pi As Double
    pi = 4 * Atn(1)

    Qui la variabile k rappresenta il punto k-esimo esaminato mentre k1 e k2 rappresentano rispettivamente il suo precedente ed il suo successivo nella lista dei vertici del poligono originario, mentre tutta la serie di azm sono gli angoli formati dai due lati del poligono (con il loro orientamento immutato) che convergono al vertice k-esimo rispetto all'asse delle x, azmb è l'angolo formato dalla bisettrice mentre alfa è l'angolo formato dai due lati che convergono al vertice k e definito automaticamente dal verso di percorrenza del poligono (quindi esterno o interno a seconda che il poligono è orario o antiorario).
    Infine dist1 è la comune proiezione della distanza tra i lati convergenti al vertice k sulla bisettrice.

    Il primo controllo

    If IsClockwise(PolyOrigine) Then flag = -flag
    La prima operazione che esegue la funzione è quella di determinare se il poligono è definito in senso orario o antioario attraverso la chiamata della funzione IsClockwise (riportata in altra parte del topic). La funzione IsClockwise restituisce vero se il senso di definizione è orario ed in tal caso cambio semòlicemente il segno al parametro flag passato come parametro.
    Questo serve per far eseguire sempre un offset verso l'interno o verso l'esterno indipendentemente dal fatto che il poligono e' in orario o in ritardo.

    Il ciclo per il popolamento della variabile che contiene il poligono offsettato:

    For k = LBound(PolyOrigine) To UBound(PolyOrigine)
    k1 = k - 1
    k2 = k + 1

    il ciclo in k esamina tutti i vertici del poligono originario e per ciascuno di essi servono il suo precedente ed il suo successivo.
    Intanto i limiti di variazione del contatore sono determinati direttamente dalla dimensioni del poligono passato e potendo essere definito in maniera generica (non per forza partire da 0 o da 1 ma anche, per esempio, da 12 a 39) inizio il ciclo dall'0indice basso del vettore (Lbound) e lo termino nell'indice alto (Ubound)
    fisso che il vertice precedente ha indice k-1 e il successivo ha indice k+1. Ma quando il contatore K assume uno dei due valori estremi, il successivo o il precedente cosi definiti andrebbero fuori dal range.

    Ed allora ecco a seguire la parte di codice che corregge queste eventualità

    If k = LBound(PolyOrigine) Then
    k1 = UBound(PolyOrigine)
    k2 = k + 1
    End If
    If k = UBound(PolyOrigine) Then
    k1 = k - 1
    k2 = LBound(PolyOrigine)
    End If

    che però potremmo abbreviare nel seguente:

    If k = LBound(PolyOrigine) Then k1 = UBound(PolyOrigine) End If
    If k = UBound(PolyOrigine) Then k2 = LBound(PolyOrigine) End If


    A questo punto abbiamo gli indici dei tre punti che formano i due segmenti da esaminare al passo k-esimo
    k punto in esame
    k1 il suo precedente
    k2 il suo successivo

    La determinazione degli angoli:

    Seguono le istruzioni che mi determinano gli angoli dei due segmenti orientati che afferiscono al vertice k, dell'angolo formato dalla bisettrice rispetto all'asse x, e dell'angolo compreso tra i due lati
    in particolare:
    azm1 angolo formato dal "lato precedente" orientato da k a k1
    azm1 angolo formato dal "lato successivo" orientato da k2 a k

    azm1 = arcotangente(PolyOrigine(k).x, PolyOrigine(k).y, PolyOrigine(k1).x, PolyOrigine(k1).y)
    azm2 = arcotangente(PolyOrigine(k2).x, PolyOrigine(k2).y, PolyOrigine(k).x, PolyOrigine(k).y)
    azmb = (azm1 + azm2 - pi) / 2
    alfa = (azmb - azm2)

    Da notare che l'operazione di "riorientamento di uno dei due lati" discussa con Francesco qualche messaggio fà è praticamente inclusa nella formula azmb = (azm1 + azm2 - pi) / 2 attraverso sottrazione dell'angolo piatto ad uno qualsiasi dei due angoli.

    Calcolo coordinate del vertice k offsettato

    Lultima parte del codice sono le tre righe:
    dist1 = flag * dist / Sin(alfa)
    PolyOffsettato(k).x = PolyOrigine(k).x + dist1 * Cos(azmb)
    PolyOffsettato(k).y = PolyOrigine(k).y + dist1 * Sin(azmb)

    con la prima si calcola la proiezione della distanza sulla bisettrice (moltiplifcata per il valore del flag passato alla funzione e poi corretto in base all'orientamento del poligono) e con le successive le coordinate di k'

    Chiude la funzione l'istruzione
    offset_poligono_afazio = True
    con la quale attribuiamo il valore Vero alla funzione che viene restituito al programma chiamante.

    Quindi il programma chiamante si trova come risultato della chiamata il Valore booleano True ed un piatto pieno di punti.


    Edited by afazio - 15/9/2012, 17:51
     
    Top
    .
  15.     +1   -1
     
    .
    Avatar

    Advanced Member

    Group
    Administrator
    Posts
    8,163
    Reputation
    +294

    Status
    Offline
    Una curiosità

    MI sono divertito a far variare l'entità dell'offset sia positivamente che negativamente. Ho notato che al crescere dell'offset e cambiando ogni volta il segno del flag, i due poligono offsettati tendono alla medesima forma ma una doppiamente speculare dell'altra

    curiosita01

    curiosita02

    pensandoci dopo aver fatto le prove concludo che non poteva essere altrimenti, ma se mi fosse stata posta la domanda a freddo prima di vedere i risultati con le prove non avrei saputo rispondere.

    Nota: li al centro, piccolo piccolo ci sta sempre il pesce palla.
     
    Top
    .
61 replies since 13/9/2012, 12:54   2164 views
  Share  
.