Un Strange Attractor es un artilugio matemático, un sistema que evoluciona, empezamos con un punto inicial y calculamos el siguiente, y el siguiente, y el siguiente, paso a paso. El resultado de esta evolución son figuras hermosas llamadas Strange Attractor.
Vamos a desarrollarlo en UE4, será la excusa perfecta para estudiar como trabajar con Splines en el motor.
Splines en UE4
Trabajar con Splines en UE4 es realmente sencillo. Disponemos del componente USplineComponent.
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "StrangeAttractor")
class USplineComponent* SplineComponent;
Vamos a crear un spline que aproxime la figura del Strange Attractor llamado DeQuan Li.
¿Cuáles son las ecuaciones que determinan, partiendo de un punto $P = (x, y, z)$, el siguiente punto?
Para el caso de DeQuan Li son las siguientes:
$$ \frac{dx}{dt} = A (y - x) + D x z $$
$$ \frac{dy}{dt} = K x + F y - x z $$
$$ \frac{dz}{dt} = C z + x y - E x ^ 2 $$
Dónde $A, D, K, F, C, E$ son parámetros cuyos valores podemos variar para crear distintas figuras.
Para otros Stange Attractor son otras ecuaciones.
Obviamente $dt$ es un infinitesimal que no podemos implementar tal cuál. En vez de eso, lo aproximaremos por un valor muy pequeño:
$$ dt = 0.00001 $$
La figura que acompaña este post está generada con los siguientes valores:
$$ P_0 = (0.349, 0, -0.16) $$
$$ A = 40, C = 1.8333, D = 0.16, E = 0.65, K = 55, F = 20 $$
Dónde $P_o$ es el punto inicial.
En UE4 podemos encapsular todos los parámetros que definen el Strange Attractor en una estructura:
Y en el constructor de nuestro actor:
Aquí el método que genera el Spline:
El bucle interior simplemente es para evitar que existan dos puntos consecutivos excesivamente juntos, como mínimo que el siguiente punto que añadamos al Spline tenga una separación de 50 unidades.
Como ves, la API de SplineComponent es trivial: con ClearSplinePoints borras todos los puntos previamente añadidos y con AddSplinePoint añades nuevos puntos.
En este caso en concreto, el método BuildSpline puede ser costoso. Para aliviar la carga, podemos aprovechar las capacidades multihilo que proporciona UE4:
Creado el Spline la siguiente pregunta de rigor es: ¿qué podemos hacer con un Spline?
Crear un mesh a partir de un Spline
Podemos crear una geometría a partir de un Spline.
Para ello nos apoyamos de otro componente: USplineMeshComponent.
El componente SplineMeshComponent está limitado a Splines de dos puntos.
En concreto, toma un static mesh, un punto y tangente inicial y un punto y tangente final. Entonces distorsiona el mesh de forma que siga este spline de dos puntos.
¿Cómo podemos usar este componente para construir un spline completo de más de 2 puntos? Fácil, crearemos un componente usando los primeros dos puntos $P_0$, $P_1$, construiremos otro nuevo componente para $P_1$ y $P_2$, otro nuevo para $P_2$, $P_3$, y así hasta $P_{n-1}$, $P_n$.
La primera tentación sería crear los componentes con:
CreateDefaultSubobject<USplineMeshComponent>(TEXT("..."))
Sin embargo este método solo es posible usarlo en el constructor. Está pensado para componentes "fijos", en el sentido de que son componentes que no construiremos de manera dinámica a lo largo del tiempo de ejecución.
Pero precisamente esto es lo que necesitamos con SplineMeshComponent, necesitamos crearlo fuera del constructor y de manera dinámica.
Para ello tenemos a nuestra disposición la siguiente función:
USplineMeshComponent* SplineMesh = NewObject<USplineMeshComponent>(this);
SplineMesh->RegisterComponent();
Dicho esto, este es el método para construir el mesh:
Seguir un Spline
Además de construir un mesh, también podemos hacer que un componente siga un spline.
Para ilustrarlo, vamos a hacer que un NiagaraComponent siga el spline.
UCLASS()
class STANGEATTRACTORS_API ASSpline : public AActor
{
GENERATED_BODY()
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "StrangeAttractor")
class UNiagaraComponent* ParticleSystemComponent;
public:
ASSpline();
// ...
private:
float SplineStep;
bool bSplineBuilt;
};
La API es bastante sencilla. Toma como un parámetro un float llamado "Time" que va de 0 a SplineComponent->GetSplineLength() y devuelve la posición correspondiente en el Spline.
Referencias
En esta web encontrarás muchas definiciones de Strange Attractor y sus parámetro usados: