Chapter 7
Misc Math

This section is about miscellaneous things we can do with rotations and arrows, and how they work.

7.0.1 Finding angles

Vector3.Angle(v1,v2) tells you the angle in degrees between two arrows. It treats them as if they were both coming from the same spot, and finds the angle of the V they make.

A nice thing is it doesn’t matter if the arrows are “diagonal” to each other. It can measure the angle just as easily. If you use RotateTowards to spin a vector, this is measuring how far you have to go.

A funny thing is it always gives 0-180, and doesn’t tell you which direction. If one arrow is 30 degrees from another, it could be clockwise, counter-clockwise, up, down or any diagonal. The second arrow could be anywhere on a 30-degree cone around the first.

That’s a common use of Angle – checking whether something is in our “vision cone.” Find the angle between your forward arrow and an arrow to the target. If the angle is too big, we can’t see it:

Vector3 toBall = ball.position-transform.position;  
float angToBall=Vector3.Angle(transform.forward, toBall);  
 
if(angToBall<30) print("I can see it");

Both inputs need to be arrows, and they usually only make sense if they really are coming from the same spot. Suppose we use the positions by mistake: this computes a wrong angle between us and the ball:

float angToBall = Vector3.Angle(transform.position, ball.position);  
if(angToBall<30) print("a person at 000 can see both at once");

transform.position isn’t an arrow. But if you say it is one, it counts as the arrow from (0,0,0) to you. So this checks the angle for someone standing at (0,0,0).

Sometimes you want the “flat” angle – like it would be on a map, not counting the up/down part of the angle. You can get that with the old flat arrow trick. Zero-out the y from both arrows:

Vector3 toBall = ball.position = transform.position;  
toBall.y=0;  
Vector3 myForward=transform.forward;  
myForward.y=0;  
 
float yAngOnly=Vector3.Angle(toBall, myForward);

This also won’t tell you left vs. right.

Similar to Vector3.Angle is Quaternion.Angle. But Quaternion.Angle(q1,q2) works in the same funny way as Quaternion.RotateTowards. It counts the aim direction and also the z-roll difference. You usually want Vector3 angle instead.

Suppose you had a game where someone has to line up two logs. If you just need them facing the same way, check if Vector3.Angle(log1.forward, log2.forward) is small. If you also want to check if they’re spun the same way (both mossy side up?) check Quaternion.Angle(log1.rotation, log2.rotation);.

7.0.2 Cross Product

Cross Product is one of those math things you don’t realize you sometimes need until you do.

Sometimes you have two arrows going into or coming out of the same spot and you need to spin one towards the other. For that, you need their combined axis-arrow, the arrow going at 90 degrees to them both. That’s the cross-product (google will show you lots of pictures.)

In Unity, Vector3 cp = Vector3.Cross(v1, v2); will get their cross-product. It’s really their axis-arrow, but people say cross product and just know that means an arrow.

Here’s a fun cross-product fact: remember FromToRotation figures out how to spin one arrow into another? It uses AngleAxis to do it. It finds their cross-product, which is the axis. Then uses Angle to get the degrees.

Another fun fact: axes have a plus and minus direction. The cross-product follows the left-hand rule to decide which way to go. If the rotation from the first angle to the second is clockwise, the cross-product goes up, otherwise it goes down.

You can use this to find the direction of Angle. This assumes the two vectors are basically flat on xz. A downward cross-product means counter-clockwise:

float degs = Vector3.Angle(v1, v2);  
// if the cross product goes down, v1 to v2 is counter-clockwise, so flip degrees:  
if(Vector3.Cross(v1,v2).y<0) degs*=-1;  
// degs is now a proper -180 to 180

Another fun cross-product fact: Vector3.Cross(transform.forward, transform.right) is transform.up. Any two can create the third. That’s one of the main purposes of cross-product.

7.0.3 Normals

A normal is a an arrow that tells you which way a surface is facing. It comes straight out of it, length one.

For examples, the normal of the floor is Vector3.up and the normal of the right-side wall is an arrow pointing left. If you have a left-to-right ramp (like a /-slash,) its normal would be pointing up and to the left. Even curved surfaces have normals, but in a game most round things are made of small flat edges.

The difference between a normal and a forward arrow is that a normal has an actual flat surface that it points away from. Suppose you have a pyramid – a base and four sides. The entire thing might be facing up. But the base’s normal is pointing down, and the side’s normals are all pointing diagonal up.

Normals are another of those things which you’ll know when you need it. Sometimes you’re doing some math and need to know “which way is that wall facing.” That’s what the normal is for.

How do you find the normal? It depends. A common trick is to raycast (often straight down.) RaycastHit’s tell you the normal of what they hit:

