Turret Rotation on a moving object with rotation limits

I’ve been working at a problem with some time, and a week+ of googling and experimenting has got me nowhere.

–THE PROBLEM–

I have a turret on a moving object (let’s say, for the sake of simplicity, it’s on a tank).

Now the turret faces the mouse position with limitations:

  1. It cannot rotate left or right so that the angle is in excess of variable maxLRArc/2.
  2. It is limited in L/R rotation speed by rotationSpeedLR
  3. It cannot rotate up or down so that the angle is in excess of variable maxUDArc/2.
  4. It is limited in U/D rotation speed by rotationSpeedUD

Here’s a picture for clarity:

EDIT: A picture of the scene in Unity:

alt text

  • The YELLOW line is the raytrace from camera to mouse position
  • The CYAN lines form the limits of the Up/Down rotation
  • The RED (less visible) lines form the limits of the Left/Right rotation
  • The BLUE line is the mouse position projected on the L/R plane
  • The MAGENTA line is the mouse position projected on the U/D plane
  • The GREEN line shows the parent’s forward vector from the position of the turret

If it’s still unclear you can think about it as having someone’s head track a moving object… The angle limitations are apparent in that scenario… but the person can only turn his head left or right and pitch up or down, but not tilt his head to one side or another.

–THE APPROACH–

I’ve been attempting to approach the problem from a step-by-step method, which is to say:

  1. Turret first rotates left or right towards the mouse.
  2. Turret then rotates up or down towards the mouse.

The way I’m doing is is first projecting the mouse position vector onto a plane in-line with the turret. Once for each step of the rotation, so that the mouse position is in-plane with the step’s angle limits.

The checks and balances all work beautifully, the problem is executing the rotations, WITH a constant speed.

When I attempted to rotate for each axis separately, the rotations interfered with one another. When I attempted to rotate them together, the turret ended up twisting along the local Z axis (separating from it’s logical point-of-contact to the tank) particularly noticeable when the tank goes up or down an incline.

–THE PLEA–

I’ve tried a lot of solutions that I’ve found but none give the desired results. Most (if not all) deal with a few of the restrictions I’ve listed individually, but not together like this.

I figure I’m at the limits of my capability and need to yield to more experienced individuals. Any help is greatly appreciated!

EDIT: The code I have is so messy and fragmented that it would be more harm than good, but if necessary, i can provide it.

Here is a script that gets you most of the way there. There some unknowns in your question, so I expect you’ll have a few changes, but I believe all the heavy lifting is done.

#pragma strict

var target : Transform;
var gun : Transform;
var turretDegreesPerSecond : float = 45.0;
var gunDegreesPerSecond : float = 45.0;

var maxGunAngle = 45.0;
var maxTurretAngle = 45.0;


private var qGunStart : Quaternion;
private var trans : Transform;

function Start() {
	trans = transform;	
	qGunStart = gun.transform.localRotation;
}

function Update () {
	var distanceToPlane = Vector3.Dot(trans.up, target.position - trans.position);
	var planePoint = target.position - trans.up * distanceToPlane;
	
	var qTurret : Quaternion = Quaternion.LookRotation(planePoint - trans.position, transform.up);
	var qForward : Quaternion = Quaternion.LookRotation(transform.parent.forward);
	if (Quaternion.Angle(qTurret, qForward) < maxTurretAngle) {
		transform.rotation = Quaternion.RotateTowards(transform.rotation, qTurret, turretDegreesPerSecond * Time.deltaTime);
	}
	else {
		Debug.Log("Target beyond turret range");
	}
	
	var v3 = Vector3(0.0, distanceToPlane, (planePoint - transform.position).magnitude);
	var qGun : Quaternion = Quaternion.LookRotation(v3);
	
	if (Quaternion.Angle(qGunStart, qGun) <= maxGunAngle)
		gun.localRotation = Quaternion.RotateTowards(gun.localRotation, qGun, gunDegreesPerSecond * Time.deltaTime);
	else
		Debug.Log("Target beyond gun range");
}

And here is a unitypackage with this script in use:

www.laughingloops.com/Turret2.unitypackage

Note that maxGunAngle() will define the maximum delta from the start position of the gun. For maxTurretAngle, the maximum angle is between the forward of the parent object and the gun.

You can use just one segment of code from robertbu’s answer. Here:

var toTarget : Quaternion = Quaternion.LookRotation(target.position - turret.position, turret.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toTarget, turretSpeed * Time.deltaTime);
//freeze Z-axis to stop turret from rolling
transform.localRotation.z = Quaternion.identity.z;

With these alone, the turret will rotate towards the target on all axes, except Z.

Use the ClampAngle function specified in aldonaletto’s answer here:

Limit Local Rotation

It automatically converts negative values into usable eulers. Of course, you’ll need minimum and maximum values for your X and Y axes. Use it like this:

var angleX = ClampAngle(turret.localEulerAngles.y,minX,maxX);
var angleY = ClampAngle(turret.localEulerAngles.x,minY,maxY);

turret.localEulerAngles = new Vector3(angleY,angleX,turret.localEulerAngles.z);

After attempting the solutions in the answers provided, I eventually came upon a working solution.

Since the code was functional for turn movement and pitch movement individually, I calculated the proper quaternions for the movement within the boundaries, then converted portions of these quaternions to vector3s of euler angles and used quaternion.euler to rotate:

Vector3 rot = new Vector3(pitchQuat.eulerAngles.x,
		          turnQuat.rotation.eulerAngles.y,
		          pitchQuat.rotation.eulerAngles.z);
transform.rotation = Quaternion.Euler(rot);

Thanks for all the responses everyone!