Camera rotation around point

I can turn left and right but it starts to glitch when i try to move up and down or both left/down, right/up, etc.

What am i doing wrong with the x axis and how can i implement zooming in/out?

private AnalogListener analogListener = new AnalogListener() { public void onAnalog(String name, float value, float tpf) { if (name.equals("CameraRight")) { rotateCamera(tpf, 1); } else if (name.equals("CameraLeft")) { rotateCamera(tpf, 2); } else if (name.equals("CameraUp")) { rotateCamera(tpf, 3); } else if (name.equals("CameraDown")) { rotateCamera(tpf, 4); } } };
protected void rotateCamera(float tpf, int dir){
    float rotationSpeed = 50; // lower is faster
    Quaternion rotation = new Quaternion();
    if(dir == 1) {
        rotation.fromAngleAxis(FastMath.HALF_PI/rotationSpeed, Vector3f.UNIT_Y);
    } else if(dir == 2) {
        rotation.fromAngleAxis(-FastMath.HALF_PI/rotationSpeed, Vector3f.UNIT_Y);
    } else if(dir == 3) {
        rotation.fromAngleAxis(-FastMath.HALF_PI/rotationSpeed, Vector3f.UNIT_X);
    } else if(dir == 4) {
        rotation.fromAngleAxis(FastMath.HALF_PI/rotationSpeed, Vector3f.UNIT_X);
    }
    Vector3f rotationResult = rotation.mult(cam.getLocation());
    cam.setLocation(rotationResult);

    cam.lookAt(new Vector3f(0f,0f,0f), new Vector3f(0f,1f,0f));
}

I cant give you a good answer to your question in terms of following your approach as I dont really have time for it.
But here’s my solution to a camera following a spatial, being able to pan around it, tilt it up and down and zoom in and out.

Perhaps it’ll give you some idea of how to solve your problem.

Heres some snippets:

[java]
private Spatial target;
private final Vector3f position = new Vector3f();
private float rotation = 0;
private float yaw = FastMath.PI + (1f / 3);
private float distance = 17.5f;

private final float pitchShiftSpeed = 2.5f;
private final float yawShiftSpeed = 0.4f;
public final static float minYaw = FastMath.PI + 0.35f;
public final static float maxYaw = FastMath.PI + 1f;
public final static boolean limitCamera = false;

public final static float zoomSpeed = 40f;
public final static float minDist = 2f,
public final static float maxDist = 100f;

public final float FRUSTUM_FAR = 2000;

private Vector3f lastPlayerPos;
private final float interpolation = 4f;

@Override
public void update(final float tpf) {
	updatePosition(tpf);
	doAdjustments(tpf);
}

public final void updatePosition(final float tpf) {
	final Vector3f playerPos = target.getLocalTranslation().clone();

	final Vector3f finalVec = playerPos.clone();
	final float dY = distance * (float) Math.tan(yaw);
	final float dX = distance * (float) Math.sin(rotation);
	final float dZ = distance * (float) Math.cos(rotation);
	finalVec.addLocal(dX, dY, dZ);

	final float actualInterpolation = (interpolation * tpf) > 1 ? 1
			: interpolation * tpf;

	// Interpolate position
	if (position.distance(finalVec) < 0.01f) {
		position.set(finalVec);
	} else {
		position.interpolate(finalVec, actualInterpolation);
	}
	cam.setLocation(position);

	if (lastPlayerPos == null) {
		lastPlayerPos = playerPos.clone();
	}

	// Interpolate rotation
	if (lastPlayerPos.distance(playerPos) < 0.01f) {
		cam.lookAt(lastPlayerPos, Vector3f.UNIT_Y);
	} else {
		cam.lookAt(
				lastPlayerPos.interpolate(playerPos, actualInterpolation),
				Vector3f.UNIT_Y);
	}

	cam.setFrustumFar(distance + FRUSTUM_FAR);
}


