Jump to content

Mis-understanding rotations and translations Local and World


JohnK
 Share

Recommended Posts

Despite reading and help from jerome and RananW rotate and translate along with local and world were still getting me confused so to help be grasp what was going on I created a 'pilot'  and put him in a 'helicopter'  (helicopter has pilot as parent)

 

As a pilot is the centre of his own universe I gave him his own axes  (ie each axis has the pilot as parent)

 

I then tried out a variety of sequences which you can try out here. (You will have to change comments around and swap LOCAL and WORLD around as needed)

 

I have put in all the stages to show how my understanding developed and then came to a halt. At first things go as expected, it is when I tried translations following rotations that confusion set in. If you want to find my questions skip towards the end.

 

Initially all these were created at the origin and world axes are also shown.

 

x axis red,  y axis green, z axis blue

 

startH.PNG

 

I flew the plane to (2, 2, 0) in two stages

 

starttransWX.PNG pilot.translate(BABYLON.Axis.X,2, BABYLON.Space.LOCAL);

 

translatestartH.PNG  pilot.translate(BABYLON.Axis.Y,2, BABYLON.Space.LOCAL);

 

At this point the LOCAL and WORLD axes are parallel and the first parameter of the method translate is just a vector. BABYLON.Axis.X is the unit vector (1, 0, 0) and BABYLON.Axis.Y is the unit vector (0, 1, 0) so I would expect when local and world axes are parallel and in the same direction using LOCAL or WORLD does not matter.

 

As expected the translations are cumulative.

 

Now a clockwise rotation using either world or local.

 

rotateWZ.PNG Starting Point for sequences 1 to 4

 

pilot.rotate(BABYLON.Axis.Z, -Math.PI/2, BABYLON.Space.LOCAL) 

pilot.rotate(BABYLON.Axis.Z, -Math.PI/2, BABYLON.Space.WORLD)

 

give the same result. The first parameter of the rotate method is just a vector to act as an axle for the rotation and does not allow for a centre of rotation to be specified so this must be set by Babylon.js. So in either case the pilot is still the centre of his world so is the centre of origin and the rotation is around the vector (0, 0, 1) parallel to the z axis.  Prior to the rotation the local and world z axis are parallel and so the result will be the same which ever one is used.

 

The next four sequences of movements all start from the above starting point.

 

Sequence 1 Following Starting Point clockwise rotation of PI/2 around LOCAL X axis.

 

 

rotateLX.PNG pilot.rotate(BABYLON.Axis.X, -Math.PI/2, BABYLON.Space.LOCAL);

 

So in this case as expected since LOCAL is used BABYLON.Axis.X is the x axis in the pilots own local axes system and the rotation is around the vertical axis.

 

Sequence 2 Following Starting Point clockwise rotation of PI/2 about WORLD X axis.

 

At this point the local x axis and the world x axis are no longer parallel.

 

rotateWX.PNG pilot.rotate(BABYLON.Axis.X, -Math.PI/2, BABYLON.Space.WORLD);

 

The centre of rotation is again the pilot and since WORLD is used BABYLON.Axis.X is the world x axis and the rotation is about the vector parallel to the world x axis.

 

Note again that the world rotations are cumulative.

 

Now for translations.

 

Sequence 3 Following Starting Point LOCAL translation in direction of X axis.

 

So what happens after pilot.translate(BABYLON.Axis.X, 5, BABYLON.Space.LOCAL);

 

translateLX.PNG pilot.translate(BABYLON.Axis.X, 5, BABYLON.Space.LOCAL);

 

WHAT?????.  Since I am using LOCAL I would expect, as it was for rotation BABYLON.Axis.X to now refer to the x axis (now vertical) of the pilot's local axes system.

 

So what happens when I tried WORLD for the translation?

 

Sequence 4 Following Starting Point WORLD translation in direction of X axis.

 

So what happens after pilot.translate(BABYLON.Axis.X, 2, BABYLON.Space.WORLD);

 

translateWX.PNG pilot.translate(BABYLON.Axis.X, 2, BABYLON.Space.WORLD);

 

