Note : I went fast on matrix concepts in this article. The details of transformation matrices’ inner workings can be found in this article.
I’ve been trying to billboard on a mesh local Z-axis, to make for example, single-plane lasers or bullet trails that always face the camera, yet have X and Y orientations. This is not possible using basic TV billboarding, however by using a custom rotation matrix and some vector operations, it is very possible and pretty useful. And since the information on the subject is somewhat hard to find on the net (most info is using OpenGL and transposed/right-handed systems), I thought I’d share.
Let’s say you have a rectangular plane you want to billboard on its Z axis.
This was created using :
TVMesh plane = scene.CreateMeshBuilder(); plane.AddFloorGrid(0, -0.5f, 0, 0.5f, 1); plane.SetMeshCenter(0, 0, 0); plane.SetScale(1, 1, 10); plane.RotateY(-20); plane.RotateX(-40);
Its Z axis, the one I want to billboard on, is highlighted by a magenta line here. The rest are the world axis.
By using TVMesh.GetBasisVectors(), I can get three things : the side vector (cyan), the top vector (yellow) and the direction vector (magenta). The direction happens to be the Z axis I’m looking for. The two others aren’t useful for me. The good thing about those vectors is that they follow the transformations of the mesh, so they’re “mesh-aligned”.
plane.GetBasisVectors(look, notneeded, notneeded);
The vector I’m going to use, the direction one, has to be normalized so I can do vector math with it.
The trick is to make a rotation matrix in the following format :
| side.x side.y side.z 0 | | top.x top.y top.z 0 | | look.x look.y look.z 0 | | 0 0 0 1 |
This is how vector-based rotation matrices “work”. Well, at least they *can* work like this, and it’s how billboarding uses them. Since we already know our look (or “direction”) vector, we’ll fix that one. This one will not change. Might as well set the things we are sure about before looping :
TV_3DMATRIX billboardingRotationMatrix = new TV_3DMATRIX(); billboardingRotationMatrix.m44 = 1f; billboardingRotationMatrix.m31 = look.x; billboardingRotationMatrix.m32 = look.y; billboardingRotationMatrix.m33 = look.z;
Err yeah, I’m using 6.5, so I guess people with 6.2 need to change TV_3D to D3D. It’s all the same thing in the end. The (4, 4) cell is set to 1, otherwise the matrix goes crazy, the mesh disappears. I’m not exactly sure why, since setting a rotation matrix shouldn’t influence anything else than rotation... it’s a 3×3 matrix in essence.
Then in the render loop, we need to calculate the side (also called “right”) and top (also called “up”) vectors using the camera-to-mesh vector. Here’s a summary :
side = camera-to-mesh x look top = look x side
The x symbol means “cross product” here, meaning that it will find a vector that is orthogonal to both vectors. Anyone with basic linear algebra in mind will follow me here.
The “mesh” position normally represents the center of the billboard, but to get accurate results on non-square billboards, we need to do another calculation to get the closest projection of the camera position ON the vector that the billboard looks towards. (which is the look vector)
This is done using the following (calculation detailed on http://www.calc3d.com/help/ifaq.html, second question) : (Note : I have a TVMathLibrary object called “maths”)
float projectedPointFactor = maths.TVVec3Dot((camera.GetPosition() - plane.GetPosition()), look) / maths.TVVec3Dot(look, look); TV_3DVECTOR projectedPoint = plane.GetPosition() + projectedPointFactor * look;
Then we can use that value to do calculate “cameraToMesh”.
In (6.5) code, that translates to :
TV_3DVECTOR cameraToMesh = camera.GetPosition() - projectedPoint; maths.TVVec3Normalize(ref cameraToMesh, cameraToMesh); maths.TVVec3Cross(ref side, cameraToMesh, look); maths.TVVec3Cross(ref top, look, side); billboardingRotationMatrix.m11 = side.x; billboardingRotationMatrix.m12 = side.y; billboardingRotationMatrix.m13 = side.z; billboardingRotationMatrix.m21 = top.x; billboardingRotationMatrix.m22 = top.y; billboardingRotationMatrix.m23 = top.z; plane.SetRotationMatrix(billboardingRotationMatrix);
And that’s it, really! Now you’ve got a plane that will follow you as much as possible at all times, with fixed X and Y rotations.
Here’s the complete function for VB.net:
Public Sub AlignToCamera(ByVal Mesh As TVMesh) Dim billboardingRotationMatrix As New TV_3DMATRIX Dim look As TV_3DVECTOR Dim side As TV_3DVECTOR Dim top As TV_3DVECTOR Dim up_notused As TV_3DVECTOR Dim projectedPointFactor As Single Dim projectedPoint As TV_3DVECTOR Dim cameraToMesh As TV_3DVECTOR Mesh.GetBasisVectors(look, up_notused, side) TVMath.TVVec3Normalize(look, look) TVMath.TVVec3Normalize(side, side) billboardingRotationMatrix.m44 = 1.0F billboardingRotationMatrix.m31 = look.x billboardingRotationMatrix.m32 = look.y billboardingRotationMatrix.m33 = look.z projectedPointFactor = TVMath.TVVec3Dot((TVCamera.GetPosition() - Mesh.GetPosition()), look) / TVMath.TVVec3Dot(look, look) projectedPoint = Mesh.GetPosition + projectedPointFactor * look cameraToMesh = TVCamera.GetPosition - projectedPoint TVMath.TVVec3Normalize(cameraToMesh, cameraToMesh) TVMath.TVVec3Cross(side, cameraToMesh, look) TVMath.TVVec3Cross(top, look, side) billboardingRotationMatrix.m11 = side.x billboardingRotationMatrix.m12 = side.y billboardingRotationMatrix.m13 = side.z billboardingRotationMatrix.m21 = top.x billboardingRotationMatrix.m22 = top.y billboardingRotationMatrix.m23 = top.z Mesh.SetRotationMatrix(billboardingRotationMatrix) End Sub