public void doAdjustments(final float tpf) {
	if (zoomIn) {
		if (distance > minDist) {
			distance -= zoomSpeed * tpf;
		} else {
			distance = minDist;
		}
		zoomIn = false;
	}
	if (zoomOut) {
		if (distance < maxDist) {
			distance += zoomSpeed * tpf;
		} else {
			distance = maxDist;
		}
		zoomOut = false;
	}
	if (up) {
		if (yaw < maxYaw || !limitCamera) {
			yaw += yawShiftSpeed * tpf;
		} else {
			yaw = maxYaw;
		}
	}
	if (down) {
		if (yaw > minYaw || !limitCamera) {
			yaw -= yawShiftSpeed * tpf;
		} else {
			yaw = minYaw;
		}
	}

	if (left) {
		rotation += pitchShiftSpeed * tpf;
	}
	if (right) {
		rotation -= pitchShiftSpeed * tpf;
	}

	if (rotation < 0) {
		rotation += FastMath.TWO_PI;
	} else {
		rotation = rotation % (FastMath.TWO_PI);
	}
}

[/java]

I’m not even sure where to start, up/down work when the program is first run but after left/right movement it glitches unless you are in the starting position.

What behavior do you expect and what behavior are you seeing? “glitches” is not a very descriptive way of describing the problem.

I can tell you that by using the camera’s current location to calculate the next location, you will probably have endless trouble. Even if you get all of the math right then you will eventually run into accumulating errors.

It’s better to keep distance, yaw, and pitch separately and then calculate the position and direction when those values change.

By glitches I mean an elliptical motion and how would you suggest i convert this to distance, yaw and pitch. I’m not sure how interpolate works or what it does.

@piemaker16 said: By glitches I mean an elliptical motion and how would you suggest i convert this to distance, yaw and pitch. I'm not sure how interpolate works or what it does.

I didn’t see anywhere that you were interpolating.

Keep yaw. Adjust it up and down with your left/right.

Keep pitch. Adjust it up and down with your up/down.

Use Quaternion.fromAngles() to calculate an absolute rotation. Turn this into a direction vector. Multiply the direction vector by your desired distance (zoom).

Point the camera in the inverse direction.

how would i set yaw and pitch? Would it be something like float pitch = 0; or…?

@piemaker16 said: how would i set yaw and pitch? Would it be something like float pitch = 0; or...?

Yes, of course. Then just add FastMath.HALF_PI/rotationSpeed to it or whatever.

You may want to take a look at
http://hub.jmonkeyengine.org/forum/topic/rts-camera-control/

Updated version, with mouse-wheel support is here
http://subversion.assembla.com/svn/vmat/trunk/src/main/java/net/virtualmat/jme3/RtsCam.java

but it is z-up instead of y-up, so it might be harder to adapt directly.

Okay so I have tried to adapt off of those links but how can i have it start at the position I input? And i think there are some bugs somewhere in here?

