Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Suggestion / Idea  Multiple user data and more flexible diffuse
#1
Currently, there is only one Vecotr4List userdata provided and all the terms of each vecotr4 will be manipulated by the diffuse. 
However, I want to use the userdata for different usages, like temperature and fluid number(like fluid1: water, fluid2: lava, fluid3: wine......) at the same time.
It will be great if you can either make diffuse a vector4 or make the userdata a List of vector4list with multiple diffuse. Really thanks! Sonrojado
Reply
#2
(18-11-2021, 06:02 AM)Chua Polar Wrote: Currently, there is only one Vecotr4List userdata provided and all the terms of each vecotr4 will be manipulated by the diffuse. 
However, I want to use the userdata for different usages, like temperature and fluid number(like fluid1: water, fluid2: lava, fluid3: wine......) at the same time.
It will be great if you can either make diffuse a vector4 or make the userdata a List of vector4list with multiple diffuse. Really thanks! Sonrojado

User data is intended to be used to store values that can vary wildly per-particle and change as a result of particle interaction, such as temperature, viscosity, color, etc.

Using it to store a "type" does not make sense, as many particles will share the exact same type (many "wine" particles, "lava", "water", etc) and there can be no intermediate values. Unity already contains a functionality for this: tags.

You can tag each emitter with the type of fluid it's emitting, then at runtime retrieve the type of a particle like this:
Code:
var type = solver.particleInActor[particleIndex].actor.tag;

This way the "type" is stored once for all particles of the same type, instead of storing it for every single one.

FYI, the reason why there's only 4 user data channels is because 4 is the length of the SIMD registers used to perform vector operations. This means operating with pairs of user data with 4 components is very efficient. In addition to this we know the data for particle n is at n*4 in the data arrays, making memory access efficient too. Using a variable size for the amount of user data channels would make both accessing the data and operating with it considerably less efficient.

Note that if you really want extra per-particle storage, nothing prevents you from declaring an array of the same size as the emitter's capacity and store your own data there. It can be as simple as floating point values, or as complex as per-particle structs/classes.
Reply
#3
Dedo arriba 
(18-11-2021, 09:44 AM)josemendez Wrote: User data is intended to be used to store values that can vary wildly per-particle and change as a result of particle interaction, such as temperature, viscosity, color, etc.

Using it to store a "type" does not make sense, as many particles will share the exact same type (many "wine" particles, "lava", "water", etc) and there can be no intermediate values. Unity already contains a functionality for this: tags.

You can tag each emitter with the type of fluid it's emitting, then at runtime retrieve the type of a particle like this:
Code:
var type = solver.particleInActor[particleIndex].actor.tag;

This way the "type" is stored once for all particles of the same type, instead of storing it for every single one.

FYI, the reason why there's only 4 user data channels is because 4 is the length of the SIMD registers used to perform vector operations. This means operating with pairs of user data with 4 components is very efficient. In addition to this we know the data for particle n is at n*4 in the data arrays, making memory access efficient too. Using a variable size for the amount of user data channels would make both accessing the data and operating with it considerably less efficient.

Note that if you really want extra per-particle storage, nothing prevents you from declaring an array of the same size as the emitter's capacity and store your own data there. It can be as simple as floating point values, or as complex as per-particle structs/classes.
Nice and clean explanation!!Thanks for it.

The original purpose of these questions of mine is that I want to realize some chemical reaction in obi, yet there are still a few obstacles for now:

0. If I use a custom array to store info of particles, how should I synchronize it with the particle index? Like when a particle goes to sleep or a new particle is emitted, will the previous indices be messed up? Is there a method to tell me the order changing?

1. As you say I can use an emitter to store particles hence implying the particle type, however, it seems trouble to instantiate a single new particle from an emitter using the function Emit(). I learned that the Emit can only specify the distance from the emitting surface, is that so? And if I want to emit one particle at a specifically 3d position, what should I do? To move the emitter and use the distance? Or Emit one particle, grab its ref and modify its position? Or grab a particle ref from the emit idle pool, wake it up, init it and modify its position? And should I use lifetime=0 to sleep particles, is there any other ways?

2. I notice the diffusion data can be spread among neighbor particles, so do you use spatial query to realize that, the query you provided? I would like to query every little volume in a cup to check whether there are particles in the same volume that will trigger some reaction and update them. Can I realize it performancely just like the diffusion spreading?
Reply
#4
(24-11-2021, 06:43 AM)Chua Polar Wrote: Nice and clean explanation!!Thanks for it.

