www.jpct.net
jPCT  a 3d engine for Java => Support => Topic started by: fireside on November 26, 2008, 05:31:45 am

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.

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 lookdirection and updirection vectors. The lookdirection 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 upvector for the source. I'll look into this, and let you know if I can figure it out.

The only aditional piece of information you would need to make a look at method is to figure out how to calculate the final upvector 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().

The difference is that for a "look at" method, you would be given the lookdirection but not an updirection. You couldn't use the object's original updirection (say you wanted to "look at" a target above the object). So I will need to figure out how to determine what the updirection 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 crossproduct 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 ).

Create 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?

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.

I've been thinking about the rotationmatrix idea some more. I could give you a "setOrientation" method, no problem. However I really don't see a catchall way to determine what the updirection should be if it is not given, because for any lookdirection, there are a virtually infinite number of possible updirections. 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 "lookat" a target behind it. If I rotate along the y/z plane, the resulting updirection would be one direction, but if I rotate along the x/z plane, the resulting updirection would be the opposite direction. I could also rotate along an infinite number of other "inbetween" planes to end up with any number of other possible updirections.
Or an even easier way to think about it is you could easily spin the upvector 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 upvector, 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?

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 upvector, 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 standardlookatapproach. 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.0E128d;
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;
}

BTW: The getRotationMatrix() from SimpleVector uses the same lookAtcode. You may feed it with a direction vector from the object to the lookAtpoint 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).

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 lookAtcode as the camera's lookAt method, I assume it would behave the same way as well, which is what you want.

OK, I'll try it out. Thanks.

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.

It seems to work if I set the scale to 1 before setting the rotation and then rescaling.

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

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!

Thanks for the help. I should have read the docs, but you know how that goes. ;D Things like this would be nice to put in a wiki, if we had a wiki. ;D

I had this code working fine as a regular app like hello world, however, when I converted it to an applet nothing seems to work right. The collision point seems to be off, not overly far, but off, and the lookAt function no longer works properly.

The mathematics don't care about the target platform. This can only be a problem with the screen coordinates being off either in the applet or in the application IHMO.

I guess I'll try to build two simpler applications and try to figure out what's going on. I'm thoroughly confused right now. ???

Wow, that really is strange. I do know that for applications, you sometimes have to take into account the window titlebar and left border width (for example like in the paint() method), whereas for applets, the coordinates are exact (i.e. 0,0 is the topleft pixel of the applet).
However, that wouldn't explain the problem with your lookAt method, unless you were looking at something you got from another method which produced the wrong coordinate to look at.
I would test the lookAt method seperately. Place two objects in the world, then have one of them "lookAt" the other one's getTransformedCenter(). See if it works the same in both an applet and in an application. If there's no problem with that, then you know the problem is coming from somewhere in your program before lookAt() gets called.

Oh, BTW, calculating collision points in an applet seems to be working fine for me. For reference, here are a couple of methods I am using now that I know work in an applet:
public void mousePressed( MouseEvent e )
{
// Get the mouse's coordinates on the applet window:
int x = e.getX();
int y = e.getY();
// Get the 3D coordinates in Camera space:
SimpleVector position = new SimpleVector( Interact2D.reproject2D3D(
camera, buffer, x, y ) );
// Convert the coordinates to World space:
position.matMul( camera.getBack().invert3x3() );
position.add( camera.getPosition() );
// Determine the direction from the camera position to the click point:
SimpleVector direction = position.calcSub( camera.getPosition() ).normalize();
// Calculate the distance to whatever was clicked on:
float distance = world.calcMinDistance( position, direction, 10000 );
// Check if we clicked on something:
if( distance != Object3D.COLLISION_NONE )
{
// Calculate the exact 3D coordinates for the point that was clicked:
SimpleVector collisionPoint = new SimpleVector( direction );
collisionPoint.scalarMul( distance );
collisionPoint.add( position );
// collisionPoint is the exact 3D coordinate.
clickMap( collisionPoint ); // user clicked on the map
}
}
public void move( long currentTime )
{
// if bullet expired or crashed, do nothing
if( expired  crashed )
return;
// check if bullet should expire
if( currentTime  creationTime >= lifeSpan )
{
expired = true;
return;
}
// calculate how far to move the bullet
float distance = ( currentTime  lastMoveTime ) * distancePerMilli;
// save the current time
lastMoveTime = currentTime;
// check if the bullet should expire
if( lastMoveTime  creationTime >= lifeSpan )
{
expired = true;
return;
}
// check if the movement will result in a collision:
SimpleVector position = object3D.getTransformedCenter();
float targetDistance = world.calcMinDistance( position, direction,
100000 );
// See if the bullet is going to collide with something:
if( (targetDistance != Object3D.COLLISION_NONE)
&& (targetDistance <= distance) )
{
// collision occurred, move bullet to the collision point:
SimpleVector smallTranslation = new SimpleVector( direction );
smallTranslation.scalarMul( targetDistance );
object3D.translate( smallTranslation );
crash();
return;
}
// move bullet the full distance:
SimpleVector translation = new SimpleVector( direction );
translation.scalarMul( distance );
object3D.translate( translation );
}

I did a simple lookAt test on a basic applet and it seems to be working fine. I'll have to slowly build up from this one one step at a time I guess. Thanks for the posting the code.