WHOOPS????? the BABYLON.Axis.X has now been taken in the direction of the pilot's local axes system even though I have set it to WORLD.

 

Are my expectations confused or is something wrong?

 

I tried something simpler using WORLD.

 

Went back to creating the pilot and helicopter at (0, 0, 0) and translate using

 

.

 

Sequence 5 A World translation in the X direction followed by one in the Y direction

 

pilot.translate(BABYLON.Axis.X,2, BABYLON.Space.WORLD) followed by

 

pilot.translate(BABYLON.Axis.Y,2, BABYLON.Space.WORLD);

 

the result was

 

starttransWXY.PNG

 

The first translation has been wiped - no accumulation. This also happened using multiple translations and WORLD -only the last translation was taken.

 

QUESTIONS

 

  1. In the translate method have WORLD and LOCAL got exchanged or has my brain blown?
  2. Following a sequence of translates why does only the last translation take place?
Link to comment
Share on other sites

I have read it jerome. It was the page that got me started on trying the rotate and translate methods.

 

I have now noticed that starting at

 

rotateWZ.PNG Start Position

 

either helicopter.translate(BABYLON.Axis.X, 2, BABYLON.Space.LOCAL) or helicopter.translate(BABYLON.Axis.X, 2, BABYLON.Space.WORLD)

lead to this

 

helicopter%252520translate.PNG

 

I think perhaps I can accept this as the helicopter is part of the pilot's world and so for the helicopter both local and world systems are the same and are the pilots local system. Maybe this is a clue to how the pilot behaves.

 

Have just checked with rotate and from the Starting Point

 

either  helicopter.rotate(BABYLON.Axis.X, -Math.PI/2, BABYLON.Space.LOCAL) or  helicopter.rotate(BABYLON.Axis.X, -Math.PI/2, BABYLON.Space.WORLD) lead to

 

helicopter%252520rotate.PNG

 

 

Will have another go at getting my head around it.

Link to comment
Share on other sites

Had a look at babylon.2.1-beta.debug.js and found that when space is WORLD translate uses

this.setAbsolutePosition(this.getAbsolutePosition().add(displacementVector));

and then within the function setAbsolutePosition when the mesh has no parent the code called is

if (this.parent) {                                var invertParentWorldMatrix = this.parent.getWorldMatrix().clone();                invertParentWorldMatrix.invert();                var worldPosition = new BABYLON.Vector3(absolutePositionX, absolutePositionY, absolutePositionZ);                this.position = BABYLON.Vector3.TransformCoordinates(worldPosition, invertParentWorldMatrix);            }            else {                this.position.x = absolutePositionX;     // ----------------------------                this.position.y = absolutePositionY;     //  These lines called when mesh has no parent                this.position.z = absolutePositionZ;     //                         }

Now I expected that setAbsolutePosition would do just that and after calling with  this.getAbsolutePosition().add(displacementVector) as the parameter then reading this.getAbsolutePosition afterwards would give the sum of the previous this.getAbsolutePosition and displacementVector however it reads as (0, 0, 0)

 

At the start pilot has no parent and

 

pilot.position = { x: 0, y: 0, z: 0 }

pilot.getAbsolutePosition = { x: 0, y: 0, z: 0 }

 

pilot.translate(BABYLON.Axis.X,2, BABYLON.Space.WORLD) sets displacementVector = { x: 2, y: 0, z: 0 }

 

and pilot.getAbsolutePosition().add(displacementVector) is set to { x: 2, y: 0, z: 0 }

but no change is made to pilot.getAbsolutePosition

 

so after the call to setAbsolutePosition

 

pilot.position = { x: 2, y: 0, z: 0 }

pilot.getAbsolutePosition = { x: 0, y: 0, z: 0 } still

 

and pilot.getAbsolutePosition is used as the base for the next translation we start from the origin again and so

 

although pilot.translate(BABYLON.Axis.Y,2, BABYLON.Space.WORLD) sets displacementVector = { x: 0, y: 2, z: 0 } because

pilot.getAbsolutePosition = { x: 0, y: 0, z: 0 } 

 

the call to setAbsolutePosition returns

 

 

pilot.position = { x: 0, y: 2, z: 0 }

again leaving pilot.getAbsolutePosition = { x: 0, y: 0, z: 0 }

 

 

At this stage I am not sure what the different roles that position and AbsolutePosition play within the code so it could be that leaving the AbsolutePosition as

(0, 0, 0) is intentional and I have still not got my thinking straight.

 

Anybody got any idea which way round this is i.e. intentional or not?

Link to comment
Share on other sites

If you are having any issues with the rotations in this scene, it's because your parenting is not logical.  You need to create nulls as parents to transform your objects. Use one master null for translation, a child of the master null to use for rotations, parent the helicopter to the child null and you will also be able to rotate the helicopter on this axis to avoid flipping due to euler vectors.  Only then parent the pilot to the helicopter for it's own unique transforms.  I recomend using quaternions along with euler transforms, but if you use a unique null (or object with the same orientation) for every rotation, this will solve a great deal of issues.

Link to comment
Share on other sites

I do hope your brain is not blown :-)