You're most welcome Sonrisa

(24-11-2021, 06:43 AM)Chua Polar Wrote: 0. If I use a custom array to store info of particles, how should I synchronize it with the particle index? Like when a particle goes to sleep or a new particle is emitted, will the previous indices be messed up? Is there a method to tell me the order changing?

Whenever a new particle is emitted, the emitter's OnEmitParticle event is called. You can subscribe to this event and get the index of the particle that was just emitted. Similarly, there's a OnKillParticle event that provides the index of the particle being killed. You can use this to keep your array in sync.

(24-11-2021, 06:43 AM)Chua Polar Wrote: 1. As you say I can use an emitter to store particles hence implying the particle type, however, it seems trouble to instantiate a single new particle from an emitter using the function Emit(). I learned that the Emit can only specify the distance from the emitting surface, is that so?

Yes. Fluid emitters and regular particle emitters (I mean Unity's built-in particles, that do not interact with each other) have some important differences. Even though fluid is made of particles, they represent a continuous volume. This means that creating two particles very close together or on top of each other will cause a pressure spike (since they'd represent a volume with very high density) and would "explode" or ricochet off each other. So fluid particles must be very precisely placed to avoid these unwanted pressure spikes and a smooth flow.

For this reason, the Emit() function does not allow you to place particles *anywhere* you want. That would create many more problems than it solves. When you Emit(), the emission point is picked from the EmitterShape you're using. Since the points in an EmitterShape are guaranteed to have the correct spacing between them, this guarantees the particle will be emitted at a "free" position.

Note you can write your own ObiEmitterShapes, and create your own point distributions by overriding its GenerateDistribution() method. Be careful though to ensure points are at roughly "particleSize" distance from each other, for the reasons given above. Check the existing emitter shapes for reference. Remember: with great power comes great responsibility. Guiño

(24-11-2021, 06:43 AM)Chua Polar Wrote: And if I want to emit one particle at a specifically 3d position, what should I do? To move the emitter and use the distance?

You could move the emitter indeed, or you could create your own emitter shape(s) as described above. Which one is best (most comfortable) depends on what you use case is.

(24-11-2021, 06:43 AM)Chua Polar Wrote: Or Emit one particle, grab its ref and modify its position?

Yet another possibility! It all depends on your needs. You can hook to the emitter's OnEmitParticle event to do this as soon as the particle is emitted.

(24-11-2021, 06:43 AM)Chua Polar Wrote: Or grab a particle ref from the emit idle pool, wake it up, init it and modify its position?

If you want to be in full control of the particle's lifetime, yes. You might as well derive your own custom emitter component from ObiEmitter at this point, though. Tímido

(24-11-2021, 06:43 AM)Chua Polar Wrote: And should I use lifetime=0 to sleep particles, is there any other ways?

You can either set the lifetime to zero (in that case, the ObiEmitter will kill it at the start of the next step), or you can call KillParticle(index) passing the index of the particle in the emitter. That will kill it instantly. Note there's usually not much difference between both approaches.

(24-11-2021, 06:43 AM)Chua Polar Wrote: 2. I notice the diffusion data can be spread among neighbor particles, so do you use spatial query to realize that, the query you provided? 

Not *exactly* the queries exposed by the API, but close. During simulation, every particle needs to know about all neighbor particles within the smoothing radius (not just for diffusion, but for pressure, tension...pretty much everything). Making one query per particle would be wasteful, so all particles are inserted into a grid. Then for all particles inside each grid cell, particles in the neighboring cells are considered.

When you make a query using Obi's spatial query API, the same grid structure is reused. For instance if you query the contents inside a rectangle, only grid cells overlapping the rectangle are considered.


(24-11-2021, 06:43 AM)Chua Polar Wrote: I would like to query every little volume in a cup to check whether there are particles in the same volume that will trigger some reaction and update them. Can I realize it performancely just like the diffusion spreading?

There's two ways to do this: use collision callbacks with trigger colliders, or use a spatial query.

Every frame Obi calculates which colliders are in close proximity of a particle, create a contact between the particle and each collider, and then -for non-triggers- solve the contact. When you subscribe to collision callbacks, Obi gives you the list of contacts generated in the current frame. You can then use this information as you wish.

Spatial queries reuse the grid structure I explained above to determine which particles are close to the query volume. So both approaches reuse a lot of the stuff Obi needs to calculate anyway, which is a good thing because very little extra work is done.

