Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Getting Direction of Increasing Userdata per-particle
#1
I'm trying to store Vector3 "directions" in Userdata to do per-particle pathfinding. This direction is calculated from userdata "Input", so together they fit into one Vec4. Custom code also allows multiple Vec4s per Particle.
I'm stuck and have debug vectors pointing in unexpected directions, and would like some advice before I go trying more things  Ángel
(PS: I may use Direction/Differential/etc to mean the same thing, pardon me)

In this video, userdata "Input" receives value of 1 at the particle nearest the Blue Cursor.
With target in the center, I expect all the Green Lines to point Inwards, and some sort of do but not quite....
At 0:10 I move the Cursor around in a Circle, and the expected radial symmetry is just not there...
Differential of Center Mass vs Input (Vectors) - YouTube

In this video I change between color debug for "Center of Mass" and "PlayerInputDifferential" (Particle colors are normalized vectors)
Note that: CenterMass has unique regions of Purple and Pink, and is very Stable.
But: PlayerInputDiff has Pink in both the Upper Left and Lower Right, and there's some Flickering.
Differential of Center Mass vs Input (Colors) - YouTube


Here's the important formula in the script below, where dataDelta is a float difference in Userdata PlayerInput between two particles.
It seems correct, but invovled guesswork and AI so who knows.
Code:
float4 oneDeltaA2B = dataDelta * distA2B / math.length(distA2B);

The custom multiple userdata per particle doesn't seem to be the problem, because all channels seem to dilute/diffuse fine.

Maybe the problem is having one Burst Job use the same userdata for two purposes at once: Regular Diffusion of "Direction" as Userdata AND +/- "Direction" based on another?

I've Tried:
- Considering the above dual-write, only diffuse one Vec4 per-particle and do not diffuse the Vec4 with this Direction in it. This resulted in all particles having Vector3.zero, with some randomish color near where Input userdata was being written. (However, may need to revisit this, because I errantly stopped "Input" channel from diffusing, so obviously "Direction" has no data to derive from elsewhere....)
- Instead of storing Vector3 in Userdata, create new Solver array Vector3[]. In debug color mode, they all seemed randomized (indexing error?). And where previously the +Y half of slime had some purple/blue shades, it seemed half of all directions were now missing. (I have no clue how batching works in Constraints, or why this change of indexing into Userdata Vector4[2*n] V.S. Solver Vector4[n] was off.)

(snip)

Modified UpdateDensitiesJob:
[Image: VszZAiQDHqpY.png?o=1]
Helpers
[Image: 6SSDgEfcwRCL.png?o=1]
[Image: uMLrqKG02hII.png?o=1]

PS: Automod makes pasting code very difficult. Maybe all the {} () [].
Pasting images directly in looks like it works, but Errors because the message is too long, and raw view shows raw data in there.
External image host works once I ctrl-click to copy image link instead of the link they gave me!
Reply
#2
(19-11-2023, 06:37 AM)slimedev Wrote: Here's the important formula in the script below, where dataDelta is a float difference in Userdata PlayerInput between two particles.
It seems correct, but invovled guesswork and AI so who knows.
Code:
float4 oneDeltaA2B = dataDelta * distA2B / math.length(distA2B);

All this formula does is normalize distA2B and multiply it by dataDelta. That is, get a vector in the direction of "distA2B" with "dataDelta" magnitude. However it will result in NaN values when distA2B is zero, which can happen when particles get close to each other.

A simpler and safer alternative:

Code:
float4 oneDeltaA2B = dataDelta * math.normalizesafe(distA2B);


(19-11-2023, 06:37 AM)slimedev Wrote: Maybe the problem is having one Burst Job use the same userdata for two purposes at once: Regular Diffusion of "Direction" as Userdata AND +/- "Direction" based on another?

I'm not too sure of what you're trying to do, to be honest. However you're seem to be writing userData's xyz values based on partial diffusion results of userdata.w (playerInputIndex.iInVec4?) which will yield undefined results.

You want to make sure the diffusion step has finished (which will only happen once all batches have finished UpdateDensitiesJob, so all particles have had a chance to gather/scatter userData values from/to their neighbors) and only then do some calculation based on the results.

kind regards,
Reply
#3
Thanks for the tip on normalizesafe. It will be battle-tested and smashed to pieces!
I removed the custom writes to userdata during the userdata job, which was sad because the blending did a fantastic job of smoothing out spazzy data. But yes that mid-job writing certainly seems unsafe. I want to keep it in BurstDensity because it's a great spot to handle all positions and userdata deltas, plus I'm scared of learning more about batchData and Constraints.

With some hacky help from Copilot, it seems about 90% there. Debug Vectors looks good - Possible to pause and cherrypick bad-direction lines, but oh well.
Obi userDirection vectors - YouTube

But Debug Colors are still SO FLASHY and random. Ideally, the per-particle colors look similar for CenterMass and InputDirection, since idle Input writes userdata near Center Mass. 
The good news is that moving around does show that colors are affected by direction of movement.
(Flashing Colors) Obi userDirection colors - YouTube

