Helper class for camera operations

Started by Wojtek, February 09, 2010, 01:15:14 AM

Previous topic - Next topic

Wojtek

Hello,

I am trying to create a helper class that will attach camera to an object and allow to do following things:
1. show object from different angles (orbit camera around object)
2. follow the object (ie. when object moves, camera is also moving after it)
3. zoom in / zoom out
4. ignore object rotations (ie. when object is rotating, the camera is rotating with it, so it is looking always at the same point).

With your help (especialy paulscode - thanks a lot btw), I have created a simple class that does the most of that functionality (except 4th point).
Recently I started correcting it to make the 1st point working better and to implement missing one.

Well I have made a first thing working ok, but because of my lacks of 3d math knowledge I stucked in the last task.

I do not know how to get horizontal rotation angle (Y) and vertical rotation angle (Z) by which object3D is rotated. I need those angles to be able to rotate camera.
Can anybody help me with it?
I am asking about angles because matrix operations are still a little bit too difficult for me to understand so I have made my class working in following way:

1. During the initialization, I am positioning the camera after object
SimpleVector vec = new SimpleVector(0, -getDistance(), 0);
vec.add(object.getTransformedCenter());
cam.setPosition(vec);

and setting the camera orientation by:
cam.setOrientation(new SimpleVector(0, 1, 0), new SimpleVector(0, 0, -1));
2. During the update of camera position (just before rendering) I am setting camera to the same point as object is and move it out by getDistance():
cam.setPosition(target.getTransformedCenter());    
cam.moveCamera(Camera.CAMERA_MOVEOUT, getDistance());

3. During camera rotation (orbiting effect) I am:
* moving camera in by getDistance(), saving its position, and setting pos to [0,0,0]
* rotating back X and Y by angles the camera was rotated in previous step
* increasing angles by delta values
* rotating Y and X by new angles
* restoring camera last position by moving camera out and adding saved position

private void restoreCameraPosition(Camera cam, SimpleVector shift)
{
cam.moveCamera(Camera.CAMERA_MOVEOUT, getDistance());
shift.add(cam.getPosition());
cam.setPosition(shift);
}

private SimpleVector moveCameraToZeroPosition(Camera cam)
{
cam.moveCamera(Camera.CAMERA_MOVEIN, getDistance());
SimpleVector shift = cam.getPosition();
cam.setPosition(new SimpleVector(0, 0, 0));
return shift;
}

@Override
public void rotate(double deltaX, double deltaY)
{
Camera cam = getCamera();
SimpleVector shift = moveCameraToZeroPosition(cam);
cam.rotateX((float) -vAngle);
cam.rotateY((float) -hAngle);
vAngle = limitVerticalAngle(MathUtil.normalizeAngle(vAngle + deltaY
/ 200));
hAngle = MathUtil.normalizeAngle(hAngle - deltaX / 200);
cam.rotateY((float) hAngle);
cam.rotateX((float) vAngle);
restoreCameraPosition(cam, shift);
}



Now, what I am planning to do is to modify the method that is updating camera position and, get (somehow) the rotation angles from target object and apply them to camera in the same way as I am doing it during camera rotation (ie. I will add them to the stored hAngle and vAngle). Probably I will have to store both angles separately to distinguish what angle is related to change the camera orbit position and what is related to object rotation.

When I finish that work I will post the full code for that class hoping that it may be usefull for others too...

Thanks,
Wojtek

.jayderyu

#1
Egon posted this a while back. Personally I think it should be part of Object3D.

public SimpleVector deriveAngles(Matrix mat) {
   SimpleVector s=new SimpleVector();
   float[] m=mat.getDump();
   s.x=(float) Math.atan(m[9]/m[10]);
   s.y=(float) Math.asin(-m[2]);
   s.z=(float) Math.atan(m[4]/m[0]);
   return s;
}


'It's funny, because I was going to work on a camera helper class too. Though I wasn't sure it's features so I haven't done much :P

paulscode

