Random Vectors problem solved

Hi guys, some days ago, i asked, if someone knows, how to produce a random vector around a “normal”. I didn´t get so much replies, maybe because it is hard to explain.
However, thanks to my math-teacher, i solved the problem now, and i think, it would be interessting for all those people, who think of the same problem, because it can be very usefull in particle-systems.
So here´s the solution (quite long):

VECTOR RandomVector (VECTOR Normal, int iMaxAlpha, float iDesiredLength)
{
/* THEORY
We want to produce a vector, which points in the direction of “Normal”
but has a deviation of maximal “iMaxAlpha” into any direction.

To produce a random vector, we take a vector parallel to the x-axis (1,0,0)
and rotate it first around the z-axis. If we did this by 45 degree, we would
for example get (0.7, 0.7, 0)
Then we rotate it around the x-axis by a random angle between 0 and 360 degree.
For example, if we did this by 90 degree, we would get (0.7, 0, 0.7).

Then we have a random vector around the x-axis. To get it to be a vector around
"Normal", we have to rotate it around two axis by the angle between "Normal" and 
the used axis.

Thats all ;-)

To do this, you need a simple 3x3 matrix class and a vector class. And you
need a function, to multiply the matrix with the vector.

Because you need sin and cos very often, you may precalculate the values and
store them in an array, just as i did. (Therefore i shorten the float-angles
to int-angles.)

Hope, you may find it usefull.
*/

//calculate the length of "Normal" in the XY-plane
float lengthxy = Normal.x*Normal.x + Normal.y*Normal.y; // sqared length
lengthxy = sqrt (lengthxy); // real length

//calculate the angle between the x-axis and "Normal" in the XY-plane
float alphaxy = Normal.y / lengthxy; // sin alpha = y / hypotenuse
int ialphaxy = (int) (asin (alphaxy)*180.0f/3.141f);//convert it to degree

//calculate the length of "Normal" in the XZ-plane
float lengthxz = Normal.x*Normal.x + Normal.z*Normal.z; // sqared length
lengthxz = sqrt (lengthxz); // real length

//calculate the angle between the x-axis and "Normal" in the XZ-plane
float alphaxz = Normal.z / lengthxz; // sin alpha = z / hypotenuse
int ialphaxz = (int) (asin (alphaxz)*180.0f/3.141f);//convert it to degree


//now we take a vector, which is parallel to the x-axis and rotate it to create a
//random vector

VECTOR n (1,0,0);//x-axis

int alpha = rand () % iMaxAlpha;//we will rotate it by alpha around the z-axis

//here comes our matrix, which will rotate the vector around the z-axis
MATRIX3X3 mat (COS[alpha], -SIN[alpha], 0,
			   SIN[alpha],  COS[alpha], 0,
					0	 ,		0	  ,	1);


//we multiply mat and n (matrix multiplication), to rotate n and store it in d
VECTOR d = mat * n;

//our new vector lies in the XY-plane (because we rotated it around the z-axis),
//now we rotate it around the x-axis, so it will lie in all three planes
int beta = rand () % 360; // rotation around x-axis

//here comes our rotation matrix
mat.SetMatrix (1,0,0,
	0,COS[beta],-SIN[beta],
	0,SIN[beta],COS[beta]);

// multiply d and mat
d = mat * d;

//now we have a random vector around the x-axis
//but we want it to be a random vector around "Normal", so we will rotate it by
//ialphaxy and ialphaxz, to produce a vector around "Normal"


//rotate the vector around the z-axis (by ialphaxy)
mat.SetMatrix (COS[ialphaxy],-SIN[ialphaxy],0,
				SIN[ialphaxy],COS[ialphaxy],0,
				0,0,1);
d = mat * d;

//rotate the vector around the y-axis (by ialphaxz)
mat.SetMatrix (COS[ialphaxz],0,SIN[ialphaxz],
				0,1,0,
				-SIN[ialphaxz],0,COS[ialphaxz]);
d = mat * d;

//thats all, we have a random vector, now we can do some extra stuff

d.Normalize (); // unit-length

//and strecht it to the desired length
d *= iDesiredLength;

return (d);

}

Have fun with it.
Jan.

Lol! I was just working on tidying up my Vector class. I’ll add this method in there too. I can imagine its going to be really useful.

Thanks!

Why not just add guassian noise to your vector?

I don´t know, what gaussian noise is, but when you explain it to me, i might do it.

Jan.

and when you’ve done it, I’ll copy and paste it into my class

There is a problem – a divide by zero if the vector is parallel to the Y axis.

The easiest way to do it is to give your emitter an orientation (rather than just a direction), generate a random vector in the emitter’s space according to your criteria, and then transform it into world space. My emitter creates random vectors in a cone around the Z axis:

vector r, v;

a = randfloat( 0.f, CONE_ANGLE );
b = randfloat( 0.f, TWO_PI );

r.x = cos( b ) * sin( a );
r.y = sin( b ) * sin( a );
r.z = cos( a );

v = EmitterSpaceToWorldSpace( r );

Originally posted by Jambolo:
[b]
vector r, v;

a = randfloat( 0.f, CONE_ANGLE );
b = randfloat( 0.f, TWO_PI );

r.x = cos( b ) * sin( a );
r.y = sin( b ) * sin( a );
r.z = cos( a );

v = EmitterSpaceToWorldSpace( r );

[/b]

How to convert the emitter space to world space? Given Eye, Right and Up for the emitter, are you performing some kind of orthonormal basis transform? If so, how do you do this?

Cheeeaaarrs.

You are right, it should crash, if Normal is parallel to the y-axis because then Normal.xNormal.x + Normal.zNormal.z is zero. And it should also crash, when Normal is parallel to the z-axis, because then the same thing happens again.
However i used the function with a vector that is parallel to the y-axis and it didn´t crash, that´s strange.

Well, you should just add two if-statements and test if lengthxy is zero or lengthxz is zero and if it is then you set ialphaxy or ialphaxz to zero. That should do it, i didn´t test it, yet.

Chears,
Jan.

Wouldn’t it be easier to just take the “approximate direction” vector, add some random number to each component, then normalize?

Wouldn’t work. Well it would depend on the direction vector. Same with the spherical version above; most points will be around the poles of the sphere. What we need is a function that will provide a uniform distribution around the normal. No idea how to do this!

Well, you should just add two if-statements and test if lengthxy is zero or lengthxz is zero and if it is then you set ialphaxy or ialphaxz to zero.

That should fix the problem. I would check for close to 0, rather than equal to 0. Another thing – you are computing the sine, then computing the angle, then computing the sine. You could skip the computing the angle part and cos = sqrt( 1 - sin**2).

How to convert the emitter space to world space?

Nothing special about the EmitterSpaceToWorldSpace function, it just multiplies a rotation matrix that was set when you set the emitter’s orientation. It works the same as the camera. You could even write a LookAt function for the emitter (which is pretty much what Jan2000 does).

What we need is a function that will provide a uniform distribution around the normal. No idea how to do this!

The code above does exactly what you want. It generates random vectors in a cone around an axis, and then it rotates them in the direction you want.

Another thing – you are computing the sine, then computing the angle, then computing the sine. You could skip the computing the angle part and cos = sqrt( 1 - sin**2).

Yes! I knew i forgot something to optimize! Thanks.

The code above does exactly what you want. It generates random vectors in a cone around an axis, and then it rotates them in the direction you want.

I thought it was clear, that this function should do this sort of thing.

It seems, that many people think of this problem. I still hope that my function is usefull to you. Well, after you added those things, which are mentioned above.

Jan.