-
.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.CODICEfloat 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 VBACODICEFunction 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 FunctionCODICEstruct vertici_poligono
{
float x[100];
float y[100];
int numv;
};
la stessa struttura di tipo in VBACODICEPublic Type vertici_poligono
x(0 To 100) As Double
y(0 To 100) As Double
numv As Integer
End Type. -
.CODICEvoid 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 VBACODICEFunction 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
. -
.
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 segueCODICESub 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:
Con offset negativo:
questo è il solito file di test al quale aggiungo le cose nuove
https://www.box.com/s/ldzels9ie0v92lld5yuv. -
francesco.coppola.
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:CODICEIf (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:CODICEIf (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.. -
francesco.coppola.
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.CODICEvoid 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.CODICEFunction 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.. -
.
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
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. -
francesco.coppola.
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:
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):
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". -
francesco.coppola.
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ì:CODICEif (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!. -
.
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
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.. -
.
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. -
.
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. -
.
ecco per esempio un pescepalla orario (lo si vede dall'ordine dei suoi primi due vertici) col suo offset esterno
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.
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. -
francesco.coppola.
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..... -
.
La funzione dedicata alla esecuzione dell'offset del poligono è la seguente: CODICEFunction 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. -
.
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
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..