Friday, January 1, 2016

unity - How can I rotate an object based on another's offset to it?


I have a 3D model of a turret that con rotate around the Y-axis. This turret has a cannon that is significantly off the center of the object. I want the cannon, not the turret, to aim at a specified target. I can only rotate the turret, however, and thus I don't know what equation I need to apply in order to accomplish by objective.


The following image illustrates my problem:enter image description here


If I have the turret "LookAt()" the target, a laser originating from the cannon will completely miss said target.


If this were a completely top-down scenario, and the cannon were exactly parallel to the turret, then my logic tells me that the fake target should be located at a position that's equal to the actual target plus an offset that's equal to that between the turret and the cannon. However, in my actual scenario, my camera is angled 60ยบ, and the cannon has a slight rotation.



The following image illustrates the scenario: Illustrative Scenario


I'm not sure exactly why, but if I apply that same offset, it only seems to work while aiming at certain distances from the turret.


Is my logic flawed? Am I missing something fundamental here?


Final Edit: the solution provided by @JohnHamilton latest update solves this problem with perfect precision. I have now removed the code and images that I used to illustrate my incorrect implementations.



Answer



The answer is actually pretty easy if you do the math. You have a fixed distance of Y and a variable distance of X (See Picture 1). You need to find out the angle between Z and X and turn your turret that much more. enter image description here


Step 1 - Get distance between the turret line (V) and the gun line (W) which is Y (this is constant but doesn't hurt to calculate). Get distance from the turret to the target (which is X).


Step 2 - Divide Y by X and then get the Hyperbolic Sine of the value


double turnRadians = Mathf.Asin(Y/X);
double angle = Mathf.Rad2Deg * turnRadians;


//where B is the red dot, A is a point on the X line and C is a point on the Z line.

Step 3 - Turn the turret that much more (around the axis that goes from it's top to it's bottom, most likely up axis but only you can know that part).


gameObject.transform.Rotate(Vector3.up, turnAngle);

Of course in this case, you need it to turn counterclockwise so you might need to add a minus in front of the turnAngle there, as in -turnAngle.


Edited some parts. Thanks to @ens for pointing out the difference in distance.


The OP said his gun has an angle so here we go, image first, explanation later: enter image description here


We already know from the previous calculation where to aim the red line according to the blue line. So aiming for the blue line first:



float turnAngle = angleBetweenTurretAndTarget - angleBetweenTurretAndGun;
turret.transform.Rotate(Vector3.up, turnAngle);

The only calculation that differs here, is the calculation of "X Prime" (X') because the angle between the gun and the turret (angle "a") changed the distance between the lines.


//(this part had a mistake of using previous code inside new variable names, YPrime and Y are shown as X' and X in the 2nd picture.
float YPrime = Cos(a)*Y; //this part is what @ens is doing in his answer
double turnRadians = Mathf.Asin(YPrime/X);
double angle = Mathf.Rad2Deg * turnRadians;
turret.transform.Rotate(Vector3.up, angle);


This next part is ONLY necessary if you're doing the turret guns modular (i.e. user can change the guns on a turret and different guns have different angles). If you're doing this in editor, you can already see what the gun angle is according to the turret.


There are two methods for finding the angle "a" , one is the transform.up method:


float angleBetween = Vector3.Angle(turret.transform.up, gun.transform.up);

Above technique will calculate in 3D, so if you want a 2D result, you need to get rid of the Z axis (that's what I assume where gravity is, but if you changed nothing, in Unity it's Y axis that is up or down, i.e. gravity is on the Y axis, so you might have to change things up):


Vector2 turretVector = new Vector2(turret.transform.up.x, turret.transform.up.y);
Vector2 gunVector = new Vector2(gun.transform.up.x, gun.transform.up.y);
float angleBetween = Vector2.Angle(turretVector, gunVector);

Second way is the rotation method (I'm thinking in 2D in this case):



double angleRadians = Mathf.Asin(turret.transform.rotation.z - gun.transform.rotation.z);
double angle = 2 * Mathf.Rad2Deg * angleRadians;

Again, all these codes will give you values that are positive, so you might have to add or subtract the amount depending on the angle (there are calculations for that too, but I'm not going to go that in-depth). A good place to start on this would be the Vector2.Dotmethod in Unity.


Final block of code for additional explanation of what we're doing:


//turn turret towards target
turretTransform.up = targetTransform.position - turretTransform.position;
//adjust for gun angle
if (weaponTransform.localEulerAngles.z <180) //if the value is over 180 it's actually a negative for us
turretTransform.Rotate(Vector3.forward, 90 - b - a);

else
turretTransform.Rotate(Vector3.forward, 90 - b + a);

If you did everything right, you should get a scene like this (link for the unitypackage): enter image description here What I mean by always positive values:enter image description here


The Z method can give negative values:enter image description here


For an example scene, get the unitypackage from this link.


Here's the code I've used in the scene (on the turret):


public class TurretAimCorrection : MonoBehaviour
{
public Transform targetTransform;

public Transform turretTransform;
public Transform weaponTransform;

private float f, d, x, y, h, b, a, weaponAngle, turnAngle;
private void Start()
{
TurnCorrection();
}

private void Update()

{
TurnCorrection();
}
void TurnCorrection()
{
//find distances and angles
d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.y), new Vector2(turretTransform.position.x, turretTransform.position.y));
x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.y), new Vector2(weaponTransform.position.x, weaponTransform.position.y));
weaponAngle = weaponTransform.localEulerAngles.z;
weaponAngle = weaponAngle * Mathf.Deg2Rad;

y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
b = Mathf.Rad2Deg * Mathf.Acos(y / d);
a = Mathf.Rad2Deg * Mathf.Acos(y / x);
//turn turret towards target
turretTransform.up = targetTransform.position - turretTransform.position;
//adjust for gun angle
if (weaponTransform.localEulerAngles.z < 180)
turretTransform.Rotate(Vector3.forward, 90 - b - a);
else
turretTransform.Rotate(Vector3.forward, 90 - b + a);

//Please leave this comment in the code. This code was made by
//http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR.
//This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
}
}

3D adapted code with X and Z as the 2D plane:


public class TurretAimCorrection : MonoBehaviour
{
public Transform targetTransform; //drag target here

public Transform turretTransform; //drag turret base or turret top part here
public Transform weaponTransform; //drag the attached weapon here

private float d, x, y, b, a, weaponAngle, turnAngle;
private void Start()
{
TurnAdjustment();
}

private void Update()

{
TurnAdjustment();
}
void TurnAdjustment()
{

d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.z), new Vector2(turretTransform.position.x, turretTransform.position.z));
x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.z), new Vector2(weaponTransform.position.x, weaponTransform.position.z));
weaponAngle = weaponTransform.localEulerAngles.y;
weaponAngle = weaponAngle * Mathf.Deg2Rad;

y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
b = Mathf.Rad2Deg * Mathf.Acos(y / d);
a = Mathf.Rad2Deg * Mathf.Acos(y / x);
//turn turret towards target
turretTransform.forward = new Vector3(targetTransform.position.x, 0, targetTransform.position.z) - new Vector3(turretTransform.position.x, 0, turretTransform.position.z);
//adjust for gun angle
if (weaponTransform.localEulerAngles.y < 180)
turretTransform.Rotate(Vector3.up, - a +b-90);
else
turretTransform.Rotate(Vector3.up, + a+ b - 90);

//Please leave this comment in the code. This code was made by
//http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR.
//This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
}
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...