I've created several different varieties of "camera assemblies" as I've been calling them, and the main thing that I've learned is that if you can achieve the desired behavior with Object3Ds' parent/child relationships instead of Camera's move and rotate methods, it saves you a huge trigonometry headache (for anyone unfamiliar with the idea - you just set up the parent/child relationships and then match the Camera to an Object3D's position and orientation).  I'm pretty sure such an assembly could be created to achieve all four points that you are after - I'll see if I can come up with one.

#1 - #3 are pretty straight forward, of course, but let me make sure I understand #4 correctly.

Let's say you are having the camera follow a spaceship.  What you are talking about is if both the camera and ship happen to be "looking" at the same point (camera is not necessarily looking directly at the spaceship itself, in other words).  In that case, then as the spaceship rotates around its relative z-axis for example, the camera remains at the same relative position and rotation to the spaceship (like a child Object3D would to its parent).  From the perspective of the camera, the spaceship would not have moved in the frame, but the rest of the scene would have rotated around the spaceship's relative z-axis.

Let me know if I am misinterpreting what your after.

raft

this is the Cameraman class i use in karga.

it supports first person, third person (back, front or custom angle) and free mode cameras. in addition it smooths the camera movement (in contrass to rigid movement) and use collision detection to properly place camera. so in third person view, camera always see the target. you cannot directly compile and use it but i suppose it can help. i can also provide some of the helper methods used in that class if required

some information:
* placeCamera method should be called every frame with delta seconds as parameter
* the bones object is the target object to follow. replace it with something which provides angles. all speed information can be decoupled from it
* moveCamera method is called according to some keyboard and mouse events to orbit camera.
* moveFreeCamera is called to freely move camera around, you can omit it
* animate method starts auto orbiting from a start angle to end angle. that is used in karga when you kiss or dance someone

hope this helps,
r a f t

.jayderyu

It would be really cool that these camera classes were designed for straight JPCT and put up on the Wiki. I notice a lot of reinventing the wheel happening on this forum. Camera helper as an example. It probably puts some people off since many of the other 3d api already have many more features. Even if extension features aren't part of the core. Making them easy to find would be very helpful.

If I ever finish FilmGrain for GLSL(if I ever start). i'll post that up. As it is now it's a bit heavy duty on the fill rate.

paulscode

From my experience, camera control tends to be very specific to a particular project (which is why I have written several variations, as I mentioned).  I think it would be pretty difficult to make something standard that you could use in every case.  Raft's Camraman class looks like it would cover some of the common setups - something like that might be worth putting in the Wiki.

Wojtek