The hack solution involves checking a Dot Product on previous directions, then perhaps flipping the Direction*Data we are trying to add.
It seems like early interactions my bias later dot products, but I'm not sure.

Code:
            // Each particle tracks direction of increasing userdata
            // This is used to calculate the direction of the player input
            // Note: Directions seem to be NEGATIVE. But, Custom +A -B matches Obi +A -B. and changing the Dot product to be negative breaks it
            // so do *= -1 elsewhere on inputDirection
            void AddDirectionDiff(int solverIndex, float3 diffA2B)
            {
              // Track interaction count, so that when using Direction, we can ignore particles with few interactions
              float numInteractions = userDirection[solverIndex].w + 1;
              float3 particleDirection = userDirection[solverIndex].xyz;

              // Check if the differentialA2B points towards the higher data
              // Dot HACK-iness - Who knows what direction the first AB pair is?
              if (Vector3.Dot(diffA2B, particleDirection) < 0)
                diffA2B = -diffA2B; // Reverse if necessary

              // direction[index] += delta
              userDirection[solverIndex] = new float4(particleDirection + diffA2B, numInteractions);
            }

But anyways - it's ok that 10% of directions are wrong, if I just increase particle input force by another 20% Ángel

Attached are the Obi job and hacky helper, plus another job that Inverts a mysterious -1 bias. It also allows filtering InputDirections based on how many particles they interacted with, how much Input userdata is there, etc. In the job to add particle forces, I normalize the InputDirection.

Attachments:


Attached Files Thumbnail(s)
           
Reply
#4
Ok, now I understand better what you're trying to do: propagate a direction vector trough all particles using diffusion. Correct?

You seem to be relying on particles in a pair being in a specific order: they aren't, and the same pair might even swap its particles between frames. This is because particle pairs are created in parallel from multiple threads so depending on which thread runs first the order may change. The reason we don't care about this is that fluid interactions are symmetric, all we care about is the difference in data values between them.

This is Obi's code for diffusion:
Code:
float4 userDelta = (userData[pair.particleB] - userData[pair.particleA]) * diffusionSpeed;

// i've removed the volume ratios vA and vB for clarity
userData[pair.particleA] += userDelta;
userData[pair.particleB] -= userDelta;

Imagine userData is 0.3 for one particles and 0.7 for the other, using a diffusionSpeed of 0.5.

If particleB = 0.3 and particleA = 0.7:
userDelta will be -0.4 * 0.5 = -0.2.
particleA will end up with a userData value of 0.7 + (-0.2)  = 0.5
particleB will end up with a userData value of 0.3 - (-0.2) = 0.5

Now swap both particles:

userDelta will be 0.4 * 0.5 = 0.2.
particleA will end up with a userData value of 0.3 + 0.2  = 0.5
particleB will end up with a userData value of 0.7 - 0.2 = 0.5

As you can see, the order in which particles appear in the pair doesn't matter at all for diffusion as we arrive at the same result. The same behavior is true for all other fluid formulas (density, vorticity, viscosity, etc).

However, your formula for calculating the difference in directions does this:
Code:
float4 oneDeltaA2B = dataDelta * math.normalizesafe(distA2B);

dataDelta and distA2B will always have the same sign since they're both calculated by subtracting particleB data from particleA, so they're both either negative or positive. This is a problem when multiplying them together, because (+)*(+) = (+) and (-)*(-) = (+). Since your formula does not preserve the sign of distA2B, oneDeltaA2B will randomly point in the direction of distA2B or the opposite direction depending on particle ordering.

I'm not entirely sure what dataDelta is in your code, as it's a single float value. I assume this is the distance from the pathfinding goal?
Reply
#5
Very helpful userdata description, thank you.

Quote:dataDelta and distA2B will always have the same sign since they're both calculated by subtracting particleB data from particleA, so they're both either negative or positive. This is a problem when multiplying them together, because (+)*(+) = (+) and (-)*(-) = (+). Since your formula does not preserve the sign of distA2B, oneDeltaA2B will randomly point in the direction of distA2B or the opposite direction depending on particle ordering.
I think this is fine because I want the per-particle Direction to point towards Increasing userdata. If data decreases (-) towards a negative vector (-) then the Direction should point towards positive vector(+). If there is (-) towards (+), then now we do want to go towards (-).

Perhaps I need to store all of these direction*deltas for a later per-particle job, and that magically cleans up the vectors?

dataDelta is the diff of PlayerInput between particles A and B, read from each particle's userdata[1][0] (where [1] is the custom 2nd vec4 allocated for this particle's userdata)
The picture of the slime moving to the right shows colorized PlayerInput, with 1 (red) being written near the Blue VFX. PlayerInput fades to 0 (blue), and every particle is storing PlayerInput deltas with interacting particles.

The "Userdata details" window shows 12 enum items, 8 of which are addressable in Userdata, and 4 which are not. It also confirms that PlayerInput of any particle is at userdata[1][0]
Likewise, in the Densities job we target this userdata by setting iOfAnyVec4 = 1 and iInVec4 = 0.


Attached Files Thumbnail(s)
       
Reply