vec3:angleBetween and vec comparison

hello guys,
I’ve came to a point where I need to compare two 3d vectors. Could anyone explain how to do this?
It would be also nice, if someone could point me the right direction, on how to compute an angle between two vec3.

function vec:__eq(v)
    return self.x == v.x and self.y == v.y and self.z == v.z
end

function vec:__lt(v)
        -- NOT SURE ABOUT THIS!
	return (self.x < v.x or (self.x == v.x and self.y < v.y))
	    or (self.y < v.y or (self.y == v.y and self.z < v.z))
	    or (self.x < v.x or (self.x == v.x and self.z < v.z))
end

function vec:__le(v)
	return self.x <= v.x and self.y <= v.y and self.z <= v.z
end

function vec:angleBetween(v)
       -- NO IDEA HOW TO IMPLEMENT YET
end

@se24vad - you are stepping into deep water here.

What do you want to use angleBetween for?

Actually, I think I’ve solved it. Should give me an angle in degrees between two vec3!

function vec:len()
    return math.sqrt(self.x^2 + self.y^2 + self.z^2)
end

function vec:dot(v)
	if type(v) == "number" then v = vec(v, v, v) end
    return self.x * v.x + self.y * v.y + self.z * v.z
end

function vec:angleBetween(v)
	return math.acos(self:dot(v) / (self:len() * v:len())) * (180/math.pi)
end

Testing now. Then moving on to vec3 comparison (<= < == > >= ~=)

You do know there is already a vec3 len (and dot) function in Codea?

The “angle” between two vec3’s is not a single number, too. You have to rotate in more than one dimension to point in a particular direction - and the order in which you do the rotations affects the result.

I know that, thanks! :slight_smile: but I’m currently working in Löve2d, and therefore I need a clone of codea’s vec3 class.

Here is what I found on the Web, and according to this information I made my function. I’m not too sure about this, though.

Yes, but if you think about it, that angle can only measure in a plane, eg the XY plane. What about Z?

Your formula for angleBetween is completely correct. There is a well-defined notion of angle in arbitrary dimensions and that’s what you have.

Comparing vectors is a little more complicated. There isn’t a universally accepted notion of when one vector is less than another. Lexicographical ordering is often used if one has to have an ordering.

Oh, but you should check for zero vectors before evaluating the angle.

@LoopSpace - how would you use the angleBetween value in practice, for vec3? Surely you can’t use it to point from A to B because it is only a number?

@Ignatz, I think that you are confusing the notions of angle and of rotation. An angle is part of a rotation, but a rotation needs more. A rotation needs a plane in which to move as well as the angle to rotate. When we have two vectors in some space, they generically define a plane and the angle is taken in that plane.

In 3D, to define a plane we can use the normal vector. Given two generic vectors u and v then the normal vector to the plane containing u and v is given by their cross product. So the cross product gives the axis and the dot product gives the angle.

In this case, all that is asked for is the angle whence only the dot product is needed.

Sorry, not on iPad to show the code. But …

To decide where my bullets start relative to the spaceship, I reason as follows:

The spaceship’s nominal front is oriented to +x. I’d like the bullet to start, say, 5 pixels in front. The ship is 15 pixels from center to front, so I want the bullet to start at <20,0> if I were going x-ward.

The ship rotation is rot. So I set the bullet at <20,0> rotated by the ship rotation. I set its velocity to the ship’s velocity plus the bullet’s starting velocity, which is rotated by ship rotation.

@LoopSpace thank you! But what do you mean by: »Oh, but you should check for zero vectors before evaluating the angle.«

Like that?

function vec:is_zero()
	return self.x == 0 and self.y == 0 and self.z == 0
end

function vec:angleBetween(v)
    if self:is_zero() or v:is_zero() then
        print("Unable to calculate angle by a zero-length vector")
        return false
    end
	return math.acos(self:dot(v) / (self:len() * v:len())) * (180/math.pi)
end

I did comparison like the fallowing, because I think its most logical to use the length of the vector…

function vec:len()
    return math.sqrt(self.x^2 + self.y^2 + self.z^2)
end

function vec:__eq(v)
    return self.x == v.x and self.y == v.y and self.z == v.z
end

function vec:__lt(v)
	return self:len() < v:len()