There is always a proper explanation for everything. I assume. Not including Schrodinger's cat!

The thing that will fix everything, both questions 1 and 2, and all of the misunderstanding, is world matrix computation (If you read "peace" instead of "matrix computation", you have my full respect ;) ).

I was playing a bit with your demo - http://www.babylonjs-playground.com/#2ECRKR#3

Notice that computing the world matrix after every command makes the object move correctly. This is (probably, haven't looked deeply into the code) due to cache rotation and position objects that are not updated after the calls but only after the render. Or due to the use of the wold matrix of the object, without updating it.

I guess it would make sense to force a world matrix computation after each call to rotate and translate. I would wait for DK's answer thou, maybe there is something simpler to do :-)

Link to comment
Share on other sites

I guess the philosophy is something like this :

The flow is local => world => view => screen ... computed once each frame  because the goal is to display something on the screen, isn't it ?

So the framework, for now, provides you tools to set values, once, for each step : local (vertex positions), world (here are rotations, translations, scalings), view (cam position), screen (clipping, projection, etc)

 

The full flow is played each frame for each visible mesh.

So the (last) world matrix value set is used for the rendering.

 

 

<sci-fi moment>

 

We might imagine some intermediate tools could exist and could set differently the world matrix (as in your example by accumulating successive changes)

 

We might also imagine some upper tools could pass directly a result, instead of a stack of matrices, to some step of the flow : "I don't want you, vertex shader, to compute my vertex coordinate in the world, I rather give them directly to you, go on from these values"

 

BJS is clocked with the requestAnimationFrame tempo  ;)

We could imagine also that we do our custom logic in our own different clocked cycle (setInterval), or even in our separate custom thread, where we set as many times we want or at the frequency we want our values... and when BJS needs to render (on requestAnimationFrame call), it just gets the current values and plays its flow as usual

Engine/Logic loose-coupling

 

</sci-fi moment>

 

For now, let's just have the right world matrix ready for the next step (view) each frame  :)

Link to comment
Share on other sites

Raanam (as always) is right: the world matrix is not recomputed each time you change a value for performance reason. If you want immediate results, just call mesh.computeWorldMatrix()

 

@Jerome: Like your idea and it could be easy to implements by just adding a mesh.dontUpdateOnRender.  But is there use cases for this? Beyond the "we can do it" I want to be sure that there is a good reason to add this flag

 

More generally speaking: with the forthcoming arrival of proxies (https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-object-internal-methods-and-internal-slots) it will be easy for a dev to track changes on an object and force the world matrix recomputation.

Link to comment
Share on other sites

First of all I think it is great how fast you get a response from the people who obviously love developing and contributing to BABYLON. Thanks to you all.

Secondly I have come to the conclusion that my questions arise because I am thinking like a maths teacher (retired) not as a gamer and after all BABYLON is designed as a games engine and should behave as such.

