1

I am trying to understand how to implement angle spread for a 3D particle system, to achieve an effect similar to a fountain. I can get it to work for a 2D system, but not a 3D. I would really appreciate any help as I've tried just about everything.

Here is what I'm doing:

Compute an initial random angle between -180 to +180. spreadAmount is a float from 0.0 to 1.0 to control degree of spread.

float velangrnd = spreadAmount * ((((double)(rand() % RAND_MAX) / (RAND_MAX)) - 0.5) * 360.0 * 3.14159265359 / 180.0);

Compute angles:

float vsin_anglex_dir = -SIN(velangrnd);
float vcos_anglex_dir = -COS(velangrnd);

And finally, calculate the angle spread. Vel is the speed from 0-1:

// XY Spread
float px0 = (vel * vsin_anglex_dir);
float py0 = (vel * vcos_anglex_dir);
float pz0 = 0;

After that, I simply compute the screen position. x, y, z are the emitter coordinates:

px0 = x + px0 * time;
py0 = y + py0 * time;
pz0 = z + pz0 * time;

This creates a perfect circle of particles in the XY axis when spreadAmount is 1.0. In other words, particles will shoot out in 360 degrees and according to the velocity (vel). At a lower value, it will create a 2D fountain effect.

However, not matter what I try, I cannot expand this to a second axis. I am trying to create a 3D fountain. So that one axis shoots particles outwards, another adds random spread angle in one direction, and the third a random spread angle in the other direction. Thus, a fountain.

Are there any suggestions on how to do this before I pull out the remainder of my hair?

Thank you!

Rich95
  • 199
  • 1
  • 3
  • 18
  • why a (need more focus) close vote? its clearly a generation of random direction (velocity) within specified 3D cone problem what is unclear? – Spektre Apr 27 '22 at 06:38

1 Answers1

0

in 3D you need main direction unit vector t and your angle a from that you create 2 basis vectors u,v that are perpendicular to each and to t and from that you construct your random directions... Something like this (if I did not do any silly mistake):

float a=?,b,r;
vec3 u,v,t=vec3(?,?,?),d;

// create u,v from t
u=vec3(1,0,0); // u is any non zero vector
if (fabs(dot(u,t))>0.75) u=vec3(0,1,0); // but not (anti)parallel to t
u=normalize(cross(u,t)); // make it perpendicular and unit
v=normalize(cross(u,t)); // make it perpendicular and unit

// compute random direction d
b=2.0*M_PI*Random();   // random angle
r=tan(0.5*a)*Random(); // random radius
d=normalize((r*u*cos(b))+(r*v*sin(b))+t);

So basically its random vector within cone where t is central axis and cone angle is a. I used my GLSL_math.h however you can use any vector math like GLM ...

[Edit1] lib less and component wise version

#include <math.h>
void normalize(float &x,float &y,float &z)
    {
    float l=sqrt((x*x)+(y*y)+(z*z));
    if (l>1e-6) l=1.0/l; else l=0.0;
    x*=l; y*=l; z*=l;
    }
void cross(float &x,float &y,float &z, float ax,float ay,float az, float bx,float by,float bz)
    {
    x=(ay*bz)-(az*by);
    y=(az*bx)-(ax*bz);
    z=(ax*by)-(ay*bx);
    }
void cone(float &dx,float &dy,float &dz, float tx,float ty,float tz, float a)
    {
    // (dx,dy,dz) <- random direction inside cone
    // (tx,ty,tz) -> cone axis direction
    // a          -> cone angle in [rad]
    float b,c,s,r;
    float ux,uy,uz;
    float vx,vy,vz;
    // create u,v from t
    ux=1.0; uy=0.0; uz=0.0;                 // u is any non zero vector
    if (fabs((ux*tx)+(uy*ty)+(uz*tz))>0.75) // but not (anti)parallel to t
        { ux=0.0; uy=1.0; uz=0.0; }
    cross(ux,uy,uz, ux,uy,uz, tx,ty,tz);    // make it perpendicular
    normalize(ux,uy,uz);                    // make it unit
    cross(vx,vy,vz, ux,uy,uz, tx,ty,tz);    // make it perpendicular
    normalize(vx,vy,vz);                    // make it unit
    // compute random direction d
    b=2.0*M_PI*Random();                    // random angle
    r=tan(0.5*a)*Random();                  // random radius
    c=r*cos(b); s=r*sin(b);
    dx=(ux*c)+(vx*s)+tx;                    // random direction inside cone
    dy=(uy*c)+(vy*s)+ty;
    dz=(uz*c)+(vz*s)+tz;
    normalize(dx,dy,dz);                    // make it unit
    }

