Rotate object around world axis regardless of orientation

Hello!
I'm having a hard time wrapping my brain around this script I'm trying to write.
I want to rotate a cube along the floor in the direction that it's being animated. The problem I'm running into is that I need to be able to specify the rotation around the world axis (just like how the rotate tool does) so that when the cube is oriented in any direction, I can still rotate properly with the animation.
Here's some pseudo code and a screen grab. Any help would be greatly appreciated!
# Rolling distTraveled = pos  PREV_POS numRevs.x = (distTraveled.x / circumfrence.x) numRevs.z = (distTraveled.z / circumfrence.z) outRot = numRevs * c4d.utils.DegToRad(360.0) objMx = rotObj.GetMl() rotMx = c4d.utils.MatrixRotY(outRot.z) * c4d.utils.MatrixRotZ(outRot.x) rotObj.SetMl(objMx * rotMx)
This code almost works, but some of the time the cube rotates the wrong direction (although on the right axis).

Hello @codysorgenfrey,
thank you for reaching out to us. Your question is a bit hard to answer, given the abstract nature of the provided code and the absence of a specific setup/file. I would however point out that:
 This number of revisions expressed as a vector, i.e., separated onto axis, strikes me as a bit odd, since this is not how rotation works ^^
 Using
BaseObject.GetMl()
can make things harder to compute, depending on your setup. I would stay in world space.  Most importantly I would point that you do not seem to take order of rotation into account, i.e., that matrix multiplication is not commutative, i.e., M * N != N * M which might get in the way in your second to last line, as you do not make this dependent on the travel direction.
I personally would also take a more "transform" oriented approach. As it much easier to just rotate a vector than figuring out into which direction you are now traveling and what that means for constructing a transform in "the proper way". I have provided below a quick solution as Python scripting tag, which is more meant to line out the approach rather than being a full solution (which we cannot provide). My solution requires an "alignedwithworldframe" orientation for the start of travel. It would not be too hard to fix this, you must account for that additional delta before applying the final transform. I am just running a bit of time here, especially since this question is a bit out of scope of support (as it is just a math question).
If you need further help, also with the "alignment" condition, just ask
Cheers,
FerdinandThe result:
The file: rot_cube.c4d
The code:
import c4d import math # Global nonstatic variables are a really bad idea. Please replace this # in a production environment with a more safe approach by for example # storing this as a userdata parameter or just as an element in a # BaseContainer of a node. PREVIOUS_POSITION = None NULL_VECTOR = c4d.Vector() UP_VECTOR = c4d.Vector(0, 1, 0) def main(): """ """ # Get out when the tag is not attached to a BaseObject node = op.GetObject() if node is None: return # Get out when the position cache, PREVIOUS_POSITION, has not been yet # initialized. global PREVIOUS_POSITION if PREVIOUS_POSITION is None: PREVIOUS_POSITION = node.GetMg().off return # Compute the position delta and get out when it is the null/zero vector, # i.e., nothing happened. nodeMg = node.GetMg() currentPosition = nodeMg.off positionDelta = currentPosition  PREVIOUS_POSITION if positionDelta == NULL_VECTOR: return # Now we are going to compute the angle of rotation similarly to what you # did as a ratio of arc length (of travel) to circumference. # Compute the circumference of the traveling object as the maximum radius # of its bounding box. This also could be done for a non spherical case, # but would get quite a bit more complicated. boundingBoxRadii = node.GetRad() travelObjectCircumference = 2 * math.pi * max(boundingBoxRadii.x, boundingBoxRadii.y, boundingBoxRadii.z) # The traveled distance since the last update, i.e., the arclength of the # angle of rotation. travelDistance = positionDelta.GetLength() # The angle of rotation, based on the arclength to angle of rotation # relation: # # arc_length theta #  =  # circumference 2π # theta = travelDistance / travelObjectCircumference * 2 * math.pi # Now we are going to construct a transform for that angle. First we are # going to construct our axis rotation which is orthogonal to the travel # vector and an arbitrary upvector, i.e., we simply assume one degree or # otherwise would have to also track the banking of the traveling object. axisRot = ~positionDelta % UP_VECTOR # Now we are just computing our final desired transform, we could do this, # i.e., constructing a transform out of an axis and an angle, manually but # since the Python SDK does offer RotAxisToMatrix, we are going to be lazy # and just use that :) transform = c4d.utils.RotAxisToMatrix(axisRot, theta) # Now we are just going to write this into the object's transform in world # space, i.e., what Cinema does call its global matrix. mg = node.SetMg(nodeMg * transform) # And we have to update our position cache at the end. PREVIOUS_POSITION = currentPosition

Thanks so much @ferdinand! You all at Maxon are the best. Sorry my code is a shit show, just a quick hack at a rolling cube rig.
For anyone in the future looking for this solution here's what I needed:
# Rolling distTraveled = pos  PREV_POS numRevs.x = (distTraveled.x / circumfrence.x) numRevs.z = (distTraveled.z / circumfrence.z) outRot = numRevs * c4d.utils.DegToRad(360.0) objMx = rotObj.GetMl() worldXInLocal = ~objMx * c4d.Vector(1, 0, 0) worldZInLocal = ~objMx * c4d.Vector(0, 0, 1) xTrans = c4d.utils.RotAxisToMatrix(worldXInLocal, outRot.z) zTrans = c4d.utils.RotAxisToMatrix(worldZInLocal, outRot.x) rotObj.SetMl(objMx * xTrans * zTrans)
I needed the cube to be able to roll the correct way regardless of orientation. Admittedly this was a simple matrix transform issue that anyone who actually understood graphics programming would've known. (LOL not me)
Here's the full scene for anyone interested. test_cube_roll.c4d

Hello @codysorgenfrey,
without any further questions, we will consider this topic as solved by Monday and flag it accordingly.
Thank you for your understanding,
Ferdinand