Studio di una clotoide

Giorni fa mi sono imbattuto casualmente in un’equazione che mi ha incuriosito e che ho scoperto essere quella di una clotoide.
Si tratta in sostanza di una curva usata (anche) nell’ingegneria “stradale” per effettuare dei raccordi tra vari tratti di strada.
In particolare permette di passare da tratti con curvatura nulla a tratti con curvatura costante, in modo progressivo, evitando discontinuità.
Quando questi criteri sono rispettati, entrando in curva, l’accelerazione laterale aumenta gradualmente e pertanto la manovra è fluida, il veicolo entra in curva in modo naturale, a tutto a vantaggio del confort ma, soprattutto, della sicurezza.

Mi sono chiesto se, in ambiente Rhino, sia possibile disegnare una curva NURBS che presenti un andamento della curvatura il più possibile regolare e, idealmente, lineare.
Preciso subito che non intendo sostituirmi ai i metodi analitici formali per il calcolo della clotoide, né tanto meno pormi in concorrenza con eventuali algoritmi di minimizzazione degli errori o plugin specialistici che, immagino, siano disponibili sul mercato.
Si tratta semplicemente di cercare una metodologia euristica e consapevole per approssimare una spirale di transizione all’interno di un ambiente NURBS, sfruttando le proprietà intrinseche delle curve.
Sono abbastanza convinto che non si possano ricavare i CV di una NURBS come soluzione diretta delle equazioni della clotoide in forma chiusa.
Il motivo non è tanto l’implementazione ma è strutturale, visto che la clotoide è definita da integrali di Fresnel mentre una NURBS è una combinazione polinomiale.
La clotoide è definita rispetto alla lunghezza d’arco s, mentre una NURBS è parametrizzata in funzione di un parametro t.
Inoltre, la curva risultante non interpola il poligono di controllo ma lo “filtra”, introducendo una redistribuzione della curvatura, una sorta di filtro passa-basso spaziale.
Questo implica che una discretizzazione matematicamente rigorosa della clotoide non dovrebbe produrre necessariamente un andamento lineare del grafico di curvatura di Rhino.

Esempio pratico: Ipotizziamo di volere raccordare un tratto di strada rettilineo con un tratto curvilineo distante circa 100 metri con raggio di curvatura di 500 metri.
Anticipo qui i punti di controllo calcolati per chi non fosse interessato alla trattazione matematica.
Allego comunque le curve in formato R6.