I see a lot of comments, thanks a lot. Let me create and post there a simple example app, so I will be able to explain better what I mean (especially with 4th point) and show how it works.
Regarding to paulscode suggestion about using parent/child relationship I have tried that (attach a child dummy object to target object, rotate/move it and set the camera position to it) but I spent one evening on that without success :(

For now I tried to use a code that .jayderyu posted and apply it to my class, however the range of returned angles for x and z (I am not using y so I have not checked it well) is from -PI/2 to PI/2 range, so they does reflect a full angle.
I have done a little modification in that code and it works for me now at least in most cases (I will show that also on example app):

    public SimpleVector deriveAngles(Matrix mat)
    {
SimpleVector s = new SimpleVector();
float[] m = mat.getDump();
s.x = (float) Math.atan(m[9] / m[10]);
s.y = (float) Math.asin(-m[2]);
s.z = (float) Math.atan(m[4] / m[0]);

if (m[0] < 0 && m[5] < 0)
    s.z += Math.PI;
if (m[5] < 0 && m[10] < 0)
    s.x += Math.PI;
return s;
    }


When I only wrap up code to an example app, I will post it here.

Thanks,
Wojtek

paulscode

Quote from: Wojtek on February 09, 2010, 11:33:22 PMRegarding to paulscode suggestion about using parent/child relationship I have tried that (attach a child dummy object to target object, rotate/move it and set the camera position to it) but I spent one evening on that without success :(
Figuring out the correct parent/child relationships can get a little tricky for more complicated systems, but I would argue that using math to accomplish the same thing is even trickier.  The most complex setup I've done was for my "new and improved" Helicopter Balls demo applet.  In that case, I needed the helicopter to be able to translate and rotate freely relative to the camera sometimes, while also having the camera follow the helicopter's overall translations and rotations through the world, and at the same time I needed the skybox move to match the camera's translations but not its rotations, and also do the same for the scene's main light source.  You can look at the source code to see how I ultimately accomplished this problem, but as I mentioned, camera control tends to be unique for each project, so I doubt it would be very useful for your needs.  My point is, even a complex camera assembly like that was done entirely with Object3D parent/child relationships.  I'd hate to even imagine trying to do something equivalent with straight trigonometry..

Quote from: Wojtek on February 09, 2010, 11:33:22 PMWhen I only wrap up code to an example app, I will post it here.
Awesome, it will definitely help to have a clear idea of your goal.

.jayderyu

Quote from: paulscode on February 09, 2010, 11:28:46 PM
From my experience, camera control tends to be very specific to a particular project (which is why I have written several variations, as I mentioned).  I think it would be pretty difficult to make something standard that you could use in every case.  Raft's Camraman class looks like it would cover some of the common setups - something like that might be worth putting in the Wiki.

oh, i'm going to disagree. I'm sure we both played ALOT of games. From a games perspective I have found cameras are broken into 3 different groups.
Floating, Rigid, FPS. What I found more common was that some games change the camera type.

Though I agree that Raft has an excellent base camera :)

paulscode

#9
Quote from: .jayderyu on February 10, 2010, 05:33:57 AM
From a games perspective I have found cameras are broken into 3 different groups.
Floating, Rigid, FPS.

True, but that is like saying "Animals on earth can be broken into 5 different groups: Fish, Amphibians, Reptiles, Mammals, and Birds".  It is a true statement, but it doesn't capture the uniqueness of any specific animal.  The 3 groups you list can then be subdivided into a limitless number of possibilities, depending on the needs of a specific game.  My helicopter demo, for example, falls into category "FPS", but as I described above, it is a little bit more complicated than that once you try to write it into an actual program and have to consider a number of other interdependent characteristics unique to that application.

--EDIT-- Let me point out that I'm NOT knocking Raft's work or calling it useless -- it would definitely be more than sufficient for a lot of applications.  But at the same time it will probably also limit what you are able to do with the camera in your game.  I'd compare it to using a program like GameMaker which easily makes "cookie cutter" games that all seem to look alike.  I think most developers would prefer to tailor components to their game, rather than having to tailor their game to the limited parameters of a particular component.

But then, I guess I contradict myself, since Raft provided the source for his Cameraman class, so it actually COULD be tailored to a specific application..  This is the very reason why I always provide the source code for all my stuff as well.

.jayderyu

Well true enough. Various settings, parameters will/can substantially alter the behavior of follow or floating cameras.

I didn't think your were downplaying rafts cameraman at all.

Well I've always appreciated you posting your source. I have used it here and there.

Wojtek

#11
Hi, there is an example app that is using my camera managers: sources and app
Use left mouse button to rotate camera, arrows to rotate ship, mouse wheel to zoom, F1 or F2 to change camera manager.

Well, if you press F1, you will use OrbitingCameraManager which supports points 1-3:
It orbits around the ship so you can see it from different angles (there is a limitation for vertical rotations, so you cannot flip the ship).
It also looks at the ship from the same distance, so if ship moves, camera moves too.
And it allows to zoom in/zoom out (with some hardcoded limitations)

If you press F2 (it is default when you run the app), you will use FollowingCameraManager which supports 1-4 points.
It works similar as previous camera, but when ship is rotating, camera is rotating with it (so there is an effect of rotating world) while the first camera is not rotating (so there is an effect of rotating ship).

Personally I think that the second camera could be better for my purpose because it is not need to change camera perspective when ship is turning back.

Currently, it is only possible to turn left/right in my game (turning up and down is reserved for special effects like hyperjumps) so both cameras works well in that case, but I have noticed problem with FollowingCameraManager when you rotate ship in both directions.
Probably it is something wrong with the method SimpleVector MathUtil.deriveAngles(Matrix mat) or somewhere in the code of camera manager.

PS. I someone find them usefull, I can parametrize all the hardcoded constants, and make it more user friendly...

Thanks,
Wojtek

paulscode

#12
I see the problem you are talking about with rotating along both x and y axis'.

I wrote an implementation of your ICameraManager which I called DummyObjectCameraManager, and I believe it meets all 4 of your requirements:

package CameraManagers;

import com.threed.jpct.Camera;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;

/**
* The DummyObjectCameraManager demonstrates using Object3D parent/child
* relationships to simplify camera control.
*
* @author Paul Lamb
*/
public class DummyObjectCameraManager extends AbstractCameraManager
{
    // Position for the camera to start out at (relative to cameraTarget):
    private final SimpleVector INITIAL_CAMERA_POSITION =
                                                  new SimpleVector( 0, 0, -10 );

    // Object for the camera to follow (spaceship, for example):
    private Object3D followObject = null;
    // Point that the camera is looking towards (not necessarily followObject):
    private Object3D cameraTarget = null;
    // Simplifies setting the camera's orientation in the update() method:
    private Object3D cameraUp = null;
    // Pivot for rotations around the x-axis:
    private Object3D xPivot = null;
    // Pivot for rotations around the y-axis:
    private Object3D yPivot = null;
    // Actual position of the camera:
    private Object3D cameraSatellite = null;

    // Handle to the actual camera:
    private Camera camera = null;

/**
* Constructor: Sets up the camera assembly.
* @param followObject Object for the camera to follow (spaceship for example)
*/
    public DummyObjectCameraManager( Object3D followObject )
    {
        this.followObject = followObject;
       
        // Create the dummy objects needed for the camera assembly:
        cameraTarget = Object3D.createDummyObj();
        cameraUp = Object3D.createDummyObj();
        xPivot = Object3D.createDummyObj();
        yPivot = Object3D.createDummyObj();
        cameraSatellite = Object3D.createDummyObj();

        // Place the up object in the correct position:
        // IMPORTANT: take cameraTarget direction into account.
        cameraUp.translate( new SimpleVector( 0, -1, 0 ) );

        // Set the initial relative position of the camera satellite:
        cameraSatellite.translate( INITIAL_CAMERA_POSITION );
       
        // Set up the parent/child relationships:
        followObject.addChild( cameraTarget );
        cameraTarget.addChild( xPivot );
        xPivot.addChild( yPivot );
        yPivot.addChild( cameraSatellite );
        cameraSatellite.addChild( cameraUp );
    }

/**
* Sets the camera to be managed.
* @param camera Handle to the camera to control.
*/
    @Override
    public void setCamera( Camera camera )
    {
    this.camera = camera;
    }

/**
* Orbits the camera around its target.
* @param deltaX Mouse's linear change in x-position.
* @param deltaY Mouse's linear change in y-position.
*/
    @Override
    public void rotate( final double deltaX, final double deltaY )
    {
        // Convert the delta's into some angles:
        float thetaX = -(float)Math.PI * ( (float)(deltaY) / 480f );
        float thetaY = (float)Math.PI * ( (float)(deltaX) / 640f );

        // Rotate the pivots:
        xPivot.rotateX( thetaX );
        yPivot.rotateY( thetaY );
    }

/**
* Zooms the camera toward or away from its target.
* @param delta Distance to translate the camera.
*/
    @Override
    public void zoom( double delta )
    {
        // translate along the satellite's z-axis:
        cameraSatellite.translate( new SimpleVector( 0, 0, (float) delta ) );
    }

/**
* Moves the camera to the correct position and orientation.
* @param followObject Object for the camera to follow (spaceship for example).
*/
    @Override
    public void update( Object3D followObject )
    {
        // check if the followObject has changed:
        if( this.followObject != followObject )
        {
            // New object to follow with the camera:
            this.followObject.removeChild( cameraTarget );
            followObject.addChild( cameraTarget );
            this.followObject = followObject;
        }

        // Position the camera at the cameraSatellite's position:
        camera.setPosition( cameraSatellite.getTransformedCenter() );

        // Determine the camera's look and up directions:
        SimpleVector look = cameraTarget.getTransformedCenter().calcSub(
                           cameraSatellite.getTransformedCenter() ).normalize();
        SimpleVector up = cameraUp.getTransformedCenter().calcSub(
                           cameraSatellite.getTransformedCenter() ).normalize();

        // Set the camera's orientation based on target and up directions:
        camera.setOrientation( look, up );
    }
}


Here is a demo applet:

Demo Applet  (Source code)

You can press "F3" to view the new implementation in action.  One thing I noticed is that the "ship" is pointing "up", which I didn't worry about when I wrote the class since it is just a demo.  It should be easy enough to translate/rotate the camera to the correct position depending on your actual project.  Also, when I was looking through your original source, you were using jPCT a little differently than I am used to (the lwjgl stuff, specifically), so I hope you don't mind I converted it to a form that I am more used to.

I tried to make the class somewhat flexible so you can tailor it to your program more easily.  For example, I made it so you have a cameraTarget dummy Object3D which is a child of the followObject (i.e. the spaceship).  This will allow you to point the camera in some arbitrary direction and still have it follow the spaceship's translations and rotations.

Wojtek

Hi,

Thanks for help and example code :) Regarding to way I am using JPCT I just wanted to wrap it quickly to show example - normally I use GLCanvasRenderer and swing so you can change freely if you like ;-)

