24-11-2021, 09:07 AM
(This post was last modified: 24-11-2021, 09:30 AM by josemendez.)
(24-11-2021, 06:43 AM)Chua Polar Wrote: Nice and clean explanation!!Thanks for it.
You're most welcome
(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.
(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.
(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,