object 3d look at

Started by fireside, November 26, 2008, 05:31:45 AM

Previous topic - Next topic

fireside

It would be nice if there was a look at for an object 3d like there was for the camera, except that you could specify the axis of rotation.  Most of the time I think rotation around the y axis to face another object would be nice.
click here->Fireside 7 Games<-

paulscode

This could probably be done with something similar to the method I wrote for the camera setOrientation method, where you make a rotation matrix out of look-direction and up-direction vectors.  The look-direction vector would be easy to get, of course:

look = new SimpleVector( target.getTransformedCenter().calcSub( source.getTransformedCenter() ) ).normalize();

The only aditional piece of information you would need to make a look at method is to figure out how to calculate the final up-vector for the source.  I'll look into this, and let you know if I can figure it out.

EgonOlsen

Quote from: paulscode on November 26, 2008, 01:38:54 PM
The only aditional piece of information you would need to make a look at method is to figure out how to calculate the final up-vector for the source.  I'll look into this, and let you know if I can figure it out.
That should be very very similar to the code that you've posted for the camera's setOrientation().

paulscode

#3
The difference is that for a "look at" method, you would be given the look-direction but not an up-direction.  You couldn't use the object's original up-direction (say you wanted to "look at" a target above the object).  So I will need to figure out how to determine what the up-direction should be.  After that, creating the new rotation matrix will be basically the same as for the camera.

The other way to do this is with trigonometry to calculate actual rotations along each axis.  I'll explore this option if I can't get the first one to work.

--EDIT--
I thought of an even simpler solution:
1) Create normalized vectors for the object's previous direction and the new direction.  Now you know the length of two sides are length=1.
2) Calculate the cross-product of these two directions.  Use this as the axis of rotation.
3) Get the length of the triangle's third side using distance between two points on the two direction vectors.
4) With three sides, it is simple to calculate the angle to rotate.
5) Then just call object.rotateAxis( axis, angle ).

fireside

QuoteCreate normalized vectors for the object's previous direction and the new direction.

I don't quite understand what you mean there.  Lets say I start with an object pointed north on the screen, but what if I didn't really know it was pointed north?  I know the next point (x,z) I want the object to go to in world space.  How would I find the rotation? 
click here->Fireside 7 Games<-

paulscode

The object's initial direction should be:
SimpleVector directionI = new SimpleVector( object.getZAxis() ).normalize();

The object's destination direction should be:
SimpleVector directionD = new SimpleVector( target.getTransformedCenter().calcSub( object.getTransformedCenter() ) ).normalize();

Axis to rotate around should be:
SimpleVector axis = new SimpleVector( directionI.calcCross( directionD ) ).normalize();

Length of the triangle's third side should be:
float distC = directionD.calcSub( directionI ).length();

Angle to rotate should be: (using the cosine rule)
float angle = (float) Math.acos( (2.0f - (distC * distC)) / 2.0f );

And then rotate the object:
object.rotateAxis( axis, angle );

Actually, while writing out this explanation, I thought of a problem with it.  Because I am using the arc cosine in the above formula, it will not allow the object to rotate more than PI radians at a time (anything larger, and the formula will return 2 * PI - angle).  So this solution is not good enough (at least not without further logic).  I'll look at the first solution when I get a chance (creating a rotation matrix)  That one is probably going to be the best way to solve this problem.

paulscode

I've been thinking about the rotation-matrix idea some more.  I could give you a "setOrientation" method, no problem.  However I really don't see a catch-all way to determine what the up-direction should be if it is not given, because for any look-direction, there are a virtually infinite number of possible up-directions.  Let me give you a simple example of what I'm talking about:

Say I have an object facing into the screen, and I wanted to have it "look-at" a target behind it.  If I rotate along the y/z plane, the resulting up-direction would be one direction, but if I rotate along the x/z plane, the resulting up-direction would be the opposite direction.  I could also rotate along an infinite number of other "in-between" planes to end up with any number of other possible up-directions.

Or an even easier way to think about it is you could easily spin the up-vector like a top while still looking at the same target.

So to be honest with you, I have no idea how the camera's lookAt method works.  I could come up with a number of various formulas for calculating some up-vector, but the behavior would most likely not be identical to what the camera lookAt method does.  Perhaps Egon could give us a rough explanation for what jPCT does behind the scenes in this method?

EgonOlsen