P_0=(0,0)
P_1=(20,0)
P_2=(40,0)
P_3 = (59.98, 0.35)
P_4 = (79.99,1.33
P_5=(99.90,3.33)

Trattazione matematica:

Partiamo dalla clotoide.
Dal punto di vista teorico, è descritta da una curvatura lineare rispetto alla lunghezza d’arco \kappa(s) = a \cdot s
Integrando rispetto a s, si ottiene l’angolo della tangente \theta(s) = \frac{a}{2}s^2
Il parametro fondamentale della clotoide è A (vedi dopo).

Possiamo calcolare le coordinate esatte del punto finale e l’angolo della tangente, utilizzando gli integrali di Fresnel, ovvero dovremmo risolvere queste due “bestie grame”:

x(s) = \int_{0}^{s} \cos\left(\frac{t^2}{2A^2}\right) dt
y(s) = \int_{0}^{s} \sin\left(\frac{t^2}{2A^2}\right) dt

Trattandosi di integrali di Fresnel, sappiamo già che non esiste una funzione elementare che dia una soluzione esatta con formula chiusa.
Quindi o ci si affida ad algoritmi numerici o ci si affida allo zio Taylor! :smiling_face_with_three_hearts:
La serie di Taylor ci dice che ogni funzione “morbida” (continua e derivabile) può essere trasformata in un polinomio infinito, cioè una somma di potenze (x, x^2, x^3 \dots), e direi che è il nostro caso.
Per il coseno e il seno, le serie “madri” sono:

\cos(u) = 1 - \frac{u^2}{2!} + \frac{u^4}{4!} - \frac{u^6}{6!} + \dots
\sin(u) = u - \frac{u^3}{3!} + \frac{u^5}{5!} - \frac{u^7}{7!} + \dots

Sostituendo u con \frac{t^2}{2A^2} nella serie del coseno dovrebbe risultare una cosa del genere:

\cos\left(\frac{t^2}{2A^2}\right) = 1 - \frac{(\frac{t^2}{2A^2})^2}{2} + \dots = 1 - \frac{t^4}{8A^4} + \dots

L’integrale ora è abbordabile.

Per la X:

\int \left(1 - \frac{t^4}{8A^4}\right) dt = \left[ t - \frac{t^5}{5 \cdot 8A^4} \right] = {s - \frac{s^5}{40A^4}}

Per la Y:

Sostituiamo nella serie del seno: \sin(\frac{t^2}{2A^2}) \approx \frac{t^2}{2A^2}.

\int \left(\frac{t^2}{2A^2}\right) dt = \left[ \frac{t^3}{3 \cdot 2A^2} \right] = {\frac{s^3}{6A^2} }

Per un raccordo così “morbido” penso bastino (e avanzino) i primi due termini.
Certo che se la clotoide si arrotola su se stessa un po’ di volte penso occorra tirarsi dietro molti più termini, ma qui stiamo parlando di un raccordo autostradale e non un giochetto matematico.

In definitiva, SE&O mi risulta:

Parametro A della clotoide:

A = \sqrt{R \cdot s} = \sqrt{500 \cdot 100} \approx 223,6

Angolo finale (\theta):

\theta = \frac{s^2}{2A^2} = \frac{10.000}{100.000} = 0,1 \text{ radianti} \approx {5,73^\circ}

Quindi le coordinate target della nostra curva sono:

x \approx 99,90
y \approx 3,33

Ho ipotizzato che una bezier di quito grado possa essere sufficiente.
Per ottenere i punti di controllo per il raccordo con R=500 e s=100, ho seguito una logica a “vincoli a cascata”.
Ho scelto una distribuzione uniforme, un passo pari a 20 unità, per mantenere la parametrizzazione uniforme ed evitare che la curva acceleri bruscamente.
Il punto finale viene gratis: P_5=(99.90,3.33)
Se partiamo dall’origine del piano XY, con tangenza orizzontale, per avere una curvatura nulla occorre che i primi tre CV siano collineari.
Quindi, in maniera altrettanto semplice avremo che:

P_0=(0,0)
P_1=(20,0)
P_2=(40,0)

Per la tangente, sappiamo che l’angolo finale deve essere \theta = 5,73^\circ.
Quindi P_4 deve “tirare” la curva verso quell’angolo.
Lo posizioniamo lungo la retta che passa per P_5 con inclinazione 5,73^\circ.
Ma siccome la distanza |P_5 - P_4|, è regolata dalla stessa logica del passo 20, le coordinate sono presto calcolate:

  • x_4 = 99.90 - 20 \cdot \cos(5.73^\circ) \approx 79.99
  • y_4 = 3.33 - 20 \cdot \sin(5.73^\circ) \approx 1.33

Il problema si riduce quindi al calcolo del CV P_3
P_3 determina la linearità della curvatura.
Se lo mettiamo troppo alto, la curvatura crescerà troppo presto.
Se lo mettiamo troppo basso, la curva resterà piuttosto piatta e sarà costretta ad accelerare alla fine, cosa che stiamo cercando di evitare.
Ho calcolato la posizione di P_3 affinché l’angolo del segmento P_2-P_3 fosse circa 1/9 dell’angolo finale (secondo la serie dei numeri dispari 1, 3, 5 \dots che governa queste transizioni, vedi dopo), ottenendo:

P_3 = (59.98, 0.35)

Direi che, se vediamo il poligono di controllo come una successione di bracci, la loro rotazione totale deve seguire una progressione quadratica!
Abbiamo infatti visto che, in una clotoide, la curvatura \kappa aumenta linearmente con la lunghezza s.
Ma la curvatura è, per definizione, la rapidità con cui cambia l’angolo della tangente (\kappa = d\theta/ds).
Se la curvatura è lineare (\kappa \propto s),
Allora l’angolo della tangente deve essere quadratico (\theta \propto s^2).
In una curva di Bézier, sappiamo che la direzione della curva “insegue” la direzione dei segmenti del poligono.
Se vogliamo che la tangente della curva cresca col quadrato del tempo, dobbiamo far sì che anche gli angoli dei segmenti del poligono crescano seguendo una logica precisa.
Per ottenere una crescita “clotoidale” in una Bézier di grado 5, i segmenti del poligono non devono ruotare in modo uniforme ovviamente, altrimenti otterremmo un arco di cerchio, una curvatura costante.
Dobbiamo invece usare una progressione che mimi l’integrale del quadrato.
Abbiamo visto che, nella clotoide, l’angolo della tangente cresce con il quadrato della distanza (\theta \propto s^2).
Ma la differenza tra quadrati successivi di numeri interi segue la serie dei dispari:

1^2 - 0^2 = 1
2^2 - 1^2 = 3
3^2 - 2^2 = 5
4^2 - 3^2 = 7

In parole povere, nel costruire il poligono di controllo per la clotoide, dobbiamo “discretizzare” quella crescita quadratica dell’angolo.
Affinché la curva (che è il risultato di una media pesata continua) senta una crescita di curvatura lineare, i “comandi” che diamo tramite i bracci del poligono devono fornire step di rotazione che seguono questa progressione:

Angolo braccio P_0-P_1: 0
Angolo braccio P_1-P_2: 0 (fisso, per azzerare la derivata seconda all’inizio)
Angolo braccio P_2-P_3: \alpha (incremento di 1 unità)
Angolo braccio P_3-P_4: 3\alpha (incremento di 3 unità rispetto all’origine)
Angolo braccio P_4-P_5: 5\alpha (incremento di 5 unità rispetto all’origine

Va evidenziato che, questa proprietà, è esatta solo per “piccoli angoli” perché nella formula reale della curvatura di una Bézier il denominatore è notoriamente (\dot{x}^2 + \dot{y}^2)^{3/2}.
Quando gli angoli diventano grandi, la velocità della curva lungo il parametro t non è più costante, il denominatore inizia a pesare molto e la “magia” dei numeri dispari non basta più: la rampa di curvatura su Rhino inizierebbe a curvarsi (diventando una parabola o peggio).
Riassumendo, la serie dei dispari permette di fare ruotare i segmenti del poligono di controllo seguendo la differenza dei quadrati, così da “ingannare” la curva facendogli credere che sta seguendo una traiettoria a curvatura lineare.
Questa progressione “dolce” è ciò che inganna quindi la matematica della Bézier e la costringe a comportarsi circa come una clotoide.
Il grafico di curvatura dà soddisfazione, è quasi dritto e, con tutte le approssimazioni fatte e - soprattutto - con tutti i limiti del metodo, mi sembra un buon risultato.
Il raccordo, visto da Rhino, risulta essere G2 a meno di un piccolo errore.
Il raggio di curvatura reale è di di 488,36 contro il valore teorico di 500. :grinning_face:

Studio clotoide.3dm (34,6 KB)

ti faccio i complimenti ma ad un certo punto ho fuso il motore :exploding_head:

Grazie Lorenzo.
Purtroppo faccio sempre fatica a spiegarmi, mi spiace.
Se hai dubbi specifici chiedi pure che provo a essere più chiaro.

Grazie, Fabio !

Molto interessante … almeno quello che credo in qualche modo di capire …
:blush: :smile:

Ma in ogni caso ho gia’ il primo dubbio …

Mi sembrerebbe che questo discorso sia valido per una curva non-clamped, diciamo cosi’, cioe’ con i CV estremi che non ‘scendono’ sulla curva.
In questo caso invece, non credi che la pendenza dei vari tratti del poligono di controllo sia ‘falsata’ dalla parametrizzazione clamped, anche se in questo caso la differenza potrebbe essere trascurabile, data la curva piuttosto ‘piatta’ ?
Diciamo che e’ un dubbio ‘teorico’ …

M’associo al club.:rofl:


Ho fatto un tentativo ignorante partendo unicamente da questa tua frase:

(il resto ammetto che l’ho letto ma non capivo)


oook… è tardi XD

Molto interessante!

Ciao Emy.
Come sempre, le tue osservazioni sono estremamente acute e toccano uno dei punti più critici della teoria delle curve Bézier ovvero il rapporto tra la geometria del poligono e la velocità della curva lungo il parametro t.
Dal punto di vista teorico concordo appieno!
Stiamo parlando della non-linearità del parametro: in una Bézier, anche se i bracci del poligono sono di lunghezza costante, la velocità con cui la curva percorre lo spazio rispetto al parametro t NON è costante, a meno di non disegnare una banale retta.
Poiché la curvatura dipende dalla derivata seconda rispetto allo spazio (\kappa = \frac{d\theta}{ds}), se il rapporto tra t e s varia, la rampa di curvatura ‘teorica’ risulta inevitabilmente distorta.
Provo a riassumere il quesito: ‘Se la velocità di parametrizzazione \| \dot{C}(t) \| non è costante, la progressione quadratica applicata ai segmenti del poligono non può tradursi in una progressione perfettamente identica sulla curva’.

Ho capito bene?

Nel caso specifico della nostra clotoide di raccordo, ritengo che il metodo rimanga comunque valido per due motivi fondamentali.
Il primo è la questione dei “piccoli angoli”.
Per curve ‘piatte’ (angoli finali bassi, come i nostri 5.73°), lo scostamento tra il parametro t e l’ascissa curvilinea s è infinitesimo.
In questo regime, la Bézier si comporta quasi come una curva a velocità unitaria, e la ‘falsatura’ introdotta dai nodi estremali è, a mio avviso, numericamente trascurabile.
Il secondo è il fatto che il poligono di controllo funziona come filtro predittivo.
Non stiamo semplicemente disegnando segmenti, ma impostando ‘istruzioni di guida’ che la curva elaborerà.
Il poligono funge da filtro passa-basso spaziale: la logica dei numeri dispari (1, 3, 5) non pretende di essere una soluzione analitica esatta, ma agisce come una discretizzazione efficace della derivata seconda.
Il regime clamped ammorbidisce sicuramente l’influenza dei CV estremi ma, nella mia analisi, la struttura simmetrica e la progressione quadratica degli angoli dei bracci compensano la tendenza della curva a ‘spianare’ alle estremità.
Concordo quindi che il dubbio teorico sia corretto: se dovessimo ad esempio disegnare una clotoide che ruota di 90°, la non-linearità della parametrizzazione renderebbe il grafico della curvatura una parabola invece di una retta.
Ma per i raccordi stradali a raggio ampio, la serie dei dispari riesce a ‘ingannare’ la natura polinomiale della Bézier con una precisione che va ben oltre le necessità pratiche.
Del resto, il vantaggio del ‘clamped’ è evidente nell’allineamento di P_0, P_1, P_2: questo è l’unico modo per forzare la derivata seconda (e quindi la curvatura) a zero nell’origine.
Senza questo vincolo, perderemmo il controllo diretto sull’attacco del raccordo.
Il fatto che il raggio finale risulti 488 m invece di 500 m è la prova matematica di questa lieve ‘falsatura’ di parametrizzazione, ma nella pratica della modellazione NURBS, uno scarto così contenuto penso sia da considerarsi un successo.

PS

Visto che ho parlato del poligono di controllo come una sorta di filtro passa-basso spaziale - se lo ritrovo - posto un lavoro di qualche tempo fa dove avevo analizzato la risposta in frequenza di una Bezier al variare della posizione dei punti ci controllo.

Ciao Riccardo, è un problema postare la definizione?
Mi incuriosisce e, come sai, sono una pippa galattica con GH, per riscrivere la definizione partendo dalle immagini mi va via la mattinata! :rofl:

Bel post Fabietto, come sempre.

Diciamo che altrove se ne leggono pochi di questo tipo.

Solo una considerazione che porta a bomba sulle curve “SubD Friendly”. In cui non solo devi avere curvatura azzerata all’inizio ma anche derivata seconda annullata. Questo si ottiene geometricamente posizionando i primi tre CV con distanze 2/3 e 1/3.

Ciao Giuseppe, grazie. :blush:

Ottima osservazione sulle curve “amiche” delle sub-D.
Credo di intuire da dove venga il rapporto 1/3 2/3, quando avanzo un attimo di tempo vedo se riesco a formalizzare la cosa.
Sempre se la mia intuizione è corretta, quel rapporto vale solo per una distribuzione dei nodi uniforme, in caso contrario credo sia differente. :thinking:
Ovviamente è sempre fattibile, nella formula della curvatura “comanda” la derivata seconda, quindi basta azzerare quella.

Impeccabile direi!!!

Se ci metti mano fammi sapere. Aggiungere un approfondimento alla trattazione delle SubD sarebbe una cosa eccellente.

Ah, assolutamente no!
Non ci avevo pensato e avevo staccato che era tardi…
Ho corretto la definizione, il moltiplicatore era da mettere prima dell’operazione quadrato ^2 …

clotoide.gh (8,4 KB)
Il risultato del componente “Series” è lo spazio percorso (o un coefficiente proporzionale).
Ogni “passo” è pari al vettore “Unit X” , quindi lungo 1 unità. Cambiare la sua lunghezza cambia semplicemente l’intera scala del disegno finale, quindi l’ho ignorato e lasciato a 1.


È facile ri-creare il disegno assurdo, basta mettere valori troppo grandi nell’angolo e quindi l’angolo procede troppo velocemente rispetto all’avanzamento lineare, e quindi superando il mezzo giro di rotazione la figura inizia a “srotolarsi” e poi quando accumula abbastanza rotazione torna di nuovo ad arrotolarsi, e così via ciclicamente.
Diciamo che l’angolo “A” usato sul componente “Rotate” non dovrebbe mai superare 1/4 di giro rispetto all’angolo precedente…

clotoide 2.gh (10,4 KB)
Aggiunto ^ un secondo knob moltiplicato a 0.001 per variare lentamente i valori, divertente!

Questo a riguardo penso ti possa essere d’aiuto :raising_hands:

Ciao Daniil, ti ringrazio.
Ho letto velocemente ma non mi pare che sia spiegato il perché di quel famoso rapporto.
La mia idea era proprio di calcolarlo, capire in sostanza il perché.

Se scorri un po’ più giù Dale Lear della mcneel da una spiegazione della regola
Il topic non è troppo lungo basta che scorri un po’ giù e si vede

Non mi sembra.
Evidentemente non riesco a spiegarmi.
Nel post si dice che bisogna usare la regola 1/3 2/3 ma non si spiega il perchè.
Che sarebbe quello a cui pensavo io … perché proprio quel rapporto?
E vale sempre?
Appena ho un attimo provo a buttare giù qualcosa.

Mi ha fatto sorridere il posti di Cockey che dice a Dale che ha sbagliato! :rofl:

Ah, sinceramente non saprei dirti perché questo rapporto sia così, né se valga sempre… boh :joy::melting_face:
Sarebbe da chiederlo a Dale!
È fuori dalla mia portata: sinceramente faccio già fatica a seguire i tuoi post, che per me sono abbastanza complessi. Ma non è un problema tuo — è che le tue capacità matematiche sono molto superiori alle mie. Rimango comunque sempre affascinato nel vedere i tuoi topic.

Però alla fine lascia un file 3dm con delle curve: magari guardandole riesci a capirci qualcosa.

Fortunatamente per lui e sfortunatamente per noi, si sta godendo la meritata pensione.
Era un pilastro di Mcneel. :smiling_face_with_three_hearts:
Ma da alcune novità di R9, mi pare di capire che ci sono al lavoro menti pimpanti: alcuni comandi credo abbiano alle spalle un cambiamento significativo della matematica di gestione.

Ah cavolo, non lo sapevo. Ho sempre trovato molto interessanti le sue argomentazioni nel forum, non pensavo fosse andato in pensione… un vero peccato. Si vede che ne sa davvero tanto.

Così a memoria, ti ricordi cosa in specifico?

Una delle cose più notevoli che ho visto è che finalmente hanno deciso di svecchiare la gumball.
Più volte avevo sottolineato come un singolo UI-widget (la gumball) vincolato a una terna di frecce ortogonali, non fosse degno dell’epoca moderna.
Finalmente hanno fatto qualcosa e ora la classe gumball è molto più versatile, e la stanno implementanto in più comandi, tipo gli slider nuovi per grasshopper che quindi risultano interattivi sulle viewport… semplicemente fantastico.

ArrayAlongCrv

:wink: