Java-Gaming.org
 Featured games (81) games approved by the League of Dukes Games in Showcase (497) Games in Android Showcase (114) games submitted by our members Games in WIP (563) games currently in development
 News: Read the Java Gaming Resources, or peek at the official Java tutorials
Pages: [1]
 ignore  |  Print
 Rotation matrix for billboarding textured quads  (Read 2559 times) 0 Members and 1 Guest are viewing this topic.
archo

Senior Newbie

 « Posted 2007-05-16 02:50:35 »

Billboarding is the process of rotating a quad to face the camera so that no matter what the camera position, the quad is rotated to directly face the camera.

I've been fooling around with particle effects, and I was looking for some spherical billboard rotation code - but everything I found seemed to be tutorial style, and written for clarity instead of efficiency. All I wanted was to generate a 3x3 rotation matrix that I could apply to the quad vertices given a camera position and a particle pivot position.  The pivot position is the center of the quad, with each of the 4 vertices in a different quadrant on the xy plane.  I also wanted to avoid calling any trigonometric functions, and rely on simple pythagorean relationships.  So, I tried to simplify the calculations necessary to orient a quad to face the camera, and after a little algebraic refactoring, I came up with the following function. It does need to calculate 3 square roots, but there are no trig functions or cross products. It references the java vecmath package for the Point3f, Vector3f and Matrix3f implementations, but not for anything really important (just vector normalize and dot product). For simplicity, I present it here as a static function that creates new objects instead of reusing members.

Here's the stripped down version:

 1  2  3  4  5  6  7  8  9  10 `public static Matrix3f calculateBillboardRotation(Point3f eye, Point3f p){  Vector3f view = new Vector3f(eye.x - p.x, eye.y - p.y, eye.z - p.z);  Vector3f xzp = new Vector3f(view.x, 0, view.z);  xzp.normalize();  view.normalize();  float cosp = view.dot(xzp);  float sinp = (float) Math.sqrt(1 - cosp * cosp) * (view.y > 0 ? -1 : 1);  return new Matrix3f(xzp.z, xzp.x * sinp, xzp.x * cosp, 0, cosp, -sinp, -xzp.x, xzp.z * sinp, xzp.z * cosp);}`

If that seemed interesting to you, here's the version with comments that hopefully explain what the heck I was thinking:

 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61 `   /**    * Calculates a 3x3 rotation matrix used to orient a quad billboard to directly face the camera eye.    * The camera eye and the point p about which the billboard should pivot should be in world co-ordinates.    * When applying the rotation matrix, first translate the vertices about the pivot point to the origin,    * transform the vertices with the matrix, and then translate the new vertices back to world space.    * The rotation is a 360 degree rotation around the y axis,     * combined with a +/- 90 degree rotation about the x axis (the tilt up or down).    * The initial billboard position assumes it is parallel to the xy plane with the normal    * vector pointing to (0,0,1).    * This method uses no trig functions, but it does calculate 3 square roots.    */   public static Matrix3f calculateBillboardRotation(Point3f eye, Point3f p)   {      // The view vector is the direction from the point to the camera eye      Vector3f view = new Vector3f(eye.x - p.x, eye.y - p.y, eye.z - p.z);      // The xzp vector has the y component set to zero and is a projection      // of the view vector onto the xz plane.      Vector3f xzp = new Vector3f(view.x, 0, view.z);      // Once normalized, the components of the xzp vector will be direction cosines.      // So, xzp.z contains the direction cosine of the angle between xzp and the z-axis,      // and xzp.x contains the direction cosine of the angle between xzp and the x-axis.      // Consider that since xzp.y is zero, then xzp.x and xzp.y are two sides      // of a right triangle, and the magnitude of the xzp vector is the hypotenuse.      // Let theta be the angle between xzp and the z-axis (which is also the      // counter-clockwise rotation about the y axis that we want for our billboard).      // Using standard trigonometric ratios, we know that:      // cos(theta) = xzp.z / ||xzp||      // We can remove the ||xzp|| (magnitude) since it equals 1 (normalized vector)      // therefore, cos(theta) = xzp.z.      // Using the same approach, the trigonometric ratio for sine, would be:      // sin(theta) = xzp.x / ||xzp||      // So, xzp.z and xzp.x are the exact sines and cosines we need for the      // rotation about the y-axis, and we are done.      xzp.normalize();      // We have the sines and cosines for the rotation about the y axis, now      // we need to get the elevation (rotation about the x axis).      // The dot product of the normalized view and xzp vectors will provide      // the cosine of the angle between the two vectors.      // This cosine is related to the angle of elevation above the xz plane.      view.normalize();      float cosp = view.dot(xzp);      // We have the cosine, but for the rotation matrix we need the sine, as well.      // The sine of the elevation angle can be derived from the cosine using the      // trig identity: sine = sqrt( 1 - (cosine squared) )      // The sign must be negated when the view vector is looking up.      // (y > 0) (i.e. camera looking down)      float sinp = (float) Math.sqrt(1 - cosp * cosp) * (view.y > 0 ? -1 : 1);      // Prepopulate a 3x3 rotation matrix with the rotation around the y axis      // and the rotation around the x axis. This is how the matrix would wind      // up if we created a separate rotation matrix for the y axis rotation      // and the x axis rotation and then multiplied them together.      // This is quicker than actually doing a full matrix multiplication      // because the lack of a z axis rotation makes a lot of terms become      // zero and drop out.      return new Matrix3f(xzp.z, xzp.x * sinp, xzp.x * cosp, 0, cosp, -sinp, -xzp.x, xzp.z * sinp, xzp.z * cosp);   }`

Anyone care to check my math?

Should I add special code to handle angles close to zero?
Riven
« League of Dukes »

JGO Overlord

Medals: 799
Projects: 4
Exp: 16 years

 « Reply #1 - Posted 2007-05-16 05:33:08 »

even a cross-product is faster than a single square-root. 3 sqrts per quad is seriously going to kill performance.

There is another way (and often preferable way) to orientate your quads: let all quads face the camera-PLANE. Almost no math required, and no intersecting particles, ever.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
archo

Senior Newbie

 « Reply #2 - Posted 2007-05-16 12:54:06 »

I agree, the cross product isn't nearly as expensive as sqrt() or acos().  Three sqrts per quad is definitely more than I would like, and upon further thought, I think I can eliminate one of them. Then it would be better than the two sqrt() plus two acos() calls that I found in some code in this tutorial: http://www.lighthouse3d.com/opengl/billboarding/index.php?billSphe but, hey, I am a newbie, so I have to start somewhere.

If I use a point near the center of the particles to get the vector to the camera eye, then I can calculate this matrix just once per frame instead of once per quad, with the drawback that some quads near the corners of the screen may look slightly askew. For particle effects that's probably good enough.

I am very interested in the quad orientation technique you mentioned. Have you, by any chance, got a link or some code you can paste here?

archo

Senior Newbie

 « Reply #3 - Posted 2007-05-16 13:26:03 »

I mentioned in the last reply that I thought I could eliminate a sqrt(), and it was simple. I had used a trig identity to get a sine from a cosine, but the sine was sitting there in the normalized view vector the whole time. I just had to negate it.

 1  2  3  4  5  6  7  8  9 `public static Matrix3f calculateBillboardRotation(Point3f eye, Point3f p){  Vector3f view = new Vector3f(eye.x - p.x, eye.y - p.y, eye.z - p.z);  Vector3f xzp = new Vector3f(view.x, 0, view.z);  xzp.normalize();  view.normalize();  float cosp = view.dot(xzp);  return new Matrix3f(xzp.z, xzp.x * -view.y, xzp.x * cosp, 0, cosp, view.y, -xzp.x, xzp.z * -view.y, xzp.z * cosp);}`

So, I got it down to two square roots in the vector normalize() calls . I guess that's still too many?

cylab

JGO Ninja

Medals: 49

 « Reply #4 - Posted 2007-05-16 17:18:10 »

Maybe you could utilize this inverse square root algorithm

Mathias - I Know What [you] Did Last Summer!
Pages: [1]
 ignore  |  Print

You cannot reply to this message, because it is very, very old.

 Add your game by posting it in the WIP section, or publish it in Showcase. The first screenshot will be displayed as a thumbnail.
 BurntPizza (22 views) 2014-09-19 03:14:18 Dwinin (35 views) 2014-09-12 09:08:26 Norakomi (62 views) 2014-09-10 13:57:51 TehJavaDev (89 views) 2014-09-10 06:39:09 Tekkerue (44 views) 2014-09-09 02:24:56 mitcheeb (65 views) 2014-09-08 06:06:29 BurntPizza (48 views) 2014-09-07 01:13:42 Longarmx (35 views) 2014-09-07 01:12:14 Longarmx (40 views) 2014-09-07 01:11:22 Longarmx (36 views) 2014-09-07 01:10:19
 BurntPizza 37x Riven 18x Rayvolution 18x princec 17x ags1 16x basil_ 16x KevinWorkman 15x LiquidNitrogen 12x kevglass 12x theagentd 11x nsigma 11x HeroesGraveDev 9x deathpat 9x The Lion King 7x TehJavaDev 6x Gibbo3771 6x
 List of Learning Resources2014-08-16 10:40:00List of Learning Resources2014-08-05 19:33:27Resources for WIP games2014-08-01 16:20:17Resources for WIP games2014-08-01 16:19:50List of Learning Resources2014-07-31 16:29:50List of Learning Resources2014-07-31 16:26:06List of Learning Resources2014-07-31 11:54:12HotSpot Optionsby dleskov2014-07-08 01:59:08
 java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org