end

function vec:__le(v)
	return self:len() <= v:len()
end

@LoopSpace - I’m sure you’re right, but I still don’t see what you can actually do with that angle in practice, in Codea.

@RonJeffries - I do that, too - but I think this is about something different

@se24vad Yes, that’s what I meant about zero length.

With respect to the comparison, your “less than or equal to” is not what it says it is. You define v < u if the length of v is less than the length of u, but you can have two vectors with the same length which aren’t equal. The meaning of “less than or equal to” is that if v <= u but not v < u then it must be that v = u. This isn’t the case with your comparison.

@Ignatz You can define a rotation that for almost all pairs of vectors rotates the first to the second. The fails are if the vectors are in opposite directions. This rotation is also guaranteed to be the shortest in the sense that if you spherically interpolate this rotation then it is the shortest path through rotation space. This is implemented in my Quaternion library in that for vec3s u and v then v:rotateTo(u) returns a quaternion which rotates v to u.

In practice, the angle is only part of the information needed to do this. But it is still necessary information (though actually to define the rotation you don’t need to do the expensive arccosine).

@se24vad Here’s something I had to compute the angle between 2 vectors in either 2D or 3D.


function setup()
    -- vectors in 2D
    print(angleBetween(vec3(5,0,0),vec3(-5,5,0)))
    
    -- vectors in 3D
    print(angleBetween(vec3(2,-3,4),vec3(5,2,1)))
end

function angleBetween(v1,v2)
    local v=math.sqrt((v1.x^2+v1.y^2+v1.z^2)*(v2.x^2+v2.y^2+v2.z^2))
    return(math.deg(math.acos(v1:dot(v2)/v)))
end

@dave1707 thank you!

@LoopSpace I’m not sure I understand your logic with the comparison issue. I also cannot imagine how to test this correctly yet… Could you guide me step by step, or write down your suggested code? Thank you very much in advance!

PS: What is your background? You seem to be very confident about all this stuff. That’s adoreable!

@dave1707 That’s not really a 2D version as it only works with vec3s. To make it truly 2D you’d have to be able to accept both vec3 and vec2s, something like:

if u.z then
  -- code for vec3s
else
  -- code for vec2s
done

but given that angleBetween is already encoded in vec2 (isn’t it for vec3? I don’t remember off-hand) then I wouldn’t really bother.

Anyway, the formula in that function is the same as in @se24vad’s.

@se24vad the problem with “less than” is to decide what it means for two numbers to be less than two other numbers. Is (2,4) less than (3,3)? There’s no good way to decide this and it hasn’t proved to be a useful mathematical concept.

Lexicographical ordering is done by comparing term by term and waiting until there is a discrepancy. So for 2D:

function vec.lt(u,v)
    if u.x != v.x then
        return u.x < v.x
    else
        return u.y < v.y
    end
end

But unless you know that you want to compare vectors in this way, I wouldn’t bother encoding it. Putting it in simply to have something there can be more confusing than not having it at all.

The problem with comparing by length, by the way, is that then (1,0) and (-1,0) will come back as being “equal” when clearly they are not.

My background is in mathematics, so yes I do know a lot about these things and where I do know something then I’m very confident that I really do know it.

@LoopSpace There is an angleBetween for vec2, but there isn’t one for vec3. I could use vec2:angleBetween for 2D which would be easier, but I was just pointing out that the code I show could be used for 2D or 3D. To use it for 2D, you just need to pass 0’s for the third parameter of the vec3. And yes the result of our code is the same. He wrote his one way and when I wrote mine, I wrote it that way.

@dave1707 I was trying to say, obviously not very well, that a vector with 3-components is still in 3D even if some of those components are zero. Your code doesn’t work for vec2s, and when someone says “This works for 2D” then my default assumption would be that they were talking about vec2s. That’s all. Sorry for any confusion.

@LoopSpace I now understand what you were trying to say. I probably didn’t express myself very well either. I was trying to say that the angle could be calculated using vec3’s for either 3D using all 3 values or 2D using 2 values with the third value being zero. I find it hard to write exactly what I’m thinking because I think everyone knows what I’m trying to say.

EDIT: Also, sometimes I’m in a hurry and don’t realize I’m leaving things out.