I have run the example application and see that it is working much better :)
I have a one question about it.
When you run the app and press F3, and rotate ship horizontally only (using right/left arrows), you see that world is moving horizontally,
when you do the same but move vertically (up/down arrows), world is also moving vertically. Now, when you rotate ship vertically and horizontally and try for example to move up, you no longer sees that world is moving vertically but it is rather rotating.
What needs to be done to eliminate that effect, ie. no matter in which directions ship is rotated, when you rotate ship, the world will always move in the same way?

I started thinking that perhaps it is because you are looking on a world sphere from a different perspective, but in fact you are always looking at it from the same distance (sphere radius) because world sphere center is equal to camera position, so this should not be related to the perspective...

Thanks,
Wojtek

paulscode

Not sure, but I think that is actually a rotation issue with the ship itself (the camera is just following the object, whatever it does).  To get a better idea of what's going on, I would recommend placing the camera stationary at some vantage point (not following the object), and build the "camera assembly" out of visible objects so you can see how the ship and camera would behave as you rotate the ship.

I think the reason for this behavior is probably that you are using ship.rotateZ and ship.rotateX, which rotates the ship around its relative axis'.  When using one one of them, that axis is constant, so rotating only vertically or only "horizontally" behaves the way you would expect.  However, when you do rotations around both axis', they are no longer constant.

I really can't explain the problem any better than that, because honestly every time I try to figure it out logically, it seems like what you are doing should work, but it doesn't.  I've had this exact same rotation strangeness when I created my helicopter demo and in the auto-navigation system I'm using for the game I'm currently working on.  The way I finally achieved the correct desired behavior was to make the spaceship the child of one pivot which was the child of a second pivot, which was the child of a third pivot.  That way I could use one pivot for only rotating around the x-axis, another for only y-axis rotations, and the third for only the z-axis.  To translate the entire spaceship and pivots, I translate that third pivot, and all the others follow since they are all child, grandchild, etc.  Maybe that will work for you as well.  Of course, when attaching the DummyObjectCameraManager, you'd still give it a handle to the spaceship Object3D, not to the pivot (so the camera will still follow the ship's rotations that result from rotations of the 3 pivots).