Graphics & geometry: the orth function in 3D
The orth
concept in 2D geometry and graphics is where, given some vector, we find a vector (line) that is at right angles to the original. We have a tiny bit of choice about how to do it – we can pick a vector to the left, or to the right – but it’s very simple (more
here).
The equivalent function in 3D is one of those odd little problems where you are given some freedom with the answer, and you don’t really care about that freedom – just pick something please! – but you’re made to care about it anyway and do what feels like too much thinking. Bah!1
Recap: orth function in 2D
Nice and simple, there are two variants to choose from:
$$ \begin{aligned} \mathbf{a}^{\perp} &= ( -a_1, a_0 ) \\[0.5em] \mathbf{a}^{\top} &= ( a_1, -a_0 ) \end{aligned} $$There is only one possible line that can be at right angles to the original line, but expressed as a vector it can go in two directions, hence the two variants.
Orth in 3D
Add another dimension to the mix and suddenly we have more choice. Now we have an infinite number lines that are at a right-angle to the original – they form a plane.
We don’t care which of those infinite number of lines we pick – any will do. How can we execute this ‘pick any’ idea?
In 3D and above we usually create orthogonal vectors to order by using the cross product on two known vectors \( \mathbf{a} \) and \( \mathbf{b} \) to produce a third vector which is at right angles to both:
$$ \mathbf{a} \times \mathbf{b} = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ a_1 & a_2 & a_3 \\ b_1 & b_2 & b_3 \end{vmatrix} $$So we need to choose a \( \mathbf{b} \) value for our cross product with \( \mathbf{a} \).
We don’t care about the exact answer, so just pick anything for \( \mathbf{b} \), right? And the result will definitely be orthogonal to \( \mathbf{a} \).
So we might just throw in something like \( \mathbf{b} = (1, 0, 0) \).
If you do this you might never notice a problem; hey, it works! But fate cackles in the corner and rubs its maltesers patiently…2
The problem is that if we try this with any \( \mathbf{a} \) that goes in the same direction as \( \mathbf{b} \), i.e. anything of form \( (k, 0, 0) \), we’ll get \( (0, 0, 0) \) for the answer, because the two vectors are co-linear. No good.
And obviously you can’t just use a variant like \( (0, 1, 0) \) or even \( (1, 1, 1) \) because they have a similar issue. Are you feeling lucky, punk?
What to do?
Clearly, randomly choosing some value for \( \mathbf{b} \) is a bad idea. It has to be chosen with some knowledge of what is in \( \mathbf{a} \). In particular, we need \( \mathbf{b} \) to:
- not be co-linear with \( \mathbf{a} \)
- not be the zero vector \( (0, 0, 0) \)
Happily, there’s a fruitful strategy right under our nose: we can use the 2D orth function. After all, it makes a 2D vector into another which is definitely not co-linear.
Sketching out an example: suppose we have \( \mathbf{a} = (2, 0, 0) \). We can use 2D orth on two parts of that vector, say \( a_1 \) and \( a_2 \), and we’ll end up with a new vector that is definitely not co-linear:
$$ \begin{aligned} \mathbf{b} &= ( -a_2, a_1, a_3) \\[0.5em] &= (0, 2, 0) \end{aligned} $$Looks good!
However, we hand-picked the two elements for our 2D orth here, whereas a proper implementation would choose the two suitable elements.
What criteria could we use to choose? We know we want to avoid a zero vector output. So how about we select some element of \( \mathbf{a} \) that’s non-zero? And pair it with one of the other two elements (either, just pick one).
This works, to some extent. But it has problem with the ‘non-zero’ aspect: this involves an equality check on real values – an anti-pattern to be avoided if at all possible.
There’s another subtle issue too – given this input:
$$ \mathbf{a} = ( 0, 0.0000001, 10) $$the ‘look for something non-zero’ idea might find the second element, the small value 0.0000001, and choose for the other element the 0. Then we’d end up with:
$$ \mathbf{b} = ( -0.0000001, 0, 0) $$This is orthogonal to \( \mathbf{a} \), but it’s also a small vector. When we’re using vectors to express directions, we prefer to avoid tiny values in order to reduce numerical errors. For example: say the code made a unit vector of the resulting \( \mathbf{b} \) – we’d be dividing by a tiny value, which is more likely to result in numerical inaccuracies.
Go big or go hame
So let’s turn the idea of looking for merely non-zero values on its head a little. Why don’t we look for large (absolute) values? In fact, the largest.
Now we’re onto something! Concentrating on large values would remove the real value equality with zero checks (instead we might use operators max
or \( > \)) and dispel our small value accuracy woes.
So given this value:
$$ \mathbf{a} = ( 0, 0.0000001, 10) $$We use the largest value (10) for one of our components. For the other it’s enough to just arbitrarily pick one of the other remaining elements; suppose we picked the 0. Then our 2D orth idea gives us:
$$ \mathbf{b} = ( -10, 0.0000001, 0) $$Done! That’s a nice choice for \( \mathbf{b} \).
Coding our algorithm
Now we have the solution, we need to make the code happen.
This is where you can whittle away at writing the most elegant and compact way of coding these steps:
- choose the element with largest value
- choose one remaining element (either of the two is fine)
- perform 2D orth on those two chosen elements in the 3D vector
Here’s the most compact way I came up with (Swift pseudocode):
// orth for 3D vector
func orth(a: Vec3) -> Vec3 {
let ax = abs(a[0])
let ay = abs(a[1])
let az = abs(a[2])
if ax > ay || (az > max(ax, ay)) {
return rot_xz(a)
}
return rot_xy(a)
}
func rot_xy(v: Vec3) -> Vec3 {
// do an orth 2D on the x, y components
return Vec3(-v[1], v[0], v[2])
}
func rot_xz(v: Vec3) -> Vec3 {
// do an orth 2D on the x, z components
return Vec3(-v[2], v[1], v[0])
}
When is 3D orth useful?
If you want to transform some coordinate system into another basis, and you only care about locking down one axis, you can use 3D orth.
A practical example:

In 3D CSG graphics, e.g. OpenSCAD, if you want to truncate (cut off) the corners of 3D objects like polyhedra, you are going to be effectively slicing away stuff on one side of a plane. You only care about the normal direction (axis) of the plane, not about how exactly the two vectors inside that plane are oriented. But if you use the usual matrix trick for changing basis (coordinate system) in order to make the cut, you have to provide the two vectors you don’t care about; so 3D orth to the rescue: it helps you make your two ‘meh’ vectors.