If you interested the vector math equations (and even array version implementation) is in here (Edit2 near bottom):

Using array or even classes is much more convenient (less amount and much clearer code as you can see when you compare with previous version).

[Edit2] better version using sort of spherical coordinates allowing spread <0 , 2*PI> [rad] with "uniform" distribution

I was thinking about merging my cone approach with spherical coordinate system to avoid the rectangular cone cap ... Here the result:

vec3 rnd_dir(vec3 t,float spread)
    {
    vec3 b,n;
    float a,r,x,y,z;
    static const float  pih=0.5*M_PI;
    static const float _pih=2.0/M_PI;
    // (x,y,z) = unit spread where +x is main direction (central axis)
    x=Random();                 // random angle within spread scaled to <0,1>
    a=x*spread*0.5;             // random angle within spread [rad]
    if (a>pih)                  // linearize the point distribution (avoid high density on poles)
        {
        a=M_PI-a;
        a=sqrt(a*_pih)*pih;     // sqrt looks good probably because surface area is scaled with ^2
        a=M_PI-a;
        }
    else{
        a=sqrt(a*_pih)*pih;     // sqrt looks good probably because surface area is scaled with ^2
        }
    x=cos(a);                   // convert angle to main axis coordinate
    r=sqrt(1.0-(x*x));          // max radius of cone cap still inside unit sphere
    a=Random()*2.0*M_PI;        // random polar angle inside the cone cap [rad]
    y=r*cos(a);                 
    z=r*sin(a);
    // create normal n, binormal b from tangent t ... TBN matrix
//  t=normalize(t);             // t should be unit if it is not uncomment this line
    n=vec3(1,0,0);              // n is any non zero vector
    if (fabs(dot(n,t))>0.75) n=vec3(0,1,0); // but not (anti)parallel to t
    n=normalize(cross(n,t));    // make it perpendicular and unit
    b=normalize(cross(n,t));    // make it perpendicular and unit
    // convert (x,y,z) so t is main direction
    return (t*x)+(b*y)+(n*z);
    }

Its using vec3 again (I am too lazy to code in x,y,z) however the axis aligned spread generation itself is not using vectors at all so you can directly use that and use the conversion to t,b,n from previous version (t,u,v) its the same...

Here preview of all spreads with 5 deg step (1000 points):

spread

rendered in OpenGL like this:

void gl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    float aspect=float(xs)/float(ys);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0/aspect,aspect,0.1,100.0);
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0,0.0,-5.5);
    static float a=0.0; a+=5.5; if (a>360.0) a-=360.0;
    a=-75.0;
    glRotatef(a,0.0,1.0,0.0);

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);

    int i;
    vec3 d,t=normalize(vec3(1.0,0.5,-0.2));

    Randomize();
    RandSeed=0x1234567;
    glPointSize(1);

    glBegin(GL_POINTS);
    glColor3f(0.1,0.8,1.0);
    for (i=0;i<1000;i++)
        {
        d=rnd_dir(t,spread);
        glVertex3fv(d.dat);
        }
    glEnd();

    glPointSize(1);

    glFlush();
    SwapBuffers(hdc);
    }