Computing the world matrix after each transformation,as suggested by RaananW, works for me and I am quite happy to add this each time.

Keep up the good work.

Link to comment
Share on other sites

@DK

about adding an extra entry points in the flow :

well, I guess it's not that simple as just setting a flag. For the end user, it would require to have some new vertex shaders under the hood.

Example : for this very mesh, I don't want it to be world computed from its world matrix but I want to pass its vertex world coordinates directly so the flows goes on, with my values, computing view and projection. It probably would require a way to inject my data in place of the world matrix multiplication result in the flow.

Quite complex, isn't  it ?

Use case : I just wouldn't have spent two weeks fusing my brain to find a way to deduct rotation angles from a given know orientation  :D  :D  :D

I would rather have passed directly the new coordinates of the mesh vertices in the target system (known target orientation), in other terms I would have given directly their final world settings.

 

Maybe is it quite an exotic need...  :P

So forget it for now

 

 

I believe the logic/render loose-coupling is a better lead to investigate imho

Link to comment
Share on other sites

I'm currently investigating how the computeWorldMatrix() works. I almost understand, I think.

 

But there's something I don't get in the former JohnK's examples : if he computes the world matrix after each rotation settings, as Raanan suggested here : http://www.babylonjs-playground.com/#2ECRKR#3 , it seems intermediate results are taken in account...

I don't understand why.

As far as I understand, computeWorldMatrix() gets current mesh rotation properties and sets, I guess, the values for the three angles in the global transformation matrix (many cos and sin).

#1 : This setting erases former mesh WorldMatrix values, doesn't it ?

So why re-calling it does accumulate user rotation settings ?

 

#2 ) Then this transformation matrix is passed once via a uniform to the vertex shader so the world position of each mesh vertex is computed GPU side. Am I right ?

If yes, I imagine this transfert is done only once per frame.

Or is it done each computeWorldMatrix call ? Even in this case, I don't understand why it would accumulate intermediate results :mellow:

 

I feel a bit lost in the understanding of the flow ... :(

Link to comment
Share on other sites

Hey Jerome,

 

to your first question - the computeWolrdMatrix() only takes the current values of position, scaling and (quaternion)rotation and computes the world matrix. The accumulation happens in the rotate and translate functions (https://github.com/BabylonJS/Babylon.js/blob/master/Babylon/Mesh/babylon.abstractMesh.ts#L204). So, the flow would be:

 

  1. mesh.rotate(...) changes the mesh's quaternion rotation object.
  2. computeWolrdMatrix() computes a new world transformation matrix using the changed values
  3. back to 1 for the next rotate-translate call

rotate is connected with the world matrix only in case it has a parent and space != LOCAL. so technically, there is no need to run the new matrix computation is the object has no parent or is Space is LOCAL (rotate after rotate would accumulate correctly).

Translate uses the world matrix - https://github.com/BabylonJS/Babylon.js/blob/master/Babylon/Mesh/babylon.abstractMesh.ts#L234 (absolutePosition is being calculated in the computeWorldMatrix) . Also only in case the space is not LOCAL.

 

computeWolrdMatrix() is also called in the render function, so, in general, if you run one change a frame, you won't need to update it :-)

Link to comment
Share on other sites

ok, understood  :)  : https://github.com/BabylonJS/Babylon.js/blob/master/Babylon/Mesh/babylon.abstractMesh.ts#L212

I didn't investigate the rotate() method so far, just focused on the computeWorldMatrix().

The accumulation happens in here :

this.rotationQuaternion = this.rotationQuaternion.multiply(rotationQuaternion);

I just believed rotation.x,y,z properties were gotten each call and erased the former ones. That was wrong !

Thank you Raanan :) :)

 

So, actually, in JohnK's example, we could just have multiplied the current mesh.rotationQuaternion by the new wanted value each step instead of calling the whole world matrix re-computation, I think ...

Link to comment
Share on other sites

if you jump into the code and want a quick solution, this would be one of the simplest solutions. Rotate accumulates except for when you have a parent. So no need there. The problem starts when you don't check if there is a parent or when you forgot or don't really know :-)

