TurnEntity - Quaternion Example
BlitzMax Forums/MiniB3D Module/TurnEntity - Quaternion Example
| ||
As far as I'm aware the only way to achieve true quaternion rotations in Blitz3D is to use TurnEntity with the global flag set to true. Seeing as Blitz3D uses quaternions internally, and MiniB3D does not (it uses eulers instead), I don't think I'll be able to support this in MiniB3D natively without a rewrite, which I don't fancy doing. However, if you are desperate to get this working in MiniB3D (say, for example you are working on a spaceship game and you need to avoid gimbal lock), then it is easy enough to do yourself. All you need to do is create a quaternion yourself, apply the necessary multiplication with a 'turn' quaternion then convert it to a matrix which your entity will use. Here's an example: Import "../MiniB3D.bmx" ' TurnEntity quaternion example - mimics the effect of Blitz3D's TurnEntity with the global flag set to true Strict Graphics3D 640,480 Local camera:TCamera=CreateCamera() PositionEntity camera,0,0,-5 Local light:TLight=CreateLight() Local cone:TMesh=CreateCone(4) ' create cone Local cone_x#=0,cone_y#=0,cone_z#=0,cone_sx#=1.0,cone_sy#=1.0,cone_sz#=1.0 Local quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create cone quat Local turn_quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create turn quat Local p#=0,y#=0,r#=0 ' pitch, yaw, roll values While Not KeyDown(KEY_ESCAPE) p=0; y=0; r=0 ' reset pitch, yaw, roll values ' Change rotation values depending on the key pressed If KeyDown( KEY_UP )=True Then p=-1 If KeyDown( KEY_DOWN )=True Then p=1 If KeyDown( KEY_LEFT )=True Then y=-1 If KeyDown( KEY_RIGHT )=True Then y=1 If KeyDown( KEY_Z )=True Then r=-1 If KeyDown( KEY_X )=True Then r=1 turn_quat=EulerToQuat(y,r,-p) ' set turn quat quat=MultiplyQuats(quat,turn_quat) ' multiply cone quat with turn quat quat=NormalizeQuat(quat) ' normalise quat ' rotate, scale and translate cone - use this instead of RotateEntity, ScaleEntity, PositionEntity etc TQuaternion.QuatToMat(-quat.w,quat.x,quat.y,-quat.z,cone.mat) ' convert cone quat to cone mat (rotate) cone.mat.Scale(cone_sx#,cone_sy#,cone_sz#) ' scale cone mat cone.mat.grid[3,0]=cone_x#; cone.mat.grid[3,1]=cone_y; cone.mat.grid[3,2]=-cone_z ' translate cone mat RenderWorld Flip Wend ' Leadwerks function Function EulerToQuat:TQuaternion(pitch#,yaw#,roll#) Local cr#=Cos(-roll#/2.0) Local cp#=Cos(pitch#/2.0) Local cy#=Cos(yaw#/2.0) Local sr#=Sin(-roll#/2.0) Local sp#=Sin(pitch#/2.0) Local sy#=Sin(yaw#/2.0) Local cpcy#=cp#*cy# Local spsy#=sp#*sy# Local spcy#=sp#*cy# Local cpsy#=cp#*sy# Local q:TQuaternion=New TQuaternion q.w#=cr#*cpcy#+sr#*spsy# q.x#=sr#*cpcy#-cr#*spsy# q.y#=cr#*spcy#+sr#*cpsy# q.z#=cr#*cpsy#-sr#*spcy# Return q End Function Function MultiplyQuats:TQuaternion(q1:TQuaternion,q2:TQuaternion) Local q:TQuaternion=New TQuaternion q.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z q.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y q.y = q1.w*q2.y + q1.y*q2.w + q1.z*q2.x - q1.x*q2.z q.z = q1.w*q2.z + q1.z*q2.w + q1.x*q2.y - q1.y*q2.x Return q End Function Function NormalizeQuat:TQuaternion(q:TQuaternion) Local uv#=Sqr(q.w*q.w+q.x*q.x+q.y*q.y+q.z*q.z) q.w=q.w/uv q.x=q.x/uv q.y=q.y/uv q.z=q.z/uv Return q End Function |
| ||
Simonh, your the best. Ferret |
| ||
I get the same results as RotateEntity() Ferret |
| ||
No, the above example doesn't suffer from gimbal lock. RotateEntity does. |
| ||
hehe now ppl are going to start asking you what gimbal lock is :o) ... |
| ||
Here you are: http://en.wikipedia.org/wiki/Gimbal_lock |
| ||
ROFL ! |
| ||
Cool, i am confused now. No matter what command i use, i cant rotate the cube around its 3 local(!) axis (like the camera in a flight sim). Tried rotateentity with a cube, its always like two axis are local and yaw is global. Anyone could show me a cube rotating around all three local axis? |
| ||
I don't see how i can use this for a space sim, in b3d i would set the global flag to False so that the ship rotates around its own axis. If i pitch up, the nose of the ship should go up no matter what rotation. TurnEntity() with the global flag set to false did the trick in b3d, how do i do this in bmax? Ferret |
| ||
OK, this works the same as Blitz3D's TurnEntity with the global flag set to false:Import "../MiniB3D.bmx" ' TurnEntity quaternion example - mimics the effect of Blitz3D's TurnEntity with the global flag set to false Strict Graphics3D 640,480 Local camera:TCamera=CreateCamera() PositionEntity camera,0,0,-5 Local light:TLight=CreateLight() Local cone:TMesh=CreateCone(4) ' create cone Local cone_x#=0,cone_y#=0,cone_z#=0,cone_sx#=1.0,cone_sy#=1.0,cone_sz#=1.0 Local quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create cone quat Local turn_quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create turn quat Local p#=0,y#=0,r#=0 ' pitch, yaw, roll TurnEntity values Local pitch#,yaw#,roll# ' pitch, yaw, roll, RotateEntity values While Not KeyDown(KEY_ESCAPE) p=0; y=0; r=0 ' reset pitch, yaw, roll values ' Change rotation values depending on the key pressed If KeyDown( KEY_UP )=True Then p=-1 If KeyDown( KEY_DOWN )=True Then p=1 If KeyDown( KEY_LEFT )=True Then y=-1 If KeyDown( KEY_RIGHT )=True Then y=1 If KeyDown( KEY_Z )=True Then r=-1 If KeyDown( KEY_X )=True Then r=1 quat=EulerToQuat(EntityPitch(cone,True),EntityYaw(cone,True),EntityRoll(cone,True)) ' set cone quat turn_quat=EulerToQuat(p,y,r) ' set turn quat quat=MultiplyQuats(quat,turn_quat) ' multiply cone quat with turn quat quat=NormalizeQuat(quat) ' normalise quat QuatToEuler2(quat.x,quat.y,quat.z,quat.w,pitch#,yaw#,roll#) ' cone quat to euler RotateEntity cone,pitch#,yaw#,roll# ' rotate cone RenderWorld Flip Wend ' Leadwerks function Function EulerToQuat:TQuaternion(pitch#,yaw#,roll#) Local cr#=Cos(-roll#/2.0) Local cp#=Cos(pitch#/2.0) Local cy#=Cos(yaw#/2.0) Local sr#=Sin(-roll#/2.0) Local sp#=Sin(pitch#/2.0) Local sy#=Sin(yaw#/2.0) Local cpcy#=cp#*cy# Local spsy#=sp#*sy# Local spcy#=sp#*cy# Local cpsy#=cp#*sy# Local q:TQuaternion=New TQuaternion q.w#=cr#*cpcy#+sr#*spsy# q.x#=sr#*cpcy#-cr#*spsy# q.y#=cr#*spcy#+sr#*cpsy# q.z#=cr#*cpsy#-sr#*spcy# Return q End Function ' Leadwerks function Const QuatToEulerAccuracy#=0.001 Function QuatToEuler2(x#,y#,z#,w#,pitch# Var,yaw# Var,roll# Var) Local sint#=(2.0*w*y)-(2.0*x*z) Local cost_temp#=1.0-(sint#*sint#) Local cost# If Abs(cost_temp#)>QuatToEulerAccuracy cost#=Sqr(cost_temp#) Else cost#=0.0 EndIf Local sinv#,cosv#,sinf#,cosf# If Abs(cost#)>QuatToEulerAccuracy sinv#=((2.0*y*z)+(2.0*w*x))/cost# cosv#=(1.0-(2.0*x*x)-(2.0*y*y))/cost# sinf#=((2.0*x*y)+(2.0*w*z))/cost# cosf#=(1.0-(2.0*y*y)-(2.0*z*z))/cost# Else sinv#=(2.0*w*x)-(2.0*y*z) cosv#=1.0-(2.0*x*x)-(2.0*z*z) sinf#=0.0 cosf#=1.0 EndIf pitch#=ATan2(sint#,cost#) yaw#=ATan2(sinf#,cosf#) roll#=-ATan2(sinv#,cosv#) End Function Function MultiplyQuats:TQuaternion(q1:TQuaternion,q2:TQuaternion) Local q:TQuaternion=New TQuaternion q.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z q.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y q.y = q1.w*q2.y + q1.y*q2.w + q1.z*q2.x - q1.x*q2.z q.z = q1.w*q2.z + q1.z*q2.w + q1.x*q2.y - q1.y*q2.x Return q End Function Function NormalizeQuat:TQuaternion(q:TQuaternion) Local uv#=Sqr(q.w*q.w+q.x*q.x+q.y*q.y+q.z*q.z) q.w=q.w/uv q.x=q.x/uv q.y=q.y/uv q.z=q.z/uv Return q End Function Is that what you want? |
| ||
On your last code post (the TurnEntity False): It works fine if I just randomly hit arrow keys. It will turn in any direction. However, if you just hit the up arrow until the cone is facing you, it locks and will not move no matter what keys you press. Is that a function of the False flag, or is it a bug? Update: if you reach the right angles with the other keys pressed, it will do the same thing. This doesn't seem to be gimbal lock as even the keys which move the cone along the non-zero axes don't work)... |
| ||
Yeah, that's a bug. |
| ||
ups...nevermind. Thought setting Const QuatToEulerAccuracy# to zero would help. But then the cube vanishes sometimes.. |
| ||
Ignoring the bug, is this the behaviour you want? |
| ||
That Cone rotates like crazy if you scale it down to negative values. Beside of that, exactly what i needed. A big thank you:) |
| ||
No matter what command i use, i cant rotate the cube around its 3 local(!) axis (like the camera in a flight sim). When rotating with euler angles you do not rotate an object around three axis', you rotate an object around a single axis, three times. Using rotations with quaternions is more like axis angle math, where you get to rotate around an arbitrary axis (and thus all three axis' at once). It's faster, and more accurate (although less intuitive, as you need a fairly good spatial sense to visualize it). |
| ||
Like i said, your the best. Ignoring the bug, this exactly what i want. |
| ||
Has anyone been able to fix this bug? Ferret |
| ||
I have spent several hours trying to achieve some kind of free flight rotations(e.g. plane) but nothing worked as intended. Pitch and Yaw are ok as long as Roll doesnt come into play. Rolling an entity should update its local axis and maybe it does that but subsequent alterations to pitch and yaw values just seem to use a global coordinate system. e.g. rolling 90 deg. left and then pitching 90 deg. up should result in the entity pointing left.(local x axis = global y axis) With my tries and with the quaternion examples above the entity ends pointing upwards with a rotation of -90 degrees. Does anyone please have a hint for me how to achieve a kind of plane-bahaviour? |
| ||
What you are describing is gimbal lock! The only real way to get round this is the correct application of quat's, I had a half hearted attempt at converting and old opengl quat example I had to minib3d but I didnt have enough time to finish it... mail me via the address in my profile and I can let you have the opengl example if you think it will be of any use... |
| ||
If you mean me Chris - no im not talking about gimbal lock. Its just easier to visualize 90 degree rotations in text. This here also happens from angles 1-89. ill draw a picture... |
| ||
whew what a journey with mspaint and non-functional tools but here the pics: This is what id expect from local rotations: ![]() And this is what i get using rotateentity, turnentity and the quaternion rotation method: ![]() |
| ||
the first image is using local rotation, the second is using global rotation you'll have to multiply a quat (of the planes local rotations) by the entities current matrix to get that effect without gimbol lock You would be better off not storing the local rotations as eulars but as a quat instead You can the apply a local rotation as a quat by making a "turn quat" from eular x y and z rotations needed for that frame |
| ||
turn your total x,y,z eular rotations into a quat and apply like this, is this what you were meaning? my old quat routines aint a best fit for minib3d but they could easily be modified to work with a 2d ([,]) array instead of a flat 1d ([]) array at any rate I dont want to spoil the fun, (gotta leave you somthing to do! ;) ) |
| ||
Hi Chris, thanks for your help. I picked MiniB3D in order to not go through such math intensive things(artist here) but your code 1)actually works and 2)is somewhat understandable for a person like me. So thanks again - ill digest now. North -- Update: How did you derive these numbers: xturn.w=0.99996192306417131 ' a z roatation xturn.x=0.0087265354983739347 You multiply this turnvalue with the objects quaternion. Changing this values often results in an exploding object. What would be valid numberranges here? Or am i on the wrong track? I'll keep playing. |
| ||
i just turned some eular angles into quat withlocal v:Tvec=new Tvec3 v.set(0,0,1) local q:Tquat=new Tquat q.fromEuler() Print q.w+" "+q.x+" "+q.y+" "+q.z what you need to do each frame is to work out the total amount you need to rotate in pitch heading and roll after you have applied the effects of drag, gyroscopic intertia etc on the orientation changes, the change that into a quat with Tquat.fromEular like above For someone who doesn't want a lot of math you have chosen a hum dinger of a subject, flight simulation is a nightmare! incidentally the *vast* amount of people will NEVER need to use eulars as they are wandering along a reasonably constrained landscape (or flat platform) keep plugging away you'll get it! |
| ||
Yay this seems the way to go! Is there an actual need to convert your one dimensional array to 2d? Works as is doesn't it? Update: I think it is because you have to build your own matrix for every object which is redundant. That it? I'll keep at it. Thanks a bunch Chris! |
| ||
you just need to rewrite (and post here!) the quat routines so they can directly operate on a minb3d matrix I've given you all the code you need and more and I'm a little busy and as other people could make use I think its fair enough! I'd also recommend dropping the Tvec3 object and just use 3 floats instead, while a Tvec3 is in theory the more OO way to go (cause you can then build a whole bunch of vector operations) in this case its just too unwieldy I'm itching to know how you intend to deal with issues such as gyroscopic inertia btw >:) mail me via my profile if you become badly unstuck! |
| ||
Hi, i just had some time to look through this and here are the derived methods for miniB3D. I put these into geom.bmx/TQuaternion for better integration. Method FromMatrix: Method ToMatrix: And a Method to input eular angles directly: Example Usage: The rest of your methods is also in there although i did not look through all of them yet. And to adress your hint with gyroscopic inertia: I am on the brink of creating a simple spaceshooter so most physics goodies won't be nescessary. I hope a linear dampening system will give enough feeling of mass for the average spacecowboy^^ After all none of the atmospheric aerodynamics or visuals(shockwaves, flameballs, lasers, etc.) used in most spaceshooters apply in reality anyway so i'll fake it where possible. Thanks again Chris, you helped me to understand this a little better :) |
| ||
ok... so I might have been poking fun a little but :o nice to see some of my code improved and contributed back! only one minor point.... If KeyDown (KEY_W) = True xturn.SetFromEulerXYZ(1,0,0) cubeQuat.multiply(xturn) EndIf ...ETC... might be better as local xt:float,zt:float xt=0;zt=0 If KeyDown (KEY_W) Then xt=1 If KeyDown (KEY_S) Then xt=-1 If KeyDown (KEY_D) Then zt=1 If KeyDown (KEY_A) Then zt=-1 EndIf if xt or zt then xturn.SetFromEulerXYZ(xt,0,zt) cubeQuat.multiply(xturn) endif This minimizes the number of computationally expensive calls and allows you to add in any other effects that might change the way the craft behaves (you might for example want to multiply xt and zt by 0.75 if the craft is overloaded) 90% of game programming is "faking it" and the reason most commercial games need such stupid hardware requirements is because the designers in their ivory towers have forgotten it! I remember elite from the BBC 'B' micro and pine for the days when games actually had game play. Please feel free to contact me via the email in my profile if you'd like any further help with this project. |