Optional ability of Node to inherit full transform of parent Node

#533 Merged
Deleted repository
default (2a79e4809a8d)
  1. Eugene Golushkov

Many 3D file formats organize 3D scene as hierarchy on nodes, where nodes has associated transform, be it matrix or position, scale and rotation that are converted to per-node matrix, and such matrices are multiplied to obtain combined transform for the content of the node. At the first glance Ogre organizes scene in the same way (nodes, positions, rotations, scales), and therefore can be used for example to create viewer for such 3D file formats, or more complex application based on ability to import scenes from such files, preserving hierarchy, using mesh instancing if it was used in original file and so on. The problem is that Ogre uses another formula to combine positions, rotations and scales of nested nodes to full transform - all scales are collected and applied first ('derived scale'), then all rotations ('derived orientation') and then offset that is calculated using more complex rules. If corresponded scale transforms of nested nodes are marked as S1..Sn, rotations as R1..Rn and translations as T1..Tn then leaf node in Ogre has combined transform FT2 = TT * (R1 * ... * Rn) * (S1 * ... * Sn) rather than expected FT1 = (T1 * R1 * S1) * ... * (Tn * Rn * Sn) = TT * (R1 * S1) * ... * (Rn * Sn). Note, that if all scales are uniform (i.e. scalar) - then both formulas return the same result, difference occurs if non-uniform scale appear in non-leaf node. Imagine: scene is constructed from elongated blocks with the same mesh (instances), part of them are used as bricks and part of them are rotated vertically and used as columns. You can not decrease height of this building using root node transform without causing holes in the wall, as scaling that decreases length of columns decreases length of bricks rather than height of bricks.

I understand the reason of using such formula in Ogre - vertical and horizontal axes remains perpendicular after transform, combined transform can always be decomposed into the scale, rotation and offset. But it also means that Ogre usage is restricted to situations where only such transforms could occur. There is partial workaround, unsupported transforms could be baked into the mesh data, but it means that mesh should be cloned if it is used in more then one entity, surely brokes instancing support, increases memory consumption and so on.

This relative small patch introduces ability for nodes to inherit full transform from parent node. This support is per-node opt-in, node->setInheritFullTransform(true) should be called. Usage of _getDerivedPosition & _getDerivedScale is reduced in favor of convertWorldToLocalXxx and convertLocalToWorldXxx to make this possible, that has nice side effect of decreased number of virtual function calls and arithmetic operations. In two cases backward incompatible changes were made - cameras and lights has own positions that were affected by parent node orientation and position, but not affected by scale. It was probably just unnoticed mistake, as position of particles and billboards were affected by scale of parent node, and for example scaling of candle holder with lights and particle emitters causes lights not moved with candle wicks. These changes are in separate commits.

Comments (4)

  1. Matias Goldberg

    This is related to OGRE-241 Non-uniform vs uniform scaling is always a subject of debate and controversy, like git vs hg or Emacs vs Vim vs Visual Studio. See http://www.ogre3d.org/forums/viewtopic.php?p=498919#p498919

    Some pipelines use uniform scaling for efficiency, numerical stability, less memory requirements and simplicity in calculations (i.e. Havok Animation, Ogre). Other pipelines use non-uniform scaling by doing 4x4 matrices (modelling animations). In Ogre 2.0 the new Skeletal Animation system uses non-uniform scaling for skeletons because sadly this is what the major modelling packages work with (Maya, Max, Blender); and therefore the animators expect the engine to reproduce it the same (specially the principles of animations: squash, stretch & squeeze; which is very important and are hard to achieve with uniform scaling). However for general nodes (non-bones), Ogre 2.x remains using uniform scaling.

    As for this patch for 1.10, it looks fine. Though I'm not comfortable with the selection of the method happening at runtime. Ogre 1.10 is slow already at node performance, adding a 4x4 matrix, a boolean, and more branches just makes it worse. Is it feasible/easy to update this PR to make the choice at compile time (i.e. CMake) using #ifdef instead?

    1. Eugene Golushkov author

      OK, I`ll do it via #ifdef OGRE_NODE_INHERIT_TRANSFORM. With current patch sizeof(Node) == 368 (was 304), therefore under this #ifdef I can also:

      1. Remove mDerivedPosition/Orientation/Scale and calculate them on demand, as _getDerivedXxx should be called very infrequently under this #ifdef (-40 bytes)
      2. Make mCachedTransform and mCachedInversedTransform of type Matrix3x4 rather than Matrix4 (-32 bytes)
      3. Keep mCachedTransform as Matrix4 but remove mCachedInversedTransform and calculate on demand (-64 bytes)
      4. Make mCachedTransform of type Matrix3x4 and remove mCachedInversedTransform (-80 bytes)
      5. Do 1 & 4 together (-120 bytes)

      What do you think?

        1. Eugene Golushkov author

          OK, I did conservative rewriting - mCachedInversedTransform removed (-64 bytes), but no Matrix3x4 (potential -16 bytes) used, and mDerivedXxx (potential -40 bytes) were preserved too, as there are some places in Ogre where calculations rely on it, and adding another execution path under #ifdef seems to be unnecessary complification.

          Usage of the Matrix3x4 is still possible, but is outside of the scope of this pull request.