Which to use depends on how often you need to know which particles are inside the volume, and how many volumes you need to check. If you need that every frame and have a lot of volumes, using collision callbacks&triggers will be more efficient. If you only what to check this once, or a few times, and there's just one or few volumes, using a query would be more convenient.

Let me know if I can help!

kind regards,
Reply
#5
(24-11-2021, 09:07 AM)josemendez Wrote: You're most welcome Sonrisa


Whenever a new particle is emitted, the emitter's OnEmitParticle event is called. You can subscribe to this event and get the index of the particle that was just emitted. Similarly, there's a OnKillParticle event that provides the index of the particle being killed. You can use this to keep your array in sync.


Yes. Fluid emitters and regular particle emitters (I mean Unity's built-in particles, that do not interact with each other) have some important differences. Even though fluid is made of particles, they represent a continuous volume. This means that creating two particles very close together or on top of each other will cause a pressure spike (since they'd represent a volume with very high density) and would "explode" or ricochet off each other. So fluid particles must be very precisely placed to avoid these unwanted pressure spikes and a smooth flow.

For this reason, the Emit() function does not allow you to place particles *anywhere* you want. That would create many more problems than it solves. When you Emit(), the emission point is picked from the EmitterShape you're using. Since the points in an EmitterShape are guaranteed to have the correct spacing between them, this guarantees the particle will be emitted at a "free" position.

Note you can write your own ObiEmitterShapes, and create your own point distributions by overriding its GenerateDistribution() method. Be careful though to ensure points are at roughly "particleSize" distance from each other, for the reasons given above. Check the existing emitter shapes for reference. Remember: with great power comes great responsibility. Guiño


You could move the emitter indeed, or you could create your own emitter shape(s) as described above. Which one is best (most comfortable) depends on what you use case is.


Yet another possibility! It all depends on your needs. You can hook to the emitter's OnEmitParticle event to do this as soon as the particle is emitted.


If you want to be in full control of the particle's lifetime, yes. You might as well derive your own custom emitter component from ObiEmitter at this point, though.  Tímido


You can either set the lifetime to zero (in that case, the ObiEmitter will kill it at the start of the next step), or you can call KillParticle(index) passing the index of the particle in the emitter. That will kill it instantly. Note there's usually not much difference between both approaches.


Not *exactly* the queries exposed by the API, but close. During simulation, every particle needs to know about all neighbor particles within the smoothing radius (not just for diffusion, but for pressure, tension...pretty much everything). Making one query per particle would be wasteful, so all particles are inserted into a grid. Then for all particles inside each grid cell, particles in the neighboring cells are considered.

When you make a query using Obi's spatial query API, the same grid structure is reused. For instance if you query the contents inside a rectangle, only grid cells overlapping the rectangle are considered.



There's two ways to do this: use collision callbacks with trigger colliders, or use a spatial query.

Every frame Obi calculates which colliders are in close proximity of a particle, create a contact between the particle and each collider, and then -for non-triggers- solve the contact. When you subscribe to collision callbacks, Obi gives you the list of contacts generated in the current frame. You can then use this information as you wish.

Spatial queries reuse the grid structure I explained above to determine which particles are close to the query volume. So both approaches reuse a lot of the stuff Obi needs to calculate anyway, which is a good thing because very little extra work is done.

Which to use depends on how often you need to know which particles are inside the volume, and how many volumes you need to check. If you need that every frame and have a lot of volumes, using collision callbacks&triggers will be more efficient. If you only what to check this once, or a few times, and there's just one or few volumes, using a query would be more convenient.

Let me know if I can help!

kind regards,

Thanks again. I think I get close to it. So the solution is like using a big amount of obi colliders to fill in a mesh and move with it, right?
Reply
#6
(25-11-2021, 09:06 AM)Chua Polar Wrote: Thanks again. I think I get close to it. So the solution is like using a big amount of obi colliders to fill in a mesh and move with it, right?

Yes. Note that depending on how fine your custom "grid" of colliders is, you may need a ton of them. Depending in what you need your "grid" to do, it could be much faster and simpler to just use an actual grid of your own. These are very easy and inexpensive to make: just iterate over all particle positions, divide their position by the cell width and convert to an int: this gives you the index of the cell in the grid in which the particles are.

Look for regular grid implementation tutorials online, there's quite a few:
https://hub.packtpub.com/how-do-your-own...detection/
http://johnmcroberts.com/index.php/2016/...detection/
Reply