Computing the world matrix is the safest way for you to know that everything will work afterwards. Cache is updated, world matrix is updated, absolute position is updated. Everything you need. 

For an advanced programmer, who knows exactly what he wants to do and knows the code very well - no, there would be no need to do that. But what are a few milliseconds during bootstrap?  ;)

Link to comment
Share on other sites

I was rather about the case, as JohnK's case, where we could have hundred of meshes and a step by step rotation process to do.

So, just maybe just a need to multiply quaternions

and finally, to set everything right, compute the world matrix only once before rendering

 

 

thank you again about this very accurate, as usual, explanation :)

Link to comment
Share on other sites

Actually, I heard about the computeWorldMatrix performance impact, although, just reading its code, I don't realize why it would be that noticeable even for many meshes (need to investigate about the quaternionYawPitchRoll generation).

 

And after reading your article about inline workers, I'm wondering about a way (hypothetical) to attach a worker (= world matrix computer) per  mesh in order to run all world matrix computations quite simultaneously (if possible)

well, just a prototype, something to stress just to check if it's worth it

Link to comment
Share on other sites

I am not sure an async calculation of the world matrix will word. This is something that needs to be constantly done in the main thread, otherwise actual objects movement won't be shown until an answer will be sent back from the worker. This is a "blocker" :-) 

Link to comment
Share on other sites

yep...

I know

 

well, it's a more general thinking :

- having a render thread (UI) : collect DOM events, pass data to GPU, clocked at requestAnimationFrame tempo. This thread doesn't care about value updates : it just reads current mesh, transform matrix and view matrix values and draws with these values.

- an engine thread : this one computes all values at its own tempo.

 

Loose coupling between rendering and world/view data computation.

 

 

For now, I'm just wondering if, in case of big bunches of meshes on muscled CPU, it would better to serialize all mesh world matrix computations (current BJS behavior) whether to delegate them to dedicated different threads and "wait" for each result. I know it's an extreme special case : strong CPU and need for intensive computation. I'm talking about a mesh = a thread here.

What would be the best approach ... ? no idea, so far

Link to comment
Share on other sites

I don't know if this applies whatsoever, but in my old MOO/MUD-coding days... we had 'forked' tasks.  I guess that's the 'interface' to a threading model, and not pertinent, here.  WebWorker threads have a strict and limited interface, and are not plausible in certain circumstances, I hear.  So, although a worker thread on every mesh... sounds nice... it might not be plausible.  Maybe overhead would defeat its end objective.  I have no idea.

 

WHAT IF:  Every mesh is also a reduced-instruction-set/SIMD Virtual Machine?  We got actionManager?  Why not threadManager, too?

 

hehe.  That makes me excited... and I don't even know why!  :)

 

Ignore me, and continue, guys... there's good rag-chewin' going-on here.  Poor Johnny K is probably wondering why so many people joined him at his campfire, and how the subject got so big.  :)  Don't worry, John, these "topic blooms" seem to happen easily here in the BJS forum.  Just strap yourself-in and hang-on for the ride... there's a first aid truck at the finish line.  (but these threads never finish).

Link to comment
Share on other sites

yep, it could look like some frok-join model

SIMD is an orthogonal approach because it parallelizes numerical computation (very efficient for matrices for instance)

so you can have threads AND simd simultaneously

Link to comment
Share on other sites

@Wingnut et al

 

All the better to have a sing song round the campfire if you are not by yourself. After some further trial and error on my part I thought I had finally got translate and rotate sorted out after finding mesh.locallyTranslate but coming back to the topic today it has sort of got over my head. I was singing 'wheels on the bus' and now we have a full blown opera. What is brilliant is that the experts on this forum take simple questions very seriously.

 

I have submitted my thinking on translations and rotations on a new page for the docs site. I hope it makes sense and other newcomers to BJS will find it useful, if it passes moderation. Thought I would return something for all the help I had been given.

Link to comment
Share on other sites

  • 9 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...