Quote from: paulscode on November 27, 2008, 02:10:57 PM
So to be honest with you, I have no idea how the camera's lookAt method works.  I could come up with a number of various formulas for calculating some up-vector, but the behavior would most likely not be identical to what the camera lookAt method does.  Perhaps Egon could give us a rough explanation for what jPCT does behind the scenes in this method?
It's a simple implementation of a standard-look-at-approach. To be honest, i don't know how it works exactly anymore...too much time has passed since i wrote that part. However, this is the code:


public void lookAt(SimpleVector lookAt) {

double lavx = lookAt.x - backBx;
double lavy = lookAt.y - backBy;
double lavz = lookAt.z - backBz;

final double FIXER = 1.0E-128d;

if (lavx == 0 && lavz == 0) {
lavx += FIXER;
}

double n = Math.sqrt(lavx * lavx + lavy * lavy + lavz * lavz);
if (n != 0) {
lavx /= n;
lavy /= n;
lavz /= n;
}

Matrix cameraMatMAT = new Matrix();
float[][] cameraMat = cameraMatMAT.mat;

cameraMat[0][1] = 0.0f;
cameraMat[1][1] = 1.0f;
cameraMat[2][1] = 0.0f;

cameraMat[0][2] = (float) lavx;
cameraMat[1][2] = (float) lavy;
cameraMat[2][2] = (float) lavz;

double x1 = 0d;
double y1 = 1d;
double z1 = 0d;

double vx1 = lavx;
double vy1 = lavy;
double vz1 = lavz;

double resx = y1 * vz1 - z1 * vy1;
double resy = z1 * vx1 - x1 * vz1;
double resz = x1 * vy1 - y1 * vx1;

n = Math.sqrt(resx * resx + resy * resy + resz * resz);
if (n != 0) {
resx /= n;
resy /= n;
resz /= n;
}

double resx2 = vy1 * resz - vz1 * resy;
double resy2 = vz1 * resx - vx1 * resz;
double resz2 = vx1 * resy - vy1 * resx;

n = Math.sqrt(resx2 * resx2 + resy2 * resy2 + resz2 * resz2);
if (n != 0) {
resx2 /= n;
resy2 /= n;
resz2 /= n;
}

cameraMat[0][0] = (float) resx;
cameraMat[1][0] = (float) resy;
cameraMat[2][0] = (float) resz;

cameraMat[0][1] = (float) resx2;
cameraMat[1][1] = (float) resy2;
cameraMat[2][1] = (float) resz2;

cameraMatMAT.orthonormalizeDouble();
backMatrix = cameraMatMAT;
}

EgonOlsen

BTW: The getRotationMatrix() from SimpleVector uses the same lookAt-code. You may feed it with a direction vector from the object to the lookAt-point and feed the rotation matrix back into the object. This works best when moving on a more or less 2d plane (like in Robombs for example).

paulscode

That makes things MUCH easier.  I didn't notice the SimpleVector's getRotationMatrix method.

So the lookAt method will look like this:
public void lookAt( Object3D object, SimpleVector target )
{
    SimpleVector direction = new SimpleVector( target.calcSub( object.getTransformedCenter() ) ).normalize();
    Matrix rotationMatrix = new Matrix( direction.getRotationMatrix() );
    object.setRotationMatrix( rotationMatrix );
}


And since this uses the same lookAt-code as the camera's lookAt method, I assume it would behave the same way as well, which is what you want.

fireside

OK, I'll try it out.  Thanks.
click here->Fireside 7 Games<-

fireside

#11
It's working weird but it may be my program.  It does rotate once, but then I have to rescale it.  Every time I rotate, I have to rescale by scale*scale in order to keep it the same size.  I need to upgrade my version, also.
click here->Fireside 7 Games<-

fireside

It seems to work if I set the scale to 1 before setting the rotation and then rescaling.
click here->Fireside 7 Games<-

EgonOlsen

That's what the docs say about Object3D.setRotationMatrix()... ;)

paulscode

Ah, I see.  So taking scale into consideration:

public void lookAt( Object3D object, SimpleVector target )
{
    float initialScale = object.getScale();
    object.setScale( 1.0f );
    SimpleVector direction = new SimpleVector(
                  target.calcSub( object.getTransformedCenter() ) ).normalize();
    Matrix rotationMatrix = new Matrix( direction.getRotationMatrix() );
    object.setRotationMatrix( rotationMatrix );
    object.setScale( initialScale );
}


I can see where a lookAt method like this might come in handy.  Thanks for the idea!