Where float spread=M_PI; is global variable changed with mouse wheel ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Hi Spektre - thanks for your reply and I will try your suggestion today. Ideally, it would have been more helpful to use and adapt my posted code to exemplify your solution as this might break many other things, but I will do my best to re-write it as per your suggestion. I will write back later today after I've tried it with my results. – Rich95 Apr 27 '22 at 14:13
  • @Rich95 Just in case you do not have vector lib like GLM ... I used this [GLSL_math.h](https://ulozto.net/tamhle/Lac6wsTA65Ni#!ZJD0MwR2AJR0ZmR5LJAyZwHlLJH1MJyBAGL0I3MvJKDlF2L3LD==) ... but you can rewrite all the used operations into separate x,y,z components like you have now if you do not want to convert to vectors... – Spektre Apr 27 '22 at 16:30
  • converting to the x, y, z velocities I've been using would be ideal as I don't have much experience with vectors, and my understanding of them is limited. I would very much appreciate if you could provide some insight into converting it to my posted code. In the meantime, I just sat back down to look at this so I will try your code and see if I can get it to work with my current app. – Rich95 Apr 27 '22 at 17:16
  • Yup, as expected, my VS 2019 doesn't recognize vec3, dot(), normalize() or cross(). Perhaps it comes down to libraries that need to be used. I am not so advanced so forgive me for asking these questions. – Rich95 Apr 27 '22 at 17:22
  • @Rich95 I added component wise version ... however I strongly advise to move from `float ax,ay,az;` to at least `float a[3];` for convenience and speed (you know putting 3 components as operands to any function is worse overhead than just pass single pointer) – Spektre Apr 28 '22 at 06:16
  • Wow. First of all, let me say thank you for taking the time to do this, @Spektre. I really appreciate it and it really helps me understand your first post better. Today I started implementing it and trying to understand it better. I can definitely see how much more overhead this second version adds so it is certainly in my best interest to better understand your first post after I get this working. Speaking of working, I have questions! – Rich95 Apr 29 '22 at 23:11
  • Which part of this would velocity be introduced into? I thought it would be controlled via ux, uy and uz but that is not so. My main questions are more about clarifications: tx, ty, tz are the direction that the cone points to. So these would be values of -1.0 to 1.0, correct? Then what is cone angle a? Is that the spread? Axis vs angle... aren't they they same thing? Secondly, the random values for b & r, which are between 0.0-1.0, control the spread in both remaining directions, correct? I will investigate further in the meantime... – Rich95 Apr 29 '22 at 23:29
  • My last question is (in working with it further), is that the spread seems to cap at a hemispherical maximum of 180 degrees. It seems pushing it beyond 180 degrees of spread, to achieve a 360 degree emission in all directions, results in the particles moving inwards again towards the emission axis. Any ideas why would that be happening? Otherwise, I believe it is for the most part working (still working with it)... – Rich95 Apr 30 '22 at 00:22
  • @Rich95 the function `cone` will return random unit size direction `(dx,dy,dz)` that is inside cone with spherical cap which central axis is `tx,ty,tz` and cone angle (yes its spread) is `a`. It works by describing a circular disc at the end of `tx,ty,tz` with max radius `tan(0.5*a)` so it matches the spread. the `b,r` are just random polar coordinates within the disc ... No `a` can not go above 180deg not even 180deg is allowed as there is singularity in tangents (the disc radius would be infinite large... ) – Spektre Apr 30 '22 at 06:06
  • @Rich95 now to get your initial speed just call `cone` and then multiply the resulting `dx,dy,dz` with velocity magnitude so `px0=vel*dx; py0=vel*dy; pz0=vel*dz;` ... unit vector means its size is `1` so `sqrt(x*x + y*y +z*z) = 1` so yes values in range `<-1 , +1>` if your axis is not unit then just call `normalize` ... In case you want to go for vector math class before coding check mine in both links in my answer and also these: [vector and matrix math](https://stackoverflow.com/a/55439139/2521214) especially the template then pick what best suits you or use 3th party lib like GLM – Spektre Apr 30 '22 at 06:12
  • @Rich95 also beware the angle `a` (spread) is in radians so `< 0 , M_PI )` instead of `< 0 , 180 )` !!! as math functions `sin,cos` in C++ usually accepts radians ... to convert `deg -> rad` just multiply by `M_PI/180.0` ... Now to your simulation check this [Can't flip direction of ball without messing up gravity](https://stackoverflow.com/a/53637567/2521214) its 2D but you just change the array and loops from 2 to 3 ... its Newton d'Alembert physics (integration) with air friction and gravity and correct names for the variables like velocity `vel` , acceleration `acc` and friction constants – Spektre Apr 30 '22 at 06:14
  • @Rich95 and here [Bouncing an object off a wall](https://stackoverflow.com/a/71827702/2521214) the same using vec2 instead of arrays with more complicated collisions ... If you want your spread to go above 180deg then you have to add ranges of angle ... for example 0-120deg use cone 120-240 use different approach (recompute `t` from `u,v,b` and then use cone 240-360 use cone with negated `t` .... Also check this [3D Fireworks Effect in C/C++ using sine or cosine function](https://stackoverflow.com/a/27214318/2521214) its 3D particle sim very similar to what you are doing (if not the same) – Spektre Apr 30 '22 at 06:26
  • Hi @Spektre! The info you’ve provided is really, really helpful! I have actually seen so many examples similar to the fireworks one for 360 degree emission and, like that example, it’s much easier to just pick a random value of -1 to 1 for each axis. The problem is that when you decrease the range (spread) to 0.0, and then multiple it by velocity, it results in 0% velocity. That’s not correct because you can have particles moving at 100% velocity but 0% divergence. I would much rather use that simpler approach but that is the issue with it. The bouncing example are great and to be used! – Rich95 Apr 30 '22 at 16:19
  • I will study your suggestion on how to expand the cone to a 360 degree spherical emission. And I will try again the fireworks explosion approach to see if I can also control the spread with it. Again, thank you. I’ve learned a lot here, especially from your detailed explanations and links. I’ll mark the answer correct and if it’s ok with you, I’ll post my attempts / results in this thread once I try them. – Rich95 Apr 30 '22 at 16:25
  • @Rich95 there is also another possibility to use spherical coordinates instead of polar ... so generate latitude within spread and then longitude within spread while still using basis vectors `t,u,v` however that will lead to "square" like cap of the cone instead of circular (possible bigger angles than spread up to the "square" diagonal) ... maybe some normalization back to circle like this [Finding point inside disk defined by a radius](https://stackoverflow.com/a/71967769/2521214) in the angles would help... – Spektre May 01 '22 at 06:29
  • @Rich95 And also there is the obvious way generating random 3D direction in loop and throwing away too big angles using `dot` until generated value is OK – Spektre May 01 '22 at 06:31
  • Thank you! I'll wrap up this thread with one final question. In further research I found this example which allows me to control the particle divergence from 0 to 360 degrees in all directions, which is what I was looking for. It is short, clean and relatively simple. Do you see any problems with it? https://pastebin.com/PQsYKy79 – Rich95 May 01 '22 at 18:47
  • @Rich95 NO this is not what you want!!! 1. the spread is only in `lat` axis and `long` covers fully 360 deg no matter what 2. `spread` is non linear if I see it right its more like: `spread = 1-cos(spread_angle/2)` 3. it will not generate unit vectors as `r` is also random and non linear (probably to have certain point density distribution within the sphere volume) 4. even if the spread would be in both axises it would be not a circular cone but rectangular one causing that diagonal would have bigger spread... I will try to think of something better ... – Spektre May 02 '22 at 07:42
  • @Rich95 I added edit2 with much much better version of this ... allowing full spread from 0-360 deg , circular cone cap ... the "uniform" distribution linearization I did empiricaly look like `sqrt` is best (had tried all powers with step 0.01 visualy)... the function you linked used `cbrt` instead but that is because they do not generate direction (points on sphere) but point inside sphere volume and volume is scaled with `r^3` instead surface which is `r^2` ... – Spektre May 02 '22 at 11:12