RaycastHit RH; // stores raycast data  
if(Physics.Raycast(transform.position, Vector3.down, out RH)) {  
  Vector3 norm=RH.normal;  
  ...

If we’re using official Unity terrain, we can ask the terrain for it’s normal. It’s a little messy since it wants the 0-1 percent (if the ground is 400 wide and you’re at 100, you have to give it 0.25.)

This looks up the normal of a Terrain where you’re standing:

public Terrain ground;  
 
TerrainData gd=ground.terrainData;  
Vector3 myPos=transform.position, gPos=ground.transform.position;  
// convert my position into 0-1 ground x and y:  
float gx=(myPos.x-gPos.x)/gd.size.x;  
float gy=(myPos.z-gPos.z)/gd.size.z;  
// lookup:  
Vector3 gNorm=gd.GetInterpolatedNormal(gx, gy);

If you know three of the corners, the normal is the cross-product of two connecting edges. Suppose v0 is a corner, and v1 and v2 are adjacent corners. The normal is:
Vector3.Cross(v1-v0, v2-v0);

7.0.4 Reflect

An example of using a normal is the Reflect command. It takes a line aimed at a wall, and figures out what direction it would bounce off.

Obviously, this depends on which way the wall is facing – that’s the normal. So the inputs to Reflect are the arrow and the wall’s normal. Here’s a laser that can bounce off one wall (it raycasts, and if it hits a wall, reflects and raycasts again):

Vector3 laserDir;  
 
RaycastHit RH = new RaycastHit();  
bool gotaHit=false;  
if(Physics.Raycast(transform.position, laserDir, out RH)) {  
    if(RH.transform.name!="wall") gotaHit=true;  
    else {  
      // bounce off wall:  
      Vector3 hitPos = RH.point;  
      Vector3 wallNorm = RH.normal;  
      Vector3 dir2=Vector3.Reflect(laserDir, wallNorm);  
      print("hit wall, bouncing");  
      // now shoot from where we bounced:  
      if(Physics.Raycast(hitPos, dir2, out RH)) {  
        if(RH.transform.name!="wall") gotaHit=true;  
    }  
  }  
  if(gotaHit) print("We hit "+RH.transform.name);  
}

If you’re wondering, the way reflect works is by finding the cross product and angle between you and the normal, then spinning that much past it.

7.0.5 Opposite of an angle

The same way q*0.5f won’t give you half an angle, getting -q is a pain. The official way to flip an angle is Quaternion.Inverse(q);.

It cancels out the original rotation: q*qInverse is rotation (0,0,0).

To be cute, you can also use a no-limit Lerp to get it. Start at your angle and go 100% past 0: qInv = Quaternion.LerpUnclamped(q, Quaternion.indentity, 2.0);. But using Inverse is probably easier to read.

7.0.6 Square magnitude

There’s a built-in function that gives you the square of the length of a line. If v has length 3, v.sqrMagnitude is 9.

That seems insanely pointless. Why would you need your length squared? And if you did, wouldn’t it just be easier to write len*len?

It’s just a trick to speed up the math, and does nothing else useful. Here’s the explanation:

The formula to find the length of an arrow is Mathf.Sqrt(x*x+y*y+z*z). In other words, when you run v.magnitude the first step gets 9, then the second step square roots it to get 3.

When you use sqrMagnitude, you’re saying to save time by only running step 1, and you’ll take it from there.

Suppose you want to find everything 3 away from you. It’s faster to find everyone who’s sqrMagnitude from you is 9 or less. If you want to find the nearest enemy, you may as well find the smallest sqrMagnitude from yourself.

The important thing is, sqrMagnitude does nothing useful – it just runs faster but makes you think harder to write the program.

7.1 Trig you should never use

If you’re using trig to do things, it tends to take more testing to get it to work, and there’s almost always an easier way. Almost. But just in case, here’s some of the trig you should be avoiding.

7.1.1 Radians

Real trig functions, like sin, cosine …use radians instead of degrees. They are different in three ways:

Unity has a builtin Mathf.Rad2Deg conversion, but it’s just 57. It won’t do anything about starting in the wrong place or going backwards. Mathf.Deg2Rad is just 1/57.

If you really need to convert a trig angle in radians to a unity angle in degrees, it’s:

degs=-rads*Mathf.Rad2Deg+90;  
 
rads=-degs*Mathf.Deg2Rad+Mathf.PI/2;

There’s also a shorter but more confusing method involving switching x and y.

7.1.2 atan2

A standard trig trick is turning a line’s slope into its angle. If a line has a slope of 1, it’s at 45 degrees. Arc-tangent does that, but it crashes on 90 degrees (the slope is infinity) and only works for half a circle. The standard computer trig trick is a rewrite using x and y called atan2. It gives a correct full-circle angle.

In Unity Mathf.Atan2(transform.forward.z, transform.forward.x) gives your “flat” y-spin. But in radians, not degrees. Using Angle is almost always easier.

7.1.3 dot product

Dot product is the trig version of angle. It tells you the angle between two arrows except: they have to be length 1 (you can normalize them,) and it goes from 1 to -1, which is odd. An exact match gives 1, 90 degrees apart gives 0, and exactly opposite gives -1.

It’s actually the cosine of the angle between them, so 45 degrees give 0.71.

Vector3.Angle is just better. The only reason to use dot product is if you happen to have some formula that needs it.

7.1.4 Rotation matrixes

A 4x4 grid of numbers, called a Matrix4x4 in Unity, is an alternate way to represent a rotation. Graphics cards use these, so Shader programmers tend to know them.

Quaternions are a better way to handle rotations. There’s no reason to use a rotation matrix unless you’re forced to – like setting a value for a shader or using the old GUI system (the only way to spin was to set a rotation matrix.)

Just so you know, a 4x4 matrix really stores a rotation and a position and scale, all coded together. But we have those already, in easy to use form.