[java]public class CenterCamera implements ActionListener, AnalogListener {

public enum DoF {
    YAW,
    PITCH,
    DISTANCE
}
private Vector3f position;
private Vector3f oldPosition = new Vector3f();
private Vector3f center;
private Vector3f oldCenter = new Vector3f();
private Camera cam;

private int[] direction = new int[3];
private float[] accelTime = new float[3];
private float[] offsetMoves = new float[3];

private final float[] maxSpeedPerSecondOfAccell = new float[5];
private final float[] maxAccellPeriod = new float[5];
private float[] minValue = new float[5];
private float[] maxValue = new float[5];

private float pitch = FastMath.PI / 4; //testing
private float yaw = -FastMath.PI; //testing
private float distance = 10; //testing

private static final int YAW = DoF.YAW.ordinal();
private static final int PITCH = DoF.PITCH.ordinal();
private static final int DISTANCE = DoF.DISTANCE.ordinal();

public CenterCamera(Camera cam, Vector3f position, Vector3f center) {
    this.position = position;
    this.center = center;
    this.cam = cam;
    this.cam.setLocation(position); //needs to be put into yaw, pitch and distance???
    this.cam.lookAt(center, new Vector3f(0, 1, 0));
    
    setMaxSpeed(DoF.YAW,2f,0.4f);
    setMaxSpeed(DoF.PITCH,1f,0.4f);
    setMaxSpeed(DoF.DISTANCE,15f,0.4f);
}

public void initKeys(InputManager inputManager) {
    inputManager.addMapping("CameraRight", new KeyTrigger(KeyInput.KEY_D), new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping("CameraLeft", new KeyTrigger(KeyInput.KEY_A), new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping("CameraUp", new KeyTrigger(KeyInput.KEY_W), new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping("CameraDown", new KeyTrigger(KeyInput.KEY_S), new KeyTrigger(KeyInput.KEY_DOWN));
    
    inputManager.addMapping("CameraIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
    inputManager.addMapping("CameraOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
    
    inputManager.addListener(this, new String[]{"CameraRight", "CameraLeft", "CameraUp", "CameraDown", "CameraIn", "CameraOut"});
}

public void update(float tpf) {
    System.out.print("update! ");
    for (int i = 0; i < direction.length; i++) {
        int dir = direction[i];
        switch (dir) {
        case -1:
                accelTime[i] = limit(-maxAccellPeriod[i],accelTime[i]-tpf,accelTime[i]);
                break;
        case 0:
                if (accelTime[i] != 0) {
                        double oldSpeed = accelTime[i];
                        if (accelTime[i] > 0) {
                                accelTime[i] -= tpf;
                        } else {
                                accelTime[i] += tpf;
                        }
                        if (oldSpeed * accelTime[i] < 0) {
                                accelTime[i] = 0;
                        }
                }
                break;
        case 1:
                accelTime[i] = limit(accelTime[i],accelTime[i]+tpf,maxAccellPeriod[i]);
                break;
        }
    }

    float distanceChange = maxSpeedPerSecondOfAccell[DISTANCE] * accelTime[DISTANCE] * tpf;
    distance += distanceChange;
    distance += offsetMoves[DISTANCE];
    offsetMoves[DISTANCE] =0;

    pitch += maxSpeedPerSecondOfAccell[PITCH] * accelTime[PITCH] * tpf;
    yaw += maxSpeedPerSecondOfAccell[YAW] * accelTime[YAW] * tpf;

    //distance = checker(minValue[DISTANCE],distance,maxValue[DISTANCE]);
    //yaw = checker(minValue[YAW],yaw,maxValue[YAW]);
    //pitch = checker(minValue[PITCH],pitch,maxValue[PITCH]);
    
    position.x = center.x + (float)(distance * Math.cos(pitch) * Math.sin(yaw));
    position.y = center.y + (float)(distance * Math.cos(pitch) * Math.cos(yaw));
    position.z = center.z + (float)(distance * Math.sin(pitch));
    
    if(oldPosition.equals(position) && oldCenter.equals(center)) {
        return;
    }
    
    cam.setLocation(position);
    cam.lookAt(center, new Vector3f(0,1,0));
    
    oldPosition.set(position);
    oldCenter.set(center);
}

private float limit(float min, float value, float max) {
    if(value < min) {
        return min;
    } else if(value > max) {
        return max;
    } else {
        return value;
    }
}

public void onAction(String name, boolean isPressed, float tpf) {
    int val = isPressed ? 1 : 0;
    if(name.equals("CameraRight")) {
        direction[DoF.YAW.ordinal()] = val;
        System.out.print("right! ");
    } else if(name.equals("CameraLeft")) {
        direction[DoF.YAW.ordinal()] = -val;
        System.out.print("left! ");
    } else if(name.equals("CameraUp")) {
        direction[DoF.PITCH.ordinal()] = val;
        System.out.print("up! ");
    } else if(name.equals("CameraDown")) {
        direction[DoF.PITCH.ordinal()] = -val;
        System.out.print("down! ");
    } else {
        return;
    }
    update(tpf);
}

public void onAnalog(String name, float value, float tpf) {
    if(name.equals("CameraIn")) {
        offsetMoves[DISTANCE] -= value;
        System.out.print("in! ");
    } else if(name.equals("CameraOut")) {
        offsetMoves[DISTANCE] += value;
        System.out.print("out! ");
    } else {
        return;
    }
    update(tpf);
}

public Vector3f getCenter() {
    return center;
}
public Vector3f getPosition() {
    return position;
}
public void setMinMaxValues(DoF dg, float min, float max) {
    minValue[dg.ordinal()] = min;
    maxValue[dg.ordinal()] = max;
}
public void setMaxSpeed(DoF deg, float maxSpd, float accelTime) {
    maxSpeedPerSecondOfAccell[deg.ordinal()] = maxSpd/accelTime;
    maxAccellPeriod[deg.ordinal()] = accelTime;
}
public void setCenter(Vector3f newCenter) {
    center = newCenter;
}
public void setPosition(Vector3f newPosition) {
    position = newPosition;
}

}[/java]