Basically to specify the View i.e. the eye location and direction of view you need to specify corresponding transformation…
In other words you can treat the View (for simplicity) just as normal scene object, for instance cone. The sharp top of the cone - would be eye location and the base of the cone would be pointing in view direction. Now, simply applying certain Transform3D to the cone it would bring it anywhere in the scene where you want, you can rotate it, translate… whatever.
And to do that transformations with the View you just need to do:
View.setTransform(myCustomTransform3D);
or
scView.getTransform().set(myTransformMatrix);
and this would be most basic, most efficient and fastest way to specify the View, but a bit more complicated to achive (you have to create transformation matrix i.e. trigonometrical calculations etc), that is why you have another option to use lookAt function, which is not necessary meant to be applied for the View, you can call lookAt for any Transform3D.
And what it does (lookAt), it does just a dunky job, it is creating that transformation matrix on basis of the location of eye and direction of view you specify… but you should understand that to do that, certain equations needs to be resolved to get that transformation matrix. And this is exactly why you are getting this singularities exceptions. From mathematics you know, that not always the system of equations is “stable” to be able to resolve it.
In other words you always can apply transformation M (matrix) to the vector A and get another vector B, but not always having B (what you actually specifying in lookAt function) and having A (which is by default (0,0,-1)) you can get the M-matrix;
lookAt function is resolving equation B = MxA, trying to found what is M. Then this M is actually your Transform3D matrix needed to specify the View (View.setTransform(M));
So three points here:
- Using lookAt will roughly double the amount of calculations needed to specify the View;
- Using lookAt will lead to singularity exception for certain ViewDirection specified. Wherever you point your up vector, there always will be VewDirection which will crush inverse calculations of the M-matrix.
- If I need to translate only the View I can simply change only translation vector in transform matrix (or its individual components if I want), I don’t need to tuch rotation part of matrix!!! and vise versa, but if using lookAt always both rotation and translation part would recalculated - not efficient.
So basically, what I do I am defining:
Matrix4f viewMx;
setting my default values for rotation and translation components; and then if I need to change the View I am changing only those elements of the matrix viewMx wich needs to be really changed and then applying changes simply doing:
myView.getTransform().set(viewMx);
that is it, and you never ever will have any singularities doing that way, and you can point your View anywhere you want regardless what is UP vector direction.
To be even more specific:
if you define elevation angle (el), azimut angle (az), translation T of your View then the viewMx would be:
viewMx.setRow(0, cos_az, -sin_azsin_el, sin_azcos_el, Tx );
viewMx.setRow(1, sin_az, cos_azsin_el, -cos_azcos_el, Ty );
viewMx.setRow(2, 0, cos_el, sin_el, Tz );
viewMx.setRow(3, 0, 0, 0, 1 );
now if I want to translate the View by X axis I just change Tx in the matrix (viewMx.m03 = …) and applying matrix by:
myView.getTransform().set(viewMx);
working very fast!!!
if I would try to use lookAt… hmmm all the viewMx, each and every element of it will be recalculated from scratch… what for? 
The last thing is - this is just my understanding, I have never tried to dig in lookAt, I just think it is logical things to be that way… And I’m not sure how exactly the View is specified on the low level… If I am wrong anywhere - somebody please correct me.
Sincerely,
Bohdan.