[Closed] Matrix and Vector Math questions
Happy new (2019) to all you nice people who live inside my computer!
I’m pretty much a new to the concept of Matrix and vertex maths (studying in progress), but I understand just a bit, enough to be able to create some decent maxscripts.
I have 2 questions (one on vector(aiming)offsets, and another on parenting, local and global spaces)
I did some research beforehand,
such as
vectors – this one was extremely helpful, I kinda wish I could continue from this thread
parenting local / global spaces – in preparation for my second task.
but please bear with me, people absorb information at different paces.
(1)
At the moment I am trying to make a tool that simulates the exact function as a basic look at constraint (but I need to do it via 3D math)
see below:
tgt = target's position
obj = constrainee's.position
x = normalize (tgt - obj)
z = [0, 0, 1]
y = normalize (cross z x)
xx = normalize (cross x y)
resultMatrix = matrix3 x y xx origin
in coordsys (transmatrix obj.position) obj.rotation = resultMatrix.rotation
At this point, I understand how to change the z, and the result matrix’s order to match that of changing different axis settings in Max’s look at constraint.
I am currently stuck on replicating the process when we check “keep initial offset” option.
I am not sure I’f I am understanding the situation correctly, please correct me here:
to get an offset must I(?):
zz * (inverse resultMatrix.transform)
where zz = = (matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0]) <— default world matrix values?)
this is where I am stuck at the moment, I am not entirely sure on which calculation needs to be done here, and trial and error hasn’t gotten me far
(2)
My next task involves replicating in the same way a normal parent constraint complete with offsets and the ability to blend over time between changing target parents.
Because I’m still pretty much focused on task #1, I havent gotten around to fully studying matrix and inverse matrix math. I found a few nice tutorials on vimeo and I’ve already saved for watching later a couple of old vids from Borislav Petrov. I was wondering if any of you guys could point me to more learning material on the subject matter I welcome any suggestions.
Thank you all, and may you have a wonderful year ahead!
Maybe you will find this helpful:
-- Legal mumbo-jumbo
--
-- Copyright: Copyright (C) 1999 by Digital Wizards
-- Author: MІ (msquared)
-- Email: msquared@digitalwizards.com.au
-- URL: http://www.digitalwizards.com.au/
-- License: Free for personal and not-for-profit use by the
-- 3D Studio MAX user community. Please contact
-- us before you use this software commercially.
--
-- Name
--
-- calculateLookatMatrix - Calculate a lookat transformation
--
-- Description
--
-- This function takes a source point in 3D, a target in 3D, and
-- an orientation matrix. An optional up vector can be provided too,
-- the default is the Z axis. The result is a transformation matrix
-- that causes the X axis in the orientation matrix to point toward
-- the target, with the Z axis in the orientation matrix pointing
-- upward. The orientation matrix permits the object to which the
-- final matrix is applied to be oriented so that whatever axis
-- as desired may be pointed toward the target. If you want the
-- object's X axis pointing toward the target, with Z upwards, then
-- just pass an identity matrix as the orientation matrix. The
-- translation applied to the orientation matrix (row 4) allows
-- you to change the effective centre of rotation of the object for
-- the lookat rotation. The lookat final translation is the
-- origin, so that the resulting matrix can be applied directly to
-- the object as its transformation matrix, which will move it to
-- the origin and point it to the target.
--
function calculateLookatMatrix orientation origin target up:[0,0,1] =
(
local lookatmatrix = matrix3 1
lookatmatrix.row1 = normalize(target-origin)
lookatmatrix.row2 = normalize (cross up lookatmatrix.row1)
lookatmatrix.row3 = normalize (cross lookatmatrix.row1 lookatmatrix.row2)
lookatmatrix.row4 = origin
orientation * lookatmatrix
)
And this
From: msquared <msquared@digitalwizards.com.au>
To: Swami <swami@worldramp.net>
Subject: Re: calculateLookatMatrix()
Date: Monday, January 24, 2000 4:49 AM
On Wed, 19 Jan 2000, Swami wrote:
> Hello M^2,
>
> HAPPY NEW YEAR!
Hey, same to you, I hope you had a good one. :-)
> I have been attempting to decipher your calculateLookatMatrix() function
> and need some help. Hopefully you can tell me...
OK, I'll brief you on how calculateLookAtMatrix() generates a matrix to
make an object look at something...
Firstly, how a matrix works...
A MAX matrix consists of 3 rows, that represent the X axis, Y axis, Z
axis, and location respectively. Each row is a point3 vector.
The first row indicates where in 3D space the object's X axis will point.
The second row indicates where in 3D space the object's Y axis will point.
The third row indicates where in 3D space the object's Z axis will point.
The fourth row indicates where in 3D space the object's origin will be
located. You can easily test this last point by changing
obj.transform.row4 to be a location on space.
The identity matrix is:
X Y Z
[1,0,0] X axis
[0,1,0] Y axis
[0,0,1] Z axis
[0,0,0] origin
The labels show that the object's X axis points along the world's X axis
(X axis row is [1,0,0]). The same is true for Y and Z axis. The origin
is at [0,0,0], which places the object's origin at the world's origin.
So, how do you generate a useful matrix to point something somewhere?
Firstly, I recommend you play with an object by rotating it a little, then
having a look at its transformation matrix, and note how the first three
rows correspond to where the axis handles are located in the view.
Note the following useful properties about a normal transformation matrix:
* The axis vectors are one unit long (unless the object is scaled).
* The axis vectors are all at right angles to one another.
Hold your right hand palm-up with your fingers all pointing forward.
Extend your thumb to the right, keep your index finger pointing forwards,
point your middle finger up, and curl up your ring and little fingers.
Your thumb is X, index is Y, and middle finger is Z. This orientation of
axis is known as the "right hand rule". Do the same with your left hand,
and the same axis mapping to fingers creates the left hand rule. Note
that when you hold your left hand with palm down, X and Y are the same,
but Z is now down.
OK, now for some simple mathematics: The cross product of two vectors
yields another that is at right-angle to both original vectors (unless
both original vectors are coincident). The cross product in MAX follows
the right-hand rule, which means that X cross Y yields Z, Y cross Z yields
Z, and Z cross X yields Y. Baer this in mind when examining
calculateLookatMatrix() (examined further down).
Also, he length of the result of the cross-product is related to how close
to being at right-angles the original two vectors are. Assuming the
original two vectors are unit length (that is one unit long): If the
original vectors are at right angles, the cross product will be one unit
long. If the original vectors are coincident, the cross product will be
zero units long (this is because you can't find a -single- vector that is
at right angles to two coincident vectors, there are an infinite number
of them).
The act of converting a vector to a unit vector (ie: a vector of length
one) is referred to as "normalizing" the vector, hence MAX provides the
normalize() function to do this.
Now for the analysis of calculateLookatMatrix():
function calculateLookatMatrix orientation origin target up:[0,0,1] =
(
local lookatmatrix = matrix3 1
lookatmatrix.row1 = normalize(target-origin)
lookatmatrix.row2 = normalize (cross up lookatmatrix.row1)
lookatmatrix.row3 = normalize (cross lookatmatrix.row1 lookatmatrix.row2)
lookatmatrix.row4 = origin
orientation * lookatmatrix
)
Start with an identity matrix:
local lookatmatrix = matrix3 1
We want the X axis of the result to coincide with a vector from the target
to the origin:
lookatmatrix.row1 = normalize(target-origin)
We want the Z axis of the result to be as close to the specified "up"
vector as possible. We achieve this by pretending that it is the Z axis,
and calculating a Y axis. Note that this means that the Y axis will be at
right angle to the X axis and the initial up vector:
lookatmatrix.row2 = normalize (cross up lookatmatrix.row1)
Chances are, the up vector wasn't at right angles to the initial lookat
vector (target-origin). So, we generate a Z axis that is right angle to X
and Y. This will create a "proper" transformation matrix, as all
axis vectors will be right angle to one another. Note that this means
that the Z vector will be as close to the up vector as it can, but
constrained by the rule that it must be right angle to the X axis. This
is what I refer to as the Z axis "tending toward" the up vector:
lookatmatrix.row3 = normalize (cross lookatmatrix.row1 lookatmatrix.row2)
Now, we translate the matrix so that the resultant transformation is
relative to the position that was specified as the origin:
lookatmatrix.row4 = origin
Now, we can perform a little magic that makes calculateLookatMatrix() much
more useful (I'll explain below):
orientation * lookatmatrix
The last line there allows you to apply calculateLookatMatrix() to another
transformation matrix of some sort. Normally, you would pass an identity
matrix as orientation. This would produce a matrix that moves X to point
towards target and Z to tend towards up. However, what if you wanted the
object's Y axis to point toward target, and its X axis to tend toward the
up vector? To do this, you provide an orientation matrix to perform the
following transformation:
* Y axis -> X axis
* X axis -> Z axis
This means that the Y axis of the object becomes the X axis for the lookat
calculation, and the X axis of the object becomes the Z axis for the
lookat calculation, resulting in Y pointing at the target, and X tending
toward the up vector. How do you generate this orientation matrix?
Remember what a matrix's 4 rows represent:
* X axis' final orientation
* Y axis' final orientation
* Z axis' final orientation
* origin's final location
With that in mind, you want your orientation matrix to contain:
* Z vector (you want this axis to point along Z)
* X vector (you want this axis to point along X)
* ? vector (you want this axis right angle to X and Y)
* [0,0,0]
To calculate the third row (Z result), perform a cross product of the
first and second. The result is probably something like this (done in my
head, so you want to check the 3rd row :-) :
* [0,0,1]
* [1,0,0]
* [0,1,0]
* [0,0,0]
You feed this matrix into calculateLookatMatrix() as an orientation
matrix, and it will force Z to look at the target, and will tend Y toward
up.
> Here's what I want to do...
>
> Make an object follow a path on a mesh surface so it is always pointing
> forward AND aligned with the face normal. I am shooting a ray thru the
> surface to get the intersection point and the face normal.
It seems as though you might be able to use calculateLookatMatrix() as
follows:
* Use the intersection of the ray and the surface as the origin.
* Project a point along the normal from the origin, and use that
as the target.
* Use the direction you mean as "forward" for the up vector.
* Finally, generate a matrix that transforms your object so that:
* Whatever vector in that object should follow the surface normal
ends up as the X axis. This might be the object's Z axis?
* Whatever vector in that object should face "forward" ends up
as the Z axis.
Use this matrix as the orientation matrix for calculateLookatMatrix().
This will calculate the transformation matrix of the object at a given
point in time. Animating that is your next mission. :-)
> P.S. - I have been studying matrices and specifically how they work in
> MAXScript. I have a fair understanding, but some things still elude me. I
> am confident, that with time, I will master them! "Never give up. Never
> surrender." (quote from 'Galaxy Quest', funny movie, go see...).
Hehe. :-) Hopefully this will help you somewhat. Sorry for the delay,
but I've been rather busy of late... :-/
2
Best regards, /|/|
/ |
Miauu,
Thank you!
yes yes – orientation * lookatmatrix was the last missing piece that got my script to work. Appreciate it a lot!
i was so excited with the quick result I was getting that I spoke too soon
Sorry about that my bad ~
But did some tests in this way:
Given the calculate matrix above (or) my own original calculation in my first post…
For both of them, I was getting the correct rotations whenever I need to apply the resultmatrix (or the end result of the calculatelookatmatrix in the response) directly on to the constrainee.
As for orientation * resultmatrix <– I piped in a new variable from another object’s rotation, and I guess being able to rotate this new object cause for me to think that I could get the offset I wanted provided I rotate it accordingly. I was travelling when I initially responded a few hours ago, and after sitting down to test, I found myself stuck again
here’s what my code looks like now:
tgt = (in coord sys World )target's position
obj = constrainee's.position
offset = <Additional new object's> .rotation
-- ======================================
-- (same process as calculateLookatMatrix before multiplying by orientation )
-- ======================================
x = normalize (tgt - obj)
z = [0, 0, 1]
y = normalize (cross z x)
xx = normalize (cross x y)
resultMatrix = matrix3 x y xx origin
-- ======================================
newMatrix = offset * resultMatrix.rotation
in coordsys local obj.rotation = newMatrix .rotation
the choice to add an offset variable from a new object was arbitrary, in that I just needed to be able to physicaly control an object while i look at the process in action.
I came to realize that if I wanted to get the correct offset behavior (wherein in this case the constrainee’s starting matrix is (matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0]) ) then I will need to rotate the offset object in a very specific way .
in miauu’s response:
“The orientation matrix permits the object to which the final matrix is applied to be oriented so that whatever axis as desired may be pointed toward the target”
I agree ^
My question now is how to I properly calculate an orientation matrix so that the end result when multiplying this orientation with the resultmatrix will result in the object being offset to the global defaults? (OR the default rotations they were in prior to being run thru the calculation as per the behavior of an offset).
here is a snippet that shows how to make a simple “scripted” orientation constraint.
with redraw off
(
delete objects
target = dummy name:#target
source = point name:#source axis:on box:on pos:[40,0,0] wirecolor:orange
ss = source.rotation.controller = Rotation_Script()
ss.addnode #target target
ss.addnode #source source
ss.addconstant #up_vector z_axis -- initial Z UP
ss.addconstant #rot_offset (quat 0 0 0 1) -- (eulerangles 0 0 45) add any rotation offset if you want
scr = ""
scr += "front = normalize (target.pos - source.pos)\n"
scr += "side = normalize (cross up_vector front)\n"
scr += "up = normalize (cross front side)\n"
scr += "tm = matrix3 front side up [0,0,0]\n"
scr += "(tm as quat) * (rot_offset as quat)\n"
ss.setexpression scr
)
play with it and let us know if you have any questions
Dank you DennisT,
I just woke up ~ i’ll give it a try in a bit…
(btw – I keep seeing your posts in a lot of my searches lol)
Thank you guys!
The math hasn’t changed at all (which has been correct since miauu’s first reply)
I guess what I truly failed to understand beforehand was the fact that the scripted controller was constantly evaluating the variables that i kept feeding it – hence applying the orientation * resultmatrix ended up with me just having an object that isnt moving at all (since it was offseting to the default identitymatrix)
What DennisT gave in his response that truly pushed me into getting the correct behavior is seeing the existence of addconstant ~ piping in the offset computation of offsetmatrixvalues * (inverse resultmatrix) as a constant.
Fot that Dennis, thank you. I’ve been getting myself lost among the help pages – not knowing what I actually needed search for my specific needs, more often than not I end up spending too much time looking at the wrong help pages before I realize they arent relevant what I need. This is a pitfall I guess that comes with self learning, where I don’t really have a streamlined direction, but instead I’m trying to puzzle bits and pieces of information until they form a working idea. – on that note anyone have advice for amateur self learners like me?
Hi Dennis,
I’m actually doing this for a plugin, (again thanks to you and miauu for the responses)
quick question about creating script controllers via maxscript:
Since, the body of the script is written out via string, im guessing it’s not possible to update the values fed into a plugin’s params without having to rewrite script expression each time?
(testing via trial and error atm ~ )