Jump to content

Node::setRotation() is also affecting scale


photo

Recommended Posts

Dear Unigine team,

we have encountered an annoying numerical issue when running setRotation() on a scaled node.
It seems that the scale is slightly changing. As long as the scale is very close to 1 everything goes well and the scale stays very close to one when applying a rotation.
But a original scale factor of 0.99 of 1.01 already makes the node growing or shrinking a tiny little bit, which accumulates to significant error when setRotation() is called very often.
This becomes a serious issue if a rotation is performed per frame, in particular for applications which run for a long time.

I have modified the objects/mesh_01.cpp example, so that it shows up the described behaviour at 700fps after running about 3 days.
It should show up much earlier if the scale is set to 0.9 instead of 0.99.

Furthermore it seems that the scale of a node is cached somewhere and not updated when setRotation() is called.
That is my only explanation why
 





quat currenRot = someNode.getRotation();
float ifps = engine.game.getIFps() * 0.1f;
quat dRot = quat(1.0f, 1.0f, 1.0f, ifps);

vec3 preScale = someNode.getScale();
someNode.setRotation(currenRot * dRot);
someNode.setScale(preScale);

does not solve the problem and the following code:





vec3 preScale = someNode.getScale();
someNode.setRotation(currenRot * dRot);
vec3 postScale = someNode.getScale();

if((preScale.x != postScale.x) || (preScale.y != postScale.y) || (preScale.z != postScale.z))
{
  double dx = double(preScale.x - postScale.x);
  double dy = double(preScale.y - postScale.y);
  double dz = double(preScale.z - postScale.z);
  log.warning("%s: scale changed, squared dist is %0.9f\n", __FUNC__, dx*dx+dy*dy+dz*dz);
}

doesn't reach the log.warning().


Here is the modified objects/mesh_01.cpp example, I had it running with the UNIGINE_Game_binary_windows_2014-07-07 sdk:
 





#include <samples/objects/common/objects.h>

/*
 */
ObjectMesh meshes[0];

/*
 */
void update_scene() {
	
	while(1) {
		
		float ifps = engine.game.getIFps() * 0.1f;
		
		for(int i = 0; i<meshes.size(); i+=1)
		{
			ObjectMesh mesh = meshes[i];
			quat rot = mesh.getRotation();
			//mesh.setWorldTransform(Mat4(mesh.getTransform() * quat(1.0f,1.0f,1.0f,ifps * i)));
			mesh.setRotation(rot * quat(1.0f,1.0f,1.0f,ifps * float(i)));
		}
		
		wait;
	}
}

/*
 */
void create_scene() {
	
	int num = 0;
	int size = 2;
	
	for(int z = -size; z <= size; z++) {
		for(int y = -size; y <= size; y++) {
			for(int x = -size; x <= size; x++) {
				ObjectMesh mesh = add_editor(new ObjectMesh("samples/objects/meshes/mesh_00.mesh"));
				mesh.setWorldTransform(translate(Vec3(x,y,z + 6.0f) * 2.5f));
				if((num & 1) == 0)
				{
					mesh.setScale(vec3(1.01f, 1.01f, 1.01f));
				} else
				{
					mesh.setScale(vec3(0.99f, 0.99f, 0.99f));
				}
				mesh.setMaterial(get_mesh_material(x ^ y ^ z),"*");
				mesh.setProperty("surface_base","*");
				meshes.append(mesh);
				num++;
			}
		}
	}
	
	return format("%d ObjectMesh",num);
}

I have attached an image showing the modified mesh_01.cpp after a few days running with about 700fps:

post-990-0-66228000-1414508211_thumb.jpg

 

Cheers!

 Helmut





 

Link to comment

Hey, Helmut!

 

Please take my apologies for the late reply. I think it'll be better to take only rotational part of your transformation and do a normalization once the error get big enough.

Link to comment
  • 2 weeks later...

Well I think it is easy to deal with the issue, once you are aware of it. (Either by setting the whole transformation matrix explicitly or by calling setScale() after setRotation() for scaled nodes or if the rotation is changed per frame)

 

Still I would consider this as a bug. I don't understand how setRotation() can have numerical issues at all. Given a translation vector t, a scale vector s and a rotation quaternion r the node transformation matrix is:

 

translate(t) * mat4(r) * scale(s)

 

Now if I change the rotation r via setRotation() I can expect that s and scale(s) remains unchanged.

But that is exactly what is not the case.

 

I think this is really dangerous, In our case it caused the star billboard node to collapse on the ground after a week. We are rotating the star billboard slowly in order to mimic earth rotation (By accident the billboard node was scaled in the world file). I have attached this situation as an image (the white dots are the stars).

Now we are a small team here in our studio, I can ask/tell the artist(s) that they shouldn't scale nodes which are getting rotated or if this is not possible I can work around this easily.

 

But in a long running application it can affect any scaled node which is getting rotated per frame. I'm just thinking of the blades of a helicopter or of the wheels of a vehicle. And issues which are appearing in your software only after a week of running are quite annoying to test and to debug.

 

OT: Back in my university days I was working on a virtual reality framework where we had the same challenges. It is convenient to work with scale, rotation and position but in some situations you need a transformation matrix. I know that the conversion from matrix to scale, rotation and translation is not trivial (Its even not possible for all matrices and in some cases it is not unique).

Still this setRotation() problem was quite unexpected to me.

 

I should mention that I'm writing this without having access to the source code of unigine.

 

Cheers

 Helmut

post-990-0-54453000-1415790460_thumb.jpg

Link to comment

Well, it's totally not a bug and here's the explanation.

 

Inside, the engine stores node transformation as 4x4 matrix. That means when you want to get rotation and modify it, the engine has to decompose that matrix, return rotation quaternion to the script and compose transformation matrix back with updated values. And there goes our floating point errors.

 

Even if you just multiply whole transformation matrix by some rotation you'll get that errors. For you case I recommend to build transformation from two angles (pitch and yaw). It's easy to store them in the script. In that way you'll get easier controls for star movement and also you won't get any errors because you're building transformation from ground up.

Link to comment

Thank you for the clarification!

 

That leads me to another question: Is there a significant performance benefit when using setTransform() compared to setRotation() or setScale()?

Long time ago in the virtual reality framework we were using a singular value decomposition in order to split a matrix into scale and rotation parts which involves quite a lot of math, but I can imagine there exists a better way of doing that.

 

I'm asking because it happens quite often that we have to rotate at runtime a large number of nodes. (For this project it is a group of fans, for the last project it was a huge number of mirrors.)

 

cheers

 Helmut

Link to comment

Helmut,

 

I'm glad you interested in details! It's also nice that you mentioned eigenvalues which exactly has application in linear algebra. But for our 4x4 case all the stuff from linear algebra become much simpler as we don't have to find all eigenvalues and do QR or LDU decomposition.

 

What I mean by this is there's no need to run general algorithm. Instead, translation vector can be achieved by getting the last column or row (depends or row/column order) of your matrix. And if you get 3x3 matrix from original matrix and orthonormalize (Gram-Schmidt process, for example) it then you'll get rotation matrix which can be converted to quaternion if needed. Fast and simple!

 

You don't need to do that by hands as we have decomposeTransform / composeTransform / getRotation / setRotation / getScale / setScale functions which will do that for you.

 

That said, you are able to answer your question by yourself now! (spoiler: setTransform method is faster that setRotation / setScale)

 

P.S. we have a sample located in samples/objects/mesh_01 which rotates a lot of objects with good performance. Hacky linear algebra!

Link to comment
×
×
  • Create New...