Calculating Velocity/Rotation from manually driven Texture Positions in Niagara?

I’m working on a highly optimized custom ECS / Data-Oriented flocking framework in C++ capable of running 150,000+ boids. To render this, I am calculating all the math in C++ memory arenas and pushing the data to Niagara via a DMA Texture Bridge (UpdateTextureRegions).

The Setup: In Niagara, I use a Sample Texture module (reading UVs via ExecutionIndex) to grab the exact 3D world coordinate for each boid, and then I use a Set Parameters module to directly set Particles.Position = SampledPosition. This effectively teleports the particles every frame. The translation works flawlessly and runs incredibly fast.

The Problem: I am trying to get the meshes to orient to their direction of travel. Because I am manually teleporting them and bypassing Solve Forces and Velocity, Niagara doesn’t automatically generate a velocity vector for them.

What I’ve Tried: I tried to calculate the velocity manually by creating a frame-delay line using a custom Particles.OldPos vector. The logic was:

  1. Cache the old position (Particles.OldPos = Particles.Position).
  2. Sample the new texture position (Particles.Position = SampledTexture).
  3. Calculate the flight path (Facing Direction = Position - OldPos).

The Result: The boids spawn and seem to rotate initially, but then they become permanently locked in that rotation. Depending on where I place the OldPos module in the stack, they either lock facing radially outward from the center of the map, or directly inwards. They do not actively update their rotation as they fly around.

My Question: Is there a specific way this needs to be ordered in the Niagara stack, or a specific namespace (Previous) that must be used? My theory is that the Niagara HLSL compiler might be aggressively reordering the modules or optimizing out the OldPos variable into a same-frame alias of Position, causing the delta math to break or evaluate to (0,0,0).

Does anyone know the proper, standard way to calculate finite difference (Position - PreviousPosition) for manually teleported particles in Niagara? I know the foolproof solution is to just generate a second Velocity Texture in C++ and pass it in, but I am trying to keep things as hyper-optimized as possible and avoid the extra PCIe/VRAM overhead if Niagara is capable of just calculating the math natively on the GPU.

Thanks in advance!

"I found a previous thread suggesting to store the Last Position and Current Position to calculate Velocity (link), but when I try to implement this using Set Parameter modules, the Niagara compiler seems to alias the variables. The math evaluates to exactly (0,0,0), causing the Orientation module to lock to its default fallback rotation. - How can I manually calculate Particle Velocity in Niagara - #2 by BRS_GerDAngelo

since you teleport the particles, Niagara never integrates anything, so Particles.Velocity stays dead. compute velocity yourself and feed it to orientation — with your architecture the clean answer is to never compute it in Niagara at all:

  1. do the difference in C++. you already hold the position arenas for frame N and N-1. before uploading, compute v = (posN - posN-1) * invDt per boid and write it into a second DMA texture channel (or pack it into the region layout). in Niagara the same Sample Texture setup reads channel 0 for position and channel 1 for velocity. no frame-delay state machine inside the emitter, no ordering hazards, deterministic, and it costs nothing on the GPU side — one more texture fetch per particle. this also fixes the why of your OldPos attempt failing: inside the particle update stack, OldPos written in the same pass as the teleport usually gets sampled before/after in a way that collapses the diff to zero or garbage, and stacking a one-frame delay next to Set Parameters that overwrite Position is an ordering trap. the C++ diff sidesteps all of it.
  2. orientation: same logic — build the quaternion in C++ where you own the math. velocity direction + your bank/pitch rules → quat → upload as a third/fourth channel → set Particles.MeshOrientation from it after the sample. engine-side “align to velocity” nodes will fight teleported particles since they derive rotation from integrated velocity that never exists.
  3. if you insist on engine-side anyway: the pattern that works is copying Position to OldPos in the Emitter Update stage (runs before particle update), then in particle update doing the diff with Engine.DeltaTime — but you must keep your Set Parameters teleport after the velocity module in the stack, and the sample of OldPos happens on the frame-delayed copy. it works but it is fragile and you are maintaining ordering that breaks on every emitter refactor.
    given 150k+ boids and an ECS host, option 1 + 2 is the right call: treat Niagara purely as a renderer of uploaded state, which is what it has become in your setup anyway.