Basically... Well, it pops up a lot in video games, that you want an object lying on something that's moving and rotating around. The problem is that it can be tricky to figure out how to position the object.
This program solves that problem, using matrix math. I tried to make it well commented, so that anyone who was learning how to handle transformations could learn from this code.
To use it, make three objects: One is the platform, another is the "child" object, that is sitting on the platform, and another is the platform last frame (hidden, preferably). You call a function with the object numbers of those three arguments, and the function updates the child object's position and rotation where it should be.
This also works with scaling!
#constant worldmat 1 `world matrix; A world matrix is responsible for transformingb an object's coordinates
`(what you would see if you placed the model at the origin) to the world's coordinates (what you would see
`after rotating/scaling/translating the object)
#constant xrotmat 2 `A matrix responsible for the rotation of a particular object about the x axis.
#constant yrotmat 3 `A matrix responsible for the rotation of a particular object about the y axis.
#constant zrotmat 4 `A matrix responsible for the rotation of a particular object about the z axis.
#constant transmat 5 `A matrix responsible for translating the object in the world.
#constant scalemat 6 `a matrix responsible for scaling the object in the world.
#constant tempmatrix1 7 `temporary matrices for storing stuff
#constant tempmatrix2 8
#constant tempmatrix3 9
#constant tempVector 10 `a value to hold a vector, used in transformations.
//three return variables, used for returning a vector from a function.
global ret_x as float
global ret_y as float
global ret_z as float
set normalization on //when you scale an object, that messes up the lighting.
//"set normalization on" fixes the lighting. never scale an object without using set normalization on!
make object cube 1,1 //the main object to be shown
make object cube 2,.9999 //the object last frame
ghost object on 2
color object 1,rgb(0,255,0)
color object 2,rgb(255,0,0)
make object cube 3,.5 //the object position relative to the first object.
color object 3,rgb(0,0,255)
position object 3,0,2,0
current_selected as integer
current_selected=0
change as float
position camera 0,5,-5
point camera 0,0,0
init_matrices()
sync on
sync rate 0
sync
sync
do
rotate object 2,object angle x(1),object angle y(1),object angle z(1)
position object 2,object position x(1),object position y(1),object position z(1)
scale object 2,object scale x(1),object scale y(1),object scale z(1)
change=(upkey()-downkey())*1.0/screen fps()
if keystate(2) then current_selected=0
if keystate(3) then current_selected=1
if keystate(4) then current_selected=2
if keystate(5) then current_selected=3
if keystate(6) then current_selected=4
if keystate(7) then current_selected=5
if keystate(8) then current_selected=6
if keystate(9) then current_selected=7
if keystate(10) then current_selected=8
if current_selected=0
text 0,0,"Selected: X Position"
position object 1,object position x(1)+change,object position y(1),object position z(1)
text 0,16,"Value: "+str$(object position x(1))
endif
if current_selected=1
text 0,0,"Selected: Y Position"
position object 1,object position x(1),object position y(1)+change,object position z(1)
text 0,16,"Value: "+str$(object position y(1))
endif
if current_selected=2
text 0,0,"Selected: Z Position"
position object 1,object position x(1),object position y(1),object position z(1)+change
text 0,16,"Value: "+str$(object position z(1))
endif
if current_selected=3
text 0,0,"Selected: X Rotation"
rotate object 1,object angle x(1)+change*90.0,object angle y(1),object angle z(1)
text 0,16,"Value: "+str$(object angle x(1))
endif
if current_selected=4
text 0,0,"Selected: Y Rotation"
rotate object 1,object angle x(1),object angle y(1)+change*90.0,object angle z(1)
text 0,16,"Value: "+str$(object angle y(1))
endif
if current_selected=5
text 0,0,"Selected: Z Rotation"
rotate object 1,object angle x(1),object angle y(1),object angle z(1)+change*90.0
text 0,16,"Value: "+str$(object angle z(1))
endif
if current_selected=6
text 0,0,"Selected: X Scale"
scale object 1,object scale x(1)+change*20.0,object scale y(1),object scale z(1)
text 0,16,"Value: "+str$(object scale x(1))
endif
if current_selected=7
text 0,0,"Selected: Y Scale"
scale object 1,object scale x(1),object scale y(1)+change*20.0,object scale z(1)
text 0,16,"Value: "+str$(object scale y(1))
endif
if current_selected=8
text 0,0,"Selected: Z Scale"
scale object 1,object scale x(1),object scale y(1),object scale z(1)+change*20.0
text 0,16,"Value: "+str$(object scale z(1))
endif
pitch object up 3,mousemovey()
turn object left 3,mousemovex()
turn object left 3,0 //a hack
//object 3 was position relative to object 2 LAST frame, but THIS
//frame, we want it relative to object 1.
lock_transformObject(2,1,3)
text 0,32,"Object 3 Position: ("+str$(object position x(3),4)+","+str$(object position y(3),4)+","+str$(object position z(3),4)+")"
text 0,48,"Object 3 Scale: ("+str$(object scale x(3),4)+","+str$(object scale y(3),4)+","+str$(object scale z(3),4)+")"
text 0,64,"Object 3 Rotation: ("+str$(object angle x(3),4)+","+str$(object angle y(3),4)+","+str$(object angle z(3),4)+")"
text 0,80,"Screen FPS: "+str$(screen fps())
sync
loop
cleanup_matrices()
end
function init_matrices()
//the matrix4 commands return a variable. I *think* that this variable is completely useless.
null=make matrix4(worldmat)
null=make matrix4(xrotmat)
null=make matrix4(zrotmat)
null=make matrix4(yrotmat)
null=make matrix4(transmat)
null=make matrix4(scalemat)
null=make matrix4(tempmatrix1)
null=make matrix4(tempmatrix2)
null=make matrix4(tempmatrix3)
null=make vector4(tempVector)
endfunction
function cleanup_matrices()
null=delete matrix4(worldmat)
null=delete matrix4(xrotmat)
null=delete matrix4(yrotmat)
null=delete matrix4(zrotmat)
null=delete matrix4(transmat)
null=delete matrix4(scalemat)
null=delete matrix4(tempmatrix1)
null=delete matrix4(tempmatrix2)
null=delete matrix4(tempmatrix3)
null=delete vector4(tempVector)
endfunction
function lock_transformObject(obj_from,obj_to,obj)
//Take object obj_from. You want to take the position and rotation of another object, "obj",
//and change that so that it's relative to obj_to. This function does that - it takes object obj's
//position relative to obj_from, and positions/rotates it so it's in the same position relative to
//obj_to.
//create some nice named variables so that the code will be clearer.
objFromInvWM=tempmatrix1 //object "obj_from"'s inverse world matrix. (short for "Object From's Inverse World Matrix")
objToWM=tempmatrix2 //object "obj_to"'s world matrix. (short for "Object To's World Matrix")
objWM=tempmatrix3 //object "obj"'s world matrix.
getObjectsWorldMatrix(obj) //get obj's world matrix,
copy matrix4 worldmat,objWM //store it in objWM
getObjectsInvWorldMatrix(obj_from) //get obj_from's inverse world matrix
copy matrix4 worldmat,objFromInvWM //store it in objFromInvWM
getObjectsWorldMatrix(obj_to) //get obj_to's world matrix
copy matrix4 worldmat,objToWM //store it in objFromInvWM
//We want objWM*ObFromInvWM*objToWM. This will give us our transformation, and when we're done,
//we want to extract translation, rotation, and scaling information.
multiply matrix4 worldmat,objWM,objFromInvWM `worldmat=objWM*objFromInvWM
multiply matrix4 worldmat,worldmat,objToWM `worldmat=worldmat*objToWM=objWM*objFromInvWM*objToWM
//now world mat is the desired transformation matrix.
scalex as float
scaley as float
scalez as float
//If you do the math, where you're using row vectors and a left handed coordinate system,
//you see that each row of the 3x3 transformation matrix (excluding the affine part)
//is multiplied by the corresponding scale value. Since the length of each row of the rotation
//matrix is one, the length of each row of this Scale and Rotation matrix, is the scaling value.
//since DBPro uses scaling values as percentages, just multiply this by 100.
scalex=sqrt(M4Element(worldmat,1,1)*M4Element(worldmat,1,1)+M4Element(worldmat,1,2)*M4Element(worldmat,1,2)+M4Element(worldmat,1,3)*M4Element(worldmat,1,3))*100
scaley=sqrt(M4Element(worldmat,2,1)*M4Element(worldmat,2,1)+M4Element(worldmat,2,2)*M4Element(worldmat,2,2)+M4Element(worldmat,2,3)*M4Element(worldmat,2,3))*100
scalez=sqrt(M4Element(worldmat,3,1)*M4Element(worldmat,3,1)+M4Element(worldmat,3,2)*M4Element(worldmat,3,2)+M4Element(worldmat,3,3)*M4Element(worldmat,3,3))*100
scale matrix4 scalemat,100.0/scalex,100.0/scaley,100.0/scalez
multiply matrix4 worldmat,scalemat,worldmat //worldmat=Scale^(-1)*worldmat=Scale^(-1)*Scale*Rot*Translation
//=Rot*Translation
//we want to isolate the rotation matrix so we can extract euler angles from it.
//the translation matrix doesn't touch the upper 3x3 part of the matrix - so we don't have to do anything with it.
//However, the scale matrix scales by everything, so we need to mutiply worldmat by the inverse of scale, to get
//worldmat=Rot*Translation.
//Now, we can extract the euler angles from the rotation matrix.
//The bottommost row of the matrix is the translation part, but it doesn't affect the rotation
//part of the matrix (the 3x3 top left values), so we can ignore it. It's still stored in worldmat,
//but that doesn't matter.
position object obj,M4Element(worldmat,4,1),M4Element(worldmat,4,2),M4Element(worldmat,4,3)
rotMatrixToEulerAng(worldmat)
scale object obj,scalex,scaley,scalez
rotate object obj,wrapvalue(ret_x),wrapvalue(ret_y),wrapvalue(ret_z)
endfunction
function transformObjectSpaceToWorldSpace(matrixnum,x as float, y as float, z as float)
`Say you have a vector that - when the object in question is positioned at the origin - lies right on top
`of the object. If you rotate, scale, and position the object, where does it go now?! This function solves
`that problem.
`Code example:
remstart
make object cube 1,1
position object 1,rnd(100),rnd(100),rnd(100)
rotate object 1,rnd(360),rnd(360),rnd(360)
getObjectsWorldMatrix(1)
transformObjectSpaceToWorldSpace(worldmat,0,1,0)
make object sphere 2,.5
position object 2,ret_x,ret_y,ret_z
remend
set vector4 tempVector,x,y,z,1
transform vector4 tempVector,tempVector,matrixnum
ret_x=x vector4(tempVector)
ret_y=y vector4(tempVector)
ret_z=z vector4(tempVector)
endfunction
function getObjectsWorldMatrix(object_number) //takes an object's position, rotation, and scaling,
`turns that data into a matrix, and then stores that matrix in matrix "worldmat".
rotate x matrix4 xrotmat,wrapvalue(object angle x(object_number))*.01745329 `calculate x, y, and z rotation matrices.
rotate y matrix4 yrotmat,wrapvalue(object angle y(object_number))*.01745329 `we need to convert angles into radians here.
rotate z matrix4 zrotmat,wrapvalue(object angle z(object_number))*.01745329
`calculate the translation matrix
translate matrix4 transmat,object position x(object_number),object position y(object_number),object position z(object_number)
`calculate the object's scaling matrix.
scale matrix4 scalemat,object scale x(object_number)/100.0,object scale y(object_number)/100.0,object scale z(object_number)/100.0
multiply matrix4 xrotmat,xrotmat,yrotmat `X'=X*Y
multiply matrix4 xrotmat,xrotmat,zrotmat `X'=X'*Z=X*Y*Z
multiply matrix4 xrotmat,scalemat,xrotmat `X'=Scale*X'=Scale*X*Y*Z
multiply matrix4 worldmat,xrotmat,transmat
`Object's World matrix=Scale*Xrot*Yrot*Zrot*Translation
`in other words:
`object's position = vertex position*Scale*Xrot*Yrot*Zrot*Translation
`in other other words:
`object = for each vertex, first scale the vertex from the origin on each axis. Then rotate it about the x axis,
`then rotate it about the y axis, then rotate it about the z axis, and then translate each vertex.
endfunction
function getObjectsInvWorldMatrix(object_number)
//this function creates the inverse of an object's world matrix.
//There's a property of matrices: A matrix multiplied by its inverse gives the identity matrix.
//this is like saying a number times its inverse is equal to one. A vector multiplied by the
//identity matrix leaves the vector unchanged. So, if you have a point transformed by a matrix,
//T (vector*T), then if you multiply by the inverse matrix of T, you get the vector.
//(vector*T*T^(-1)=vector*I=vector)
//let A' denote the inverse of matrix A, from this point on in this function.
//(normally A' denotes the transpose of matrix A. DON'T USE THIS NOTATION! IM JUST USING IT
//SO THAT I DON'T HAVE TO WRITE OUT "^(-1)" A GAZILLION TIMES. PPL WILL BE CONFUSED IF YOU USE IT
//WITHOUT SAYING IN BIG LETTERS THAT YOU'RE USING YOUR OWN NOTATION!)
//(A*B*C*...*N)'=N'*...*C'*B'*A'
//Where X(x) is a rotation of x degrees on the X axis, Y(y) is y degrees on the Y axis, etc.
//R=X(x)*Y(y)*Z(z)
//R'=Z(-z)*Y(-y)*X(-x)
rotate x matrix4 xrotmat,wrapvalue(-object angle x(object_number))*.01745329
rotate y matrix4 yrotmat,wrapvalue(-object angle y(object_number))*.01745329
rotate z matrix4 zrotmat,wrapvalue(-object angle z(object_number))*.01745329
//where T(x,y,z) is a matrix translating x units on the x axis, y units on the y axis, etc.
//T(x,y,z)'=T(-x,-y,-z)
//this makes sense, because if you translate something, and then translate it back the same amount,
//you get zero overall translation.
translate matrix4 transmat,-object position x(object_number),-object position y(object_number),-object position z(object_number)
//where S(x,y,z) is a matrix scaling x times on the x axis, y times on the y axis, etc.
//S(x,y,z)'=S(1/x,1/y,1/z)
//this makes sense, because scaling multiplies a value. If you multiply something by its inverse, you
//get 1. Something scaled by "1", isn't really scaling anything at all.
scale matrix4 scalemat,100.0/object scale x(object_number),100.0/object scale y(object_number),100.0/object scale z(object_number)
//NOTE: you cannot use this command if you
//scale an object to 0%. Scaling an object to 0% can be used to make fake shadows, or super flat objects.
//Now, last time we had:
//WorldMatrix=Scale*Xrot*Yrot*Zrot*Translation
//So, this time we have:
//WorldMatrix'=Translation'*Zrot'*Yrot'*Xrot'*Scale'
//Where A*A'=I, and A*I=A, you can see that...
//WorldMatrix*WorldMatrix'=I=Scale*Xrot*Yrot*Zrot*Translation*Translation'*Zrot'*Yrot'*Xrot'*Scale'
multiply matrix4 transmat,transmat,zrotmat //translation'*zrot'
multiply matrix4 transmat,transmat,yrotmat //translation'*zrot'*yrot'
multiply matrix4 transmat,transmat,xrotmat //translation'*zrot'*yrot'*xrot'
multiply matrix4 worldmat,transmat,scalemat //translation'*zrot'*yrot'*xrot'*scalerot'
endfunction
//uses the algorithm described on this website:
//http://www.gregslabaugh.name/publications/euler.pdf
//I've mirrored it on my site at
//http://www.neurofuzzydev.com/downloads/Papers/rotMatrixToEulerAngles.pdf
function rotMatrixToEulerAng(C as integer)
C31#=M4Element(C,1,3) //transposed
C32#=M4Element(C,2,3)
C33#=M4Element(C,3,3)
C21#=M4Element(C,1,2)
C11#=M4Element(C,1,1)
C12#=M4Element(C,2,1)
C13#=M4Element(C,3,1)
if absolut(C31#)<>1
ret_y=-asin(C31#)
ret_x=atanfull(C32#/cos(ret_y),C33#/cos(ret_y))
ret_z=atanfull(C21#/cos(ret_y),C11#/cos(ret_y))
else
ret_z=0
if C31#=-1
ret_y=90
ret_x=atanfull(C12#,C13#)
else
ret_y=270
ret_x=atanfull(-C12#,-C13#)
endif
endif
endfunction
function posneg(a#,num#)
if a#<0 then exitfunction -num#
if a#>0 then exitfunction num#
endfunction 0.0
function absolut(n as float)
if n>0 then exitfunction n
n=-n
endfunction n
//gets an element of a 4x4 matrix.
function M4Element(m4,row,column)
n#=get matrix4 element(m4,column-1+(row-1)*4)
endfunction n#
function printM4(m4, x,y,decimals) //prints a matrix: 74 units tall.
line x,y,x+10,y
line x,y,x,y+74
line x,y+74,x+10,y+74
dim s(3) as String
s(0)=""
s(1)=""
s(2)=""
s(3)=""
tw as integer
for y2=1 to 4
for x2=1 to 4
s(y2-1)=s(y2-1)+str$(M4Element(m4,y2,x2),decimals)
if x2<3 then s(y2-1)=s(y2-1)+" "
next x2
n=text width(s(y2-1))
if tw<n then tw=n
next y2
tw=tw+10
line x+tw,y,x+tw-10,y
line x+tw,y,x+tw,y+74
line x+tw,y+74,x+tw-10,y+74
for l=0 to 3
text x+5,y+5+16*l,s(l)
next l
endfunction tw
