Project an Angle onto a Plane (Turret Rotation)

Flipbookee has a mastery of concise english, so I am copying his explanation almost verbatim:

I have a tank on an arbitrarily inclined surface. I know the direction in world space (euler angles) where I want it to shoot. I need to know the x and y rotation (also euler angles) in the tank’s local space where the turret needs to aim in order to shoot at that desired direction.

Any insight? Thank you very much!

If you just want to rotate the turret and the gun around their local Y and X axes in order to aim to some target point, there’s a much easier alternative: project the target position into the turret’s local horizontal plane and use LookAt to rotate the turret to this point, then simply use LookAt(target) to control the gun direction (does exactly the same effect that a gun childed to the turret and rotating only around its local X axis).

This approach requires that you child the turret and the gun to an intermediate empty object - the Base object - which in turn is childed to the tank, like below:

  • Hierarchy:

    Tank ← tank is the parent of the Base empty object
    Base ← control script goes here
    Turret ← turret is childed to Base
    Gun ← gun is childed to Base

The script gets references to Gun and Turret, and control both:

var target: Transform;
var damping: float = 5.0;

private var turret: Transform;
private var gun: Transform;
private var trf: Transform;

function Start () { // gun and turret are childed to Base:
  trf = transform;
  gun = trf.Find("Gun");
  turret = trf.Find("Turret");
}

var aimPos: Vector3; // position the gun must aim at
var elevationAngle: float = 30; // fixed elevation angle
var projectileVel: Vector3; // velocity needed to reach the target

function AimAtTarget(euler: Vector3, distance: float){
  // get vector Base->target:
  var dir = Quaternion.Euler(euler) * Vector3.forward * distance;
  var h = dir.y;  // get height difference
  dir.y = 0;  // retain only the horizontal direction
  var dist = dir.magnitude ;  // get horizontal distance
  var a = elevationAngle * Mathf.Deg2Rad;  // convert elevation angle to radians
  dir.y = dist * Mathf.Tan(a);  // set dir to the elevation angle
  dist += h / Mathf.Tan(a);  // correct for small height differences
  // calculate the velocity magnitude
  var vel = Mathf.Sqrt(dist * Physics.gravity.magnitude / Mathf.Sin(2 * a));
  projectileVel = vel * dir.normalized; // save projectile velocity vector
  aimPos = gun.position + dir; // save position to aim at
}
  
function Update () {
  // convert target position to local space:
  var pos = trf.InverseTransformPoint(aimPos);
  pos.y = 0; // project it in the horizontal plane
  // convert pos back to world space:
  pos = trf.TransformPoint(pos);
  // find the desired turret rotation and Slerp to it:
  var rot = Quaternion.LookRotation(pos-trf.position, trf.up);
  turret.rotation = Quaternion.Slerp(turret.rotation, rot, damping*Time.deltaTime); 
  // just Slerp the gun to the target:
  rot = Quaternion.LookRotation(aimPos-gun.position, trf.up);
  gun.rotation = Quaternion.Slerp(gun.rotation, rot, damping*Time.deltaTime); 
}

// how to use:

var projPrefab: Rigidbody; // projectile prefab

function Shoot(eulerAngles: Vector3, distance){
  AimAtTarget(eulerAngles, distance);
  yield WaitForSeconds(3); // give some time for gun positioning
  var proj: Rigidbody = Instantiate(projPrefab, gun.position, gun.rotation);
  proj.velocity = projectileVel; // shoot with the calculated velocity
}

This code actually shoots the projectile according to the calculated trajectory - the turret/gun orientation is just a visual aid. By the way, I’m assuming here that the Euler angles define the target direction based on the Gun object. If they are relative to the tank position, replace gun by the transform.parent (assuming Base is childed to the tank) in the Instantiate instruction.

NOTE: You must use some trick to avoid collisions between the projectile and the tank, turret or gun. An alternative is to create two new layers, Tank and Projectile, disable collisions between them in the Physics Manager and move the tank/turret/gun and projectile to the appropriate layers. Another alternative: make the projectile collider be a trigger during a few cycles, so that it can ignore collisions until it’s at some distance from the tank - like this:

  ...
  var proj: Rigidbody = Instantiate(projPrefab, gun.position, gun.rotation);
  proj.collider.isTrigger = true; // projectile is a trigger, initially
  proj.velocity = projectileVel;  // shoot with the calculated velocity
  yield WaitForSeconds(0.2);      // let it reach a safe distance...
  proj.collider.isTrigger = false; // and make it a regular collider again
}

I can’t really visualize your problem. Can you make like a set of isometric pictures?

I honestly didn’t know the definition of “azimuth” and upon looking it up, it does not sound like the “elevation”. The terms, without pictures or a perhaps a better description, can be a source of confusion. If you want to get more answers, I suggest editing those parts.

Now, it seems that you want to determine which way the turret is facing (the “pan”). This may sound dum, since I can’t really understand the problem, can’t you have the turret as a separate game object attached to the top of the tank, and use its euler Y value?