Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Bézier, curve e percorsi
Le curve di Bézier sono un'approssimazione matematica delle forme geometriche naturali. Le utilizziamo per rappresentare una curva con il minor numero di informazioni possibile e con un elevato livello di flessibilità.
A differenza dei concetti matematici più astratti, le curve di Bézier sono state create per il design industriale. Sono uno strumento popolare nell'industria dei software di grafica.
Si basano sull'interpolazione, che abbiamo visto nell'articolo precedente, combinando più passaggi per creare curve lisce. Per capire meglio come funzionano le curve di Bézier, partiamo dalla loro forma più semplice: la Bézier quadratica.
Bézier quadratica
Prendi tre punti, il minimo richiesto affinché la Bezier quadratica funzioni:
Per tracciare una curva tra di essi, interpoliamo prima gradualmente trai due vertici di ciascuno dei due segmenti formati dai tre punti, utilizzando valori compresi tra 0 e 1. Ciò ci fornisce due punti che si muovono lungo i segmenti man mano che modifichiamo il valore di t da 0 a 1.
func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
private Vector2 QuadraticBezier(Vector2 p0, Vector2 p1, Vector2 p2, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
}
Interpoliamo poi q0 e q1 per ottenere un singolo punto r che si muove lungo una curva.
var r = q0.lerp(q1, t)
return r
Vector2 r = q0.Lerp(q1, t);
return r;
Questo tipo di curva è chiamata curva di Bézier quadratica.
(Credito dell'immagine: Wikipedia)
Bezier cubica
Partendo dall'esempio precedente, possiamo ottenere un più controllo interpolando tra quattro punti.
Per prima cosa utilizziamo una funzione con quattro parametri per prendere quattro punti come input, p0, p1, p2 e p3:
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
public Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
}
Applichiamo un'interpolazione lineare a ciascuna coppia di punti per ridurli a tre:
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
Poi prendiamo i nostri tre punti e li riduciamo a due:
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
E a uno:
var s = r0.lerp(r1, t)
return s
Vector2 s = r0.Lerp(r1, t);
return s;
Ecco l'intera funzione:
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
var s = r0.lerp(r1, t)
return s
private Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
Vector2 s = r0.Lerp(r1, t);
return s;
}
Il risultato sarà una curva liscia che interpola tra tutti e quattro i punti:
(Credito dell'immagine: Wikipedia)
Nota
L'interpolazione di Bezier cubica funziona allo stesso modo in 3D, basta utilizzare Vector3 invece di Vector2.
Aggiunta di punti di controllo
Basandoci sulla Bézier cubica, possiamo modificare il modo in cui due dei punti funzionano per controllare liberamente la forma della nostra curva. Invece di avere p0, p1, p2 e p3, li memorizzeremo come:
point0 = p0: è il primo punto, la sorgentecontrol0 = p1 - p0: è un vettore relativo al primo punto di controllocontrol1 = p3 - p2: è un vettore relativo al secondo punto di controllopoint1 = p3: è il secondo punto, la destinazione
In questo modo, abbiamo due punti e due punti di controllo che sono vettori relativi ai rispettivi punti. Se hai già utilizzato software di grafica o animazione, questo potrebbe sembrarti familiare:
Ecco come i software di grafica presentano le curve di Bézier agli utenti, e come funzionano e appaiono in Godot.
Curve2D, Curve3D, Path e Path2D
Ci sono due oggetti che contengono curve: Curve3D e Curve2D (rispettivamente per 3D e 2D).
Possono contenere più punti, consentendo di avere percorsi più lunghi. È anche possibile impostarli sui nodi: Path3D e Path2D (altrettanto rispettivamente per 3D e 2D):
Tuttavia, il loro utilizzo potrebbe non essere del tutto evidente. Pertanto di seguito si descrivono i casi d'uso più comuni per le curve di Bézier.
Valutarle
Valutarle e basta può essere un'opzione, ma nella maggior parte dei casi non è molto utile. Il grande difetto delle curve di Bézier è che se le si attraversa a velocità costante, da t = 0 a t = 1, l'interpolazione effettiva non si muoverà a velocità costante. Anche la velocità è un'interpolazione tra le distanze tra i punti p0, p1, p2 e p3 e non esiste un modo matematicamente semplice per attraversare la curva a velocità costante.
Facciamo un esempio con il seguente pseudocodice:
var t = 0.0
func _process(delta):
t += delta
position = _cubic_bezier(p0, p1, p2, p3, t)
private float _t = 0.0f;
public override void _Process(double delta)
{
_t += (float)delta;
Position = CubicBezier(p0, p1, p2, p3, _t);
}
Come puoi vedere, la velocità (in pixel al secondo) del cerchio varia, anche se t è aumentata a velocità costante. Questo rende le curve di Bézier difficili da utilizzare direttamente per scopi pratici.
Disegnare
Disegnare curve di Bézier (o oggetti basati su di esse) è un caso d'uso molto comune, sebbene non sia facile. Praticamente in ogni caso, le curve di Bézier devono essere convertite in una sorta di segmenti. Questo, tuttavia, è solitamente difficile, senza creare moltissimi segmenti.
La ragione è che alcune sezioni di una curva (in particolare gli angoli) potrebbero richiedere una quantità considerevole di punti, mentre altre sezioni potrebbero non richiederne:
Inoltre, se entrambi i punti di controllo fossero 0, 0 (ricorda che sono vettori relativi), la curva di Bézier sarebbe semplicemente una linea retta (quindi disegnare un numero elevato di punti sarebbe uno spreco).
Prima di disegnare le curve di Bézier, una tassellazione è necessaria. Questa operazione è spesso eseguita attraverso una funzione ricorsiva (o "dividi et impera") che suddivide la curva finché il valore di curvatura non scende al di sotto di una certa soglia.
Le classi Curve forniscono ciò tramite la funzione Curve2D.tessellate(), la quale riceve argomenti facoltativi per le fasi di ricorsione (stages) e la tolleranza angolare (tolerance). Disegnare qualcosa basato su una curva è più facile così.
Percorrerle
L'ultimo caso d'uso comune per le curve è percorrerle. A causa di quanto menzionato prima riguardo alla velocità costante, anche questo è difficile.
Per facilitare il processo, le curve devono essere pre-calcolate in punti equidistanti. Facendo così, è possibile approssimarle con un'interpolazione regolare (che può essere ulteriormente migliorata con un'opzione cubica). Per farlo, basta usare il metodo Curve3D.sample_baked() insieme a Curve2D.get_baked_length(). La prima chiamata a uno dei due eseguirà il pre-calcolo interno della curva.
Una traversata a velocità costante si può quindi effettuare con il seguente pseudocodice:
var t = 0.0
func _process(delta):
t += delta
position = curve.sample_baked(t * curve.get_baked_length(), true)
private float _t = 0.0f;
public override void _Process(double delta)
{
_t += (float)delta;
Position = curve.SampleBaked(_t * curve.GetBakedLength(), true);
}
E il risultato si muoverà quindi a velocità costante: