Finding an orthogonal vector to any vector

Given an arbitrary vector, how can I find one of the infinitely many orthogonal vectors?

image

One method would be to cross it with a pure up vector <0, 1, 0>

image

but that wouldn’t work for a purely up or down vector.

image

This could be fixed by implementing edge cases for purely up/down vectors, but I’m curious if there’s another method altogether?

6 Likes

You could probably use CFrame mechanics to turn this vector into a LookVector and rotate the CFrame 90 degrees around the X or Y axis:

--generates a random vector orthogonal to `SomeVector`
function CreateOrthoVector(SomeVector)
	local CF = CFrame.new(Vector3.new(),SomeVector)
	local newCF = CF
		* CFrame.Angles(0,0,math.random() * 2 * math.pi)
		* CFrame.Angles(math.pi/2,0,0)

	return newCF.LookVector
end
1 Like

Say you have a vector <x,y,z> and you want to find an orthogonal vector to it <a,b,c>
You know that the dot product of the two vectors has to be 0
So then you have ax+by+cz=0
I think you can just let a and b be anything and solve for c?

If it needs to be normalized you have the additional constraint that a^2+b^2+c^2=1, so you can let a be any value in the interval [-1,1] and you have two equations with two unknowns

But I am guessing it suffices to do the first approach and take the unit vector of it

also I really like your visuals xd

4 Likes

Your first method seems to work, but it’s suspiciously convenient lol. I’m gonna try to see if there are any edge cases.

EDIT: I see no reason why it wouldn’t work. Set two components to 1 and solve for the third, but you have to ensure the component you’re solving for isn’t multiplied by a 0. Otherwise, you get an indeterminate.

image

So much for the nice visuals >.<

3 Likes

Solutions


@goldenstein64’s solution:

  • Consistent floating-point error on order of 10-8
  • Creates 4 CFrames
local function GetOrthogonalVector(vec)
	if vec.Magnitude == 0 then error("Vector has no magnitude") end

	local newCF = CFrame.new(Vector3.new(), vec)
		* CFrame.Angles(0, 0, math.random() * 2 * math.pi)
		* CFrame.Angles(math.pi / 2, 0, 0)

	return newCF.LookVector
end

@Acreol’s solution:

  • Sometimes has a floating-point error on order of 10-8
  • Creates 1 Vector3
local function GetOrthogonalVector(vec)
	if vec.Magnitude == 0 then error("Vector has no magnitude") end
	
	if vec.X ~= 0 then
		return Vector3.new((-vec.Y - vec.Z) / vec.X, 1, 1).Unit
	elseif vec.Y ~= 0 then
		return Vector3.new(1, (-vec.X - vec.Z) / vec.Y, 1).Unit
	else
		return Vector3.new(1, 1, (-vec.X - vec.Y) / vec.Z).Unit
	end
end

@AstroCode’s solution:

  • Rarely has a floating-point error on order of 10-13
  • Creates 3 Vector3s
local function GetOrthogonalVector(vec)
	if vec.Magnitude == 0 then error("Vector has no magnitude") end
	
	if vec == Vector3.new(0, 1, 0) or vec == Vector3.new(0, -1, 0) then
		return Vector3.new(1, 0, 0)
	else
		return Vector3.new(vec.Z, 0, -vec.X) -- <0, 1, 0> cross vec
	end
end

The floating-point errors come from dotting the orthogonal vector with the original. I tested them on randomly oriented surfaces and surfaces in pure directions.

8 Likes

How are you making the initial random vectors?

for i=1,1e2 do
	local pitch=(math.random()-.5)*math.pi
	local yaw=math.pi*2
	local co=math.cos(pitch)
	local si=math.sin(pitch)
	local vec=Vector3.new(math.cos(yaw)*co,si,math.sin(yaw)*co)
	
	print(vec.Magnitude)--and if you don't trust .Magnitude, vec:Dot(vec) is also imperfect
end
My output
1 (x2)
0.99999994039536 (x2)
1 (x3)
0.99999994039536
1 (x2)
0.99999994039536
1 (x11)
0.99999994039536
1 (x7)
0.99999994039536
1 (x3)
0.99999994039536
1 (x6)
0.99999994039536
1
0.99999994039536
1
0.99999994039536
1 (x2)
0.99999994039536
1
0.99999994039536 (x2)
1 (x8)
0.99999994039536
1
0.99999994039536
1 (x5)
0.99999994039536
1 (x9)
0.99999994039536
1 (x19)
0.99999994039536 (x2)

As you can see, just generating the random unit vectors has floating point error on the order of 10^-8

edit: I guess this isn’t necessarily too relevant to the dot product floating point but I’m still curious

edit2: how did you get 10^-13 for cross product?

I get 10^-23
local function GetOrthogonalVector(vec)
	if vec.Magnitude == 0 then error("Vector has no magnitude") end
	
	if vec == Vector3.new(0, 1, 0) or vec == Vector3.new(0, -1, 0) then
		return Vector3.new(1, 0, 0)
	else
		return Vector3.new(0, 1, 0):Cross(vec).Unit
	end
end

local worst=0
for i=1,1e5 do
	local pitch=(math.random()-.5)*math.pi
	local yaw=math.pi*2
	local co=math.cos(pitch)
	local si=math.sin(pitch)
	local vec=Vector3.new(math.cos(yaw)*co,si,math.sin(yaw)*co)
	worst=math.max(worst,math.abs(GetOrthogonalVector(vec):Dot(vec)))
end
print(worst)

edit 3:

local function GetOrthogonalVector(vec)
	local x=vec.x
	local y=vec.y
	local z=vec.z
	
	if (y==1 or y==-1)and x==0 and z==0 then
		return Vector3.new(1,0,0)
	end
	return Vector3.new(z,0,-x)
end

local worst=0
for i=1,1e5 do
	local pitch=(math.random()-.5)*math.pi
	local yaw=math.pi*2
	local co=math.cos(pitch)
	local si=math.sin(pitch)
	local vec=Vector3.new(math.cos(yaw)*co,si,math.sin(yaw)*co)
	worst=math.max(worst,math.abs(GetOrthogonalVector(vec):Dot(vec)))
end
print(worst)

@AstroCode this has a 0 precision error but the orthogonal vector is not necessarily unit
When its unit it has 10^-23 precision (I just expanded out the cross product)

Also I never knew Vector3s overloaded the equal operator, thats pretty cool print(Vector3.new(0,1,0)==Vector3.new(0,1,0))--true

edit:: oh no it turns out I was doing local yaw=math.pi*2 instead of local yaw=math.pi*2*math.random() doing random yaw makes me get 10^-8 error for cross product (still 10^-8 for dot)

So still very curious about how you make random unit vecs:P

I have a raycast in my game that gets the normal vector of a surface. I’m not making them. :stuck_out_tongue:

Their magnitudes can sometimes have floating-point errors, too.

1 Like

dang if its for a game why are you concerned about such small floating point errors xd

Haha I’m not. I just figured I’d put the information there. But even if it needed to be accurate, those errors are so negligible that it wouldn’t matter.

2 Likes