www.jpct.net

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

Title: object 3d look at
Post 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.
Title: Re: object 3d look at
Post by: paulscode on November 26, 2008, 01:38:54 pm
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:

Code: [Select]
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.
Title: Re: object 3d look at
Post by: EgonOlsen on November 26, 2008, 11:37:20 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().
Title: Re: object 3d look at
Post by: paulscode on November 27, 2008, 02:07:26 am
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 ).
Title: Re: object 3d look at
Post by: fireside on November 27, 2008, 03:04:24 am
Quote
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? 
Title: Re: object 3d look at
Post by: paulscode on November 27, 2008, 12:18:25 pm
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.
Title: Re: object 3d look at
Post by: paulscode on November 27, 2008, 02:10:57 pm
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?
Title: Re: object 3d look at
Post by: EgonOlsen on November 27, 2008, 02:46:05 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:

Code: [Select]
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;
}
Title: Re: object 3d look at
Post by: EgonOlsen on November 27, 2008, 02:54:12 pm
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).
Title: Re: object 3d look at
Post by: paulscode on November 27, 2008, 03:35:39 pm
That makes things MUCH easier.  I didn't notice the SimpleVector's getRotationMatrix method.

So the lookAt method will look like this:
Code: [Select]
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.
Title: Re: object 3d look at
Post by: fireside on November 27, 2008, 04:11:59 pm
OK, I'll try it out.  Thanks.
Title: Re: object 3d look at
Post by: fireside on November 28, 2008, 03:43:48 am
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.
Title: Re: object 3d look at
Post by: fireside on November 28, 2008, 05:23:08 am
It seems to work if I set the scale to 1 before setting the rotation and then rescaling.
Title: Re: object 3d look at
Post by: EgonOlsen on November 28, 2008, 08:43:49 am
That's what the docs say about Object3D.setRotationMatrix()... ;)
Title: Re: object 3d look at
Post by: paulscode on November 28, 2008, 03:07:21 pm
Ah, I see.  So taking scale into consideration:

Code: [Select]
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!
Title: Re: object 3d look at
Post by: fireside on November 28, 2008, 04:44:27 pm
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
Title: Re: object 3d look at
Post by: fireside on December 03, 2008, 09:55:56 pm
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.
Title: Re: object 3d look at
Post by: EgonOlsen on December 03, 2008, 10:42:31 pm
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.
Title: Re: object 3d look at
Post by: fireside on December 03, 2008, 10:57:04 pm
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. ???
Title: Re: object 3d look at
Post by: paulscode on December 03, 2008, 11:12:43 pm
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 top-left 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.
Title: Re: object 3d look at
Post by: paulscode on December 03, 2008, 11:31:01 pm
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:

Code: [Select]
    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
        }
    }

Code: [Select]
    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 );
    }
Title: Re: object 3d look at
Post by: fireside on December 04, 2008, 01:56:34 am
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.