Rotate a 3D object to align with a vector

Previous topic - Next topic

Slydog

I'm very new to 3D programming, and therefore 3D math.

I'm creating a path system where you create a bunch of path points and have an object (model, camera) follow that path.
Its working fairly well and follows the path, interpolating the position between the current two points based on those points time duration.  (It's only a linear interpolating for now, no smoothing).

I'm trying to implement an 'Orient To Path' option that will rotate the object to face the direction of the current path.
(Similar to the camera's 'LookAt' parameters).
So the numbers I have are the path start point and end point, and they are stored in a 3D vector TYPE (x,y,z).
I can take the difference of the two (end point - start point) to end up with one vector (named 'v') starting at (0,0,0).

Now, I want to rotate my object to match that vector's angle/direction.
I have the 'Y' rotation working with this:
   v_rotate.y = ATAN(v.z, v.x)
But even that acts weird and I had to add 180 or something to get it working as expected.
I'm not even sure of the range of numbers ATAN returns?  (-180 to 180?)  So then just add 180?
Or I've seen this?:  v_rotate.y = MOD(ATAN(v.z, v.x), 360)

But, I can't even come close to getting the 'X' rotation working.  (And I figured out that the 'Z' rotation is needed too?)
I had thought that if I rotate on the 'Y' axis first so I'm heading in the desired direction, then I could just figure out my X angle (relative to the direction I'm facing, ie. how much to tilt up or down).  It would be the same formula somewhat.  But I think I've come to learn that the 'X' rotation is always 'World' aligned?  And I can't make it relative to the new 'Y' rotation?  I hope that makes sense.

I think I have to somehow figure out this 'Angle' for the 'X' rotation, and split that value between 'X' and 'Z' somehow?

I've tried this many times and have failed.  (I find so many 2D examples, but that third dimension is eluding me!)
Any ideas?

Thanks,
-Doug
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

#1
Would using the Entity System make rotating easier?
ie: is there a built in 'Look At' option or something?
Or can I change an object's X axis relative to that object, and not the world?
So therefore I would only need to set its Y and X (and not Z)?

Or, is there a fancy way to use 'X_ROTATION' to do this?
I don't understand the parameters, besides using it the standard way such as:
X_ROTATION 90, 0, 1, 0 (would rotate on object 90 degrees on its Y axis).

But I've seem people use numbers other than 1 for the axis, such as "0, -1, 0", or "3, 4, 5", what would that do?

If I tried:
X_ROTATION 45, 1, 0, 1 - would that just rotate an object 45 degrees on both X and Z each?  Like the following:

X_ROTATION 45, 1, 0, 0
X_ROTATION 45, 0, 0, 1

Or does it split the value between the axis, or something else?
(ie: creates a new axis to rotate around, somehow combing X and Z?)

I hope I don't have to get into matrix math!!

Thanks again.
- Doug
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Kitty Hello

if you have 3 dimensional direction you want to align to, it's pretty easy.
Just normalize that direction vector to the lenght of 1 (divide all components by SQR(x*x+y*y+z*z) )
Then (the vector is x,y,z) make a matrix from that:
local mat[]
dimdata mat[], x,0,0,0,  0,y,0,0, 0,0,z,1
and use X_MULTMATRIX mat[]
that should be about it.

Hemlos

You can use 2 atans, but you need to add angles also.

Matrix is definetly the way to go, producing same results with less math.
Bing ChatGpt is pretty smart :O

Slydog

#4
Thanks for the ideas.

I've been playing with the X_MULTMATRIX a bit this weekend, but so far no luck.
I've never used this command before, and I'm not sure exactly how to use it, or where to put what values in the mat[] array.
I'm not sure if I have to push/pop the matrix, if so, when?
I've tried different values in different matrix array positions. 
Is the last set of 4 values supposed to be zero?  Or can I put the position in there and skip the X_MOVEMENT command?

What I see (depending of what 'tweaks' I try) is either:
- the player disappears while this code runs [When I put the position into the matrix maybe, or maybe that causes the next problem]
- resets to the [0,0,0] position
- stretches and flickers beyond recognition [most common]

Here's what I've tried (using some code from the forums):
(my code looks very different, just simplified for the forum)

Code (glbasic) Select

CONSTANT kTollerance = 0.0001

// Subtract v2 from v1
FUNCTION Vector_Subtract AS TVector: v1 AS TVector, v2 AS TVector
   LOCAL rv AS TVector
   rv.x = v1.x - v2.x
   rv.y = v1.y - v2.y
   rv.z = v1.z - v2.z
   RETURN rv
ENDFUNCTION

// Normalize a vector so it's length = 1 and apply tolerance
FUNCTION Vector_Normalize: v AS TVector
LOCAL m
m = SQR(v.x*v.x + v.y*v.y + v.z*v.z)

IF m <= kTollerance THEN m = 1
v.x = v.x / m
v.y = v.y / m
v.z = v.z / m
IF ABS(v.x) < kTollerance THEN v.x = 0
IF ABS(v.y) < kTollerance THEN v.y = 0
IF ABS(v.z) < kTollerance THEN v.z = 0
ENDFUNCTION

// p_start - starting point vector
// p_end   - end point vector
FUNCTION Player_Draw:
  LOCAL rotation as TVector
  rotation = Vector_Subtract(p_end, p_start)
  Vector_Normalize(rotation)

  DIM mat[16]
  mat[ 0] = rotation.x
  mat[ 1] = 0
  mat[ 2] = 0
  mat[ 3] = 0

  mat[ 4] = 0
  mat[ 5] = rotation.y
  mat[ 6] = 0
  mat[ 7] = 0

  mat[ 8] = 0
  mat[ 9] = 0
  mat[10] = rotation.z
  mat[11] = 1

  mat[12] = 0
  mat[13] = 0
  mat[14] = 0
  mat[15] = 0

  X_MOVEMENT position.x, position.y, position.z
// X_PUSHMATRIX here?
  X_MULTMATRIX mat[]
  X_DRAWOBJ player_id, 0
END FUNCTION


Just a thought.
I had the rotation on the Y axis working before the X_MULTMATRIX code was added.
Can I just use the X_PUSHMATRIX after I rotate on the Y, then rotate on the X to look up or down (determined using the ATAN command again) to rotate relative to the player and not the world?

Thanks for all your help!
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Kitty Hello

Try these fixes:
Code (glbasic) Select

  mat[12]=position.x
  mat[13]=position.y
  mat[14]=position.z
  mat[15] = 1 // ONE HERE - important!!

  // if you have 12,13,14 empty, the commented lines would apply
  // X_MOVEMENT position.x, position.y, position.z
  // X_PUSHMATRIX
  X_MULTMATRIX mat[]
  X_DRAWOBJ player_id, 0
  // X_POPMATRIX


The matrix you provide in X_MULTMARIX is simply an OpenGL matrix, which is a vector [0,1,2,3] for the direction and scaling of the X-axis, [4,5,6,7] for the y axis and [8,9,10,11] for the z axis. [12,13,14,15] is the x_movement, so you can put that in one matrix call.

Slydog

Getting close.  I found a good explanation of how to do this online at:
http://www.opengl.org/resources/faq/technical/lookat.cpp

I created a 'Look At' function, that takes the id of the Entity to rotate, and a vector (v) as a point to look at.

Code (glbasic) Select
FUNCTION Entity_LookAt: id%, v AS TVector
LOCAL ex%
LOCAL v_at AS TVector
LOCAL v_up AS TVector
LOCAL v_xaxis AS TVector

ex = Entity_Find(id)
IF ex <= 0 THEN RETURN

v_up = Vector_New(0.0, 1.0, 0.0)

v_at = Vector_Subtract(v, _Entity[ex].position)
Vector_Normalize(v_at)

v_xaxis = Vector_CrossProduct(v_at, v_up)
Vector_Normalize(v_xaxis)

v_up = Vector_CrossProduct(v_xaxis, v_at)
Vector_Normalize(v_up)

DIM _Entity[ex].matrix[16]
_Entity[ex].matrix[ 0] = v_xaxis.x
_Entity[ex].matrix[ 1] = v_xaxis.y
_Entity[ex].matrix[ 2] = v_xaxis.z
_Entity[ex].matrix[ 3] = 0

_Entity[ex].matrix[ 4] = v_up.x
_Entity[ex].matrix[ 5] = v_up.y
_Entity[ex].matrix[ 6] = v_up.z
_Entity[ex].matrix[ 7] = 0

_Entity[ex].matrix[ 8] = v_at.x
_Entity[ex].matrix[ 9] = v_at.y
_Entity[ex].matrix[10] = v_at.z
_Entity[ex].matrix[11] = 0

_Entity[ex].matrix[12] = _Entity[ex].position.x
_Entity[ex].matrix[13] = _Entity[ex].position.y
_Entity[ex].matrix[14] = _Entity[ex].position.z
_Entity[ex].matrix[15] = 1
ENDFUNCTION


// Subtract v2 from v1
FUNCTION Vector_Subtract AS TVector: v1 AS TVector, v2 AS TVector
   LOCAL rv AS TVector
   rv.x = v1.x - v2.x
   rv.y = v1.y - v2.y
   rv.z = v1.z - v2.z
   RETURN rv
ENDFUNCTION

// Normalize a vector so it's length = 1
FUNCTION Vector_Normalize: v AS TVector
LOCAL m
m = SQR(v.x*v.x + v.y*v.y + v.z*v.z)

IF m <= kTollerance THEN m = 1
v.x = v.x / m
v.y = v.y / m
v.z = v.z / m
IF ABS(v.x) < kTollerance THEN v.x = 0
IF ABS(v.y) < kTollerance THEN v.y = 0
IF ABS(v.z) < kTollerance THEN v.z = 0
ENDFUNCTION

// Takes v1 AND v2 AND returns the cross product v1 X v2.
// The cross product is a vector perpendicular TO both v1 AND v2
// This is the normal of 2 vectors
FUNCTION Vector_CrossProduct AS TVector: v1 AS TVector, v2 AS TVector
   LOCAL rv AS TVector
   rv.x = (v1.y * v2.z) - (v1.z * v2.y)
   rv.y = (v1.z * v2.x) - (v1.x * v2.z)
   rv.z = (v1.x * v2.y) - (v1.y * v2.x)
   RETURN rv
ENDFUNCTION


And I use it like:
Code (glbasic) Select

X_MOVEMENT 0, 0, 0
X_ROTATION 0, 1, 0, 0
X_ROTATION 0, 0, 1, 0
X_ROTATION 0, 0, 0, 1
X_MULTMATRIX _Entity[ex].matrix[]
X_DRAWOBJ  _Entity[ex].id_object, 0


(Is there a better way to reset the matrix?  (Identity Matrix?))
Now, it rotates and faces properly towards a point.

BUT now it displays the player object as black (no colours) while its using this code, but fine otherwise.
Is it something to do with the normals?
I've tried using 'X_AUTONORMALS' just before the X_MOVEMENT command, but with no luck.
Any ideas?
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

I'm not sure why, but if I change the Cull Mode back to '0' (both sides) it works.

So I'm guessing that when the matrix is multiplied onto the stack, either:
- my normals get altered (but then why would I see them with Cull Mode '0'?)
- my normals get flipped inside out (but I would think you would see nothing, and not black)

I'm not sure of the performance hit setting Cull Mode to '0' (both sides), but it works, so I'm happy.

Oh, and the Entity Type and functions my code refers to is my own Entity system (very basic), and not the main one from the forums.
It should be very simple to convert the 'Entity_LookAt()' function to work for any vectors/points.

Thanks for all the help.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

#8
Ok, here it is converted to a Generic 'LookAt' function:

Code (glbasic) Select
TYPE TVector
x
y
z
ENDTYPE

// matrix[]    - matrix array to hold the results, for use later at rendering
// v_look_from - Point to look from
// v_look_to   - Point to look at
FUNCTION Vector_LookAt: matrix[], v_look_from AS TVector, v_look_to AS TVector
LOCAL v_at AS TVector
LOCAL v_up AS TVector
LOCAL v_xaxis AS TVector

v_at = Vector_Subtract(v_look_to, v_look_from)
Vector_Normalize(v_at)

v_up = Vector_New(0.0, 1.0, 0.0)
v_xaxis = Vector_CrossProduct(v_at, v_up)
Vector_Normalize(v_xaxis)
v_up = Vector_CrossProduct(v_xaxis, v_at)
Vector_Normalize(v_up)

DIM matrix[16]
matrix[ 0] = v_xaxis.x
matrix[ 1] = v_xaxis.y
matrix[ 2] = v_xaxis.z
matrix[ 3] = 0

matrix[ 4] = v_up.x
matrix[ 5] = v_up.y
matrix[ 6] = v_up.z
matrix[ 7] = 0

matrix[ 8] = v_at.x
matrix[ 9] = v_at.y
matrix[10] = v_at.z
matrix[11] = 0

matrix[12] = v_look_from.x
matrix[13] = v_look_from.y
matrix[14] = v_look_from.z
matrix[15] = 1
ENDFUNCTION

FUNCTION Vector_New AS TVector: x, y, z
LOCAL v AS TVector
v.x = x
v.y = y
v.z = z
RETURN v
ENDFUNCTION

// Subtract v2 from v1
FUNCTION Vector_Subtract AS TVector: v1 AS TVector, v2 AS TVector
   LOCAL v AS TVector
   v.x = v1.x - v2.x
   v.y = v1.y - v2.y
   v.z = v1.z - v2.z
   RETURN v
ENDFUNCTION

// Normalize a vector so it's length = 1
FUNCTION Vector_Normalize: v AS TVector
   LOCAL m
   m = SQR(v.x*v.x + v.y*v.y + v.z*v.z)

   IF m <= kTollerance THEN m = 1
   v.x = v.x / m
   v.y = v.y / m
   v.z = v.z / m
   IF ABS(v.x) < kTollerance THEN v.x = 0
   IF ABS(v.y) < kTollerance THEN v.y = 0
   IF ABS(v.z) < kTollerance THEN v.z = 0
ENDFUNCTION

// Takes v1 AND v2 AND returns the cross product v1 X v2.
// The cross product is a vector perpendicular TO both v1 AND v2
// This is the normal of 2 vectors
FUNCTION Vector_CrossProduct AS TVector: v1 AS TVector, v2 AS TVector
   LOCAL rv AS TVector
   rv.x = (v1.y * v2.z) - (v1.z * v2.y)
   rv.y = (v1.z * v2.x) - (v1.x * v2.z)
   rv.z = (v1.x * v2.y) - (v1.y * v2.x)
   RETURN rv
ENDFUNCTION


To use it:
Code (glbasic) Select

LOCAL mat[]
LOCAL v_from AS TVector
LOCAL v_to AS TVector

v_from = Vector_New(1.0, 1.0, 1.0)
v_to = Vector_New(20.0, 5.0, 3.0)
Vector_LookAt(mat[], v_from, v_to)

X_MOVEMENT 0, 0, 0
X_ROTATION 0, 1, 0, 0
X_ROTATION 0, 0, 1, 0
X_ROTATION 0, 0, 0, 1
// Is there a shorter way to do the above? (I find if I don't, the current matrix affects it)

X_MULTMATRIX mat[]
X_DRAWOBJ  object_id, 0
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Kitty Hello

Code (glbasic) Select

X_MOVEMENT 0, 0, 0
X_ROTATION 0, 1, 0, 0
X_ROTATION 0, 0, 1, 0
X_ROTATION 0, 0, 0, 1


You don't need that. An X_MAKE3D will put you in that state. If you push/pop matrix, you always are there. If you really want to reset it, X_MOVEMENT 0,0,0 is enough (it clears all rotations).